Uname: Linux premium294.web-hosting.com 4.18.0-553.45.1.lve.el8.x86_64 #1 SMP Wed Mar 26 12:08:09 UTC 2025 x86_64
User: 768 (artoqlkw)
Group: 773 (artoqlkw)
Disabled functions: NONE
Safe mode: On[ PHPinfo ]
//usr/local/sbin      ( Reset | Go to )
File Name: wp
#!/usr/bin/env php
<?php
Phar
::mapPhar();
include 
'phar://wp-cli.phar/php/boot-phar.php';
__HALT_COMPILER(); 
?>
�w` wp-cli.phar#vendor/wp-cli/wp-cli/php/wp-cli.php��a���1vendor/wp-cli/wp-cli/php/WP_CLI/ExitException.phpS��aS{uׄ�2vendor/wp-cli/wp-cli/php/WP_CLI/SynopsisParser.phpp��ap�W�]�4vendor/wp-cli/wp-cli/php/WP_CLI/Context/Frontend.php���a�Br���1vendor/wp-cli/wp-cli/php/WP_CLI/Context/Admin.php%
��a%
e�eȤ/vendor/wp-cli/wp-cli/php/WP_CLI/Context/Cli.php���a��A���0vendor/wp-cli/wp-cli/php/WP_CLI/Context/Auto.php���a��2��+vendor/wp-cli/wp-cli/php/WP_CLI/Context.phpM��aM�[NG�-vendor/wp-cli/wp-cli/php/WP_CLI/Formatter.php%��a%�W���0vendor/wp-cli/wp-cli/php/WP_CLI/UpgraderSkin.php���a����Jvendor/wp-cli/wp-cli/php/WP_CLI/Compat/Min_PHP_5_6/FeedbackMethodTrait.php���a�<�NӤJvendor/wp-cli/wp-cli/php/WP_CLI/Compat/Min_PHP_5_4/FeedbackMethodTrait.php��aܠ�Ӥ>vendor/wp-cli/wp-cli/php/WP_CLI/Compat/FeedbackMethodTrait.php���a���閤5vendor/wp-cli/wp-cli/php/WP_CLI/Loggers/Execution.phpa��aat�W�3vendor/wp-cli/wp-cli/php/WP_CLI/Loggers/Regular.php��a5��!�0vendor/wp-cli/wp-cli/php/WP_CLI/Loggers/Base.php���a���Ee�1vendor/wp-cli/wp-cli/php/WP_CLI/Loggers/Quiet.php    ��a    -n���2vendor/wp-cli/wp-cli/php/WP_CLI/ContextManager.php���a��Z�Ϥ6vendor/wp-cli/wp-cli/php/WP_CLI/WpHttpCacheManager.phpk ��ak j��*vendor/wp-cli/wp-cli/php/WP_CLI/Runner.php����a���<� �5vendor/wp-cli/wp-cli/php/WP_CLI/SynopsisValidator.php���a�k�%�/vendor/wp-cli/wp-cli/php/WP_CLI/Completions.php9��a9�j�ۤ.vendor/wp-cli/wp-cli/php/WP_CLI/Autoloader.php-��a-    l�Ѥ-vendor/wp-cli/wp-cli/php/WP_CLI/DocParser.php��a
xP�@vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/InitializeContexts.php��a0Ca��:vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/LaunchRunner.php��aA�^U�<vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/BootstrapState.php���a����פHvendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/DeclareAbstractBaseCommand.phpA��aA�@<@�>vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/DeclareMainClass.php��a�Tj�<vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/LoadDispatcher.php5��a5U��4�Dvendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/InitializeColorization.phpL��aL��'=�=vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/ConfigureRunner.php5��a5�߶-�Evendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/DefineProtectedCommands.php���a�8�>2�Fvendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/DeclareFallbackFunctions.phpC��aC@*Bg�=vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/LoadExecCommand.phpW��aWy~��;vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/BootstrapStep.php���a�A7/ �Avendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/LoadRequiredCommand.php���a����ӤBvendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/LoadUtilityFunctions.php)��a)TYMݤ<vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/AutoloaderStep.php)    ��a)    ��¬�Gvendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/RegisterFrameworkCommands.phpg��ag�>�V�>vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/InitializeLogger.php*��a*\��3�Hvendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/IncludeFrameworkAutoloader.php���a��t?ߤFvendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/RegisterDeferredCommands.php���a��k    �Gvendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/IncludeFallbackAutoloader.php���a����S�Fvendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/IncludePackageAutoloader.php��a��F�<vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/RunnerInstance.php���a��k��1vendor/wp-cli/wp-cli/php/WP_CLI/Fetchers/Base.php���a����U�1vendor/wp-cli/wp-cli/php/WP_CLI/Fetchers/Post.php9��a9����1vendor/wp-cli/wp-cli/php/WP_CLI/Fetchers/User.php0��a0����4vendor/wp-cli/wp-cli/php/WP_CLI/Fetchers/Comment.php}��a}����1vendor/wp-cli/wp-cli/php/WP_CLI/Fetchers/Site.php���a�xkX�3vendor/wp-cli/wp-cli/php/WP_CLI/Iterators/Query.php ��a ��ֽ�7vendor/wp-cli/wp-cli/php/WP_CLI/Iterators/Transform.php���a�ӀtY�3vendor/wp-cli/wp-cli/php/WP_CLI/Iterators/Table.phpe
��ae
@+�1vendor/wp-cli/wp-cli/php/WP_CLI/Iterators/CSV.php���a���bO�7vendor/wp-cli/wp-cli/php/WP_CLI/Iterators/Exception.phph��ah0L6Ҥ-vendor/wp-cli/wp-cli/php/WP_CLI/Extractor.phpB'��aB'<�ZϤ0vendor/wp-cli/wp-cli/php/WP_CLI/Configurator.php�%��a�%.<y    �Avendor/wp-cli/wp-cli/php/WP_CLI/PackageManagerEventSubscriber.php���a�g9�ä,vendor/wp-cli/wp-cli/php/WP_CLI/WpOrgApi.php���a��9ܤ.vendor/wp-cli/wp-cli/php/WP_CLI/ProcessRun.php(��a(c�홤=vendor/wp-cli/wp-cli/php/WP_CLI/Dispatcher/CommandFactory.php
"��a
"��n6�?vendor/wp-cli/wp-cli/php/WP_CLI/Dispatcher/CommandNamespace.php���a�rF|�?vendor/wp-cli/wp-cli/php/WP_CLI/Dispatcher/CompositeCommand.php���a�����:vendor/wp-cli/wp-cli/php/WP_CLI/Dispatcher/RootCommand.phpK��aK�yդ9vendor/wp-cli/wp-cli/php/WP_CLI/Dispatcher/Subcommand.php�5��a�5psݤ>vendor/wp-cli/wp-cli/php/WP_CLI/Dispatcher/CommandAddition.php#��a#�躛�(vendor/wp-cli/wp-cli/php/WP_CLI/NoOp.php���a�D�1�-vendor/wp-cli/wp-cli/php/WP_CLI/Inflector.php�?��a�?b���+vendor/wp-cli/wp-cli/php/WP_CLI/Process.phpT ��aT �Dk�-vendor/wp-cli/wp-cli/php/WP_CLI/FileCache.php�!��a�!k �Ӥ.vendor/wp-cli/wp-cli/php/WP_CLI/ComposerIO.php���a���+Q�%vendor/wp-cli/wp-cli/php/utils-wp.php�9��a�9Ӏ��#vendor/wp-cli/wp-cli/php/compat.phpn��an�ze�/vendor/wp-cli/wp-cli/php/fallback-functions.php���a��[F�,vendor/wp-cli/wp-cli/php/wp-settings-cli.phpoC��aoC��g�)vendor/wp-cli/wp-cli/php/class-wp-cli.php����a����vy�"vendor/wp-cli/wp-cli/php/utils.php����a��i���&vendor/wp-cli/wp-cli/php/bootstrap.php���a�^Y�f�(vendor/wp-cli/wp-cli/php/config-spec.php��a����1vendor/wp-cli/wp-cli/php/class-wp-cli-command.php���a��*���'vendor/wp-cli/wp-cli/php/dispatcher.phpM��aM��:J�$vendor/wp-cli/wp-cli/php/boot-fs.php���a�<�ײ�;vendor/wp-cli/wp-cli/php/commands/src/CLI_Cache_Command.php���a��n?��;vendor/wp-cli/wp-cli/php/commands/src/CLI_Alias_Command.phpy3��ay3�>�i�5vendor/wp-cli/wp-cli/php/commands/src/CLI_Command.php�N��a�N�ga�6vendor/wp-cli/wp-cli/php/commands/src/Help_Command.phpW��aW�q�*vendor/wp-cli/wp-cli/php/commands/help.php���a�lD��)vendor/wp-cli/wp-cli/php/commands/cli.php���a�jn���php/boot-phar.php9��a9Y��s�0vendor/mustache/mustache/bin/build_bootstrap.php;��a;�����0vendor/mustache/mustache/src/Mustache/Loader.phpB��aBQ��y�0vendor/mustache/mustache/src/Mustache/Engine.php�c��a�c5��1vendor/mustache/mustache/src/Mustache/Context.php���a���D�0vendor/mustache/mustache/src/Mustache/Logger.php$ ��a$ �@Y9�2vendor/mustache/mustache/src/Mustache/Compiler.php.X��a.X��-��Avendor/mustache/mustache/src/Mustache/Loader/FilesystemLoader.php���a�L$y��@vendor/mustache/mustache/src/Mustache/Loader/CascadingLoader.php���a����=vendor/mustache/mustache/src/Mustache/Loader/InlineLoader.php���a�\�    �>vendor/mustache/mustache/src/Mustache/Loader/MutableLoader.php���a���g�Kvendor/mustache/mustache/src/Mustache/Loader/ProductionFilesystemLoader.phpf ��af ����<vendor/mustache/mustache/src/Mustache/Loader/ArrayLoader.php���a��!��=vendor/mustache/mustache/src/Mustache/Loader/StringLoader.php���a�<%��0vendor/mustache/mustache/src/Mustache/Parser.php�*��a�*�Vʄ�Lvendor/mustache/mustache/src/Mustache/Exception/InvalidArgumentException.php���a���Q�Bvendor/mustache/mustache/src/Mustache/Exception/LogicException.php���a��}A��Cvendor/mustache/mustache/src/Mustache/Exception/SyntaxException.php���a��N���Lvendor/mustache/mustache/src/Mustache/Exception/UnknownTemplateException.php���a�-�[�Jvendor/mustache/mustache/src/Mustache/Exception/UnknownFilterException.php���a��f�ޤJvendor/mustache/mustache/src/Mustache/Exception/UnknownHelperException.php���a��H�j�Dvendor/mustache/mustache/src/Mustache/Exception/RuntimeException.php���a�1]�Ӥ=vendor/mustache/mustache/src/Mustache/Cache/AbstractCache.php���a��Y`�9vendor/mustache/mustache/src/Mustache/Cache/NoopCache.phpA��aA���٤?vendor/mustache/mustache/src/Mustache/Cache/FilesystemCache.phph��ah(}4�/vendor/mustache/mustache/src/Mustache/Cache.php���a��W��0vendor/mustache/mustache/src/Mustache/Source.php��ab׶m�=vendor/mustache/mustache/src/Mustache/Logger/StreamLogger.php���a����J�?vendor/mustache/mustache/src/Mustache/Logger/AbstractLogger.phpv ��av n�Y6�6vendor/mustache/mustache/src/Mustache/LambdaHelper.php���a��pդ:vendor/mustache/mustache/src/Mustache/HelperCollection.php���a���Ӥ2vendor/mustache/mustache/src/Mustache/Template.php���a�8��4vendor/mustache/mustache/src/Mustache/Autoloader.php��a=c�֤Avendor/mustache/mustache/src/Mustache/Source/FilesystemSource.php���a�V����3vendor/mustache/mustache/src/Mustache/Exception.phpR��aR]u��3vendor/mustache/mustache/src/Mustache/Tokenizer.php�-��a�-�!X��6vendor/rmccue/requests/library/Requests/Cookie/Jar.php��a����4vendor/rmccue/requests/library/Requests/Response.php�    ��a�    q����1vendor/rmccue/requests/library/Requests/Hooks.phpx��axK�l5�/vendor/rmccue/requests/library/Requests/SSL.php���a���6ʤ2vendor/rmccue/requests/library/Requests/Hooker.php���a�=�u��:vendor/rmccue/requests/library/Requests/Transport/cURL.php�B��a�B(�}�?vendor/rmccue/requests/library/Requests/Transport/fsockopen.php�2��a�2�m�Dvendor/rmccue/requests/library/Requests/Exception/Transport/cURL.php���a�]񐒤:vendor/rmccue/requests/library/Requests/Exception/HTTP.php���a���r�Bvendor/rmccue/requests/library/Requests/Exception/HTTP/Unknown.phpd��ad��>vendor/rmccue/requests/library/Requests/Exception/HTTP/410.phpo��ao�_�7�>vendor/rmccue/requests/library/Requests/Exception/HTTP/428.php���a�J��2�>vendor/rmccue/requests/library/Requests/Exception/HTTP/409.php{��a{�wZB�>vendor/rmccue/requests/library/Requests/Exception/HTTP/504.php���a���Y�>vendor/rmccue/requests/library/Requests/Exception/HTTP/416.php���a����r�>vendor/rmccue/requests/library/Requests/Exception/HTTP/405.php���a��z]�>vendor/rmccue/requests/library/Requests/Exception/HTTP/501.php���a�a4���>vendor/rmccue/requests/library/Requests/Exception/HTTP/505.php���a���+f�>vendor/rmccue/requests/library/Requests/Exception/HTTP/418.php���a�݄�>vendor/rmccue/requests/library/Requests/Exception/HTTP/407.php���a�ko���>vendor/rmccue/requests/library/Requests/Exception/HTTP/402.php���a�Ha��>vendor/rmccue/requests/library/Requests/Exception/HTTP/415.php���a�R�;פ>vendor/rmccue/requests/library/Requests/Exception/HTTP/404.php~��a~_��>vendor/rmccue/requests/library/Requests/Exception/HTTP/408.php���a��1�"�>vendor/rmccue/requests/library/Requests/Exception/HTTP/431.php��a�֍�>vendor/rmccue/requests/library/Requests/Exception/HTTP/403.php~��a~�D1�>vendor/rmccue/requests/library/Requests/Exception/HTTP/400.php���a�Di�Q�>vendor/rmccue/requests/library/Requests/Exception/HTTP/306.php���a�M�o��>vendor/rmccue/requests/library/Requests/Exception/HTTP/511.php��a��f�>vendor/rmccue/requests/library/Requests/Exception/HTTP/503.php���a��I��>vendor/rmccue/requests/library/Requests/Exception/HTTP/401.php���a�j��X�>vendor/rmccue/requests/library/Requests/Exception/HTTP/502.php���a��.��>vendor/rmccue/requests/library/Requests/Exception/HTTP/413.php���a���H��>vendor/rmccue/requests/library/Requests/Exception/HTTP/414.php���a��ΰ�>vendor/rmccue/requests/library/Requests/Exception/HTTP/304.php���a�9�AJ�>vendor/rmccue/requests/library/Requests/Exception/HTTP/412.php���a�����>vendor/rmccue/requests/library/Requests/Exception/HTTP/305.php~��a~4-O��>vendor/rmccue/requests/library/Requests/Exception/HTTP/500.php���a��g?�>vendor/rmccue/requests/library/Requests/Exception/HTTP/411.php���a��y>vendor/rmccue/requests/library/Requests/Exception/HTTP/429.php&��a&mMc'�>vendor/rmccue/requests/library/Requests/Exception/HTTP/406.php���a�V�"�>vendor/rmccue/requests/library/Requests/Exception/HTTP/417.php���a�    E��?vendor/rmccue/requests/library/Requests/Exception/Transport.phpJ��aJ�9[��6vendor/rmccue/requests/library/Requests/Auth/Basic.php���a��J�A�0vendor/rmccue/requests/library/Requests/Auth.php*��a*u��6vendor/rmccue/requests/library/Requests/Proxy/HTTP.php� ��a� A�$�0vendor/rmccue/requests/library/Requests/IPv6.php���a��j{m�1vendor/rmccue/requests/library/Requests/Proxy.php-��a-��ɧ�7vendor/rmccue/requests/library/Requests/IDNAEncoder.php-��a-�̓�5vendor/rmccue/requests/library/Requests/Exception.php��a�%Ф2vendor/rmccue/requests/library/Requests/Cookie.php�2��a�2����3vendor/rmccue/requests/library/Requests/Session.php��a��p�Mvendor/rmccue/requests/library/Requests/Utility/CaseInsensitiveDictionary.php���a����D�Dvendor/rmccue/requests/library/Requests/Utility/FilteredIterator.php���a�    ��^�/vendor/rmccue/requests/library/Requests/IRI.php�o��a�o n �<vendor/rmccue/requests/library/Requests/Response/Headers.phpW��aW�=Q7�5vendor/rmccue/requests/library/Requests/Transport.php���a���*��+vendor/rmccue/requests/library/Requests.php�v��a�v���L�vendor/composer/ClassLoader.php�>��a�>�5Ky�>vendor/composer/composer/src/Composer/Autoload/ClassLoader.php�>��a�>�5Ky�Avendor/composer/composer/src/Composer/Autoload/PhpFileCleaner.php���a�Kآ�Dvendor/composer/composer/src/Composer/Autoload/AutoloadGenerator.php%���a%�H ���Dvendor/composer/composer/src/Composer/Autoload/ClassMapGenerator.php�1��a�1�R��Fvendor/composer/composer/src/Composer/Config/ConfigSourceInterface.php    ��a    ���o�Avendor/composer/composer/src/Composer/Config/JsonConfigSource.phpB)��aB)P��Cvendor/composer/composer/src/Composer/Downloader/PathDownloader.php�.��a�.g;x��Evendor/composer/composer/src/Composer/Downloader/FossilDownloader.php���a������Avendor/composer/composer/src/Composer/Downloader/HgDownloader.php� ��a� �|p�Cvendor/composer/composer/src/Composer/Downloader/GzipDownloader.phpx��ax�˕��Hvendor/composer/composer/src/Composer/Downloader/FilesystemException.php#��a#Q{�(�Fvendor/composer/composer/src/Composer/Downloader/ArchiveDownloader.php�!��a�!3(i�Bvendor/composer/composer/src/Composer/Downloader/RarDownloader.php�    ��a�    �K��Avendor/composer/composer/src/Composer/Downloader/XzDownloader.php��a���ʤBvendor/composer/composer/src/Composer/Downloader/ZipDownloader.php    ,��a    ,G[c�Hvendor/composer/composer/src/Composer/Downloader/DownloaderInterface.php���a�x��D�Gvendor/composer/composer/src/Composer/Downloader/PerforceDownloader.php/ ��a/ ��:�Cvendor/composer/composer/src/Composer/Downloader/FileDownloader.phpwL��awL�����Bvendor/composer/composer/src/Composer/Downloader/SvnDownloader.php�"��a�"8+�Lvendor/composer/composer/src/Composer/Downloader/DvcsDownloaderInterface.php��a�v�(�Dvendor/composer/composer/src/Composer/Downloader/DownloadManager.phpa>��aa>����Jvendor/composer/composer/src/Composer/Downloader/ChangeReportInterface.php���a��)Ÿ�Qvendor/composer/composer/src/Composer/Downloader/MaxFileSizeExceededException.phpo��ao�:���Cvendor/composer/composer/src/Composer/Downloader/PharDownloader.php��a��`�Bvendor/composer/composer/src/Composer/Downloader/TarDownloader.php$��a$���3�Bvendor/composer/composer/src/Composer/Downloader/GitDownloader.php�a��a�a�7yj�Gvendor/composer/composer/src/Composer/Downloader/TransportException.php)��a)4N�Bvendor/composer/composer/src/Composer/Downloader/VcsDownloader.phpJ2��aJ2��{�Rvendor/composer/composer/src/Composer/Downloader/VcsCapableDownloaderInterface.php��a ���Mvendor/composer/composer/src/Composer/Question/StrictConfirmationQuestion.php�
��a�
�O��=vendor/composer/composer/src/Composer/Plugin/CommandEvent.php���a���Y>�Fvendor/composer/composer/src/Composer/Plugin/PostFileDownloadEvent.php� ��a� ��Q��=vendor/composer/composer/src/Composer/Plugin/PluginEvents.php'��a'��=��Evendor/composer/composer/src/Composer/Plugin/PreFileDownloadEvent.php���a���R�Fvendor/composer/composer/src/Composer/Plugin/Capability/Capability.php���a�Y9�3�Kvendor/composer/composer/src/Composer/Plugin/Capability/CommandProvider.php_��a_�μ�Cvendor/composer/composer/src/Composer/Plugin/PrePoolCreateEvent.php���a��r��@vendor/composer/composer/src/Composer/Plugin/PluginInterface.php���a�L3]�8vendor/composer/composer/src/Composer/Plugin/Capable.php���a�'�}�>vendor/composer/composer/src/Composer/Plugin/PluginManager.php�z��a�zNyh�Cvendor/composer/composer/src/Composer/Plugin/PreCommandRunEvent.phpM��aMÒ@    �=vendor/composer/composer/src/Composer/Util/NoProxyPattern.php�,��a�,���g�>vendor/composer/composer/src/Composer/Util/ProcessExecutor.phpW:��aW:�p2��1vendor/composer/composer/src/Composer/Util/Hg.phpK ��aK �2�t�7vendor/composer/composer/src/Composer/Util/Perforce.php�Q��a�Qzl7^�;vendor/composer/composer/src/Composer/Util/ErrorHandler.php�
��a�
{0��2vendor/composer/composer/src/Composer/Util/Svn.phpi(��ai(�mޣ�5vendor/composer/composer/src/Composer/Util/GitLab.php���a���⏤2vendor/composer/composer/src/Composer/Util/Zip.php� ��a� f�U:�2vendor/composer/composer/src/Composer/Util/Git.phpQ��aQ�"�Ȥ?vendor/composer/composer/src/Composer/Util/RemoteFilesystem.php����a�����r�9vendor/composer/composer/src/Composer/Util/AuthHelper.php�3��a�3�m���>vendor/composer/composer/src/Composer/Util/ConfigValidator.php�"��a�"��^ �7vendor/composer/composer/src/Composer/Util/Silencer.php���a��>��8vendor/composer/composer/src/Composer/Util/TlsHelper.phpq��aqz w�8vendor/composer/composer/src/Composer/Util/Bitbucket.phpr$��ar$�AZԤ=vendor/composer/composer/src/Composer/Util/HttpDownloader.phpUG��aUG��+�?vendor/composer/composer/src/Composer/Util/MetadataMinifier.phpq��aq�tX�<vendor/composer/composer/src/Composer/Util/Http/Response.php� ��a� �ڦ�?vendor/composer/composer/src/Composer/Util/Http/ProxyHelper.php���a�;:h�@vendor/composer/composer/src/Composer/Util/Http/ProxyManager.php���a�t��¤@vendor/composer/composer/src/Composer/Util/Http/RequestProxy.php���a��,:N�Bvendor/composer/composer/src/Composer/Util/Http/CurlDownloader.phpl��al���O�@vendor/composer/composer/src/Composer/Util/Http/CurlResponse.php���a��t��2vendor/composer/composer/src/Composer/Util/Tar.phpX��aX�W    W�9vendor/composer/composer/src/Composer/Util/Filesystem.php6o��a6o� Ƥ7vendor/composer/composer/src/Composer/Util/Platform.php���a�O��Cvendor/composer/composer/src/Composer/Util/StreamContextFactory.phpF%��aF%{)Az�9vendor/composer/composer/src/Composer/Util/SyncHelper.php�    ��a�    �ۊd�5vendor/composer/composer/src/Composer/Util/GitHub.php[��a[�����8vendor/composer/composer/src/Composer/Util/IniHelper.phpc��ac�,��=vendor/composer/composer/src/Composer/Util/ComposerMirror.php
��a
�D��<vendor/composer/composer/src/Composer/Util/PackageSorter.php�
��a�
)��\�3vendor/composer/composer/src/Composer/Util/Loop.php��a�=,ͤ2vendor/composer/composer/src/Composer/Util/Url.php���a��L��0vendor/composer/composer/src/Composer/Config.php�X��a�XW��A�6vendor/composer/composer/src/Composer/Script/Event.php[ ��a[ &m�Ԥ=vendor/composer/composer/src/Composer/Script/ScriptEvents.php'��a'� �Ƥ2vendor/composer/composer/src/Composer/Compiler.phpV0��aV0Pڈ�Bvendor/composer/composer/src/Composer/Exception/NoSslException.php���a�8M5��Rvendor/composer/composer/src/Composer/Exception/IrrecoverableDownloadException.php���a���-�;vendor/composer/composer/src/Composer/InstalledVersions.php�:��a�:T��"�/vendor/composer/composer/src/Composer/Cache.php�(��a�(����Gvendor/composer/composer/src/Composer/Repository/PlatformRepository.php�}��a�}��ߤGvendor/composer/composer/src/Composer/Repository/ComposerRepository.php"���a"��'%z�Hvendor/composer/composer/src/Composer/Repository/LockArrayRepository.phpm��am��!��Mvendor/composer/composer/src/Composer/Repository/InstalledArrayRepository.php���a��i��Jvendor/composer/composer/src/Composer/Repository/RootPackageRepository.php
��a
bS�"�Bvendor/composer/composer/src/Composer/Repository/VcsRepository.php�Q��a�Q(�u�Dvendor/composer/composer/src/Composer/Repository/ArrayRepository.php�)��a�)��bZ�Hvendor/composer/composer/src/Composer/Repository/InstalledRepository.php�2��a�2�8h�Tvendor/composer/composer/src/Composer/Repository/ConfigurableRepositoryInterface.php��a���Evendor/composer/composer/src/Composer/Repository/FilterRepository.php���a����-�Gvendor/composer/composer/src/Composer/Repository/ArtifactRepository.php_��a_1���Ovendor/composer/composer/src/Composer/Repository/InvalidRepositoryException.php���a���$�Lvendor/composer/composer/src/Composer/Repository/WritableArrayRepository.php���a��'ap�Pvendor/composer/composer/src/Composer/Repository/WritableRepositoryInterface.php��a)�놤Jvendor/composer/composer/src/Composer/Repository/VersionCacheInterface.php���a��m�Y�Pvendor/composer/composer/src/Composer/Repository/RepositorySecurityException.php���a�<9�Hvendor/composer/composer/src/Composer/Repository/RepositoryInterface.php���a���9�Cvendor/composer/composer/src/Composer/Repository/PathRepository.php: ��a: suPy�Rvendor/composer/composer/src/Composer/Repository/InstalledFilesystemRepository.php���a��ޡx�Fvendor/composer/composer/src/Composer/Repository/RepositoryFactory.php���a�Wla?�Cvendor/composer/composer/src/Composer/Repository/PearRepository.php2��a2���'�Fvendor/composer/composer/src/Composer/Repository/PackageRepository.php��a�~�ФHvendor/composer/composer/src/Composer/Repository/CompositeRepository.phpk��ak!`U�Qvendor/composer/composer/src/Composer/Repository/InstalledRepositoryInterface.php���a������Ivendor/composer/composer/src/Composer/Repository/FilesystemRepository.php�/��a�/��bˤEvendor/composer/composer/src/Composer/Repository/Vcs/GitHubDriver.php7R��a7R�+u�Gvendor/composer/composer/src/Composer/Repository/Vcs/PerforceDriver.php��a�h�c�Bvendor/composer/composer/src/Composer/Repository/Vcs/GitDriver.php���a�)Lp�Bvendor/composer/composer/src/Composer/Repository/Vcs/SvnDriver.php�0��a�0�;��Bvendor/composer/composer/src/Composer/Repository/Vcs/VcsDriver.php���a�7R�m�Kvendor/composer/composer/src/Composer/Repository/Vcs/VcsDriverInterface.php% ��a% � %��Avendor/composer/composer/src/Composer/Repository/Vcs/HgDriver.php��a2��e�Evendor/composer/composer/src/Composer/Repository/Vcs/GitLabDriver.phpO��aO�n6�Kvendor/composer/composer/src/Composer/Repository/Vcs/GitBitbucketDriver.php8?��a8?W����Evendor/composer/composer/src/Composer/Repository/Vcs/FossilDriver.php��adr�Fvendor/composer/composer/src/Composer/Repository/RepositoryManager.php���a�g}��Bvendor/composer/composer/src/Composer/Repository/RepositorySet.php�2��a�2����Hvendor/composer/composer/src/Composer/Installer/MetapackageInstaller.php� ��a� �{�Dvendor/composer/composer/src/Composer/Installer/LibraryInstaller.phpy)��ay)��i�Cvendor/composer/composer/src/Composer/Installer/PluginInstaller.php���a���Avendor/composer/composer/src/Composer/Installer/NoopInstaller.php ��a ����Fvendor/composer/composer/src/Composer/Installer/InstallerInterface.php���a��}�{�Bvendor/composer/composer/src/Composer/Installer/InstallerEvent.php���a�~�,�Cvendor/composer/composer/src/Composer/Installer/BinaryInstaller.phpA9��aA9�ȋ�@vendor/composer/composer/src/Composer/Installer/PackageEvent.phpM
��aM
?y�%�Dvendor/composer/composer/src/Composer/Installer/ProjectInstaller.php� ��a� �3+��Kvendor/composer/composer/src/Composer/Installer/BinaryPresenceInterface.php���a������Mvendor/composer/composer/src/Composer/Installer/SuggestedPackagesReporter.php!��a!�K)S�Gvendor/composer/composer/src/Composer/Installer/InstallationManager.php�b��a�b��u#�Cvendor/composer/composer/src/Composer/Installer/InstallerEvents.php{��a{��3�Avendor/composer/composer/src/Composer/Installer/PackageEvents.php���a�Z�V��=vendor/composer/composer/src/Composer/SelfUpdate/Versions.php6 ��a6 �E�Ȥ9vendor/composer/composer/src/Composer/SelfUpdate/Keys.php���a�D��nvendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilter.php     ��a     i��kvendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterFactory.php���a�~� �mvendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterInterface.php���a�Vqvendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilter.php'��a'u��8�mvendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreAllPlatformRequirementFilter.phpu��au v ��Rvendor/composer/composer/src/Composer/EventDispatcher/ScriptExecutionException.php���a�\!'Ƥ?vendor/composer/composer/src/Composer/EventDispatcher/Event.php���a�JvU��Rvendor/composer/composer/src/Composer/EventDispatcher/EventSubscriberInterface.php\��a\���*�Ivendor/composer/composer/src/Composer/EventDispatcher/EventDispatcher.phpg]��ag]�SaפKvendor/composer/composer/src/Composer/DependencyResolver/RuleWatchGraph.phpW��aWA�\ФJvendor/composer/composer/src/Composer/DependencyResolver/PoolOptimizer.php�E��a�EӒS�Cvendor/composer/composer/src/Composer/DependencyResolver/Solver.php�h��a�h�����Avendor/composer/composer/src/Composer/DependencyResolver/Rule.php�O��a�O�dk�Tvendor/composer/composer/src/Composer/DependencyResolver/SolverProblemsException.php���a���R�Jvendor/composer/composer/src/Composer/DependencyResolver/Rule2Literals.php|
��a|
���Jvendor/composer/composer/src/Composer/DependencyResolver/RuleWatchNode.phpt ��at �g���Dvendor/composer/composer/src/Composer/DependencyResolver/RuleSet.php��a)�y�Avendor/composer/composer/src/Composer/DependencyResolver/Pool.php�!��a�!�"gp�Nvendor/composer/composer/src/Composer/DependencyResolver/MultiConflictRule.php7
��a7
�lX��dvendor/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php ��a 8�ˤVvendor/composer/composer/src/Composer/DependencyResolver/Operation/SolverOperation.php��ai`�<�Vvendor/composer/composer/src/Composer/DependencyResolver/Operation/UpdateOperation.phpN ��aN �}��Yvendor/composer/composer/src/Composer/DependencyResolver/Operation/OperationInterface.php���a�
Y/z�bvendor/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php��aD
ФYvendor/composer/composer/src/Composer/DependencyResolver/Operation/UninstallOperation.php*��a*.�n�Wvendor/composer/composer/src/Composer/DependencyResolver/Operation/InstallOperation.php@��a@$Z@�Qvendor/composer/composer/src/Composer/DependencyResolver/LocalRepoTransaction.php��a,4��Lvendor/composer/composer/src/Composer/DependencyResolver/LockTransaction.php_��a_DX���Lvendor/composer/composer/src/Composer/DependencyResolver/RuleSetIterator.phpp ��ap ��k�Kvendor/composer/composer/src/Composer/DependencyResolver/RuleWatchChain.php���a��ջ�Hvendor/composer/composer/src/Composer/DependencyResolver/GenericRule.php���a�P�{��Fvendor/composer/composer/src/Composer/DependencyResolver/Decisions.php���a�=�ΤLvendor/composer/composer/src/Composer/DependencyResolver/PolicyInterface.php���a��Pj�Ovendor/composer/composer/src/Composer/DependencyResolver/SolverBugException.php,��a,f�o�Hvendor/composer/composer/src/Composer/DependencyResolver/PoolBuilder.php�x��a�x����Dvendor/composer/composer/src/Composer/DependencyResolver/Problem.php$j��a$j:\���Mvendor/composer/composer/src/Composer/DependencyResolver/RuleSetGenerator.php�4��a�4�ӟ�Hvendor/composer/composer/src/Composer/DependencyResolver/Transaction.php�6��a�6����Dvendor/composer/composer/src/Composer/DependencyResolver/Request.php* ��a* 5&>w�Jvendor/composer/composer/src/Composer/DependencyResolver/DefaultPolicy.php���a�ZŤEvendor/composer/composer/src/Composer/Console/HtmlOutputFormatter.php� ��a� :B�=vendor/composer/composer/src/Composer/Console/Application.php�b��a�bB���Cvendor/composer/composer/src/Composer/Console/GithubActionError.php$    ��a$    T�H�Bvendor/composer/composer/src/Composer/Command/RunScriptCommand.phpg��ag�ⶤ=vendor/composer/composer/src/Composer/Command/InitCommand.php����a��dN"�Dvendor/composer/composer/src/Composer/Command/ScriptAliasCommand.php���a�^f�̤@vendor/composer/composer/src/Composer/Command/RequireCommand.php�X��a�X��G�@vendor/composer/composer/src/Composer/Command/DependsCommand.php��a�m��Fvendor/composer/composer/src/Composer/Command/CreateProjectCommand.php�^��a�^8We��Evendor/composer/composer/src/Composer/Command/DumpAutoloadCommand.php���a��(4��Bvendor/composer/composer/src/Composer/Command/ReinstallCommand.php���a���pv�Cvendor/composer/composer/src/Composer/Command/ClearCacheCommand.php���a�X0<ˤ?vendor/composer/composer/src/Composer/Command/RemoveCommand.php.8��a.8ڳ�դ@vendor/composer/composer/src/Composer/Command/InstallCommand.php5��a5�ȯ�Avendor/composer/composer/src/Composer/Command/OutdatedCommand.php���a��LAvendor/composer/composer/src/Composer/Command/DiagnoseCommand.php�l��a�l=�墤Bvendor/composer/composer/src/Composer/Command/ProhibitsCommand.php���a���DD�?vendor/composer/composer/src/Composer/Command/SearchCommand.php���a��A���?vendor/composer/composer/src/Composer/Command/ConfigCommand.php{���a{�s>���=vendor/composer/composer/src/Composer/Command/BaseCommand.php(&��a(&��� �=vendor/composer/composer/src/Composer/Command/ExecCommand.php� ��a� Β���=vendor/composer/composer/src/Composer/Command/HomeCommand.php��a|̐ؤ?vendor/composer/composer/src/Composer/Command/UpdateCommand.php�A��a�A�J~X�=vendor/composer/composer/src/Composer/Command/ShowCommand.php����a���Quդ?vendor/composer/composer/src/Composer/Command/StatusCommand.php/!��a/!�����Avendor/composer/composer/src/Composer/Command/LicensesCommand.phpO��aO�w��=vendor/composer/composer/src/Composer/Command/FundCommand.php$��a$2�C�Jvendor/composer/composer/src/Composer/Command/CheckPlatformReqsCommand.phpC��aCš���?vendor/composer/composer/src/Composer/Command/GlobalCommand.php���a�z@ ~�@vendor/composer/composer/src/Composer/Command/ArchiveCommand.phpu��au�{<�>vendor/composer/composer/src/Composer/Command/AboutCommand.php��aq�y9�Cvendor/composer/composer/src/Composer/Command/SelfUpdateCommand.php�d��a�d�J��Gvendor/composer/composer/src/Composer/Command/BaseDependencyCommand.php�#��a�#�nG:�Avendor/composer/composer/src/Composer/Command/SuggestsCommand.php���a���Qc�Avendor/composer/composer/src/Composer/Command/ValidateCommand.php�+��a�+E�M�1vendor/composer/composer/src/Composer/Factory.php�o��a�o�U0��3vendor/composer/composer/src/Composer/Installer.php$���a$��ቧ�6vendor/composer/composer/src/Composer/IO/ConsoleIO.php*��a*j&U��3vendor/composer/composer/src/Composer/IO/NullIO.php���a���D�3vendor/composer/composer/src/Composer/IO/BaseIO.phpt��atx��8vendor/composer/composer/src/Composer/IO/IOInterface.php ��a ��Z �5vendor/composer/composer/src/Composer/IO/BufferIO.php� ��a� IL[�Bvendor/composer/composer/src/Composer/Package/PackageInterface.php-��a-����Avendor/composer/composer/src/Composer/Package/CompletePackage.phpU��aUdo�֤Hvendor/composer/composer/src/Composer/Package/Loader/LoaderInterface.phpz��az��3H�Pvendor/composer/composer/src/Composer/Package/Loader/InvalidPackageException.php&��a&�I��Cvendor/composer/composer/src/Composer/Package/Loader/JsonLoader.php���a��Y��Nvendor/composer/composer/src/Composer/Package/Loader/ValidatingArrayLoader.phpZq��aZq�`6e�Dvendor/composer/composer/src/Composer/Package/Loader/ArrayLoader.php�E��a�E�����Jvendor/composer/composer/src/Composer/Package/Loader/RootPackageLoader.php�,��a�,Y*�ޤ>vendor/composer/composer/src/Composer/Package/AliasPackage.php%��a%�+��8vendor/composer/composer/src/Composer/Package/Locker.php"D��a"D    �)��9vendor/composer/composer/src/Composer/Package/Package.phpF��aF_�N��Fvendor/composer/composer/src/Composer/Package/CompleteAliasPackage.phpp��ap�N\��Cvendor/composer/composer/src/Composer/Package/Comparer/Comparer.php7��a7J鱄�=vendor/composer/composer/src/Composer/Package/RootPackage.php�
��a�
z���Bvendor/composer/composer/src/Composer/Package/RootAliasPackage.php���a���;+�Fvendor/composer/composer/src/Composer/Package/RootPackageInterface.php���a�?s?�Gvendor/composer/composer/src/Composer/Package/Archiver/PharArchiver.php ��a R}���Pvendor/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFinder.phpj ��aj �_]��Lvendor/composer/composer/src/Composer/Package/Archiver/BaseExcludeFilter.php���a�m嶤Fvendor/composer/composer/src/Composer/Package/Archiver/ZipArchiver.php ��a ��).�Lvendor/composer/composer/src/Composer/Package/Archiver/ArchiverInterface.phpj��aj]aC�Ivendor/composer/composer/src/Composer/Package/Archiver/ArchiveManager.php���a��q�ۤPvendor/composer/composer/src/Composer/Package/Archiver/ComposerExcludeFilter.phpZ��aZ��k�Pvendor/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFilter.php]��a]a���Kvendor/composer/composer/src/Composer/Package/Archiver/GitExcludeFilter.php���a��̀դJvendor/composer/composer/src/Composer/Package/CompletePackageInterface.php���a��G���6vendor/composer/composer/src/Composer/Package/Link.php���a�O��E�Gvendor/composer/composer/src/Composer/Package/Version/VersionParser.php� ��a� 3�MդHvendor/composer/composer/src/Composer/Package/Version/VersionGuesser.php�?��a�?��� �Ivendor/composer/composer/src/Composer/Package/Version/VersionSelector.php�(��a�(Zb]�Ivendor/composer/composer/src/Composer/Package/Version/StabilityFilter.php)��a)?Zՠ�Dvendor/composer/composer/src/Composer/Package/Dumper/ArrayDumper.php3��a3�#���=vendor/composer/composer/src/Composer/Package/BasePackage.php���a�H�^��2vendor/composer/composer/src/Composer/Composer.php��a���:�Fvendor/composer/composer/src/Composer/Json/JsonValidationException.phpe��ae�y̤>vendor/composer/composer/src/Composer/Json/JsonManipulator.php+Q��a+Q@���<vendor/composer/composer/src/Composer/Json/JsonFormatter.php���a��D|�7vendor/composer/composer/src/Composer/Json/JsonFile.phpK+��aK+[9���?vendor/composer/composer/src/Composer/Platform/HhvmDetector.php��a�c˲�:vendor/composer/composer/src/Composer/Platform/Version.php� ��a� 4�n��:vendor/composer/composer/src/Composer/Platform/Runtime.phpN    ��aN    ��*`�*vendor/composer/composer/src/bootstrap.php��a��u)�!vendor/composer/autoload_real.phpE
��aE
�dV�%vendor/composer/autoload_classmap.php���a�;봤2vendor/composer/spdx-licenses/src/SpdxLicenses.phpw&��aw&�N��*vendor/composer/ca-bundle/src/CaBundle.php�D��a�DA�ʤvendor/composer/installed.phpQS��aQSוD�-vendor/composer/xdebug-handler/src/Status.php+��a+&e�0vendor/composer/xdebug-handler/src/PhpConfig.php���a�Q=D��4vendor/composer/xdebug-handler/src/XdebugHandler.phpoU��aoU�����.vendor/composer/xdebug-handler/src/Process.phpw ��aw �ð �#vendor/composer/autoload_static.php�.��a�.!"Ds�*vendor/composer/pcre/src/PcreException.php���a�e,��6vendor/composer/pcre/src/MatchAllWithOffsetsResult.php���a���-�"vendor/composer/pcre/src/Regex.phpa��aaX�~�!vendor/composer/pcre/src/Preg.php>*��a>*6% Ƥ(vendor/composer/pcre/src/MatchResult.php���a����+vendor/composer/pcre/src/MatchAllResult.phpv��av��*:�*vendor/composer/pcre/src/ReplaceResult.php���a��K�2�3vendor/composer/pcre/src/MatchWithOffsetsResult.php���a�pa8w�'vendor/composer/autoload_namespaces.php���a�'o泤%vendor/composer/InstalledVersions.php�:��a�:T��"�"vendor/composer/autoload_files.php� ��a� �m��,vendor/composer/semver/src/VersionParser.phpT��aTw�N�'vendor/composer/semver/src/Interval.php~��a~E|�V�4vendor/composer/semver/src/Constraint/Constraint.php�1��a�1��.�9vendor/composer/semver/src/Constraint/MultiConstraint.phpA%��aA%��w��=vendor/composer/semver/src/Constraint/ConstraintInterface.php���a���vx�/vendor/composer/semver/src/Constraint/Bound.phpe
��ae
�M��=vendor/composer/semver/src/Constraint/MatchNoneConstraint.php���a�?T>��<vendor/composer/semver/src/Constraint/MatchAllConstraint.php���a��D��(vendor/composer/semver/src/Intervals.php�O��a�O3���)vendor/composer/semver/src/Comparator.phpD
��aD
Ș܁�/vendor/composer/semver/src/CompilingMatcher.php���a�����%vendor/composer/semver/src/Semver.php? ��a? =����:vendor/composer/metadata-minifier/src/MetadataMinifier.php�
��a�
�l�3�!vendor/composer/autoload_psr4.php\ ��a\ 3��դ"vendor/composer/platform_check.php���a�T��t�'vendor/symfony/polyfill-ctype/Ctype.php}��a}\�谤+vendor/symfony/polyfill-ctype/bootstrap.php��a�01��)vendor/symfony/filesystem/LockHandler.php>��a>8H �<vendor/symfony/filesystem/Exception/IOExceptionInterface.php���a���i��3vendor/symfony/filesystem/Exception/IOException.php���a�ё�A�:vendor/symfony/filesystem/Exception/ExceptionInterface.php���a�h���=vendor/symfony/filesystem/Exception/FileNotFoundException.php���a��0z¤(vendor/symfony/filesystem/Filesystem.phpGt��aGt.WX��Dvendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php���a�A|\�0vendor/symfony/console/Input/InputDefinition.php�+��a�+�|�,vendor/symfony/console/Input/StringInput.phpF    ��aF    ��c:�.vendor/symfony/console/Input/InputArgument.phpO ��aO (���&vendor/symfony/console/Input/Input.phpr��arU�jx�9vendor/symfony/console/Input/StreamableInputInterface.phpi��ai����/vendor/symfony/console/Input/InputInterface.php���a��Έڤ4vendor/symfony/console/Input/InputAwareInterface.php:��a:� '��+vendor/symfony/console/Input/ArrayInput.php0��a0mQj�*vendor/symfony/console/Input/ArgvInput.php&/��a&/����,vendor/symfony/console/Input/InputOption.php���a���6]�,vendor/symfony/console/Question/Question.php���a��Ί��2vendor/symfony/console/Question/ChoiceQuestion.php]��a]�5�'�8vendor/symfony/console/Question/ConfirmationQuestion.php#��a#ρ��&vendor/symfony/console/Application.php6���a6��A�c�/vendor/symfony/console/Tester/CommandTester.php���a��Dd��3vendor/symfony/console/Tester/ApplicationTester.php
��a
Z���=vendor/symfony/console/Exception/InvalidArgumentException.php���a��u i�=vendor/symfony/console/Exception/CommandNotFoundException.php���a�.�o��3vendor/symfony/console/Exception/LogicException.php���a�SML��7vendor/symfony/console/Exception/ExceptionInterface.php���a����U�;vendor/symfony/console/Exception/InvalidOptionException.php���a��;�5vendor/symfony/console/Exception/RuntimeException.php���a��*b�8vendor/symfony/console/Descriptor/MarkdownDescriptor.php���a�f 0>�3vendor/symfony/console/Descriptor/XmlDescriptor.php�#��a�#��dK�9vendor/symfony/console/Descriptor/DescriptorInterface.php���a�P�Z��4vendor/symfony/console/Descriptor/JsonDescriptor.php��an��<vendor/symfony/console/Descriptor/ApplicationDescription.php���a�����0vendor/symfony/console/Descriptor/Descriptor.phpd ��ad ���T�4vendor/symfony/console/Descriptor/TextDescriptor.phpf0��af0��W�6vendor/symfony/console/EventListener/ErrorListener.php�
��a�
e?�ߤ6vendor/symfony/console/Helper/DebugFormatterHelper.phpH��aH]R`A�(vendor/symfony/console/Helper/Helper.php���a�}Ku��-vendor/symfony/console/Helper/ProgressBar.php?C��a?C8���1vendor/symfony/console/Helper/HelperInterface.phpp��ap���n�,vendor/symfony/console/Helper/TableStyle.php{��a{�pn�0vendor/symfony/console/Helper/QuestionHelper.phpB��aB�ݗ^�'vendor/symfony/console/Helper/Table.php�M��a�M�!<�2vendor/symfony/console/Helper/InputAwareHelper.php���a����0vendor/symfony/console/Helper/TableSeparator.php��a&�� �3vendor/symfony/console/Helper/ProgressIndicator.php���a��ДP�+vendor/symfony/console/Helper/TableCell.phpR��aR�J{��1vendor/symfony/console/Helper/FormatterHelper.php�
��a�
U���+vendor/symfony/console/Helper/HelperSet.php�    ��a�    �3C �2vendor/symfony/console/Helper/DescriptorHelper.php�    ��a�    ����7vendor/symfony/console/Helper/SymfonyQuestionHelper.php���a�9���/vendor/symfony/console/Helper/ProcessHelper.php���a��t��?vendor/symfony/console/CommandLoader/CommandLoaderInterface.php���a�����?vendor/symfony/console/CommandLoader/ContainerCommandLoader.php>��a>#��*�=vendor/symfony/console/CommandLoader/FactoryCommandLoader.php7��a7�m_�/vendor/symfony/console/Logger/ConsoleLogger.php���a���U�/vendor/symfony/console/Output/ConsoleOutput.php���a��JDb�1vendor/symfony/console/Output/OutputInterface.php ��a ���&�0vendor/symfony/console/Output/BufferedOutput.phpI��aI�\��(vendor/symfony/console/Output/Output.php{��a{��7�8vendor/symfony/console/Output/ConsoleOutputInterface.php���a�����.vendor/symfony/console/Output/StreamOutput.phpo��ao �~�,vendor/symfony/console/Output/NullOutput.phpn��ant�D��#vendor/symfony/console/Terminal.php���a�u��.vendor/symfony/console/Command/HelpCommand.php?    ��a?    ���'�*vendor/symfony/console/Command/Command.php�L��a�LF����0vendor/symfony/console/Command/LockableTrait.php���a���O�.vendor/symfony/console/Command/ListCommand.php�    ��a�    �#�/vendor/symfony/console/Style/StyleInterface.php( ��a( �5 �-vendor/symfony/console/Style/SymfonyStyle.phpO/��aO/A z�,vendor/symfony/console/Style/OutputStyle.php� ��a� ���~�9vendor/symfony/console/Formatter/OutputFormatterStyle.php2��a2S���Bvendor/symfony/console/Formatter/OutputFormatterStyleInterface.php<��a<���Z�4vendor/symfony/console/Formatter/OutputFormatter.php���a�N��x�>vendor/symfony/console/Formatter/OutputFormatterStyleStack.php�    ��a�    ���Ҥ=vendor/symfony/console/Formatter/OutputFormatterInterface.php���a�#7�u�6vendor/symfony/console/Event/ConsoleTerminateEvent.php��a{e��-vendor/symfony/console/Event/ConsoleEvent.php���a�xS*�6vendor/symfony/console/Event/ConsoleExceptionEvent.phpT��aT����2vendor/symfony/console/Event/ConsoleErrorEvent.php���a�l���4vendor/symfony/console/Event/ConsoleCommandEvent.php%��a%{˾�(vendor/symfony/console/ConsoleEvents.php!��a!��Ω�%vendor/symfony/finder/SplFileInfo.phpW��aW!tVb�6vendor/symfony/finder/Exception/ExceptionInterface.php"��a"ڀfW�9vendor/symfony/finder/Exception/AccessDeniedException.php���a��cWޤ3vendor/symfony/finder/Comparator/DateComparator.php���a���Gä/vendor/symfony/finder/Comparator/Comparator.php��a~4�ؤ5vendor/symfony/finder/Comparator/NumberComparator.php
��a
����:vendor/symfony/finder/Iterator/DateRangeFilterIterator.php���a�$99�=vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php.��a.�sӤ9vendor/symfony/finder/Iterator/FilenameFilterIterator.php���a� �p��:vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php���a�8K��7vendor/symfony/finder/Iterator/CustomFilterIterator.php���a���  �3vendor/symfony/finder/Iterator/SortableIterator.php@
��a@
�[ �<vendor/symfony/finder/Iterator/FilecontentFilterIterator.php���a�r�~�1vendor/symfony/finder/Iterator/FilterIterator.php���a�VASs�Avendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php�    ��a�    t�褤=vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.phpz ��az 6�즤5vendor/symfony/finder/Iterator/PathFilterIterator.php���a�}���;vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php���a��*��9vendor/symfony/finder/Iterator/FileTypeFilterIterator.php>��a>p��Y� vendor/symfony/finder/Finder.php{N��a{N�I���vendor/symfony/finder/Glob.php���a����{�Dvendor/symfony/polyfill-mbstring/Resources/mb_convert_variables.php8?��a?�Fvendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php9��a9>|zK�@vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php�`��a�`�S���@vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php�_��a�_Z����.vendor/symfony/polyfill-mbstring/bootstrap.php���a�m�|D�-vendor/symfony/polyfill-mbstring/Mbstring.phpn��anp^�C�%vendor/symfony/process/PhpProcess.php�    ��a�    ����)vendor/symfony/process/ProcessBuilder.php
��a
j'Uh�.vendor/symfony/process/PhpExecutableFinder.phpG    ��aG    _�#�=vendor/symfony/process/Exception/InvalidArgumentException.php���a�˅��3vendor/symfony/process/Exception/LogicException.php���a���W�;vendor/symfony/process/Exception/ProcessFailedException.php���a�P��5�7vendor/symfony/process/Exception/ExceptionInterface.php���a��<��=vendor/symfony/process/Exception/ProcessTimedOutException.php{��a{4��5vendor/symfony/process/Exception/RuntimeException.php���a�>H���/vendor/symfony/process/Pipes/PipesInterface.php���a��+���*vendor/symfony/process/Pipes/UnixPipes.php,��a,q�-vendor/symfony/process/Pipes/WindowsPipes.phpu��auu��.vendor/symfony/process/Pipes/AbstractPipes.php���a���<�&vendor/symfony/process/InputStream.php    ��a    w'�Ȥ+vendor/symfony/process/ExecutableFinder.php"
��a"
%"x��'vendor/symfony/process/ProcessUtils.php���a�aXa�"vendor/symfony/process/Process.php����a���̞'�,vendor/react/promise/src/RejectedPromise.php���a��M�0�%vendor/react/promise/src/Deferred.php���a�
̤&vendor/react/promise/src/functions.php�/��a�/
hz�8vendor/react/promise/src/CancellablePromiseInterface.php���a��\lb�.vendor/react/promise/src/PromisorInterface.php���a�w�;�-vendor/react/promise/src/PromiseInterface.php���a�^� Ǥ6vendor/react/promise/src/Exception/LengthException.php^��a^?q�$vendor/react/promise/src/Promise.php�"��a�"��m�5vendor/react/promise/src/ExtendedPromiseInterface.phpv ��av ��I�(vendor/react/promise/src/LazyPromise.php���a��M�J�8vendor/react/promise/src/UnhandledRejectionException.phpe��aeg4ߤ.vendor/react/promise/src/functions_include.phpa��aa�|�-vendor/react/promise/src/FulfilledPromise.php^��a^�v�.vendor/react/promise/src/CancellationQueue.phpu��au���n�6vendor/wp-cli/entity-command/src/Menu_Item_Command.php�9��a�9���h�6vendor/wp-cli/entity-command/src/Network_Namespace.php*��a*{�d�?vendor/wp-cli/entity-command/src/WP_CLI/CommandWithDBObject.php���a���#�Jvendor/wp-cli/entity-command/src/WP_CLI/Entity/NonExistentKeyException.php���a��̱h�Rvendor/wp-cli/entity-command/src/WP_CLI/Entity/RecursiveDataStructureTraverser.php��a�Lm�8vendor/wp-cli/entity-command/src/WP_CLI/Entity/Utils.php���a��y㦤<vendor/wp-cli/entity-command/src/WP_CLI/CommandWithTerms.phpe��ae(Q>��;vendor/wp-cli/entity-command/src/WP_CLI/CommandWithMeta.php;9��a;9y�� �6vendor/wp-cli/entity-command/src/Post_Meta_Command.php���a������1vendor/wp-cli/entity-command/src/User_Command.phpʼn��aʼn�2p��1vendor/wp-cli/entity-command/src/Post_Command.phpq��aq�n��9vendor/wp-cli/entity-command/src/Comment_Meta_Command.php���a�Cq��4vendor/wp-cli/entity-command/src/Comment_Command.phpB��aB�\K�1vendor/wp-cli/entity-command/src/Site_Command.php�U��a�Uν�Fvendor/wp-cli/entity-command/src/User_Application_Password_Command.phpR@��aR@�Y>��6vendor/wp-cli/entity-command/src/Post_Term_Command.php��a)7���9vendor/wp-cli/entity-command/src/User_Session_Command.php���a��i���8vendor/wp-cli/entity-command/src/Site_Option_Command.php�'��a�'WW���6vendor/wp-cli/entity-command/src/User_Meta_Command.php�$��a�$RI|K�1vendor/wp-cli/entity-command/src/Menu_Command.php    ��a    ���ͤ6vendor/wp-cli/entity-command/src/Term_Meta_Command.php���a��B�s�:vendor/wp-cli/entity-command/src/Menu_Location_Command.php���a�d��1vendor/wp-cli/entity-command/src/Term_Command.php�K��a�K�G�3vendor/wp-cli/entity-command/src/Option_Command.php,?��a,?u��9vendor/wp-cli/entity-command/src/Network_Meta_Command.php]��a]�kN�5vendor/wp-cli/entity-command/src/Taxonomy_Command.php��a�o�?�6vendor/wp-cli/entity-command/src/User_Term_Command.php&��a&�{b�6vendor/wp-cli/entity-command/src/Site_Meta_Command.php���a��v���6vendor/wp-cli/entity-command/src/Post_Type_Command.php���a��z�_�/vendor/wp-cli/entity-command/entity-command.php�
��a�
��=��3vendor/wp-cli/i18n-command/src/PhpCodeExtractor.php���a�g����1vendor/wp-cli/i18n-command/src/BlockExtractor.php���a�j�{T�3vendor/wp-cli/i18n-command/src/MapCodeExtractor.php���a��c�d�8vendor/wp-cli/i18n-command/src/IterableCodeExtractor.php"��a"�P�r�5vendor/wp-cli/i18n-command/src/JsFunctionsScanner.php8.��a8.��8K�2vendor/wp-cli/i18n-command/src/JsCodeExtractor.php��a-�K�3vendor/wp-cli/i18n-command/src/CommandNamespace.php���a��lh��6vendor/wp-cli/i18n-command/src/PhpFunctionsScanner.php-    ��a-    ��>�5vendor/wp-cli/i18n-command/src/ThemeJsonExtractor.php���a��K�w�1vendor/wp-cli/i18n-command/src/MakePotCommand.php�~��a�~���&�/vendor/wp-cli/i18n-command/src/PotGenerator.php���a����ä2vendor/wp-cli/i18n-command/src/MakeJsonCommand.php)/��a)/xk��/vendor/wp-cli/i18n-command/src/JedGenerator.phpm��am�ʓ�0vendor/wp-cli/i18n-command/src/MakeMoCommand.phpF��aF��&�+vendor/wp-cli/i18n-command/i18n-command.php���a���p�3vendor/wp-cli/config-command/src/Config_Command.phpn���an��;�\�/vendor/wp-cli/config-command/config-command.php��a7��Q�4vendor/wp-cli/widget-command/src/Sidebar_Command.php���a��d���3vendor/wp-cli/widget-command/src/Widget_Command.phpjG��ajG����/vendor/wp-cli/widget-command/widget-command.phpD��aD�� ��Evendor/wp-cli/maintenance-mode-command/src/MaintenanceModeCommand.php7 ��a7 02=@�Cvendor/wp-cli/maintenance-mode-command/maintenance-mode-command.phpL��aL9�v4�5vendor/wp-cli/shell-command/src/WP_CLI/Shell/REPL.php� ��a� Db�1vendor/wp-cli/shell-command/src/Shell_Command.phpU��aU�n*�-vendor/wp-cli/shell-command/shell-command.php
��a
�ٷ�#vendor/wp-cli/wp-cli/utils/find-php\��a\�\�)�+vendor/wp-cli/wp-cli/utils/contrib-list.php���a�TE:�@vendor/wp-cli/wp-cli/utils/get-package-require-from-composer.php���a�� �Bvendor/wp-cli/search-replace-command/src/WP_CLI/SearchReplacer.phpR��aR$e��Cvendor/wp-cli/search-replace-command/src/Search_Replace_Command.php_���a_���$�?vendor/wp-cli/search-replace-command/search-replace-command.php7��a7����<vendor/wp-cli/package-command/src/WP_CLI/JsonManipulator.phpS��aSϢ��5vendor/wp-cli/package-command/src/Package_Command.phpH���aH��;O�1vendor/wp-cli/package-command/package-command.php:��a:`��7�,vendor/wp-cli/php-cli-tools/http-console.php��a��>�$vendor/wp-cli/php-cli-tools/test.phpi��ai3M��0vendor/wp-cli/php-cli-tools/lib/cli/Progress.php� ��a� �a��-vendor/wp-cli/php-cli-tools/lib/cli/Shell.phpF ��aF 6�}^�4vendor/wp-cli/php-cli-tools/lib/cli/progress/Bar.php�    ��a�    n�(O�6vendor/wp-cli/php-cli-tools/lib/cli/notify/Spinner.php��a8�u�3vendor/wp-cli/php-cli-tools/lib/cli/notify/Dots.phpd��ad�w�z�.vendor/wp-cli/php-cli-tools/lib/cli/Colors.phpk!��ak!y6)�1vendor/wp-cli/php-cli-tools/lib/cli/Arguments.php/��a/�,7{�-vendor/wp-cli/php-cli-tools/lib/cli/Table.php���a�t�*�/vendor/wp-cli/php-cli-tools/lib/cli/Streams.php#��a#6o-�Bvendor/wp-cli/php-cli-tools/lib/cli/arguments/InvalidArguments.php��a���#�:vendor/wp-cli/php-cli-tools/lib/cli/arguments/Argument.php  ��a  =���7vendor/wp-cli/php-cli-tools/lib/cli/arguments/Lexer.php^    ��a^    �G��<vendor/wp-cli/php-cli-tools/lib/cli/arguments/HelpScreen.php�
��a�
�O$v�/vendor/wp-cli/php-cli-tools/lib/cli/Memoize.php���a����5vendor/wp-cli/php-cli-tools/lib/cli/tree/Renderer.phpO��aO�c"Ӥ2vendor/wp-cli/php-cli-tools/lib/cli/tree/Ascii.phpx��ax9����5vendor/wp-cli/php-cli-tools/lib/cli/tree/Markdown.php���a��T魤+vendor/wp-cli/php-cli-tools/lib/cli/cli.php�@��a�@�~zפ6vendor/wp-cli/php-cli-tools/lib/cli/table/Renderer.phpG��aGY�ߒ�5vendor/wp-cli/php-cli-tools/lib/cli/table/Tabular.php���a����l�3vendor/wp-cli/php-cli-tools/lib/cli/table/Ascii.php ��a �@%=�,vendor/wp-cli/php-cli-tools/lib/cli/Tree.php���a���@�.vendor/wp-cli/php-cli-tools/lib/cli/Notify.php���a�,���5vendor/wp-cli/php-cli-tools/lib/cli/unicode/regex.phpb��abɶ9ͤ1vendor/wp-cli/embed-command/src/Fetch_Command.php�$��a�$����1vendor/wp-cli/embed-command/src/Cache_Command.php���a�m�P��4vendor/wp-cli/embed-command/src/Embeds_Namespace.php���a���q��3vendor/wp-cli/embed-command/src/Handler_Command.php��a`�W�4vendor/wp-cli/embed-command/src/Provider_Command.php���a�}t4��*vendor/wp-cli/embed-command/src/oEmbed.php���a��p�>�-vendor/wp-cli/embed-command/embed-command.phpx��ax#�Τ+vendor/wp-cli/core-command/core-command.php��a�:���Ivendor/wp-cli/core-command/src/WP_CLI/Core/NonDestructiveCoreUpgrader.php���a��!z~�;vendor/wp-cli/core-command/src/WP_CLI/Core/CoreUpgrader.php���a�Z�N*�/vendor/wp-cli/core-command/src/Core_Command.php����a���׮��1vendor/wp-cli/cache-command/src/Cache_Command.php1��a1�5�Ť5vendor/wp-cli/cache-command/src/Transient_Command.php�D��a�DJo��-vendor/wp-cli/cache-command/cache-command.phpC��aC&'e�1vendor/wp-cli/media-command/src/Media_Command.php&���a&����v�-vendor/wp-cli/media-command/media-command.php���a�6�X��Gvendor/wp-cli/checksum-command/src/WP_CLI/Fetchers/UnfilteredPlugin.php:��a:<�g�<vendor/wp-cli/checksum-command/src/Checksum_Base_Command.php    ��a    N���<vendor/wp-cli/checksum-command/src/Checksum_Core_Command.php���a��!M�>vendor/wp-cli/checksum-command/src/Checksum_Plugin_Command.php1!��a1!3g21�?vendor/wp-cli/checksum-command/src/Plugin_Command_Namespace.php���a��:f�=vendor/wp-cli/checksum-command/src/Core_Command_Namespace.php���a�{���3vendor/wp-cli/checksum-command/checksum-command.php���a�[z�s�-vendor/wp-cli/mustangostang-spyc/src/Spyc.php˅��a˅�~�)vendor/wp-cli/mustangostang-spyc/Spyc.php���a����x�7vendor/wp-cli/mustangostang-spyc/includes/functions.php��a��:�/vendor/wp-cli/mustangostang-spyc/php4/spyc.php4Iu��aIu�-.�.vendor/wp-cli/mustangostang-spyc/php4/5to4.php���a�uS])�/vendor/wp-cli/mustangostang-spyc/php4/test.php4���a� f3P�?vendor/wp-cli/wp-config-transformer/src/WPConfigTransformer.php�)��a�)c��5vendor/wp-cli/cron-command/src/Cron_Event_Command.php�9��a�9�Ż��8vendor/wp-cli/cron-command/src/Cron_Schedule_Command.phpi ��ai ��@�/vendor/wp-cli/cron-command/src/Cron_Command.php� ��a� ��S��+vendor/wp-cli/cron-command/cron-command.php���a���.�5vendor/wp-cli/rewrite-command/src/Rewrite_Command.php0��a0�;��1vendor/wp-cli/rewrite-command/rewrite-command.php��aK��+vendor/wp-cli/eval-command/eval-command.php=��a=��Ak�3vendor/wp-cli/eval-command/src/EvalFile_Command.php+��a+7��>�/vendor/wp-cli/eval-command/src/Eval_Command.php]��a]'����3vendor/wp-cli/scaffold-command/scaffold-command.php��a洃�7vendor/wp-cli/scaffold-command/src/Scaffold_Command.php����a��E�A�;vendor/wp-cli/scaffold-command/templates/block-php.mustache.��a.F^��9vendor/wp-cli/super-admin-command/super-admin-command.php���a�����=vendor/wp-cli/super-admin-command/src/Super_Admin_Command.php���a��E�Cvendor/wp-cli/extension-command/src/WP_CLI/ParsePluginNameInput.php���a�G0�-�Hvendor/wp-cli/extension-command/src/WP_CLI/DestructivePluginUpgrader.php���a�I�ɤGvendor/wp-cli/extension-command/src/WP_CLI/DestructiveThemeUpgrader.php���a�S���>vendor/wp-cli/extension-command/src/WP_CLI/Fetchers/Plugin.php"��a" F� �=vendor/wp-cli/extension-command/src/WP_CLI/Fetchers/Theme.php ��a ��3��Bvendor/wp-cli/extension-command/src/WP_CLI/ParseThemeNameInput.phph��ah�G��Avendor/wp-cli/extension-command/src/WP_CLI/CommandWithUpgrade.php�\��a�\���Ť9vendor/wp-cli/extension-command/src/Theme_Mod_Command.php���a��{�(�6vendor/wp-cli/extension-command/src/Plugin_Command.php6���a6��ƒ��Bvendor/wp-cli/extension-command/src/Plugin_AutoUpdates_Command.php`��a`��(��5vendor/wp-cli/extension-command/src/Theme_Command.php(`��a(`�Ş3�Avendor/wp-cli/extension-command/src/Theme_AutoUpdates_Command.php���a�����5vendor/wp-cli/extension-command/extension-command.php)��a)��4vendor/wp-cli/export-command/src/WP_Map_Iterator.php@��a@ѱ��3vendor/wp-cli/export-command/src/Export_Command.phpZ3��aZ3I�c~�4vendor/wp-cli/export-command/src/WP_Export_Query.php25��a25����7vendor/wp-cli/export-command/src/WP_Export_Returner.phpB��aB���M�=vendor/wp-cli/export-command/src/WP_Export_Term_Exception.phpC��aC�ǤAvendor/wp-cli/export-command/src/WP_Export_Split_Files_Writer.php- ��a- %@i�:vendor/wp-cli/export-command/src/WP_Export_File_Writer.php>��a>R}T��5vendor/wp-cli/export-command/src/WP_Export_Oxymel.php���a��l;�:vendor/wp-cli/export-command/src/WP_Export_Base_Writer.php���a�Ǵb��<vendor/wp-cli/export-command/src/WP_Export_WXR_Formatter.php�&��a�&[�{4�:vendor/wp-cli/export-command/src/WP_Iterator_Exception.php9��a9�w�9vendor/wp-cli/export-command/src/WP_Post_IDs_Iterator.phpI��aI��C�8vendor/wp-cli/export-command/src/WP_Export_Exception.php>��a>M��<vendor/wp-cli/export-command/src/WP_Export_XML_Over_HTTP.phpa��aa]����*vendor/wp-cli/export-command/functions.phpp��ap��/vendor/wp-cli/export-command/export-command.php��a��2d�3vendor/wp-cli/server-command/src/Server_Command.php, ��a, ��6\�'vendor/wp-cli/server-command/router.phpN��aN7���/vendor/wp-cli/server-command/server-command.php���a��+��Dvendor/wp-cli/language-command/src/WP_CLI/CommandWithTranslation.php� ��a� ,�٤Bvendor/wp-cli/language-command/src/WP_CLI/LanguagePackUpgrader.php� ��a� �n��<vendor/wp-cli/language-command/src/Core_Language_Command.php�&��a�&)�    ʤ>vendor/wp-cli/language-command/src/Plugin_Language_Command.php3��a3�BM��Cvendor/wp-cli/language-command/src/Site_Switch_Language_Command.php���a��1�=vendor/wp-cli/language-command/src/Theme_Language_Command.php]1��a]1���k�9vendor/wp-cli/language-command/src/Language_Namespace.php���a��)|��3vendor/wp-cli/language-command/language-command.php1��a1�7�g�7vendor/wp-cli/role-command/src/Capabilities_Command.php3��a3���/vendor/wp-cli/role-command/src/Role_Command.phpr)��ar)��/Ԥ+vendor/wp-cli/role-command/role-command.php;��a;a�v�3vendor/wp-cli/import-command/src/Import_Command.php9��a9���/vendor/wp-cli/import-command/import-command.php��a��ʤ+vendor/wp-cli/db-command/src/DB_Command.phpe��ae��ψ�'vendor/wp-cli/db-command/db-command.php���a����k�vendor/nb/oxymel/Oxymel.php)��a)�(rդvendor/nb/oxymel/OxymelTest.php���a�BL�V�3vendor/psr/log/Psr/Log/InvalidArgumentException.php`��a` �X1�/vendor/psr/log/Psr/Log/LoggerAwareInterface.php)��a)�j ��+vendor/psr/log/Psr/Log/LoggerAwareTrait.php���a�Q'��)vendor/psr/log/Psr/Log/AbstractLogger.php  ��a  G���*vendor/psr/log/Psr/Log/LoggerInterface.php* ��a* 1b!q�%vendor/psr/log/Psr/Log/NullLogger.php���a����I�#vendor/psr/log/Psr/Log/LogLevel.phpP��aP���&vendor/psr/log/Psr/Log/LoggerTrait.phpW ��aW �W�j�5vendor/seld/jsonlint/src/Seld/JsonLint/JsonParser.php(Q��a(Q�{�0vendor/seld/jsonlint/src/Seld/JsonLint/Lexer.php��a`��4vendor/seld/jsonlint/src/Seld/JsonLint/Undefined.php��a�g��@vendor/seld/jsonlint/src/Seld/JsonLint/DuplicateKeyException.phpR��aR�y#�;vendor/seld/jsonlint/src/Seld/JsonLint/ParsingException.php���a��;��)vendor/seld/phar-utils/src/Timestamps.php'��a'�%v��%vendor/seld/phar-utils/src/Linter.php� ��a� '4#��Fvendor/justinrainbow/json-schema/src/JsonSchema/Entity/JsonPointer.php> ��a> �W�Ivendor/justinrainbow/json-schema/src/JsonSchema/UriRetrieverInterface.php��a5�|[�;vendor/justinrainbow/json-schema/src/JsonSchema/Rfc3339.phpv��avx�$��Avendor/justinrainbow/json-schema/src/JsonSchema/SchemaStorage.php+��a+��_�Vvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidArgumentException.phpy��ay�WΖ�]vendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaMediaTypeException.phpW��aW%*)֤Tvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidConfigException.phpQ��aQ�J�Rvendor/justinrainbow/json-schema/src/JsonSchema/Exception/UriResolverException.phpJ��aJ��-�Pvendor/justinrainbow/json-schema/src/JsonSchema/Exception/ExceptionInterface.phpI��aI%|��^vendor/justinrainbow/json-schema/src/JsonSchema/Exception/UnresolvableJsonPointerException.php���a�.�mi�Tvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaException.phpN��aN��ݠ�Svendor/justinrainbow/json-schema/src/JsonSchema/Exception/JsonDecodingException.php���a�٣��Qvendor/justinrainbow/json-schema/src/JsonSchema/Exception/ValidationException.php��aE�B*�Nvendor/justinrainbow/json-schema/src/JsonSchema/Exception/RuntimeException.phpa��aa`���Wvendor/justinrainbow/json-schema/src/JsonSchema/Exception/ResourceNotFoundException.phpT��aT��:�Wvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSourceUriException.php\��a\�iR�Hvendor/justinrainbow/json-schema/src/JsonSchema/UriResolverInterface.php��aI��ѤJvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Constraint.php���a��ד�Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/SchemaConstraint.php���a��fK �Tvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/CollectionConstraint.php���a�N���\vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/TypeCheckInterface.php���a�c>�ϤYvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/StrictTypeCheck.php+��a+��QܤXvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/LooseTypeCheck.php��a�
���Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php�"��a�"ͥ#K�Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/NumberConstraint.php� ��a� ��9|�Svendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstraintInterface.php��at�e�Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/StringConstraint.php���a�~��G�Gvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.phpF��aF:0�/�Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ObjectConstraint.php���a�Fgc��Svendor/justinrainbow/json-schema/src/JsonSchema/Constraints/UndefinedConstraint.php�>��a�>}���Nvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeConstraint.php&��a&ٌ]�Nvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/EnumConstraint.phpq��aq�`� �Nvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/BaseConstraint.php%��a%��2��Kvendor/justinrainbow/json-schema/src/JsonSchema/Iterator/ObjectIterator.php�
��a�
H���Cvendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriResolver.php;��a;���Dvendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php�$��a�$X�h��Xvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/UriRetrieverInterface.php���a�?�r@�Rvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/PredefinedArray.php��a0�Rvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/FileGetContents.phpj
��aj
[}x#�Gvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/Curl.phpo��ao�n�Tvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/AbstractRetriever.phpr��ar���M�=vendor/justinrainbow/json-schema/src/JsonSchema/Validator.php�
��a�
8���Jvendor/justinrainbow/json-schema/src/JsonSchema/SchemaStorageInterface.php"��a"x&7�2vendor/gettext/gettext/src/TranslatorInterface.php%
��a%
�����*vendor/gettext/gettext/src/Translation.php�+��a�+��9�3vendor/gettext/gettext/src/Utils/ParsedFunction.php��a��b��5vendor/gettext/gettext/src/Utils/FunctionsScanner.phpI��aIahk$�7vendor/gettext/gettext/src/Utils/JsFunctionsScanner.php�$��a�$(��d�2vendor/gettext/gettext/src/Utils/ParsedComment.php\ ��a\ �ܺC�4vendor/gettext/gettext/src/Utils/DictionaryTrait.php���a��'��8vendor/gettext/gettext/src/Utils/PhpFunctionsScanner.php���a��(lФ1vendor/gettext/gettext/src/Utils/StringReader.php���a�P����:vendor/gettext/gettext/src/Utils/HeadersExtractorTrait.php~��a~���Ф?vendor/gettext/gettext/src/Utils/MultidimensionalArrayTrait.php� ��a� �i$a�-vendor/gettext/gettext/src/Utils/CsvTrait.php���a�S�*�:vendor/gettext/gettext/src/Utils/HeadersGeneratorTrait.php7��a76ÉG�2vendor/gettext/gettext/src/Generators/PhpArray.php���a��H�;�7vendor/gettext/gettext/src/Generators/CsvDictionary.php���a�CT �8vendor/gettext/gettext/src/Generators/JsonDictionary.php=��a=��{�-vendor/gettext/gettext/src/Generators/Jed.phpp��ap�,k.�/vendor/gettext/gettext/src/Generators/Xliff.php6��a6þQ��<vendor/gettext/gettext/src/Generators/GeneratorInterface.php���a�� g�,vendor/gettext/gettext/src/Generators/Mo.php���a�0�Ф-vendor/gettext/gettext/src/Generators/Csv.php���a���{�.vendor/gettext/gettext/src/Generators/Json.phpO��aOf�'N�3vendor/gettext/gettext/src/Generators/Generator.php���a��3�8vendor/gettext/gettext/src/Generators/YamlDictionary.php���a�L5���.vendor/gettext/gettext/src/Generators/Yaml.php���a������,vendor/gettext/gettext/src/Generators/Po.php
��a
��(S�+vendor/gettext/gettext/src/Translations.php�@��a�@�;��)vendor/gettext/gettext/src/Translator.php���a�J�>�0vendor/gettext/gettext/src/GettextTranslator.php���a�޼�|�$vendor/gettext/gettext/src/Merge.phpD��aD�m6P�3vendor/gettext/gettext/src/translator_functions.php0��a0��Ȥ2vendor/gettext/gettext/src/Extractors/PhpArray.phpG��aG�/DѤ7vendor/gettext/gettext/src/Extractors/CsvDictionary.phpR��aRW�G��.vendor/gettext/gettext/src/Extractors/Twig.php<��a<��K�8vendor/gettext/gettext/src/Extractors/JsonDictionary.php(��a(�-�*�<vendor/gettext/gettext/src/Extractors/ExtractorInterface.php ��a p}YФ/vendor/gettext/gettext/src/Extractors/VueJs.phpn6��an6��>�Avendor/gettext/gettext/src/Extractors/ExtractorMultiInterface.php���a���]��-vendor/gettext/gettext/src/Extractors/Jed.php��a{c��/vendor/gettext/gettext/src/Extractors/Xliff.php� ��a� W}��0vendor/gettext/gettext/src/Extractors/JsCode.php(��a(8�vU�,vendor/gettext/gettext/src/Extractors/Mo.phpm��ame�p�-vendor/gettext/gettext/src/Extractors/Csv.php��a�m8¤.vendor/gettext/gettext/src/Extractors/Json.php.��a.�w=u�1vendor/gettext/gettext/src/Extractors/PhpCode.php?��a?…�'�/vendor/gettext/gettext/src/Extractors/Blade.php���a�5 x�8vendor/gettext/gettext/src/Extractors/YamlDictionary.phpQ��aQ�t�s�3vendor/gettext/gettext/src/Extractors/Extractor.php���a� {�n�.vendor/gettext/gettext/src/Extractors/Yaml.php]��a]jя"�,vendor/gettext/gettext/src/Extractors/Po.php���a�ۘ�-vendor/gettext/gettext/src/BaseTranslator.php���a�3v�Ĥ)vendor/gettext/gettext/src/autoloader.php��aA�p�)vendor/gettext/languages/src/CldrData.phpM6��aM6��-�)vendor/gettext/languages/src/Category.php���a���j��1vendor/gettext/languages/src/FormulaConverter.php���a�3�|L�)vendor/gettext/languages/src/Language.php�>��a�>z:���+vendor/gettext/languages/src/autoloader.phpZ��aZ�.�r�-vendor/gettext/languages/src/Exporter/Xml.php���a�e$e��4vendor/gettext/languages/src/Exporter/Prettyjson.phpD��aDV&
��.vendor/gettext/languages/src/Exporter/Docs.php}��a}�Y��-vendor/gettext/languages/src/Exporter/Php.php��aԭE�.vendor/gettext/languages/src/Exporter/Json.php���a��n�G�2vendor/gettext/languages/src/Exporter/Exporter.php��a�<�.�.vendor/gettext/languages/src/Exporter/Ruby.php���a��5    U�.vendor/gettext/languages/src/Exporter/Html.php
��a
K��/�,vendor/gettext/languages/src/Exporter/Po.php���a�A���)vendor/mck89/peast/lib/Peast/Renderer.phpA���aA��(�5�&vendor/mck89/peast/lib/Peast/Peast.php���a���}p�&vendor/mck89/peast/lib/Peast/Query.php@ ��a@ �ޖؤ8vendor/mck89/peast/lib/Peast/Selector/Node/Part/Part.phpr��ar��Y'�?vendor/mck89/peast/lib/Peast/Selector/Node/Part/PseudoIndex.php-    ��a-    �F���@vendor/mck89/peast/lib/Peast/Selector/Node/Part/PseudoSimple.phpk��akx�׷�Bvendor/mck89/peast/lib/Peast/Selector/Node/Part/PseudoSelector.php_��a_�˗ �:vendor/mck89/peast/lib/Peast/Selector/Node/Part/Pseudo.php���a�@-핤=vendor/mck89/peast/lib/Peast/Selector/Node/Part/Attribute.php���a��2��8vendor/mck89/peast/lib/Peast/Selector/Node/Part/Type.php��a��)�4vendor/mck89/peast/lib/Peast/Selector/Node/Group.php ��a H;��7vendor/mck89/peast/lib/Peast/Selector/Node/Selector.php���a��:rդ9vendor/mck89/peast/lib/Peast/Selector/Node/Combinator.phpS��aS����0vendor/mck89/peast/lib/Peast/Selector/Parser.php�B��a�BG    d�3vendor/mck89/peast/lib/Peast/Selector/Exception.php���a�BQ:��1vendor/mck89/peast/lib/Peast/Selector/Matches.php���a��ކM�*vendor/mck89/peast/lib/Peast/Traverser.php���a�8�+��7vendor/mck89/peast/lib/Peast/Syntax/ES2021/Features.php���a�KY�A�/vendor/mck89/peast/lib/Peast/Syntax/Scanner.php����a��    v8�7vendor/mck89/peast/lib/Peast/Syntax/ES2022/Features.phpC��aC� ��7vendor/mck89/peast/lib/Peast/Syntax/ES2015/Features.php���a����@�6vendor/mck89/peast/lib/Peast/Syntax/ParserAbstract.php^$��a^$�4��3vendor/mck89/peast/lib/Peast/Syntax/JSX/Scanner.phps ��as ֙Ԥ2vendor/mck89/peast/lib/Peast/Syntax/JSX/Parser.php6��a6��դ8vendor/mck89/peast/lib/Peast/Syntax/CommentsRegistry.php�(��a�(�#��<vendor/mck89/peast/lib/Peast/Syntax/Node/SwitchStatement.php���a����:vendor/mck89/peast/lib/Peast/Syntax/Node/NewExpression.php���a��>��4vendor/mck89/peast/lib/Peast/Syntax/Node/Program.php�    ��a�    �Ԁv�<vendor/mck89/peast/lib/Peast/Syntax/Node/ImportSpecifier.php���a��!�I�;vendor/mck89/peast/lib/Peast/Syntax/Node/ThrowStatement.phpl��al�����;vendor/mck89/peast/lib/Peast/Syntax/Node/ForInStatement.php    ��a    P㛤?vendor/mck89/peast/lib/Peast/Syntax/Node/PropertyDefinition.php� ��a� ���H�Cvendor/mck89/peast/lib/Peast/Syntax/Node/ImportDefaultSpecifier.php���a���J�:vendor/mck89/peast/lib/Peast/Syntax/Node/WithStatement.phpi��ai$���6vendor/mck89/peast/lib/Peast/Syntax/Node/Function_.php� ��a� w�v�=vendor/mck89/peast/lib/Peast/Syntax/Node/BinaryExpression.php���a�vX $�9vendor/mck89/peast/lib/Peast/Syntax/Node/ForStatement.phpC ��aC 1���8vendor/mck89/peast/lib/Peast/Syntax/Node/RestElement.php���a�7^g��;vendor/mck89/peast/lib/Peast/Syntax/Node/NumericLiteral.php���a���� �<vendor/mck89/peast/lib/Peast/Syntax/Node/UnaryExpression.phpR    ��aR    +%6�9vendor/mck89/peast/lib/Peast/Syntax/Node/TryStatement.php    ��a    �8�"�Evendor/mck89/peast/lib/Peast/Syntax/Node/ImportNamespaceSpecifier.php���a�M�c�<vendor/mck89/peast/lib/Peast/Syntax/Node/AwaitExpression.phpU��aU����Cvendor/mck89/peast/lib/Peast/Syntax/Node/ExportNamedDeclaration.php�    ��a�    �����<vendor/mck89/peast/lib/Peast/Syntax/Node/ArrayExpression.php��at8��@vendor/mck89/peast/lib/Peast/Syntax/Node/FunctionDeclaration.php���a�7�� �9vendor/mck89/peast/lib/Peast/Syntax/Node/MetaProperty.php��a��: �8vendor/mck89/peast/lib/Peast/Syntax/Node/IfStatement.php�
��a�
%��=vendor/mck89/peast/lib/Peast/Syntax/Node/ClassDeclaration.php���a�%^�j�?vendor/mck89/peast/lib/Peast/Syntax/Node/VariableDeclarator.php���a��b�٤>vendor/mck89/peast/lib/Peast/Syntax/Node/PrivateIdentifier.php��a�o� �>vendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXIdentifier.php���a����L�=vendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXAttribute.php}��a}���Cvendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXSpreadAttribute.php���a�K&Q��Bvendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXClosingElement.php���a��f�Gvendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXExpressionContainer.php���a�]{�W�?vendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXSpreadChild.php���a�iMb��8vendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXText.php���a��Ƒ��Cvendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXOpeningFragment.php���a����<vendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXFragment.php�
��a�
̌Է�Cvendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXClosingFragment.php���a�N�\�;vendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXElement.php�
��a�
.*kB�Cvendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXEmptyExpression.php���a�:�8��Bvendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXNamespacedName.phpz��az�Ԅ=�Bvendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXOpeningElement.php���a��D&��Cvendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXBoundaryElement.php���a����Dvendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXMemberExpression.phpf��afh�=vendor/mck89/peast/lib/Peast/Syntax/Node/MethodDefinition.php���a��w\�@vendor/mck89/peast/lib/Peast/Syntax/Node/ExpressionStatement.php���a�[�m�6vendor/mck89/peast/lib/Peast/Syntax/Node/ClassBody.php,��a,�7���8vendor/mck89/peast/lib/Peast/Syntax/Node/NullLiteral.php��a�K��=vendor/mck89/peast/lib/Peast/Syntax/Node/DoWhileStatement.php2��a2�����:vendor/mck89/peast/lib/Peast/Syntax/Node/ObjectPattern.php���a����ܤ;vendor/mck89/peast/lib/Peast/Syntax/Node/ForOfStatement.php���a���,�4vendor/mck89/peast/lib/Peast/Syntax/Node/Pattern.php���a�����1vendor/mck89/peast/lib/Peast/Syntax/Node/Node.php� ��a� �\��Evendor/mck89/peast/lib/Peast/Syntax/Node/TaggedTemplateExpression.php\��a\�-��<vendor/mck89/peast/lib/Peast/Syntax/Node/TemplateElement.php�    ��a�    7��Ѥ<vendor/mck89/peast/lib/Peast/Syntax/Node/TemplateLiteral.phpk ��ak ��;`�;vendor/mck89/peast/lib/Peast/Syntax/Node/WhileStatement.php*��a*ꮓ��8vendor/mck89/peast/lib/Peast/Syntax/Node/StaticBlock.php���a��X~V�>vendor/mck89/peast/lib/Peast/Syntax/Node/LogicalExpression.phpZ��aZ��S��:vendor/mck89/peast/lib/Peast/Syntax/Node/BigIntLiteral.php��aAͤ<vendor/mck89/peast/lib/Peast/Syntax/Node/ChainExpression.php���a���e�7vendor/mck89/peast/lib/Peast/Syntax/Node/SwitchCase.phpX��aX�E �:vendor/mck89/peast/lib/Peast/Syntax/Node/RegExpLiteral.phpJ
��aJ
tq���Dvendor/mck89/peast/lib/Peast/Syntax/Node/ArrowFunctionExpression.php���a�х���>vendor/mck89/peast/lib/Peast/Syntax/Node/ContinueStatement.php���a���ZY�4vendor/mck89/peast/lib/Peast/Syntax/Node/Literal.php��a�5ʤ;vendor/mck89/peast/lib/Peast/Syntax/Node/CallExpression.phpW��aW��� �7vendor/mck89/peast/lib/Peast/Syntax/Node/Identifier.php���a��O�7vendor/mck89/peast/lib/Peast/Syntax/Node/Expression.php���a�zw�!�>vendor/mck89/peast/lib/Peast/Syntax/Node/ImportDeclaration.php���a�X�9�9vendor/mck89/peast/lib/Peast/Syntax/Node/ArrayPattern.php���a��ʑ(�:vendor/mck89/peast/lib/Peast/Syntax/Node/StringLiteral.php�    ��a�    5�v�Evendor/mck89/peast/lib/Peast/Syntax/Node/ExportDefaultDeclaration.php"��a"S(�6�=vendor/mck89/peast/lib/Peast/Syntax/Node/LabeledStatement.php���a�(���6vendor/mck89/peast/lib/Peast/Syntax/Node/Statement.php���a�����>vendor/mck89/peast/lib/Peast/Syntax/Node/ModuleDeclaration.php���a��nt��8vendor/mck89/peast/lib/Peast/Syntax/Node/CatchClause.php���a�����@vendor/mck89/peast/lib/Peast/Syntax/Node/VariableDeclaration.phpc��ac��I�;vendor/mck89/peast/lib/Peast/Syntax/Node/BooleanLiteral.phpI��aIm��|�Dvendor/mck89/peast/lib/Peast/Syntax/Node/ParenthesizedExpression.php���a�(��Ӥ;vendor/mck89/peast/lib/Peast/Syntax/Node/BreakStatement.phpy��ay� �2�<vendor/mck89/peast/lib/Peast/Syntax/Node/YieldExpression.php0��a0�)�Avendor/mck89/peast/lib/Peast/Syntax/Node/ExportAllDeclaration.php���a��"��;vendor/mck89/peast/lib/Peast/Syntax/Node/ThisExpression.php���a��%a��:vendor/mck89/peast/lib/Peast/Syntax/Node/SpreadElement.php���a�$U�g�9vendor/mck89/peast/lib/Peast/Syntax/Node/ChainElement.php���a�"� +�<vendor/mck89/peast/lib/Peast/Syntax/Node/ExportSpecifier.php���a�(���2vendor/mck89/peast/lib/Peast/Syntax/Node/Super.php���a�RC�(�;vendor/mck89/peast/lib/Peast/Syntax/Node/EmptyStatement.php���a�n���>vendor/mck89/peast/lib/Peast/Syntax/Node/DebuggerStatement.php���a�di��4vendor/mck89/peast/lib/Peast/Syntax/Node/Comment.phps��as��ɖ�?vendor/mck89/peast/lib/Peast/Syntax/Node/SequenceExpression.php���a�����=vendor/mck89/peast/lib/Peast/Syntax/Node/ObjectExpression.php���a�'!E�<vendor/mck89/peast/lib/Peast/Syntax/Node/ModuleSpecifier.php���a��GjC�Bvendor/mck89/peast/lib/Peast/Syntax/Node/ConditionalExpression.php    ��a    �����=vendor/mck89/peast/lib/Peast/Syntax/Node/UpdateExpression.php;    ��a;     ��=vendor/mck89/peast/lib/Peast/Syntax/Node/ImportExpression.phpO��aO��L�8vendor/mck89/peast/lib/Peast/Syntax/Node/Declaration.php���a�8�4�5vendor/mck89/peast/lib/Peast/Syntax/Node/Property.php��ar��=vendor/mck89/peast/lib/Peast/Syntax/Node/MemberExpression.php`
��a`
��G��?vendor/mck89/peast/lib/Peast/Syntax/Node/FunctionExpression.php���a����U�<vendor/mck89/peast/lib/Peast/Syntax/Node/ClassExpression.php���a�p~�ä>vendor/mck89/peast/lib/Peast/Syntax/Node/AssignmentPattern.php���a��RY�<vendor/mck89/peast/lib/Peast/Syntax/Node/ReturnStatement.php���a��:C�Avendor/mck89/peast/lib/Peast/Syntax/Node/AssignmentExpression.php2    ��a2    ��T��?vendor/mck89/peast/lib/Peast/Syntax/Node/AssignmentProperty.php���a���Aۤ;vendor/mck89/peast/lib/Peast/Syntax/Node/BlockStatement.php���a�����3vendor/mck89/peast/lib/Peast/Syntax/Node/Class_.phpy��ayn���.vendor/mck89/peast/lib/Peast/Syntax/Parser.php7���a7�`��p�0vendor/mck89/peast/lib/Peast/Syntax/Features.php� ��a� �ӛ�-vendor/mck89/peast/lib/Peast/Syntax/Token.php���a���B�0vendor/mck89/peast/lib/Peast/Syntax/Position.php���a�k�wG�5vendor/mck89/peast/lib/Peast/Syntax/EventsEmitter.php��a�{u�1vendor/mck89/peast/lib/Peast/Syntax/Exception.php ��a 7]i�7vendor/mck89/peast/lib/Peast/Syntax/ES2019/Features.php���a�0��^�-vendor/mck89/peast/lib/Peast/Syntax/Utils.php�&��a�&�L�s�6vendor/mck89/peast/lib/Peast/Syntax/SourceLocation.php���a��<��+vendor/mck89/peast/lib/Peast/Syntax/LSM.php���a���/�9vendor/mck89/peast/lib/Peast/Syntax/EncodingException.php���a�L�p�7vendor/mck89/peast/lib/Peast/Syntax/ES2018/Features.php(��a(��pf�7vendor/mck89/peast/lib/Peast/Syntax/ES2017/Features.php��a��H@�7vendor/mck89/peast/lib/Peast/Syntax/ES2016/Features.php��a�t~��7vendor/mck89/peast/lib/Peast/Syntax/ES2020/Features.php��a�O�3vendor/mck89/peast/lib/Peast/Formatter/Expanded.php���a���x�2vendor/mck89/peast/lib/Peast/Formatter/Compact.php���a���t�/vendor/mck89/peast/lib/Peast/Formatter/Base.php�
��a�
�6�Ԥ6vendor/mck89/peast/lib/Peast/Formatter/PrettyPrint.phpr��ar�j�Ϥ+vendor/wp-cli/wp-cli/templates/man.mustache���a��I<�2vendor/wp-cli/wp-cli/templates/man-params.mustache?��a?��n�9vendor/wp-cli/config-command/templates/wp-config.mustache6 ��a6 x�Ǥ6vendor/wp-cli/core-command/templates/versions.mustache���a�SU"��?vendor/wp-cli/extension-command/templates/theme-status.mustachex��axǁgѤ@vendor/wp-cli/extension-command/templates/plugin-status.mustache���a�o[�5�>vendor/wp-cli/scaffold-command/templates/theme-status.mustachex��axǁgѤ9vendor/wp-cli/scaffold-command/templates/phpunit.xml.dist���a�%��F�Bvendor/wp-cli/scaffold-command/templates/plugin-gitignore.mustache^��a^��怤Avendor/wp-cli/scaffold-command/templates/plugin-packages.mustache^��a^�5�+�Avendor/wp-cli/scaffold-command/templates/theme-bootstrap.mustache���a�>��ʤDvendor/wp-cli/scaffold-command/templates/plugin-test-sample.mustache'��a'\~��Cvendor/wp-cli/scaffold-command/templates/taxonomy_extended.mustache���a��{�,�8vendor/wp-cli/scaffold-command/templates/plugin.mustacheq��aqG�qҤ8vendor/wp-cli/scaffold-command/templates/.phpcs.xml.dist=��a=[�-�Bvendor/wp-cli/scaffold-command/templates/plugin-gruntfile.mustache���a��T��Cvendor/wp-cli/scaffold-command/templates/theme-test-sample.mustache&��a&���:vendor/wp-cli/scaffold-command/templates/taxonomy.mustachei
��ai
��Rx�6vendor/wp-cli/scaffold-command/templates/.editorconfig���a��hѤBvendor/wp-cli/scaffold-command/templates/block-editor-css.mustache���a��,V]�Avendor/wp-cli/scaffold-command/templates/block-style-css.mustache��a��ؤ?vendor/wp-cli/scaffold-command/templates/plugin-circle.mustache< ��a< y�5�Cvendor/wp-cli/scaffold-command/templates/plugin-distignore.mustacheH��aH1EA��@vendor/wp-cli/scaffold-command/templates/block-index-js.mustache�
��a�
"��0�Bvendor/wp-cli/scaffold-command/templates/plugin-bitbucket.mustache���a�ZBgm�Dvendor/wp-cli/scaffold-command/templates/post_type_extended.mustache���a��;~e�Bvendor/wp-cli/scaffold-command/templates/plugin-bootstrap.mustache���a�����?vendor/wp-cli/scaffold-command/templates/plugin-gitlab.mustache\��a\��� �<vendor/wp-cli/scaffold-command/templates/install-wp-tests.sht��at����?vendor/wp-cli/scaffold-command/templates/plugin-readme.mustache��ab ��=vendor/wp-cli/scaffold-command/templates/child_theme.mustache���a������Gvendor/wp-cli/scaffold-command/templates/child_theme_functions.mustacheN��aN.���;vendor/wp-cli/scaffold-command/templates/post_type.mustache�
��a�
�3$��?vendor/wp-cli/scaffold-command/templates/plugin-travis.mustache���a�aG�R�vendor/autoload.php���a���
Q� vendor/composer/composer/LICENSE,��a,V�g��1vendor/composer/composer/res/composer-schema.json����a����-�<vendor/rmccue/requests/library/Requests/Transport/cacert.pem>��a>�UV�&vendor/wp-cli/wp-cli/COMPOSER_VERSIONS���a�iy��vendor/wp-cli/wp-cli/VERSION��aTdP�<?php

// Can be used by plugins/themes to check if WP-CLI is running or not.
define'WP_CLI'true );
define'WP_CLI_VERSION'trimfile_get_contentsWP_CLI_ROOT '/VERSION' ) ) );
define'WP_CLI_START_MICROTIME'microtimetrue ) );

if ( 
file_existsWP_CLI_ROOT '/vendor/autoload.php' ) ) {
    
define'WP_CLI_VENDOR_DIR'WP_CLI_ROOT '/vendor' );
} elseif ( 
file_existsdirnamedirnameWP_CLI_ROOT ) ) . '/autoload.php' ) ) {
    
define'WP_CLI_VENDOR_DIR'dirnamedirnameWP_CLI_ROOT ) ) );
} elseif ( 
file_existsdirnameWP_CLI_ROOT ) . '/vendor/autoload.php' ) ) {
    
define'WP_CLI_VENDOR_DIR'dirnameWP_CLI_ROOT ) . '/vendor' );
} else {
    
define'WP_CLI_VENDOR_DIR'WP_CLI_ROOT '/vendor' );
}

require_once 
WP_CLI_ROOT '/php/compat.php';

// Set common headers, to prevent warnings from plugins.
$_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.0';
$_SERVER['HTTP_USER_AGENT'] = '';
$_SERVER['REQUEST_METHOD']  = 'GET';
$_SERVER['REMOTE_ADDR']     = '127.0.0.1';

require_once 
WP_CLI_ROOT '/php/bootstrap.php';
WP_CLI\bootstrap();
<?
php

namespace WP_CLI;

use 
Exception;

class 
ExitException extends Exception {}
<?
php

namespace WP_CLI;

/**
 * Generate a synopsis from a command's PHPdoc arguments.
 * Turns something like "<object-id>..."
 * into [ optional=>false, type=>positional, repeating=>true, name=>object-id ]
 */
class SynopsisParser {

    
/**
     * @param string $synopsis A synopsis
     * @return array List of parameters
     */
    
public static function parse$synopsis ) {
        
$tokens array_filterpreg_split'/[\s\t]+/'$synopsis ) );

        
$params = [];
        foreach ( 
$tokens as $token ) {
            
$param self::classify_token$token );

            
// Some types of parameters shouldn't be mandatory
            
if ( isset( $param['optional'] ) && ! $param['optional'] ) {
                if ( 
'flag' === $param['type'] || ( 'assoc' === $param['type'] && $param['value']['optional'] ) ) {
                    
$param['type'] = 'unknown';
                }
            }

            
$param['token'] = $token;
            
$params[]       = $param;
        }

        return 
$params;
    }

    
/**
     * Render the Synopsis into a format string.
     *
     * @param array $synopsis A structured synopsis. This might get reordered
     *                        to match the parsed output.
     * @return string Rendered synopsis.
     */
    
public static function render( &$synopsis ) {
        if ( ! 
is_array$synopsis ) ) {
            return 
'';
        }
        
$bits               = [
            
'positional' => '',
            
'assoc'      => '',
            
'generic'    => '',
            
'flag'       => '',
        ];
        
$reordered_synopsis = [
            
'positional' => [],
            
'assoc'      => [],
            
'generic'    => [],
            
'flag'       => [],
        ];
        foreach ( 
$bits as $key => &$value ) {
            foreach ( 
$synopsis as $arg ) {
                if ( empty( 
$arg['type'] )
                    || 
$key !== $arg['type'] ) {
                    continue;
                }

                if ( empty( 
$arg['name'] ) && 'generic' !== $arg['type'] ) {
                    continue;
                }

                if ( 
'positional' === $key ) {
                    
$rendered_arg "<{$arg['name']}>";

                    
$reordered_synopsis['positional'] [] = $arg;
                } elseif ( 
'assoc' === $key ) {
                    
$arg_value    = isset( $arg['value']['name'] ) ? $arg['value']['name'] : $arg['name'];
                    
$rendered_arg "--{$arg['name']}=<{$arg_value}>";

                    
$reordered_synopsis['assoc'] [] = $arg;
                } elseif ( 
'generic' === $key ) {
                    
$rendered_arg '--<field>=<value>';

                    
$reordered_synopsis['generic'] [] = $arg;
                } elseif ( 
'flag' === $key ) {
                    
$rendered_arg "--{$arg['name']}";

                    
$reordered_synopsis['flag'] [] = $arg;
                }
                if ( ! empty( 
$arg['repeating'] ) ) {
                    
$rendered_arg "{$rendered_arg}...";
                }
                if ( ! empty( 
$arg['optional'] ) ) {
                    
$rendered_arg "[{$rendered_arg}]";
                }
                
$value .= "{$rendered_arg} ";
            }
        }
        
$rendered '';
        foreach ( 
$bits as $v ) {
            if ( ! empty( 
$v ) ) {
                
$rendered .= $v;
            }
        }

        
$synopsis array_merge(
            
$reordered_synopsis['positional'],
            
$reordered_synopsis['assoc'],
            
$reordered_synopsis['generic'],
            
$reordered_synopsis['flag']
        );

        return 
rtrim$rendered' ' );
    }

    
/**
     * Classify argument attributes based on its syntax.
     *
     * @param string $token
     * @return array
     */
    
private static function classify_token$token ) {
        
$param = [];

        list( 
$param['optional'], $token )  = self::is_optional$token );
        list( 
$param['repeating'], $token ) = self::is_repeating$token );

        
$p_name  '([a-z-_0-9]+)';
        
$p_value '([a-zA-Z-_|,0-9]+)';

        if ( 
'--<field>=<value>' === $token ) {
            
$param['type'] = 'generic';
        } elseif ( 
preg_match"/^<($p_value)>$/"$token$matches ) ) {
            
$param['type'] = 'positional';
            
$param['name'] = $matches[1];
        } elseif ( 
preg_match"/^--(?:\\[no-\\])?$p_name/"$token$matches ) ) {
            
$param['name'] = $matches[1];

            
$value substr$tokenstrlen$matches[0] ) );

            
// substr returns false <= PHP 5.6, and '' PHP 7+
            
if ( false === $value || '' === $value ) {
                
$param['type'] = 'flag';
            } else {
                
$param['type'] = 'assoc';

                list( 
$param['value']['optional'], $value ) = self::is_optional$value );

                if ( 
preg_match"/^=<$p_value>$/"$value$matches ) ) {
                    
$param['value']['name'] = $matches[1];
                } else {
                    
$param = [
                        
'type' => 'unknown',
                    ];
                }
            }
        } else {
            
$param['type'] = 'unknown';
        }

        return 
$param;
    }

    
/**
     * An optional parameter is surrounded by square brackets.
     *
     * @param string $token
     * @return array
     */
    
private static function is_optional$token ) {
        if ( 
'[' === substr$token0) && ']' === substr$token, -) ) {
            return [ 
truesubstr$token1, -) ];
        }

        return [ 
false$token ];
    }

    
/**
     * A repeating parameter is followed by an ellipsis.
     *
     * @param string $token
     * @return array
     */
    
private static function is_repeating$token ) {
        if ( 
'...' === substr$token, -) ) {
            return [ 
truesubstr$token0, -) ];
        }

        return [ 
false$token ];
    }
}
<?
php

namespace WP_CLI\Context;

use 
WP_CLI;
use 
WP_CLI\Context;

/**
 * Context which simulates a frontend request.
 */
final class Frontend implements Context {

    
/**
     * Process the context to set up the environment correctly.
     *
     * @param array $config Associative array of configuration data.
     *
     * @return void
     */
    
public function process$config ) {
        
// TODO: Frontend context needs to be simulated here.
    
}
}
<?
php

namespace WP_CLI\Context;

use 
WP_CLI;
use 
WP_CLI\Context;
use 
WP_Session_Tokens;

/**
 * Context which simulates the administrator backend.
 */
final class Admin implements Context {

    
/**
     * Process the context to set up the environment correctly.
     *
     * @param array $config Associative array of configuration data.
     * @return void
     */
    
public function process$config ) {
        if ( 
defined'WP_ADMIN' ) ) {
            if ( ! 
WP_ADMIN ) {
                
WP_CLI::warning'Could not fake admin request.' );
            }

            return;
        }

        
WP_CLI::debug'Faking an admin request'Context::DEBUG_GROUP );

        
// Define `WP_ADMIN` as being true. This causes the helper method
        // `is_admin()` to return true as well.
        
define'WP_ADMIN'true ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound

        // Set a fake entry point to ensure wp-includes/vars.php does not throw
        // notices/errors. This will be reflected in the global `$pagenow`
        // variable being set to 'wp-cli-fake-admin-file.php'.
        
$_SERVER['PHP_SELF'] = '/wp-admin/wp-cli-fake-admin-file.php';

        
// Bootstrap the WordPress administration area.
        
WP_CLI::add_wp_hook(
            
'admin_init',
            function () {
                global 
$wp_db_version$_wp_submenu_nopriv;

                
// Make sure we don't trigger a DB upgrade as that tries to redirect
                // the page.
                
$wp_db_version = (int) get_option'db_version' ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited

                // Ensure WP does not iterate over an undefined variable in
                // `user_can_access_admin_page()`.
                
if ( ! isset( $_wp_submenu_nopriv ) ) {
                    
$_wp_submenu_nopriv = []; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
                
}

                
$this->log_in_as_admin_user();

                require_once 
ABSPATH 'wp-admin/admin.php';
            },
            
0
        
);
    }

    
/**
     * Ensure the current request is done under a logged-in administrator
     * account.
     *
     * A lot of premium plugins/themes have their custom update routines locked
     * behind an is_admin() call.
     *
     * @return void
     */
    
private function log_in_as_admin_user() {
        
// TODO: Add logic to find an administrator user.
        
$admin_user_id 1;

        
wp_set_current_user$admin_user_id );

        
$expiration time() + DAY_IN_SECONDS;
        
$manager    WP_Session_Tokens::get_instance$admin_user_id );
        
$token      $manager->create$expiration );

        
$_COOKIEAUTH_COOKIE ] = wp_generate_auth_cookie(
            
$admin_user_id,
            
$expiration,
            
'auth',
            
$token
        
);

        
$_COOKIESECURE_AUTH_COOKIE ] = wp_generate_auth_cookie(
            
$admin_user_id,
            
$expiration,
            
'secure_auth',
            
$token
        
);
    }
}
<?
php

namespace WP_CLI\Context;

use 
WP_CLI;
use 
WP_CLI\Context;

/**
 * Default WP-CLI context.
 */
final class Cli implements Context {

    
/**
     * Process the context to set up the environment correctly.
     *
     * @param array $config Associative array of configuration data.
     *
     * @return void
     */
    
public function process$config ) {
        
// Nothing needs to be done for now, as this is the default.
    
}
}
<?
php

namespace WP_CLI\Context;

use 
WP_CLI;
use 
WP_CLI\Context;
use 
WP_CLI\ContextManager;

/**
 * Context which switches to other contexts automatically based on conditions.
 */
final class Auto implements Context {

    
/**
     * Array of commands to intercept.
     *
     * @var array<array>
     */
    
const COMMANDS_TO_RUN_AS_ADMIN = [
        [ 
'plugin' ],
        [ 
'theme' ],
    ];

    
/**
     * Context manager instance to use.
     *
     * @var ContextManager
     */
    
private $context_manager;

    
/**
     * Instantiate an Auto object.
     *
     * @param ContextManager $context_manager Context manager instance to use.
     */
    
public function __constructContextManager $context_manager ) {
        
$this->context_manager $context_manager;
    }

    
/**
     * Process the context to set up the environment correctly.
     *
     * @param array $config Associative array of configuration data.
     * @return void
     * @throws WP_CLI\ExitException If an invalid context was deduced.
     */
    
public function process$config ) {
        
$config['context'] = $this->deduce_best_context();

        
$this->context_manager->switch_context$config );
    }

    
/**
     * Deduce the best context to run the current command in.
     *
     * @return string Context to use.
     */
    
private function deduce_best_context() {
        if ( 
$this->is_command_to_run_as_admin() ) {
            return 
Context::ADMIN;
        }

        return 
Context::CLI;
    }

    
/**
     * Check whether the current WP-CLI command is amongst those we want to
     * run as admin.
     *
     * @return bool Whether the current command should be run as admin.
     */
    
private function is_command_to_run_as_admin() {
        
$command WP_CLI::get_runner()->arguments;

        foreach ( 
self::COMMANDS_TO_RUN_AS_ADMIN as $command_to_run_as_admin ) {
            if (
                
array_slice$command0count$command_to_run_as_admin ) )
                ===
                
$command_to_run_as_admin
            
) {
                
WP_CLI::debug(
                    
'Detected a command to be intercepted: '
                    
implode' '$command ),
                    
Context::DEBUG_GROUP
                
);
                return 
true;
            }
        }

        return 
false;
    }
}
<?
php

namespace WP_CLI;

/**
 * Context that can be selected in order to run commands within a properly
 * set-up environment.
 */
interface Context {

    const 
ADMIN    'admin';
    const 
AUTO     'auto';
    const 
CLI      'cli';
    const 
FRONTEND 'frontend';

    
/**
     * Debugging group to use for all context-related debug messages.
     *
     * @var string
     */
    
const DEBUG_GROUP 'context';

    
/**
     * Process the context to set up the environment correctly.
     *
     * @param array $config Associative array of configuration data.
     * @return void
     */
    
public function process$config );
}
<?
php

namespace WP_CLI;

use 
cli\Colors;
use 
cli\Table;
use 
Iterator;
use 
Mustangostang\Spyc;
use 
WP_CLI;

/**
 * Output one or more items in a given format (e.g. table, JSON).
 */
class Formatter {

    
/**
     * How the items should be output.
     *
     * @var array
     */
    
private $args;

    
/**
     * Standard prefix for object fields.
     *
     * @var string
     */
    
private $prefix;

    
/**
     * @param array $assoc_args Output format arguments.
     * @param array $fields Fields to display of each item.
     * @param string|bool $prefix Check if fields have a standard prefix.
     * False indicates empty prefix.
     */
    
public function __construct( &$assoc_args$fields null$prefix false ) {
        
$format_args = [
            
'format' => 'table',
            
'fields' => $fields,
            
'field'  => null,
        ];

        foreach ( [ 
'format''fields''field' ] as $key ) {
            if ( isset( 
$assoc_args$key ] ) ) {
                
$format_args$key ] = $assoc_args$key ];
                unset( 
$assoc_args$key ] );
            }
        }

        if ( ! 
is_array$format_args['fields'] ) ) {
            
$format_args['fields'] = explode','$format_args['fields'] );
        }

        
$format_args['fields'] = array_map'trim'$format_args['fields'] );

        
$this->args   $format_args;
        
$this->prefix $prefix;
    }

    
/**
     * Magic getter for arguments.
     *
     * @param string $key
     * @return mixed
     */
    
public function __get$key ) {
        return 
$this->args$key ];
    }

    
/**
     * Display multiple items according to the output arguments.
     *
     * @param array|Iterator $items The items to display.
     * @param bool|array      $ascii_pre_colorized Optional. A boolean or an array of booleans to pass to `format()` if items in the table are pre-colorized. Default false.
     */
    
public function display_items$items$ascii_pre_colorized false ) {
        if ( 
$this->args['field'] ) {
            
$this->show_single_field$items$this->args['field'] );
        } else {
            if ( 
in_array$this->args['format'], [ 'csv''json''table' ], true ) ) {
                
$item is_array$items ) && ! empty( $items ) ? array_shift$items ) : false;
                if ( 
$item && ! empty( $this->args['fields'] ) ) {
                    foreach ( 
$this->args['fields'] as &$field ) {
                        
$field $this->find_item_key$item$field );
                    }
                    
array_unshift$items$item );
                }
            }

            if ( 
in_array$this->args['format'], [ 'table''csv' ], true ) ) {
                if ( 
$items instanceof Iterator ) {
                    
$items Utils\iterator_map$items, [ $this'transform_item_values_to_json' ] );
                } else {
                    
$items array_map( [ $this'transform_item_values_to_json' ], $items );
                }
            }

            
$this->format$items$ascii_pre_colorized );
        }
    }

    
/**
     * Display a single item according to the output arguments.
     *
     * @param mixed      $item
     * @param bool|array $ascii_pre_colorized Optional. A boolean or an array of booleans to pass to `show_multiple_fields()` if the item in the table is pre-colorized. Default false.
     */
    
public function display_item$item$ascii_pre_colorized false ) {
        if ( isset( 
$this->args['field'] ) ) {
            
$item  = (object) $item;
            
$key   $this->find_item_key$item$this->args['field'] );
            
$value $item->$key;
            if ( 
in_array$this->args['format'], [ 'table''csv' ], true ) && ( is_object$value ) || is_array$value ) ) ) {
                
$value json_encode$value );
            }
            
WP_CLI::print_value(
                
$value,
                [
                    
'format' => $this->args['format'],
                ]
            );
        } else {
            
$this->show_multiple_fields$item$this->args['format'], $ascii_pre_colorized );
        }
    }

    
/**
     * Format items according to arguments.
     *
     * @param array      $items
     * @param bool|array $ascii_pre_colorized Optional. A boolean or an array of booleans to pass to `show_table()` if items in the table are pre-colorized. Default false.
     */
    
private function format$items$ascii_pre_colorized false ) {
        
$fields $this->args['fields'];

        switch ( 
$this->args['format'] ) {
            case 
'count':
                if ( ! 
is_array$items ) ) {
                    
$items iterator_to_array$items );
                }
                echo 
count$items );
                break;

            case 
'ids':
                if ( ! 
is_array$items ) ) {
                    
$items iterator_to_array$items );
                }
                echo 
implode' '$items );
                break;

            case 
'table':
                
self::show_table$items$fields$ascii_pre_colorized );
                break;

            case 
'csv':
                
Utils\write_csvSTDOUT$items$fields );
                break;

            case 
'json':
            case 
'yaml':
                
$out = [];
                foreach ( 
$items as $item ) {
                    
$out[] = Utils\pick_fields$item$fields );
                }

                if ( 
'json' === $this->args['format'] ) {
                    if ( 
defined'JSON_PARTIAL_OUTPUT_ON_ERROR' ) ) {
                        
// phpcs:ignore PHPCompatibility.Constants.NewConstants.json_partial_output_on_errorFound
                        
echo json_encode$outJSON_PARTIAL_OUTPUT_ON_ERROR );
                    } else {
                        echo 
json_encode$out );
                    }
                } elseif ( 
'yaml' === $this->args['format'] ) {
                    echo 
Spyc::YAMLDump$out2);
                }
                break;

            default:
                
WP_CLI::error'Invalid format: ' $this->args['format'] );
        }
    }

    
/**
     * Show a single field from a list of items.
     *
     * @param array $items Array of objects to show fields from
     * @param string $field The field to show
     */
    
private function show_single_field$items$field ) {
        
$key    null;
        
$values = [];

        foreach ( 
$items as $item ) {
            
$item = (object) $item;

            if ( 
null === $key ) {
                
$key $this->find_item_key$item$field );
            }

            if ( 
'json' === $this->args['format'] ) {
                
$values[] = $item->$key;
            } else {
                
WP_CLI::print_value(
                    
$item->$key,
                    [
                        
'format' => $this->args['format'],
                    ]
                );
            }
        }

        if ( 
'json' === $this->args['format'] ) {
            echo 
json_encode$values );
        }
    }

    
/**
     * Find an object's key.
     * If $prefix is set, a key with that prefix will be prioritized.
     *
     * @param object $item
     * @param string $field
     * @return string
     */
    
private function find_item_key$item$field ) {
        foreach ( [ 
$field$this->prefix '_' $field ] as $maybe_key ) {
            if ( ( 
is_object$item ) && ( property_exists$item$maybe_key ) || isset( $item->$maybe_key ) ) ) || ( is_array$item ) && array_key_exists$maybe_key$item ) ) ) {
                
$key $maybe_key;
                break;
            }
        }

        if ( ! isset( 
$key ) ) {
            
WP_CLI::error"Invalid field: $field." );
        }

        return 
$key;
    }

    
/**
     * Show multiple fields of an object.
     *
     * @param object|array $data                Data to display
     * @param string       $format              Format to display the data in
     * @param bool|array   $ascii_pre_colorized Optional. A boolean or an array of booleans to pass to `show_table()` if the item in the table is pre-colorized. Default false.
     */
    
private function show_multiple_fields$data$format$ascii_pre_colorized false ) {

        
$true_fields = [];
        foreach ( 
$this->args['fields'] as $field ) {
            
$true_fields[] = $this->find_item_key$data$field );
        }

        foreach ( 
$data as $key => $value ) {
            if ( ! 
in_array$key$true_fieldstrue ) ) {
                if ( 
is_array$data ) ) {
                    unset( 
$data$key ] );
                } elseif ( 
is_object$data ) ) {
                    unset( 
$data->$key );
                }
            }
        }

        switch ( 
$format ) {

            case 
'table':
            case 
'csv':
                
$rows   $this->assoc_array_to_rows$data );
                
$fields = [ 'Field''Value' ];
                if ( 
'table' === $format ) {
                    
self::show_table$rows$fields$ascii_pre_colorized );
                } elseif ( 
'csv' === $format ) {
                    
Utils\write_csvSTDOUT$rows$fields );
                }
                break;

            case 
'yaml':
            case 
'json':
                
WP_CLI::print_value(
                    
$data,
                    [
                        
'format' => $format,
                    ]
                );
                break;

            default:
                
WP_CLI::error'Invalid format: ' $format );
                break;

        }

    }

    
/**
     * Show items in a \cli\Table.
     *
     * @param array      $items
     * @param array      $fields
     * @param bool|array $ascii_pre_colorized Optional. A boolean or an array of booleans to pass to `Table::setAsciiPreColorized()` if items in the table are pre-colorized. Default false.
     */
    
private static function show_table$items$fields$ascii_pre_colorized false ) {
        
$table = new Table();

        
$enabled Colors::shouldColorize();
        if ( 
$enabled ) {
            
Colors::disabletrue );
        }

        
$table->setAsciiPreColorized$ascii_pre_colorized );
        
$table->setHeaders$fields );

        foreach ( 
$items as $item ) {
            
$table->addRowarray_valuesUtils\pick_fields$item$fields ) ) );
        }

        foreach ( 
$table->getDisplayLines() as $line ) {
            
WP_CLI::line$line );
        }

        if ( 
$enabled ) {
            
Colors::enabletrue );
        }
    }

    
/**
     * Format an associative array as a table.
     *
     * @param array     $fields    Fields and values to format
     * @return array
     */
    
private function assoc_array_to_rows$fields ) {
        
$rows = [];

        foreach ( 
$fields as $field => $value ) {

            if ( ! 
is_string$value ) ) {
                
$value json_encode$value );
            }

            
$rows[] = (object) [
                
'Field' => $field,
                
'Value' => $value,
            ];
        }

        return 
$rows;
    }

    
/**
     * Transforms objects and arrays to JSON as necessary
     *
     * @param mixed $item
     * @return mixed
     */
    
public function transform_item_values_to_json$item ) {
        foreach ( 
$this->args['fields'] as $field ) {
            
$true_field $this->find_item_key$item$field );
            
$value      is_object$item ) ? $item->$true_field $item$true_field ];
            if ( 
is_array$value ) || is_object$value ) ) {
                if ( 
is_object$item ) ) {
                    
$item->$true_field json_encode$value );
                } elseif ( 
is_array$item ) ) {
                    
$item$true_field ] = json_encode$value );
                }
            }
        }
        return 
$item;
    }

}
<?
php

namespace WP_CLI;

use 
WP_CLI;
use 
WP_Upgrader_Skin;

/**
 * A Upgrader Skin for WordPress that only generates plain-text
 *
 * @package wp-cli
 */
class UpgraderSkin extends WP_Upgrader_Skin {

    use 
WP_CLI\Compat\FeedbackMethodTrait;

    public 
$api;

    public function 
header() {}
    public function 
footer() {}
    public function 
bulk_header() {}
    public function 
bulk_footer() {}

    
/**
     * Show error message.
     *
     * @param string $error Error message.
     *
     * @return void
     */
    
public function error$error ) {
        if ( ! 
$error ) {
            return;
        }

        if ( 
is_string$error ) && isset( $this->upgrader->strings$error ] ) ) {
            
$error $this->upgrader->strings$error ];
        }

        
// TODO: show all errors, not just the first one
        
WP_CLI::warning$error );
    }

    
/**
     * Process the feedback collected through the compat indirection.
     *
     * @param string $string String to use as feedback message.
     * @param array $args Array of additional arguments to process.
     */
    
public function process_feedback$string$args ) {

        if ( 
'parent_theme_prepare_install' === $string ) {
            
WP_CLI::get_http_cache_manager()->whitelist_package$this->api->download_link'theme'$this->api->slug$this->api->version );
        }

        if ( isset( 
$this->upgrader->strings$string ] ) ) {
            
$string $this->upgrader->strings$string ];
        }

        if ( ! empty( 
$args ) && strpos$string'%' ) !== false ) {
            
$string vsprintf$string$args );
        }

        if ( empty( 
$string ) ) {
            return;
        }

        
$string str_replace'&#8230;''...'Utils\strip_tags$string ) );
        
$string html_entity_decode$stringENT_QUOTESget_bloginfo'charset' ) );

        
WP_CLI::log$string );
    }
}

<?
php

namespace WP_CLI\Compat\Min_PHP_5_6;

trait 
FeedbackMethodTrait {

    
/**
     * @param string $string
     * @param mixed  ...$args Optional text replacements.
     */
    
public function feedback$string, ...$args ) { // phpcs:ignore PHPCompatibility.LanguageConstructs.NewLanguageConstructs.t_ellipsisFound
        
$args_array = [];
        foreach ( 
$args as $arg ) {
            
$args_array[] = $args;
        }

        
$this->process_feedback$string$args );
    }
}
<?
php

namespace WP_CLI\Compat\Min_PHP_5_4;

trait 
FeedbackMethodTrait {

    
/**
     * @param string $string
     */
    
public function feedback$string ) {
        
$args func_get_args();
        
$args array_splice$args);

        
$this->process_feedback$string$args );
    }
}
<?
php

namespace WP_CLI\Compat;

// phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound,Generic.Classes.DuplicateClassName.Found

if ( PHP_VERSION_ID >= 50600 ) {
    require_once 
__DIR__ '/Min_PHP_5_6/FeedbackMethodTrait.php';

    trait 
FeedbackMethodTrait {

        use 
Min_PHP_5_6\FeedbackMethodTrait;
    }

    return;
}

require_once 
__DIR__ '/Min_PHP_5_4/FeedbackMethodTrait.php';

trait 
FeedbackMethodTrait {

    use 
Min_PHP_5_4\FeedbackMethodTrait;
}
<?
php

namespace WP_CLI\Loggers;

use 
WP_CLI;

/**
 * Execution logger captures all STDOUT and STDERR writes
 */
class Execution extends Regular {

    
/**
     * Captured writes to STDOUT.
     */
    
public $stdout '';

    
/**
     * Captured writes to STDERR.
     */
    
public $stderr '';

    
/**
     * @param bool $in_color Whether or not to Colorize strings.
     */
    
public function __construct$in_color false ) { // phpcs:ignore Generic.CodeAnalysis.UselessOverridingMethod.Found -- Provides a default value.
        
parent::__construct$in_color );
    }

    
/**
     * Similar to error( $message ), but outputs $message in a red box.
     *
     * @param array $message_lines Message to write.
     */
    
public function error_multi_line$message_lines ) {
        
$message implode"\n"$message_lines );

        
$this->writeSTDERRWP_CLI::colorize"%RError:%n\n$message\n" ) );
        
$this->writeSTDERRWP_CLI::colorize"%R---------%n\n\n" ) );
    }

    
/**
     * Write a string to a resource.
     *
     * @param resource $handle Commonly STDOUT or STDERR.
     * @param string $str Message to write.
     */
    
protected function write$handle$str ) {
        switch ( 
$handle ) {
            case 
STDOUT:
                
$this->stdout .= $str;
                break;
            case 
STDERR:
                
$this->stderr .= $str;
                break;
        }
    }

    
/**
     * Starts output buffering, using a callback to capture output from `echo`, `print`, `printf` (which write to the output buffer 'php://output' rather than STDOUT).
     */
    
public function ob_start() {
        
ob_start( [ $this'ob_start_callback' ], );
    }

    
/**
     * Callback for `ob_start()`.
     *
     * @param string $str String to write.
     * @return string Returns zero-length string so nothing gets written to the output buffer.
     */
    
public function ob_start_callback$str ) {
        
$this->writeSTDOUT$str );
        return 
'';
    }

    
/**
     * To match `ob_start() above. Does an `ob_end_flush()`.
     */
    
public function ob_end() {
        
ob_end_flush();
    }
}
<?
php

namespace WP_CLI\Loggers;

use 
cli\Colors;

/**
 * Default logger for success, warning, error, and standard messages.
 */
class Regular extends Base {

    
/**
     * @param bool $in_color Whether or not to Colorize strings.
     */
    
public function __construct$in_color ) {
        
$this->in_color $in_color;
    }

    
/**
     * Write an informational message to STDOUT.
     *
     * @param string $message Message to write.
     */
    
public function info$message ) {
        
$this->writeSTDOUT$message "\n" );
    }

    
/**
     * Write a success message, prefixed with "Success: ".
     *
     * @param string $message Message to write.
     */
    
public function success$message ) {
        
$this->_line$message'Success''%G' );
    }

    
/**
     * Write a warning message to STDERR, prefixed with "Warning: ".
     *
     * @param string $message Message to write.
     */
    
public function warning$message ) {
        
$this->_line$message'Warning''%C'STDERR );
    }

    
/**
     * Write an message to STDERR, prefixed with "Error: ".
     *
     * @param string $message Message to write.
     */
    
public function error$message ) {
        
$this->_line$message'Error''%R'STDERR );
    }

    
/**
     * Similar to error( $message ), but outputs $message in a red box.
     *
     * @param  array $message_lines Message to write.
     */
    
public function error_multi_line$message_lines ) {
        
// Convert tabs to four spaces, as some shells will output the tabs as variable-length.
        
$message_lines array_map(
            function( 
$line ) {
                return 
str_replace"\t"'    '$line );
            },
            
$message_lines
        
);

        
$longest maxarray_map'strlen'$message_lines ) );

        
// Write an empty line before the message.
        
$empty_line Colors::colorize'%w%1 ' str_repeat' '$longest ) . ' %n' );
        
$this->writeSTDERR"\n\t$empty_line\n" );

        foreach ( 
$message_lines as $line ) {
            
$padding str_repeat' '$longest strlen$line ) );
            
$line    Colors::colorize"%w%1 $line $padding%n" );
            
$this->writeSTDERR"\t$line\n" );
        }

        
// Write an empty line after the message.
        
$this->writeSTDERR"\t$empty_line\n\n" );
    }
}
<?
php

namespace WP_CLI\Loggers;

use 
cli\Colors;
use 
WP_CLI;
use 
WP_CLI\Runner;

/**
 * Base logger class
 */
abstract class Base {

    protected 
$in_color false;

    abstract public function 
info$message );

    abstract public function 
success$message );

    abstract public function 
warning$message );

    
/**
     * Retrieve the runner instance from the base CLI object. This facilitates
     * unit testing, where the WP_CLI instance isn't available
     *
     * @return Runner Instance of the runner class
     */
    
protected function get_runner() {
        return 
WP_CLI::get_runner();
    }

    
/**
     * Write a message to STDERR, prefixed with "Debug: ".
     *
     * @param string $message Message to write.
     * @param string|bool $group Organize debug message to a specific group.
     * Use `false` for no group.
     */
    
public function debug$message$group false ) {
        static 
$start_time null;
        if ( 
null === $start_time ) {
            
$start_time microtimetrue );
        }
        
$debug $this->get_runner()->config['debug'];
        if ( ! 
$debug ) {
            return;
        }
        if ( 
true !== $debug && $group !== $debug ) {
            return;
        }
        
$time   roundmicrotimetrue ) - ( defined'WP_CLI_START_MICROTIME' ) ? WP_CLI_START_MICROTIME $start_time ), );
        
$prefix 'Debug';
        if ( 
$group && true === $debug ) {
            
$prefix 'Debug (' $group ')';
        }
        
$this->_line"$message ({$time}s)"$prefix'%B'STDERR );
    }

    
/**
     * Write a string to a resource.
     *
     * @param resource $handle Commonly STDOUT or STDERR.
     * @param string $str Message to write.
     */
    
protected function write$handle$str ) {
        
fwrite$handle$str );
    }

    
/**
     * Output one line of message to a resource.
     *
     * @param string $message Message to write.
     * @param string $label Prefix message with a label.
     * @param string $color Colorize label with a given color.
     * @param resource $handle Resource to write to. Defaults to STDOUT.
     */
    
protected function _line$message$label$color$handle STDOUT ) { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore -- Used in third party extensions.
        
if ( class_exists'cli\Colors' ) ) {
            
$label Colors::colorize"$color$label:%n"$this->in_color );
        } else {
            
$label "$label:";
        }
        
$this->write$handle"$label $message\n" );
    }

}
<?
php

namespace WP_CLI\Loggers;

use 
WP_CLI;

/**
 * Quiet logger only logs errors.
 */
class Quiet extends Base {

    
/**
     * @param bool $in_color Whether or not to Colorize strings.
     */
    
public function __construct$in_color false ) {
        
$this->in_color $in_color;
    }

    
/**
     * Informational messages aren't logged.
     *
     * @param string $message Message to write.
     */
    
public function info$message ) {
        
// Nothing.
    
}

    
/**
     * Success messages aren't logged.
     *
     * @param string $message Message to write.
     */
    
public function success$message ) {
        
// Nothing.
    
}

    
/**
     * Warning messages aren't logged.
     *
     * @param string $message Message to write.
     */
    
public function warning$message ) {
        
// Nothing.
    
}

    
/**
     * Write an error message to STDERR, prefixed with "Error: ".
     *
     * @param string $message Message to write.
     */
    
public function error$message ) {
        
$this->_line$message'Error''%R'STDERR );
    }

    
/**
     * Similar to error( $message ), but outputs $message in a red box.
     *
     * @param  array $message_lines Message to write.
     */
    
public function error_multi_line$message_lines ) {
        
$message implode"\n"$message_lines );

        
$this->_line$message'Error''%R'STDERR );
        
$this->_line'''---------''%R'STDERR );
    }
}
<?
php

namespace WP_CLI;

use 
WP_CLI;

/**
 * Context manager to register_context and process different contexts that commands can
 * run within.
 */
final class ContextManager {

    
/**
     * Associative array of context implementations.
     *
     * @var array<string, Context>
     */
    
private $contexts = [];

    
/**
     * Store the current context.
     *
     * @var string Current context.
     */
    
private $current_context Context::CLI;

    
/**
     * Register a context with WP-CLI.
     *
     * @param string  $name           Name of the context.
     * @param Context $implementation Implementation of the context.
     */
    
public function register_context$nameContext $implementation ) {
        
$this->contexts$name ] = $implementation;
    }

    
/**
     * Switch the context in which to run WP-CLI.
     *
     * @param array $config Associative array of configuration data.
     * @return void
     *
     * @throws ExitException When an invalid context was requested.
     */
    
public function switch_context$config ) {
        
$context = isset( $config['context'] )
            ? 
$config['context']
            : 
$this->current_context;

        if ( ! 
array_key_exists$context$this->contexts ) ) {
            
WP_CLI::error"Unknown context '{$context}'" );
        }

        
WP_CLI::debug"Using context '{$context}'"Context::DEBUG_GROUP );

        
$this->current_context $context;
        
$this->contexts$context ]->process$config );
    }

    
/**
     * Return the current context.
     *
     * @return string Current context.
     */
    
public function get_context() {
        return 
$this->current_context;
    }
}
<?
php

namespace WP_CLI;

use 
WP_CLI;

/**
 * Manage caching with whitelisting
 *
 * @package WP_CLI
 */
class WpHttpCacheManager {

    
/**
     * @var array map whitelisted urls to keys and ttls
     */
    
protected $whitelist = [];

    
/**
     * @var FileCache
     */
    
protected $cache;

    
/**
     * @param FileCache $cache
     */
    
public function __constructFileCache $cache ) {
        
$this->cache $cache;

        
// hook into wp http api
        
add_filter'pre_http_request', [ $this'filter_pre_http_request' ], 10);
        
add_filter'http_response', [ $this'filter_http_response' ], 10);
    }

    
/**
     * short circuit wp http api with cached file
     */
    
public function filter_pre_http_request$response$args$url ) {
        
// check if whitelisted
        
if ( ! isset( $this->whitelist$url ] ) ) {
            return 
$response;
        }
        
// check if downloading
        
if ( 'GET' !== $args['method'] || empty( $args['filename'] ) ) {
            return 
$response;
        }
        
// check cache and export to designated location
        
$filename $this->cache->has$this->whitelist$url ]['key'], $this->whitelist$url ]['ttl'] );
        if ( 
$filename ) {
            
WP_CLI::logsprintf'Using cached file \'%s\'...'$filename ) );
            if ( 
copy$filename$args['filename'] ) ) {
                
// simulate successful download response
                
return [
                    
'response' => [
                        
'code'    => 200,
                        
'message' => 'OK',
                    ],
                    
'filename' => $args['filename'],
                ];
            }

            
WP_CLI::errorsprintf'Error copying cached file %s to %s'$filename$url ) );
        }
        return 
$response;
    }


    
/**
     * cache wp http api downloads
     *
     * @param array $response
     * @param array $args
     * @param string $url
     * @return array
     */
    
public function filter_http_response$response$args$url ) {
        
// check if whitelisted
        
if ( ! isset( $this->whitelist$url ] ) ) {
            return 
$response;
        }
        
// check if downloading
        
if ( 'GET' !== $args['method'] || empty( $args['filename'] ) ) {
            return 
$response;
        }
        
// check if download was successful
        
if ( is_wp_error$response ) || 200 !== wp_remote_retrieve_response_code$response ) ) {
            return 
$response;
        }
        
// cache downloaded file
        
$this->cache->import$this->whitelist$url ]['key'], $response['filename'] );
        return 
$response;
    }

    
/**
     * whitelist a package url
     *
     * @param string $url
     * @param string $group   package group (themes, plugins, ...)
     * @param string $slug    package slug
     * @param string $version package version
     * @param int    $ttl
     */
    
public function whitelist_package$url$group$slug$version$ttl null ) {
        
$ext pathinfoUtils\parse_url$urlPHP_URL_PATH ), PATHINFO_EXTENSION );
        
$key "$group/$slug-$version.$ext";
        
$this->whitelist_url$url$key$ttl );
        
wp_update_plugins();
    }

    
/**
     * whitelist a url
     *
     * @param string $url
     * @param string $key
     * @param int    $ttl
     */
    
public function whitelist_url$url$key null$ttl null ) {
        
$key                     $key ? : $url;
        
$this->whitelist$url ] = compact'key''ttl' );
    }

    
/**
     * check if url is whitelisted
     *
     * @param string $url
     * @return bool
     */
    
public function is_whitelisted$url ) {
        return isset( 
$this->whitelist$url ] );
    }

}
<?
php

namespace WP_CLI;

use 
WP_CLI;
use 
WP_CLI\Dispatcher;
use 
WP_CLI\Dispatcher\CompositeCommand;
use 
WP_CLI\Dispatcher\Subcommand;
use 
WP_CLI\Fetchers;
use 
WP_CLI\Iterators\Exception;
use 
WP_CLI\Loggers;
use 
WP_CLI\Utils;
use 
WP_Error;

/**
 * Performs the execution of a command.
 *
 * @property-read string         $global_config_path
 * @property-read string         $project_config_path
 * @property-read array          $config
 * @property-read array          $extra_config
 * @property-read ContextManager $context_manager
 * @property-read string         $alias
 * @property-read array          $aliases
 * @property-read array          $arguments
 * @property-read array          $assoc_args
 * @property-read array          $runtime_config
 * @property-read bool           $colorize
 * @property-read array          $early_invoke
 * @property-read string         $global_config_path_debug
 * @property-read string         $project_config_path_debug
 * @property-read array          $required_files
 *
 * @package WP_CLI
 */
class Runner {

    
/**
     * List of byte-order marks (BOMs) to detect.
     *
     * @var array<string, string>
     */
    
const BYTE_ORDER_MARKS = [
        
'UTF-8'       => "\xEF\xBB\xBF",
        
'UTF-16 (BE)' => "\xFE\xFF",
        
'UTF-16 (LE)' => "\xFF\xFE",
    ];

    private 
$global_config_path;
    private 
$project_config_path;

    private 
$config;
    private 
$extra_config;

    private 
$context_manager;

    private 
$alias;

    private 
$aliases;

    private 
$arguments;
    private 
$assoc_args;
    private 
$runtime_config;

    private 
$colorize false;

    private 
$early_invoke = [];

    private 
$global_config_path_debug;

    private 
$project_config_path_debug;

    private 
$required_files;

    public function 
__get$key ) {
        if ( 
'_' === $key[0] ) {
            return 
null;
        }

        return 
$this->$key;
    }

    public function 
register_context_managerContextManager $context_manager ) {
        
$this->context_manager $context_manager;
    }

    
/**
     * Register a command for early invocation, generally before WordPress loads.
     *
     * @param string $when Named execution hook
     * @param Subcommand $command
     */
    
public function register_early_invoke$when$command ) {
        
$this->early_invoke$when ][] = array_sliceDispatcher\get_path$command ), );
    }

    
/**
     * Perform the early invocation of a command.
     *
     * @param string $when Named execution hook
     */
    
private function do_early_invoke$when ) {
        if ( ! isset( 
$this->early_invoke$when ] ) ) {
            return;
        }

        
// Search the value of @when from the command method.
        
$real_when '';
        
$r         $this->find_command_to_run$this->arguments );
        if ( 
is_array$r ) ) {
            list( 
$command$final_args$cmd_path ) = $r;

            foreach ( 
$this->early_invoke as $_when => $_path ) {
                foreach ( 
$_path as $cmd ) {
                    if ( 
$cmd === $cmd_path ) {
                        
$real_when $_when;
                    }
                }
            }
        }

        foreach ( 
$this->early_invoke$when ] as $path ) {
            if ( 
$this->cmd_starts_with$path ) ) {
                if ( empty( 
$real_when ) || ( $real_when && $real_when === $when ) ) {
                    
$this->run_command_and_exit();
                }
            }
        }
    }

    
/**
     * Get the path to the global configuration YAML file.
     *
     * @param bool $create_config_file Optional. If a config file doesn't exist,
     *                                 should it be created? Defaults to false.
     *
     * @return string|false
     */
    
public function get_global_config_path$create_config_file false ) {

        if ( 
getenv'WP_CLI_CONFIG_PATH' ) ) {
            
$config_path                    getenv'WP_CLI_CONFIG_PATH' );
            
$this->global_config_path_debug 'Using global config from WP_CLI_CONFIG_PATH env var: ' $config_path;
        } else {
            
$config_path                    Utils\get_home_dir() . '/.wp-cli/config.yml';
            
$this->global_config_path_debug 'Using default global config: ' $config_path;
        }

        
// If global config doesn't exist create one.
        
if ( true === $create_config_file && ! file_exists$config_path ) ) {
            
$this->global_config_path_debug "Default global config doesn't exist, creating one in {$config_path}";
            
Process::createUtils\esc_cmd'touch %s'$config_path ) )->run();
        }

        if ( 
is_readable$config_path ) ) {
            return 
$config_path;
        }

        
$this->global_config_path_debug 'No readable global config found';

        return 
false;
    }

    
/**
     * Get the path to the project-specific configuration
     * YAML file.
     * wp-cli.local.yml takes priority over wp-cli.yml.
     *
     * @return string|false
     */
    
public function get_project_config_path() {
        
$config_files = [
            
'wp-cli.local.yml',
            
'wp-cli.yml',
        ];

        
// Stop looking upward when we find we have emerged from a subdirectory
        // installation into a parent installation
        
$project_config_path Utils\find_file_upward(
            
$config_files,
            
getcwd(),
            static function ( 
$dir ) {
                static 
$wp_load_count 0;
                
$wp_load_path         $dir DIRECTORY_SEPARATOR 'wp-load.php';
                if ( 
file_exists$wp_load_path ) ) {
                    ++ 
$wp_load_count;
                }
                return 
$wp_load_count 1;
            }
        );

        
$this->project_config_path_debug 'No project config found';

        if ( ! empty( 
$project_config_path ) ) {
            
$this->project_config_path_debug 'Using project config: ' $project_config_path;
        }

        return 
$project_config_path;
    }

    
/**
     * Get the path to the packages directory
     *
     * @return string
     */
    
public function get_packages_dir_path() {
        if ( 
getenv'WP_CLI_PACKAGES_DIR' ) ) {
            
$packages_dir Utils\trailingslashitgetenv'WP_CLI_PACKAGES_DIR' ) );
        } else {
            
$packages_dir Utils\get_home_dir() . '/.wp-cli/packages/';
        }
        return 
$packages_dir;
    }

    
/**
     * Attempts to find the path to the WP installation inside index.php
     *
     * @param string $index_path
     * @return string|false
     */
    
private static function extract_subdir_path$index_path ) {
        
$index_code file_get_contents$index_path );

        if ( ! 
preg_match'|^\s*require\s*\(?\s*(.+?)/wp-blog-header\.php([\'"])|m'$index_code$matches ) ) {
            return 
false;
        }

        
$wp_path_src $matches[1] . $matches[2];
        
$wp_path_src Utils\replace_path_consts$wp_path_src$index_path );

        
$wp_path = eval( "return $wp_path_src;" ); // phpcs:ignore Squiz.PHP.Eval.Discouraged

        
if ( ! Utils\is_path_absolute$wp_path ) ) {
            
$wp_path dirname$index_path ) . "/$wp_path";
        }

        return 
$wp_path;
    }

    
/**
     * Find the directory that contains the WordPress files.
     * Defaults to the current working dir.
     *
     * @return string An absolute path
     */
    
private function find_wp_root() {
        if ( ! empty( 
$this->config['path'] ) ) {
            
$path $this->config['path'];
            if ( ! 
Utils\is_path_absolute$path ) ) {
                
$path getcwd() . '/' $path;
            }

            return 
$path;
        }

        if ( 
$this->cmd_starts_with( [ 'core''download' ] ) ) {
            return 
getcwd();
        }

        
$dir getcwd();

        while ( 
is_readable$dir ) ) {
            if ( 
file_exists"$dir/wp-load.php" ) ) {
                return 
$dir;
            }

            if ( 
file_exists"$dir/index.php" ) ) {
                
$path self::extract_subdir_path"$dir/index.php" );
                if ( ! empty( 
$path ) ) {
                    return 
$path;
                }
            }

            
$parent_dir dirname$dir );
            if ( empty( 
$parent_dir ) || $parent_dir === $dir ) {
                break;
            }
            
$dir $parent_dir;
        }
    }

    
/**
     * Set WordPress root as a given path.
     *
     * @param string $path
     */
    
private static function set_wp_root$path ) {
        if ( ! 
defined'ABSPATH' ) ) {
            
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- Declaring a WP native constant.
            
define'ABSPATH'Utils\normalize_pathUtils\trailingslashit$path ) ) );
        } elseif ( ! 
is_null$path ) ) {
            
WP_CLI::error_multi_line(
                [
                    
'The --path parameter cannot be used when ABSPATH is already defined elsewhere',
                    
'ABSPATH is defined as: "' ABSPATH '"',
                ]
            );
        }
        
WP_CLI::debug'ABSPATH defined: ' ABSPATH'bootstrap' );

        
$_SERVER['DOCUMENT_ROOT'] = realpath$path );
    }

    
/**
     * Guess which URL context WP-CLI has been invoked under.
     *
     * @param array $assoc_args
     * @return string|false
     */
    
private static function guess_url$assoc_args ) {
        if ( isset( 
$assoc_args['blog'] ) ) {
            
$assoc_args['url'] = $assoc_args['blog'];
        }

        if ( isset( 
$assoc_args['url'] ) ) {
            
$url $assoc_args['url'];

            if ( 
true === $url ) {
                
WP_CLI::warning'The --url parameter expects a value.' );
            }

            return 
$url;
        }

        return 
false;
    }

    private function 
cmd_starts_with$prefix ) {
        return 
array_slice$this->arguments0count$prefix ) ) === $prefix;
    }

    
/**
     * Given positional arguments, find the command to execute.
     *
     * @param array $args
     * @return array|string Command, args, and path on success; error message on failure
     */
    
public function find_command_to_run$args ) {
        
$command WP_CLI::get_root_command();

        
WP_CLI::do_hook'find_command_to_run_pre' );

        
$cmd_path = [];

        while ( ! empty( 
$args ) && $command->can_have_subcommands() ) {
            
$cmd_path[] = $args[0];
            
$full_name  implode' '$cmd_path );

            
$subcommand $command->find_subcommand$args );

            if ( ! 
$subcommand ) {
                if ( 
count$cmd_path ) > ) {
                    
$child       array_pop$cmd_path );
                    
$parent_name implode' '$cmd_path );
                    
$suggestion  $this->get_subcommand_suggestion$child$command );
                    return 
sprintf(
                        
"'%s' is not a registered subcommand of '%s'. See 'wp help %s' for available subcommands.%s",
                        
$child,
                        
$parent_name,
                        
$parent_name,
                        ! empty( 
$suggestion ) ? PHP_EOL "Did you mean '{$suggestion}'?" ''
                    
);
                }

                
$suggestion $this->get_subcommand_suggestion$full_name$command );

                return 
sprintf(
                    
"'%s' is not a registered wp command. See 'wp help' for available commands.%s",
                    
$full_name,
                    ! empty( 
$suggestion ) ? PHP_EOL "Did you mean '{$suggestion}'?" ''
                
);
            }

            if ( 
$this->is_command_disabled$subcommand ) ) {
                return 
sprintf(
                    
"The '%s' command has been disabled from the config file.",
                    
$full_name
                
);
            }

            
$command $subcommand;
        }

        return [ 
$command$args$cmd_path ];
    }

    
/**
     * Find the WP-CLI command to run given arguments, and invoke it.
     *
     * @param array $args        Positional arguments including command name
     * @param array $assoc_args  Associative arguments for the command.
     * @param array $options     Configuration options for the function.
     */
    
public function run_command$args$assoc_args = [], $options = [] ) {
        
WP_CLI::do_hook'before_run_command'$args$assoc_args$options );

        if ( ! empty( 
$options['back_compat_conversions'] ) ) {
            list( 
$args$assoc_args ) = self::back_compat_conversions$args$assoc_args );
        }
        
$r $this->find_command_to_run$args );
        if ( 
is_string$r ) ) {
            
WP_CLI::error$r );
        }

        list( 
$command$final_args$cmd_path ) = $r;

        
$name implode' '$cmd_path );

        
$extra_args = [];

        if ( isset( 
$this->extra_config$name ] ) ) {
            
$extra_args $this->extra_config$name ];
        }

        
WP_CLI::debug'Running command: ' $name'bootstrap' );
        try {
            
$command->invoke$final_args$assoc_args$extra_args );
        } catch ( 
Exception $e ) {
            
WP_CLI::error$e->getMessage() );
        }
    }

    
/**
     * Show synopsis if the called command is a composite command
     */
    
public function show_synopsis_if_composite_command() {
        
$r $this->find_command_to_run$this->arguments );
        if ( 
is_array$r ) ) {
            list( 
$command ) = $r;

            if ( 
$command->can_have_subcommands() ) {
                
$command->show_usage();
                exit;
            }
        }
    }

    private function 
run_command_and_exit$help_exit_warning '' ) {
        
$this->show_synopsis_if_composite_command();
        
$this->run_command$this->arguments$this->assoc_args );
        if ( 
$this->cmd_starts_with( [ 'help' ] ) ) {
            
// Help couldn't find the command so exit with suggestion.
            
$suggestion_or_disabled $this->find_command_to_runarray_slice$this->arguments) );
            if ( 
is_string$suggestion_or_disabled ) ) {
                if ( 
$help_exit_warning ) {
                    
WP_CLI::warning$help_exit_warning );
                }
                
WP_CLI::error$suggestion_or_disabled );
            }
            
// Should never get here.
        
}
        exit;
    }

    
/**
     * Perform a command against a remote server over SSH (or a container using
     * scheme of "docker" or "docker-compose").
     *
     * @param string $connection_string Passed connection string.
     * @return void
     */
    
private function run_ssh_command$connection_string ) {

        
WP_CLI::do_hook'before_ssh' );

        
$bits Utils\parse_ssh_url$connection_string );

        
$pre_cmd getenv'WP_CLI_SSH_PRE_CMD' );
        if ( 
$pre_cmd ) {
            
$message WP_CLI::warning"WP_CLI_SSH_PRE_CMD found, executing the following command(s) on the remote machine:\n $pre_cmd);

            
WP_CLI::log$message );

            
$pre_cmd rtrim$pre_cmd';' ) . '; ';
        }
        if ( ! empty( 
$bits['path'] ) ) {
            
$pre_cmd .= 'cd ' escapeshellarg$bits['path'] ) . '; ';
        }

        
$env_vars '';
        if ( 
getenv'WP_CLI_STRICT_ARGS_MODE' ) ) {
            
$env_vars .= 'WP_CLI_STRICT_ARGS_MODE=1 ';
        }

        
$wp_binary 'wp';
        
$wp_args   array_slice$GLOBALS['argv'], );

        if ( 
$this->alias && ! empty( $wp_args[0] ) && $this->alias === $wp_args[0] ) {
            
array_shift$wp_args );
            
$runtime_alias = [];
            foreach ( 
$this->aliases$this->alias ] as $key => $value ) {
                if ( 
'ssh' === $key ) {
                    continue;
                }
                
$runtime_alias$key ] = $value;
            }
            if ( ! empty( 
$runtime_alias ) ) {
                
$encoded_alias json_encode(
                    [
                        
$this->alias => $runtime_alias,
                    ]
                );
                
$wp_binary     "WP_CLI_RUNTIME_ALIAS='{$encoded_alias}{$wp_binary} {$this->alias}";
            }
        }

        foreach ( 
$wp_args as $k => $v ) {
            if ( 
preg_match'#--ssh=#'$v ) ) {
                unset( 
$wp_args$k ] );
            }
        }

        
$wp_command      $pre_cmd $env_vars $wp_binary ' ' implode' 'array_map'escapeshellarg'$wp_args ) );
        
$escaped_command $this->generate_ssh_command$bits$wp_command );

        
passthru$escaped_command$exit_code );
        if ( 
255 === $exit_code ) {
            
WP_CLI::error'Cannot connect over SSH using provided configuration.'255 );
        } else {
            exit( 
$exit_code );
        }
    }

    
/**
     * Generate a shell command from the parsed connection string.
     *
     * @param array  $bits       Parsed connection string.
     * @param string $wp_command WP-CLI command to run.
     * @return string
     */
    
private function generate_ssh_command$bits$wp_command ) {
        
$escaped_command '';

        
// Set default values.
        
foreach ( [ 'scheme''user''host''port''path''key' ] as $bit ) {
            if ( ! isset( 
$bits$bit ] ) ) {
                
$bits$bit ] = null;
            }

            
WP_CLI::debug'SSH ' $bit ': ' $bits$bit ], 'bootstrap' );
        }

        
$is_tty function_exists'posix_isatty' ) && posix_isattySTDOUT );

        if ( 
'docker' === $bits['scheme'] ) {
            
$command 'docker exec %s%s%s sh -c %s';

            
$escaped_command sprintf(
                
$command,
                
$bits['user'] ? '--user ' escapeshellarg$bits['user'] ) . ' ' '',
                
$is_tty '-t ' '',
                
escapeshellarg$bits['host'] ),
                
escapeshellarg$wp_command )
            );
        }

        if ( 
'docker-compose' === $bits['scheme'] ) {
            
$command 'docker-compose exec %s%s%s sh -c %s';

            
$escaped_command sprintf(
                
$command,
                
$bits['user'] ? '--user ' escapeshellarg$bits['user'] ) . ' ' '',
                
$is_tty '' '-T ',
                
escapeshellarg$bits['host'] ),
                
escapeshellarg$wp_command )
            );
        }

        
// Vagrant ssh-config.
        
if ( 'vagrant' === $bits['scheme'] ) {
            
$cache     WP_CLI::get_cache();
            
$cache_key 'vagrant:' $this->project_config_path;
            if ( 
$cache->has$cache_key ) ) {
                
$cached $cache->read$cache_key );
                
$values json_decode$cachedtrue );
            } else {
                
$ssh_config shell_exec'vagrant ssh-config 2>/dev/null' );
                if ( 
preg_match_all'#\s*(?<NAME>[a-zA-Z]+)\s(?<VALUE>.+)\s*#'$ssh_config$matches ) ) {
                    
$values array_combine$matches['NAME'], $matches['VALUE'] );
                    
$cache->write$cache_keyjson_encode$values ) );
                }
            }

            if ( empty( 
$bits['host'] ) || ( isset( $values['Host'] ) && $bits['host'] === $values['Host'] ) ) {
                
$bits['scheme'] = 'ssh';
                
$bits['host']   = $values['HostName'];
                
$bits['port']   = $values['Port'];
                
$bits['user']   = $values['User'];
                
$bits['key']    = $values['IdentityFile'];
            }

            
// If we could not resolve the bits still, fallback to just `vagrant ssh`
            
if ( 'vagrant' === $bits['scheme'] ) {
                
$command 'vagrant ssh -c %s %s';

                
$escaped_command sprintf(
                    
$command,
                    
escapeshellarg$wp_command ),
                    
escapeshellarg$bits['host'] )
                );
            }
        }

        
// Default scheme is SSH.
        
if ( 'ssh' === $bits['scheme'] || null === $bits['scheme'] ) {
            
$command 'ssh -q %s %s %s';

            if ( 
$bits['user'] ) {
                
$bits['host'] = $bits['user'] . '@' $bits['host'];
            }

            
$command_args = [
                
$bits['port'] ? '-p ' . (int) $bits['port'] . ' ' '',
                
$bits['key'] ? sprintf'-i %s'$bits['key'] ) : '',
                
$is_tty '-t' '-T',
            ];

            
$escaped_command sprintf(
                
$command,
                
implode' 'array_filter$command_args ) ),
                
escapeshellarg$bits['host'] ),
                
escapeshellarg$wp_command )
            );
        }

        
WP_CLI::debug'Running SSH command: ' $escaped_command'bootstrap' );

        return 
$escaped_command;
    }

    
/**
     * Check whether a given command is disabled by the config.
     *
     * @return bool
     */
    
public function is_command_disabled$command ) {
        
$path implode' 'array_sliceDispatcher\get_path$command ), ) );
        return 
in_array$path$this->config['disabled_commands'], true );
    }

    
/**
     * Returns wp-config.php code, skipping the loading of wp-settings.php.
     *
     * @param string $wp_config_path Optional. Config file path. If left empty, it tries to
     *                               locate the wp-config.php file automatically.
     *
     * @return string
     */
    
public function get_wp_config_code$wp_config_path '' ) {
        if ( empty( 
$wp_config_path ) ) {
            
$wp_config_path Utils\locate_wp_config();
        }

        
$wp_config_code explode"\n"file_get_contents$wp_config_path ) );

        
// Detect and strip byte-order marks (BOMs).
        // This code assumes they can only be found on the first line.
        
foreach ( self::BYTE_ORDER_MARKS as $bom_name => $bom_sequence ) {
            
WP_CLI::debug"Looking for {$bom_name} BOM"'bootstrap' );

            
$length strlen$bom_sequence );

            while ( 
substr$wp_config_code[0], 0$length ) === $bom_sequence ) {
                
WP_CLI::warning(
                    
"{$bom_name} byte-order mark (BOM) detected in wp-config.php file, stripping it for parsing."
                
);

                
$wp_config_code[0] = substr$wp_config_code[0], $length );
            }
        }

        
$found_wp_settings false;

        
$lines_to_run = [];

        foreach ( 
$wp_config_code as $line ) {
            if ( 
preg_match'/^\s*require.+wp-settings\.php/'$line ) ) {
                
$found_wp_settings true;
                continue;
            }

            
$lines_to_run[] = $line;
        }

        if ( ! 
$found_wp_settings ) {
            
WP_CLI::error'Strange wp-config.php file: wp-settings.php is not loaded directly.' );
        }

        
$source implode"\n"$lines_to_run );
        
$source Utils\replace_path_consts$source$wp_config_path );
        return 
preg_replace'|^\s*\<\?php\s*|'''$source );
    }

    
/**
     * Transparently convert deprecated syntaxes
     *
     * @param array $args
     * @param array $assoc_args
     * @return array
     */
    
private static function back_compat_conversions$args$assoc_args ) {
        
$top_level_aliases = [
            
'sql'  => 'db',
            
'blog' => 'site',
        ];
        if ( 
count$args ) > ) {
            foreach ( 
$top_level_aliases as $old => $new ) {
                if ( 
$old === $args[0] ) {
                    
$args[0] = $new;
                    break;
                }
            }
        }

        
// *-meta  ->  * meta
        
if ( ! empty( $args ) && preg_match'/(post|comment|user|network)-meta/'$args[0], $matches ) ) {
            
array_shift$args );
            
array_unshift$args'meta' );
            
array_unshift$args$matches[1] );
        }

        
// cli aliases  ->  cli alias list
        
if ( [ 'cli''aliases' ] === array_slice$args0) ) {
            list( 
$args[0], $args[1], $args[2] ) = [ 'cli''alias''list' ];
        }

        
// core (multsite-)install --admin_name=  ->  --admin_user=
        
if ( count$args ) > && 'core' === $args[0] && isset( $assoc_args['admin_name'] ) ) {
            
$assoc_args['admin_user'] = $assoc_args['admin_name'];
            unset( 
$assoc_args['admin_name'] );
        }

        
// core config  ->  config create
        
if ( [ 'core''config' ] === array_slice$args0) ) {
            list( 
$args[0], $args[1] ) = [ 'config''create' ];
        }
        
// core language  ->  language core
        
if ( [ 'core''language' ] === array_slice$args0) ) {
            list( 
$args[0], $args[1] ) = [ 'language''core' ];
        }

        
// checksum core  ->  core verify-checksums
        
if ( [ 'checksum''core' ] === array_slice$args0) ) {
            list( 
$args[0], $args[1] ) = [ 'core''verify-checksums' ];
        }

        
// checksum plugin  ->  plugin verify-checksums
        
if ( [ 'checksum''plugin' ] === array_slice$args0) ) {
            list( 
$args[0], $args[1] ) = [ 'plugin''verify-checksums' ];
        }

        
// site create --site_id=  ->  site create --network_id=
        
if ( count$args ) >= && 'site' === $args[0] && 'create' === $args[1] && isset( $assoc_args['site_id'] ) ) {
            
$assoc_args['network_id'] = $assoc_args['site_id'];
            unset( 
$assoc_args['site_id'] );
        }

        
// {plugin|theme} update-all  ->  {plugin|theme} update --all
        
if ( count$args ) > && in_array$args[0], [ 'plugin''theme' ], true )
            && 
'update-all' === $args[1]
        ) {
            
$args[1]           = 'update';
            
$assoc_args['all'] = true;
        }

        
// transient delete-expired  ->  transient delete --expired
        
if ( count$args ) > && 'transient' === $args[0] && 'delete-expired' === $args[1] ) {
            
$args[1]               = 'delete';
            
$assoc_args['expired'] = true;
        }

        
// transient delete-all  ->  transient delete --all
        
if ( count$args ) > && 'transient' === $args[0] && 'delete-all' === $args[1] ) {
            
$args[1]           = 'delete';
            
$assoc_args['all'] = true;
        }

        
// plugin scaffold  ->  scaffold plugin
        
if ( [ 'plugin''scaffold' ] === array_slice$args0) ) {
            list( 
$args[0], $args[1] ) = [ $args[1], $args[0] ];
        }

        
// foo --help  ->  help foo
        
if ( isset( $assoc_args['help'] ) ) {
            
array_unshift$args'help' );
            unset( 
$assoc_args['help'] );
        }

        
// {post|user} list --ids  ->  {post|user} list --format=ids
        
if ( count$args ) > && in_array$args[0], [ 'post''user' ], true )
            && 
'list' === $args[1]
            && isset( 
$assoc_args['ids'] )
        ) {
            
$assoc_args['format'] = 'ids';
            unset( 
$assoc_args['ids'] );
        }

        
// --json  ->  --format=json
        
if ( isset( $assoc_args['json'] ) ) {
            
$assoc_args['format'] = 'json';
            unset( 
$assoc_args['json'] );
        }

        
// --{version|info}  ->  cli {version|info}
        
if ( empty( $args ) ) {
            
$special_flags = [ 'version''info' ];
            foreach ( 
$special_flags as $key ) {
                if ( isset( 
$assoc_args$key ] ) ) {
                    
$args = [ 'cli'$key ];
                    unset( 
$assoc_args$key ] );
                    break;
                }
            }
        }

        
// (post|comment|site|term) url  --> (post|comment|site|term) list --*__in --field=url
        
if ( count$args ) >= && in_array$args[0], [ 'post''comment''site''term' ], true ) && 'url' === $args[1] ) {
            switch ( 
$args[0] ) {
                case 
'post':
                    
$post_ids                array_slice$args);
                    
$args                    = [ 'post''list' ];
                    
$assoc_args['post__in']  = implode','$post_ids );
                    
$assoc_args['post_type'] = 'any';
                    
$assoc_args['orderby']   = 'post__in';
                    
$assoc_args['field']     = 'url';
                    break;
                case 
'comment':
                    
$comment_ids               array_slice$args);
                    
$args                      = [ 'comment''list' ];
                    
$assoc_args['comment__in'] = implode','$comment_ids );
                    
$assoc_args['orderby']     = 'comment__in';
                    
$assoc_args['field']       = 'url';
                    break;
                case 
'site':
                    
$site_ids               array_slice$args);
                    
$args                   = [ 'site''list' ];
                    
$assoc_args['site__in'] = implode','$site_ids );
                    
$assoc_args['field']    = 'url';
                    break;
                case 
'term':
                    
$taxonomy '';
                    if ( isset( 
$args[2] ) ) {
                        
$taxonomy $args[2];
                    }
                    
$term_ids              array_slice$args);
                    
$args                  = [ 'term''list'$taxonomy ];
                    
$assoc_args['include'] = implode','$term_ids );
                    
$assoc_args['orderby'] = 'include';
                    
$assoc_args['field']   = 'url';
                    break;
            }
        }

        
// config get --[global|constant]=<global|constant> --> config get <name> --type=constant|variable
        // config get --> config list
        
if ( count$args ) === 2
            
&& 'config' === $args[0]
            && 
'get' === $args[1] ) {
            if ( isset( 
$assoc_args['global'] ) ) {
                
$name $assoc_args['global'];
                
$type 'variable';
                unset( 
$assoc_args['global'] );
            } elseif ( isset( 
$assoc_args['constant'] ) ) {
                
$name $assoc_args['constant'];
                
$type 'constant';
                unset( 
$assoc_args['constant'] );
            }
            if ( ! empty( 
$name ) && ! empty( $type ) ) {
                
$args[]             = $name;
                
$assoc_args['type'] = $type;
            } else {
                
// We had a 'config get' without a '<name>', so assume 'list' was wanted.
                
$args[1] = 'list';
            }
        }

        return [ 
$args$assoc_args ];
    }

    
/**
     * Whether or not the output should be rendered in color
     *
     * @return bool
     */
    
public function in_color() {
        return 
$this->colorize;
    }

    public function 
init_colorization() {
        if ( 
'auto' === $this->config['color'] ) {
            
$this->colorize = ( ! Utils\isPiped() && ! Utils\is_windows() );
        } else {
            
$this->colorize $this->config['color'];
        }
    }

    public function 
init_logger() {
        if ( 
$this->config['quiet'] ) {
            
$logger = new Loggers\Quiet$this->in_color() );
        } else {
            
$logger = new Loggers\Regular$this->in_color() );
        }

        
WP_CLI::set_logger$logger );
    }

    public function 
get_required_files() {
        return 
$this->required_files;
    }

    
/**
     * Do WordPress core files exist?
     *
     * @return bool
     */
    
private function wp_exists() {
        return 
file_existsABSPATH 'wp-includes/version.php' );
    }

    
/**
     * Are WordPress core files readable?
     *
     * @return bool
     */
    
private function wp_is_readable() {
        return 
is_readableABSPATH 'wp-includes/version.php' );
    }

    private function 
check_wp_version() {
        
$wp_exists      $this->wp_exists();
        
$wp_is_readable $this->wp_is_readable();
        if ( ! 
$wp_exists || ! $wp_is_readable ) {
            
$this->show_synopsis_if_composite_command();
            
// If the command doesn't exist use as error.
            
$args                   $this->cmd_starts_with( [ 'help' ] ) ? array_slice$this->arguments) : $this->arguments;
            
$suggestion_or_disabled $this->find_command_to_run$args );
            if ( 
is_string$suggestion_or_disabled ) ) {
                if ( ! 
preg_match'/disabled from the config file.$/'$suggestion_or_disabled ) ) {
                    
WP_CLI::warning"No WordPress installation found. If the command '" implode' '$args ) . "' is in a plugin or theme, pass --path=`path/to/wordpress`." );
                }
                
WP_CLI::error$suggestion_or_disabled );
            }

            if ( 
$wp_exists && ! $wp_is_readable ) {
                
WP_CLI::error(
                    
'It seems, the WordPress core files do not have the proper file permissions.'
                
);
            }
            
WP_CLI::error(
                
"This does not seem to be a WordPress installation.\n" .
                
'Pass --path=`path/to/wordpress` or run `wp core download`.'
            
);
        }

        global 
$wp_version;
        include 
ABSPATH 'wp-includes/version.php';

        
$minimum_version '3.7';

        if ( 
version_compare$wp_version$minimum_version'<' ) ) {
            
WP_CLI::error(
                
"WP-CLI needs WordPress $minimum_version or later to work properly. " .
                
"The version currently installed is $wp_version.\n" .
                
'Try running `wp core download --force`.'
            
);
        }
    }

    public function 
init_config() {
        
$configurator WP_CLI::get_configurator();

        
$argv array_slice$GLOBALS['argv'], );

        
$this->alias null;
        if ( ! empty( 
$argv[0] ) && preg_match'#' Configurator::ALIAS_REGEX '#'$argv[0], $matches ) ) {
            
$this->alias array_shift$argv );
        }

        
// File config
        
{
            
$this->global_config_path  $this->get_global_config_path();
            
$this->project_config_path $this->get_project_config_path();

            
$configurator->merge_yml$this->global_config_path$this->alias );
            
$config                         $configurator->to_array();
            
$this->required_files['global'] = $config[0]['require'];
            
$configurator->merge_yml$this->project_config_path$this->alias );
            
$config                          $configurator->to_array();
            
$this->required_files['project'] = $config[0]['require'];
        }

        
// Runtime config and args
        
{
            list( 
$args$assoc_args$this->runtime_config ) = $configurator->parse_args$argv );

            list( 
$this->arguments$this->assoc_args ) = self::back_compat_conversions(
                
$args,
                
$assoc_args
            
);

            
$configurator->merge_array$this->runtime_config );
        }

        list( 
$this->config$this->extra_config ) = $configurator->to_array();
        
$this->aliases                             $configurator->get_aliases();
        if ( 
count$this->aliases ) && ! isset( $this->aliases['@all'] ) ) {
            
$this->aliases         array_reverse$this->aliases );
            
$this->aliases['@all'] = 'Run command against every registered alias.';
            
$this->aliases         array_reverse$this->aliases );
        }
        
$this->required_files['runtime'] = $this->config['require'];
    }

    private function 
check_root() {
        if ( 
$this->config['allow-root'] || getenv'WP_CLI_ALLOW_ROOT' ) ) {
            return; 
# they're aware of the risks!
        
}
        if ( 
count$this->arguments ) >= && 'cli' === $this->arguments[0] && in_array$this->arguments[1], [ 'update''info' ], true ) ) {
            return; 
# make it easier to update root-owned copies
        
}
        if ( ! 
function_exists'posix_geteuid' ) ) {
            return; 
# posix functions not available
        
}
        if ( 
posix_geteuid() !== ) {
            return; 
# not root
        
}

        
WP_CLI::error(
            
"YIKES! It looks like you're running this as root. You probably meant to " .
            
"run this as the user that your WordPress installation exists under.\n" .
            
"\n" .
            
"If you REALLY mean to run this as root, we won't stop you, but just " .
            
'bear in mind that any code on this site will then have full control of ' .
            
"your server, making it quite DANGEROUS.\n" .
            
"\n" .
            
"If you'd like to continue as root, please run this again, adding this " .
            
"flag:  --allow-root\n" .
            
"\n" .
            
"If you'd like to run it as the user that this site is under, you can " .
            
"run the following to become the respective user:\n" .
            
"\n" .
            
"    sudo -u USER -i -- wp <command>\n" .
            
"\n"
        
);
    }

    private function 
run_alias_group$aliases ) {
        
Utils\check_proc_available'group alias' );

        
$php_bin escapeshellargUtils\get_php_binary() );

        
$script_path $GLOBALS['argv'][0];

        if ( 
getenv'WP_CLI_CONFIG_PATH' ) ) {
            
$config_path getenv'WP_CLI_CONFIG_PATH' );
        } else {
            
$config_path Utils\get_home_dir() . '/.wp-cli/config.yml';
        }
        
$config_path escapeshellarg$config_path );

        foreach ( 
$aliases as $alias ) {
            
WP_CLI::log$alias );
            
$args           implode' 'array_map'escapeshellarg'$this->arguments ) );
            
$assoc_args     Utils\assoc_args_to_str$this->assoc_args );
            
$runtime_config Utils\assoc_args_to_str$this->runtime_config );
            
$full_command   "WP_CLI_CONFIG_PATH={$config_path} {$php_bin} {$script_path} {$alias} {$args}{$assoc_args}{$runtime_config}";
            
$pipes          = [];
            
$proc           Utils\proc_open_compat$full_command, [ STDINSTDOUTSTDERR ], $pipes );
            
proc_close$proc );
        }
    }

    private function 
set_alias$alias ) {
        
$orig_config  $this->config;
        
$alias_config $this->aliases$alias ];
        
$this->config array_merge$orig_config$alias_config );
        foreach ( 
$alias_config as $key => $_ ) {
            if ( isset( 
$orig_config$key ] ) && ! is_null$orig_config$key ] ) ) {
                
$this->assoc_args$key ] = $orig_config$key ];
            }
        }
    }

    public function 
start() {
        
// Enable PHP error reporting to stderr if testing. Will need to be re-enabled after WP loads.
        
if ( getenv'BEHAT_RUN' ) ) {
            
$this->enable_error_reporting();
        }

        
WP_CLI::debug$this->global_config_path_debug'bootstrap' );
        
WP_CLI::debug$this->project_config_path_debug'bootstrap' );
        
WP_CLI::debug'argv: ' implode' '$GLOBALS['argv'] ), 'bootstrap' );

        
$this->check_root();
        if ( 
$this->alias ) {
            if ( 
'@all' === $this->alias && ! isset( $this->aliases['@all'] ) ) {
                
WP_CLI::error"Cannot use '@all' when no aliases are registered." );
            }

            if ( 
'@all' === $this->alias && is_string$this->aliases['@all'] ) ) {
                
$aliases array_keys$this->aliases );
                
$k       array_search'@all'$aliasestrue );
                unset( 
$aliases$k ] );
                
$this->run_alias_group$aliases );
                exit;
            }

            if ( ! 
array_key_exists$this->alias$this->aliases ) ) {
                
$error_msg  "Alias '{$this->alias}' not found.";
                
$suggestion Utils\get_suggestion$this->aliasarray_keys$this->aliases ), $threshold );
                if ( 
$suggestion ) {
                    
$error_msg .= PHP_EOL "Did you mean '{$suggestion}'?";
                }
                
WP_CLI::error$error_msg );
            }
            
// Numerically indexed means a group of aliases
            
if ( isset( $this->aliases$this->alias ][0] ) ) {
                
$group_aliases $this->aliases$this->alias ];
                
$all_aliases   array_keys$this->aliases );
                
$diff          array_diff$group_aliases$all_aliases );
                if ( ! empty( 
$diff ) ) {
                    
WP_CLI::error"Group '{$this->alias}' contains one or more invalid aliases: " implode', '$diff ) );
                }
                
$this->run_alias_group$group_aliases );
                exit;
            }

            
$this->set_alias$this->alias );
        }

        if ( empty( 
$this->arguments ) ) {
            
$this->arguments[] = 'help';
        }

        
// Protect 'cli info' from most of the runtime,
        // except when the command will be run over SSH
        
if ( 'cli' === $this->arguments[0] && ! empty( $this->arguments[1] ) && 'info' === $this->arguments[1] && ! $this->config['ssh'] ) {
            
$this->run_command_and_exit();
        }

        if ( isset( 
$this->config['http'] ) && ! class_exists'\WP_REST_CLI\Runner' ) ) {
            
WP_CLI::error"RESTful WP-CLI needs to be installed. Try 'wp package install wp-cli/restful'." );
        }

        if ( 
$this->config['ssh'] ) {
            
$this->run_ssh_command$this->config['ssh'] );
            return;
        }

        
// Handle --path parameter
        
self::set_wp_root$this->find_wp_root() );

        
// First try at showing man page - if help command and either haven't found 'version.php' or 'wp-config.php' (so won't be loading WP & adding commands) or help on subcommand.
        
if ( $this->cmd_starts_with( [ 'help' ] )
            && ( ! 
$this->wp_exists()
                || ! 
Utils\locate_wp_config()
                || 
count$this->arguments ) > 2
            
) ) {
            
$this->auto_check_update();
            
$this->run_command$this->arguments$this->assoc_args );
            
// Help didn't exit so failed to find the command at this stage.
        
}

        
// Handle --url parameter
        
$url self::guess_url$this->config );
        if ( 
$url ) {
            
WP_CLI::set_url$url );
        }

        
$this->do_early_invoke'before_wp_load' );

        
$this->check_wp_version();

        if ( 
$this->cmd_starts_with( [ 'config''create' ] ) ) {
            
$this->run_command_and_exit();
        }

        if ( ! 
Utils\locate_wp_config() ) {
            
WP_CLI::error(
                
"'wp-config.php' not found.\n" .
                
'Either create one manually or use `wp config create`.'
            
);
        }

        
/*
         * Set the MySQLi error reporting off because WordPress handles its own.
         * This is due to the default value change from `MYSQLI_REPORT_OFF`
         * to `MYSQLI_REPORT_ERROR|MYSQLI_REPORT_STRICT` in PHP 8.1.
         */
        
if ( function_exists'mysqli_report' ) ) {
            
mysqli_report); // phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysqli_report
        
}

        
// phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- Declaring WP native constants.

        
if ( $this->cmd_starts_with( [ 'core''is-installed' ] )
            || 
$this->cmd_starts_with( [ 'core''update-db' ] ) ) {
            
define'WP_INSTALLING'true );
        }

        if (
            
count$this->arguments ) >= &&
            
'core' === $this->arguments[0] &&
            
in_array$this->arguments[1], [ 'install''multisite-install' ], true )
        ) {
            
define'WP_INSTALLING'true );

            
// We really need a URL here
            
if ( ! isset( $_SERVER['HTTP_HOST'] ) ) {
                
$url 'https://example.com';
                
WP_CLI::set_url$url );
            }

            if ( 
'multisite-install' === $this->arguments[1] ) {
                
// need to fake some globals to skip the checks in wp-includes/ms-settings.php
                
$url_parts Utils\parse_url$url );
                
self::fake_current_site_blog$url_parts );

                if ( ! 
defined'COOKIEHASH' ) ) {
                    
define'COOKIEHASH'md5$url_parts['host'] ) );
                }
            }
        }

        if ( 
$this->cmd_starts_with( [ 'import' ] ) ) {
            
define'WP_LOAD_IMPORTERS'true );
            
define'WP_IMPORTING'true );
        }

        if ( 
$this->cmd_starts_with( [ 'cron''event''run' ] ) ) {
            
define'DOING_CRON'true );
        }
        
// phpcs:enable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound

        
$this->load_wordpress();

        
$this->run_command_and_exit();

    }

    
/**
     * Load WordPress, if it hasn't already been loaded
     */
    
public function load_wordpress() {
        static 
$wp_cli_is_loaded;
        
// Globals not explicitly globalized in WordPress
        
global $site_id$wpdb$public$current_site$current_blog$path$shortcode_tags;

        if ( ! empty( 
$wp_cli_is_loaded ) ) {
            return;
        }

        
$wp_cli_is_loaded true;

        
// Handle --context flag.
        
$this->context_manager->switch_context$this->config );

        
WP_CLI::debug'Begin WordPress load''bootstrap' );
        
WP_CLI::do_hook'before_wp_load' );

        
$this->check_wp_version();

        
$wp_config_path Utils\locate_wp_config();
        if ( ! 
$wp_config_path ) {
            
WP_CLI::error(
                
"'wp-config.php' not found.\n" .
                
'Either create one manually or use `wp config create`.'
            
);
        }

        
WP_CLI::debug'wp-config.php path: ' $wp_config_path'bootstrap' );
        
WP_CLI::do_hook'before_wp_config_load' );

        
// Load wp-config.php code, in the global scope
        
$wp_cli_original_defined_vars get_defined_vars();

        eval( 
$this->get_wp_config_code() ); // phpcs:ignore Squiz.PHP.Eval.Discouraged

        
foreach ( get_defined_vars() as $key => $var ) {
            if ( 
array_key_exists$key$wp_cli_original_defined_vars ) || 'wp_cli_original_defined_vars' === $key ) {
                continue;
            }

            
// phpcs:ignore PHPCompatibility.Variables.ForbiddenGlobalVariableVariable.NonBareVariableFound
            
global ${$key};
            
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound
            
${$key} = $var;
        }

        
$this->maybe_update_url_from_domain_constant();
        
WP_CLI::do_hook'after_wp_config_load' );
        
$this->do_early_invoke'after_wp_config_load' );

        
// Prevent error notice from wp_guess_url() when core isn't installed
        
if ( $this->cmd_starts_with( [ 'core''is-installed' ] )
            && ! 
defined'COOKIEHASH' ) ) {
            
define'COOKIEHASH'md5'wp-cli' ) );
        }

        
// Load WP-CLI utilities
        
require WP_CLI_ROOT '/php/utils-wp.php';

        
// Set up WordPress bootstrap actions and filters
        
$this->setup_bootstrap_hooks();

        
// Load Core, mu-plugins, plugins, themes etc.
        
if ( Utils\wp_version_compare'4.6-alpha-37575''>=' ) ) {
            if ( 
$this->cmd_starts_with( [ 'help' ] ) ) {
                
// Hack: define `WP_DEBUG` and `WP_DEBUG_DISPLAY` to get `wpdb::bail()` to `wp_die()`.
                
if ( ! defined'WP_DEBUG' ) ) {
                    
define'WP_DEBUG'true );
                }
                if ( ! 
defined'WP_DEBUG_DISPLAY' ) ) {
                    
define'WP_DEBUG_DISPLAY'true );
                }
            }
            require 
ABSPATH 'wp-settings.php';
        } else {
            require 
WP_CLI_ROOT '/php/wp-settings-cli.php';
        }

        
// Fix memory limit. See https://core.trac.wordpress.org/ticket/14889
        // phpcs:ignore WordPress.PHP.IniSet.memory_limit_Blacklisted -- This is perfectly fine for CLI usage.
        
ini_set'memory_limit', -);

        
// Load all the admin APIs, for convenience
        
require ABSPATH 'wp-admin/includes/admin.php';

        
add_filter(
            
'filesystem_method',
            static function () {
                return 
'direct';
            },
            
99
        
);

        
// Re-enable PHP error reporting to stderr if testing.
        
if ( getenv'BEHAT_RUN' ) ) {
            
$this->enable_error_reporting();
        }

        
WP_CLI::debug'Loaded WordPress''bootstrap' );
        
WP_CLI::do_hook'after_wp_load' );

    }

    private static function 
fake_current_site_blog$url_parts ) {
        global 
$current_site$current_blog;

        if ( ! isset( 
$url_parts['path'] ) ) {
            
$url_parts['path'] = '/';
        }

        
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Intentional override.
        
$current_site = (object) [
            
'id'            => 1,
            
'blog_id'       => 1,
            
'domain'        => $url_parts['host'],
            
'path'          => $url_parts['path'],
            
'cookie_domain' => $url_parts['host'],
            
'site_name'     => 'WordPress',
        ];

        
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Intentional override.
        
$current_blog = (object) [
            
'blog_id'  => 1,
            
'site_id'  => 1,
            
'domain'   => $url_parts['host'],
            
'path'     => $url_parts['path'],
            
'public'   => '1',
            
'archived' => '0',
            
'mature'   => '0',
            
'spam'     => '0',
            
'deleted'  => '0',
            
'lang_id'  => '0',
        ];
    }

    
/**
     * Called after wp-config.php is eval'd, to potentially reset `--url`
     */
    
private function maybe_update_url_from_domain_constant() {
        if ( ! empty( 
$this->config['url'] ) || ! empty( $this->config['blog'] ) ) {
            return;
        }

        if ( 
defined'DOMAIN_CURRENT_SITE' ) ) {
            
$url DOMAIN_CURRENT_SITE;
            if ( 
defined'PATH_CURRENT_SITE' ) ) {
                
$url .= PATH_CURRENT_SITE;
            }
            
WP_CLI::set_url$url );
        }
    }

    
/**
     * Set up hooks meant to run during the WordPress bootstrap process
     */
    
private function setup_bootstrap_hooks() {

        if ( 
$this->config['skip-plugins'] ) {
            
$this->setup_skip_plugins_filters();
        }

        if ( 
$this->config['skip-themes'] ) {
            
WP_CLI::add_wp_hook'setup_theme', [ $this'action_setup_theme_wp_cli_skip_themes' ], 999 );
        }

        if ( 
$this->cmd_starts_with( [ 'help' ] ) ) {
            
// Try to trap errors on help.
            
$help_handler = [ $this'help_wp_die_handler' ]; // Avoid any cross PHP version issues by not using $this in anon function.
            
WP_CLI::add_wp_hook(
                
'wp_die_handler',
                function () use ( 
$help_handler ) {
                    return 
$help_handler;
                }
            );
        } else {
            
WP_CLI::add_wp_hook(
                
'wp_die_handler',
                static function () {
                    return 
'\WP_CLI\Utils\wp_die_handler';
                }
            );
        }

        
// Prevent code from performing a redirect
        
WP_CLI::add_wp_hook'wp_redirect''WP_CLI\\Utils\\wp_redirect_handler' );

        
WP_CLI::add_wp_hook(
            
'nocache_headers',
            static function ( 
$headers ) {
                
// WordPress might be calling nocache_headers() because of a dead db
                
global $wpdb;
                if ( ! empty( 
$wpdb->error ) ) {
                    
Utils\wp_die_handler$wpdb->error );
                }
                
// Otherwise, WP might be calling nocache_headers() because WP isn't installed
                
Utils\wp_not_installed();
                return 
$headers;
            }
        );

        
WP_CLI::add_wp_hook(
            
'setup_theme',
            static function () {
                
// Polyfill is_customize_preview(), as it is needed by TwentyTwenty to
                // check for starter content.
                
if ( ! function_exists'is_customize_preview' ) ) {
                    function 
is_customize_preview() {
                        return 
false;
                    }
                }
            },
            
0
        
);

        
// ALTERNATE_WP_CRON might trigger a redirect, which we can't handle
        
if ( defined'ALTERNATE_WP_CRON' ) && ALTERNATE_WP_CRON ) {
            
WP_CLI::add_wp_hook(
                
'muplugins_loaded',
                static function () {
                    
remove_action'init''wp_cron' );
                }
            );
        }

        
// Get rid of warnings when converting single site to multisite
        
if ( defined'WP_INSTALLING' ) && $this->is_multisite() ) {
            
$values = [
                
'ms_files_rewriting'             => null,
                
'active_sitewide_plugins'        => [],
                
'_site_transient_update_core'    => null,
                
'_site_transient_update_themes'  => null,
                
'_site_transient_update_plugins' => null,
                
'WPLANG'                         => '',
            ];
            foreach ( 
$values as $key => $value ) {
                
WP_CLI::add_wp_hook(
                    
"pre_site_option_$key",
                    static function () use ( 
$values$key ) {
                        return 
$values$key ];
                    }
                );
            }
        }

        
// Always permit operations against sites, regardless of status
        
WP_CLI::add_wp_hook'ms_site_check''__return_true' );

        
// Always permit operations against WordPress, regardless of maintenance mode
        
WP_CLI::add_wp_hook(
            
'enable_maintenance_mode',
            static function () {
                return 
false;
            }
        );

        
// Use our own debug mode handling instead of WP core
        
WP_CLI::add_wp_hook(
            
'enable_wp_debug_mode_checks',
            static function ( 
$ret ) {
                
Utils\wp_debug_mode();
                return 
false;
            }
        );

        
// Never load advanced-cache.php drop-in when WP-CLI is operating
        
WP_CLI::add_wp_hook(
            
'enable_loading_advanced_cache_dropin',
            static function () {
                return 
false;
            }
        );

        
// In a multisite installation, die if unable to find site given in --url parameter
        
if ( $this->is_multisite() ) {
            
$run_on_site_not_found false;
            if ( 
$this->cmd_starts_with( [ 'cache''flush' ] ) ) {
                
$run_on_site_not_found 'cache flush';
            }
            if ( 
$this->cmd_starts_with( [ 'search-replace' ] ) ) {
                
// Table-specified
                // Bits: search-replace <search> <replace> [<table>...]
                // Or not against a specific blog
                
if ( count$this->arguments ) > 3
                    
|| ! empty( $this->assoc_args['network'] )
                    || ! empty( 
$this->assoc_args['all-tables'] )
                    || ! empty( 
$this->assoc_args['all-tables-with-prefix'] ) ) {
                    
$run_on_site_not_found 'search-replace';
                }
            }
            if ( 
$run_on_site_not_found
                
&& Utils\wp_version_compare'4.0''>=' ) ) {
                
WP_CLI::add_wp_hook(
                    
'ms_site_not_found',
                    static function () use ( 
$run_on_site_not_found ) {
                        
// esc_sql() isn't yet loaded, but needed.
                        
if ( 'search-replace' === $run_on_site_not_found ) {
                            require_once 
ABSPATH WPINC '/formatting.php';
                        }
                        
// PHP 5.3 compatible implementation of run_command_and_exit().
                        
$runner WP_CLI::get_runner();
                        
$runner->run_command$runner->arguments$runner->assoc_args );
                        exit;
                    },
                    
1
                
);
            }
            
WP_CLI::add_wp_hook(
                
'ms_site_not_found',
                static function ( 
$current_site$domain$path ) {
                    
$url         $domain $path;
                    
$message     $url "Site '{$url}' not found." 'Site not found.';
                    
$has_param   = isset( WP_CLI::get_runner()->config['url'] );
                    
$has_const   defined'DOMAIN_CURRENT_SITE' );
                    
$explanation '';
                    if ( 
$has_param ) {
                        
$explanation 'Verify `--url=<url>` matches an existing site.';
                    } else {
                        
$explanation "Define DOMAIN_CURRENT_SITE in 'wp-config.php' or use `--url=<url>` to override.";

                        if ( 
$has_const ) {
                            
$explanation 'Verify DOMAIN_CURRENT_SITE matches an existing site or use `--url=<url>` to override.';
                        }
                    }
                    if ( 
$explanation ) {
                        
$message .= ' ' $explanation;
                    }
                    
WP_CLI::error$message );
                },
                
10,
                
3
            
);
        }

        
// The APC cache is not available on the command-line, so bail, to prevent cache poisoning
        
WP_CLI::add_wp_hook(
            
'muplugins_loaded',
            static function () {
                if ( 
$GLOBALS['_wp_using_ext_object_cache'] && class_exists'APC_Object_Cache' ) ) {
                    
WP_CLI::warning'Running WP-CLI while the APC object cache is activated can result in cache corruption.' );
                    
WP_CLI::confirm'Given the consequences, do you wish to continue?' );
                }
            },
            
0
        
);

        
// Handle --user parameter
        
if ( ! defined'WP_INSTALLING' ) ) {
            
$config $this->config;
            
WP_CLI::add_wp_hook(
                
'init',
                static function () use ( 
$config ) {
                    if ( isset( 
$config['user'] ) ) {
                        
$fetcher = new Fetchers\User();
                        
$user    $fetcher->get_check$config['user'] );
                        
wp_set_current_user$user->ID );
                    } else {
                        
add_action'init''kses_remove_filters'11 );
                    }
                },
                
0
            
);
        }

        
// Avoid uncaught exception when using wp_mail() without defined $_SERVER['SERVER_NAME']
        
WP_CLI::add_wp_hook(
            
'wp_mail_from',
            static function ( 
$from_email ) {
                if ( 
'wordpress@' === $from_email ) {
                    
$sitename strtolowerUtils\parse_urlsite_url(), PHP_URL_HOST ) );
                    if ( 
substr$sitename0) === 'www.' ) {
                        
$sitename substr$sitename);
                    }
                    
$from_email 'wordpress@' $sitename;
                }
                return 
$from_email;
            }
        );

        
// Don't apply set_url_scheme in get_home_url() or get_site_url().
        
WP_CLI::add_wp_hook(
            
'home_url',
            static function ( 
$url$path$scheme$blog_id ) {
                if ( empty( 
$blog_id ) || ! is_multisite() ) {
                    
$url get_option'home' );
                } else {
                    
switch_to_blog$blog_id );
                    
$url get_option'home' );
                    
restore_current_blog();
                }

                if ( 
$path && is_string$path ) ) {
                    
$url .= '/' ltrim$path'/' );
                }

                return 
$url;
            },
            
0,
            
4
        
);
        
WP_CLI::add_wp_hook(
            
'site_url',
            static function ( 
$url$path$scheme$blog_id ) {
                if ( empty( 
$blog_id ) || ! is_multisite() ) {
                    
$url get_option'siteurl' );
                } else {
                    
switch_to_blog$blog_id );
                    
$url get_option'siteurl' );
                    
restore_current_blog();
                }

                if ( 
$path && is_string$path ) ) {
                    
$url .= '/' ltrim$path'/' );
                }

                return 
$url;
            },
            
0,
            
4
        
);

        
// Set up hook for plugins and themes to conditionally add WP-CLI commands.
        
WP_CLI::add_wp_hook(
            
'init',
            static function () {
                
do_action'cli_init' );
            }
        );
    }

    
/**
     * Set up the filters to skip the loaded plugins
     */
    
private function setup_skip_plugins_filters() {
        
$wp_cli_filter_active_plugins = static function ( $plugins ) {
            
$skipped_plugins WP_CLI::get_runner()->config['skip-plugins'];
            if ( 
true === $skipped_plugins ) {
                return [];
            }
            if ( ! 
is_array$plugins ) ) {
                return 
$plugins;
            }
            foreach ( 
$plugins as $a => $b ) {
                
// active_sitewide_plugins stores plugin name as the key.
                
if ( false !== strposcurrent_filter(), 'active_sitewide_plugins' ) && Utils\is_plugin_skipped$a ) ) {
                    unset( 
$plugins$a ] );
                    
// active_plugins stores plugin name as the value.
                
} elseif ( false !== strposcurrent_filter(), 'active_plugins' ) && Utils\is_plugin_skipped$b ) ) {
                    unset( 
$plugins$a ] );
                }
            }
            
// Reindex because active_plugins expects a numeric index.
            
if ( false !== strposcurrent_filter(), 'active_plugins' ) ) {
                
$plugins array_values$plugins );
            }
            return 
$plugins;
        };

        
$hooks = [
            
'pre_site_option_active_sitewide_plugins',
            
'site_option_active_sitewide_plugins',
            
'pre_option_active_plugins',
            
'option_active_plugins',
        ];
        foreach ( 
$hooks as $hook ) {
            
WP_CLI::add_wp_hook$hook$wp_cli_filter_active_plugins999 );
        }
        
WP_CLI::add_wp_hook(
            
'plugins_loaded',
            static function () use ( 
$hooks$wp_cli_filter_active_plugins ) {
                foreach ( 
$hooks as $hook ) {
                    
remove_filter$hook$wp_cli_filter_active_plugins999 );
                }
            },
            
0
        
);
    }

    
/**
     * Set up the filters to skip the loaded theme
     */
    
public function action_setup_theme_wp_cli_skip_themes() {
        
$wp_cli_filter_active_theme = static function ( $value ) {
            
$skipped_themes WP_CLI::get_runner()->config['skip-themes'];
            if ( 
true === $skipped_themes ) {
                return 
'';
            }
            if ( ! 
is_array$skipped_themes ) ) {
                
$skipped_themes explode','$skipped_themes );
            }

            
$checked_value $value;
            
// Always check against the stylesheet value
            // This ensures a child theme can be skipped when template differs
            
if ( false !== striposcurrent_filter(), 'option_template' ) ) {
                
$checked_value get_option'stylesheet' );
            }

            if ( 
'' === $checked_value || in_array$checked_value$skipped_themestrue ) ) {
                return 
'';
            }
            return 
$value;
        };
        
$hooks                      = [
            
'pre_option_template',
            
'option_template',
            
'pre_option_stylesheet',
            
'option_stylesheet',
        ];
        foreach ( 
$hooks as $hook ) {
            
add_filter$hook$wp_cli_filter_active_theme999 );
        }
        
// Clean up after the TEMPLATEPATH and STYLESHEETPATH constants are defined
        
WP_CLI::add_wp_hook(
            
'after_setup_theme',
            static function () use ( 
$hooks$wp_cli_filter_active_theme ) {
                foreach ( 
$hooks as $hook ) {
                    
remove_filter$hook$wp_cli_filter_active_theme999 );
                }
            },
            
0
        
);
    }

    
/**
     * Whether or not this WordPress installation is multisite.
     *
     * For use after wp-config.php has loaded, but before the rest of WordPress
     * is loaded.
     */
    
private function is_multisite() {
        if ( 
defined'MULTISITE' ) ) {
            return 
MULTISITE;
        }

        if ( 
defined'SUBDOMAIN_INSTALL' ) || defined'VHOST' ) || defined'SUNRISE' ) ) {
            return 
true;
        }

        return 
false;
    }

    
/**
     * Error handler for `wp_die()` when the command is help to try to trap errors (db connection failure in particular) during WordPress load.
     */
    
public function help_wp_die_handler$message ) {
        
$help_exit_warning 'Error during WordPress load.';
        if ( 
$message instanceof WP_Error ) {
            
$help_exit_warning Utils\wp_clean_error_message$message->get_error_message() );
        } elseif ( 
is_string$message ) ) {
            
$help_exit_warning Utils\wp_clean_error_message$message );
        }
        
$this->run_command_and_exit$help_exit_warning );
    }

    
/**
     * Check whether there's a WP-CLI update available, and suggest update if so.
     */
    
private function auto_check_update() {

        
// `wp cli update` only works with Phars at this time.
        
if ( ! Utils\inside_phar() ) {
            return;
        }

        
$existing_phar realpath$_SERVER['argv'][0] );
        
// Phar needs to be writable to be easily updateable.
        
if ( ! is_writable$existing_phar ) || ! is_writabledirname$existing_phar ) ) ) {
            return;
        }

        
// Only check for update when a human is operating.
        
if ( ! function_exists'posix_isatty' ) || ! posix_isattySTDOUT ) ) {
            return;
        }

        
// Allow hosts and other providers to disable automatic check update.
        
if ( getenv'WP_CLI_DISABLE_AUTO_CHECK_UPDATE' ) ) {
            return;
        }

        
// Permit configuration of number of days between checks.
        
$days_between_checks getenv'WP_CLI_AUTO_CHECK_UPDATE_DAYS' );
        if ( 
false === $days_between_checks ) {
            
$days_between_checks 1;
        }

        
$cache     WP_CLI::get_cache();
        
$cache_key 'wp-cli-update-check';
        
// Bail early on the first check, so we don't always check on an unwritable cache.
        
if ( ! $cache->has$cache_key ) ) {
            
$cache->write$cache_keytime() );
            return;
        }

        
// Bail if last check is still within our update check time period.
        
$last_check = (int) $cache->read$cache_key );
        if ( ( 
time() - ( 24 60 60 $days_between_checks ) ) < $last_check ) {
            return;
        }

        
// In case the operation fails, ensure the timestamp has been updated.
        
$cache->write$cache_keytime() );

        
// Check whether any updates are available.
        
ob_start();
        
WP_CLI::run_command(
            [ 
'cli''check-update' ],
            [
                
'format' => 'count',
            ]
        );
        
$count ob_get_clean();
        if ( ! 
$count ) {
            return;
        }

        
// Looks like an update is available, so let's prompt to update.
        
WP_CLI::run_command( [ 'cli''update' ] );
        
// If the Phar was replaced, we can't proceed with the original process.
        
exit;
    }

    
/**
     * Get a suggestion on similar (sub)commands when the user entered an
     * unknown (sub)command.
     *
     * @param string           $entry        User entry that didn't match an
     *                                       existing command.
     * @param CompositeCommand $root_command Root command to start search for
     *                                       suggestions at.
     *
     * @return string Suggestion that fits the user entry, or an empty string.
     */
    
private function get_subcommand_suggestion$entryCompositeCommand $root_command null ) {
        
$commands = [];
        
$this->enumerate_commands$root_command ?: WP_CLI::get_root_command(), $commands );

        return 
Utils\get_suggestion$entry$commands$threshold );
    }

    
/**
     * Recursive method to enumerate all known commands.
     *
     * @param CompositeCommand $command Composite command to recurse over.
     * @param array            $list    Reference to list accumulating results.
     * @param string           $parent  Parent command to use as prefix.
     */
    
private function enumerate_commandsCompositeCommand $command, array &$list$parent '' ) {
        foreach ( 
$command->get_subcommands() as $subcommand ) {
            
/** @var CompositeCommand $subcommand */
            
$command_string = empty( $parent )
                ? 
$subcommand->get_name()
                : 
"{$parent} {$subcommand->get_name()}";

            
$list[] = $command_string;

            
$this->enumerate_commands$subcommand$list$command_string );
        }
    }

    
/**
     * Enables (almost) full PHP error reporting to stderr.
     */
    
private function enable_error_reporting() {
        if ( 
E_ALL !== error_reporting() ) {
            
// Don't enable E_DEPRECATED as old versions of WP use PHP 4 style constructors and the mysql extension.
            
error_reportingE_ALL & ~E_DEPRECATED );
        }
        
ini_set'display_errors''stderr' ); // phpcs:ignore WordPress.PHP.IniSet.display_errors_Blacklisted
    
}
}
<?
php

namespace WP_CLI;

/**
 * Checks if the list of parameters matches the specification defined in the synopsis.
 */
class SynopsisValidator {

    
/**
     * Structured representation of command synopsis.
     *
     * @var array
     */
    
private $spec;

    
/**
     * @param string $synopsis Command's synopsis.
     */
    
public function __construct$synopsis ) {
        
$this->spec SynopsisParser::parse$synopsis );
    }

    
/**
     * Get any unknown arguments.
     *
     * @return array
     */
    
public function get_unknown() {
        return 
array_column(
            
$this->query_spec(
                [
                    
'type' => 'unknown',
                ]
            ),
            
'token'
        
);
    }

    
/**
     * Check whether there are enough positional arguments.
     *
     * @param array $args Positional arguments.
     * @return bool
     */
    
public function enough_positionals$args ) {
        
$positional $this->query_spec(
            [
                
'type'     => 'positional',
                
'optional' => false,
            ]
        );

        return 
count$args ) >= count$positional );
    }

    
/**
     * Check for any unknown positionals.
     *
     * @param array $args Positional arguments.
     * @return array
     */
    
public function unknown_positionals$args ) {
        
$positional_repeating $this->query_spec(
            [
                
'type'      => 'positional',
                
'repeating' => true,
            ]
        );

        
// At least one positional supports as many as possible.
        
if ( ! empty( $positional_repeating ) ) {
            return [];
        }

        
$positional $this->query_spec(
            [
                
'type'      => 'positional',
                
'repeating' => false,
            ]
        );

        return 
array_slice$argscount$positional ) );
    }

    
/**
     * Check that all required keys are present and that they have values.
     *
     * @param array $assoc_args Parameters passed to command.
     * @return array
     */
    
public function validate_assoc$assoc_args ) {
        
$assoc_spec $this->query_spec(
            [
                
'type' => 'assoc',
            ]
        );

        
$errors = [
            
'fatal'   => [],
            
'warning' => [],
        ];

        
$to_unset = [];

        foreach ( 
$assoc_spec as $param ) {
            
$key $param['name'];

            if ( ! isset( 
$assoc_args$key ] ) ) {
                if ( ! 
$param['optional'] ) {
                    
$errors['fatal'][ $key ] = "missing --$key parameter";
                }
            } else {
                if ( 
true === $assoc_args$key ] && ! $param['value']['optional'] ) {
                    
$error_type                    = ( ! $param['optional'] ) ? 'fatal' 'warning';
                    
$errors$error_type ][ $key ] = "--$key parameter needs a value";

                    
$to_unset[] = $key;
                }
            }
        }

        return [ 
$errors$to_unset ];
    }

    
/**
     * Check whether there are unknown parameters supplied.
     *
     * @param array $assoc_args Parameters passed to command.
     * @return array|false
     */
    
public function unknown_assoc$assoc_args ) {
        
$generic $this->query_spec(
            [
                
'type' => 'generic',
            ]
        );

        if ( 
count$generic ) ) {
            return [];
        }

        
$known_assoc = [];

        foreach ( 
$this->spec as $param ) {
            if ( 
in_array$param['type'], [ 'assoc''flag' ], true ) ) {
                
$known_assoc[] = $param['name'];
            }
        }

        return 
array_diffarray_keys$assoc_args ), $known_assoc );
    }

    
/**
     * Filters a list of associative arrays, based on a set of key => value arguments.
     *
     * @param array $args An array of key => value arguments to match against
     * @param string $operator
     * @return array
     */
    
private function query_spec$args$operator 'AND' ) {
        
$operator strtoupper$operator );
        
$count    count$args );
        
$filtered = [];

        foreach ( 
$this->spec as $key => $to_match ) {
            
$matched 0;
            foreach ( 
$args as $m_key => $m_value ) {
                if ( 
array_key_exists$m_key$to_match ) && $m_value === $to_match$m_key ] ) {
                    
$matched++;
                }
            }

            if ( ( 
'AND' === $operator && $matched === $count )
                || ( 
'OR' === $operator && $matched )
                || ( 
'NOT' === $operator && === $matched ) ) {
                    
$filtered$key ] = $to_match;
            }
        }

        return 
$filtered;
    }

}
<?
php

namespace WP_CLI;

use 
WP_CLI;

class 
Completions {

    private 
$words;
    private 
$opts = [];

    
/**
     * Instantiate a Completions object.
     *
     * @param string $line Line of shell input to compute a completion for.
     */
    
public function __construct$line ) {
        
// TODO: properly parse single and double quotes
        
$this->words explode' '$line );

        
// First word is always `wp`.
        
array_shift$this->words );

        
// Last word is either empty or an incomplete subcommand.
        
$this->cur_word end$this->words );
        if ( 
'' !== $this->cur_word && ! preg_match'/^\-/'$this->cur_word ) ) {
            
array_pop$this->words );
        }

        
$is_alias false;
        
$is_help  false;
        if ( ! empty( 
$this->words[0] ) && preg_match'/^@/'$this->words[0] ) ) {
            
array_shift$this->words );
            
// `wp @al` is false, but `wp @all ` is true.
            
if ( count$this->words ) ) {
                
$is_alias true;
            }
        } elseif ( ! empty( 
$this->words[0] ) && 'help' === $this->words[0] ) {
            
array_shift$this->words );
            
$is_help true;
        }

        
$r $this->get_command$this->words );
        if ( ! 
is_array$r ) ) {
            return;
        }

        list( 
$command$args$assoc_args ) = $r;

        
$spec SynopsisParser::parse$command->get_synopsis() );

        foreach ( 
$spec as $arg ) {
            if ( 
'positional' === $arg['type'] && 'file' === $arg['name'] ) {
                
$this->add'<file> ' );
                return;
            }
        }

        if ( 
$command->can_have_subcommands() ) {
            
// Add completion when command is `wp` and alias isn't set.
            
if ( 'wp' === $command->get_name() && false === $is_alias && false === $is_help ) {
                
$aliases WP_CLI::get_configurator()->get_aliases();
                foreach ( 
$aliases as $name => $_ ) {
                    
$this->add"$name " );
                }
            }
            foreach ( 
$command->get_subcommands() as $name => $_ ) {
                
$this->add"$name " );
            }
        } else {
            foreach ( 
$spec as $arg ) {
                if ( 
in_array$arg['type'], [ 'flag''assoc' ], true ) ) {
                    if ( isset( 
$assoc_args$arg['name'] ] ) ) {
                        continue;
                    }

                    
$opt "--{$arg['name']}";

                    if ( 
'flag' === $arg['type'] ) {
                        
$opt .= ' ';
                    } elseif ( ! 
$arg['value']['optional'] ) {
                        
$opt .= '=';
                    }

                    
$this->add$opt );
                }
            }

            foreach ( 
$this->get_global_parameters() as $param => $runtime ) {
                if ( isset( 
$assoc_args$param ] ) ) {
                    continue;
                }

                
$opt "--{$param}";

                if ( 
'' === $runtime || ! is_string$runtime ) ) {
                    
$opt .= ' ';
                } else {
                    
$opt .= '=';
                }

                
$this->add$opt );
            }
        }

    }

    
/**
     * Get the specific WP-CLI command that is being referenced.
     *
     * @param array $words Individual input line words.
     *
     * @return array|mixed Array with command and arguments, or error result if command detection failed.
     */
    
private function get_command$words ) {
        
$positional_args = [];
        
$assoc_args      = [];

        foreach ( 
$words as $arg ) {
            if ( 
preg_match'|^--([^=]+)=?|'$arg$matches ) ) {
                
$assoc_args$matches[1] ] = true;
            } else {
                
$positional_args[] = $arg;
            }
        }

        
$r WP_CLI::get_runner()->find_command_to_run$positional_args );
        if ( ! 
is_array$r ) && array_pop$positional_args ) === $this->cur_word ) {
            
$r WP_CLI::get_runner()->find_command_to_run$positional_args );
        }

        if ( ! 
is_array$r ) ) {
            return 
$r;
        }

        list( 
$command$args ) = $r;

        return [ 
$command$args$assoc_args ];
    }

    
/**
     * Get global parameters.
     *
     * @return array Associative array of global parameters.
     */
    
private function get_global_parameters() {
        
$params = [];
        foreach ( 
WP_CLI::get_configurator()->get_spec() as $key => $details ) {
            if ( 
false === $details['runtime'] ) {
                continue;
            }

            if ( isset( 
$details['deprecated'] ) ) {
                continue;
            }

            if ( isset( 
$details['hidden'] ) ) {
                continue;
            }
            
$params$key ] = $details['runtime'];

            
// Add additional option like `--[no-]color`.
            
if ( true === $details['runtime'] ) {
                
$params'no-' $key ] = '';
            }
        }

        return 
$params;
    }

    
/**
     * Store individual option.
     *
     * @param string $opt Option to store.
     *
     * @return void
     */
    
private function add$opt ) {
        if ( 
'' !== $this->cur_word ) {
            if ( 
!== strpos$opt$this->cur_word ) ) {
                return;
            }
        }

        
$this->opts[] = $opt;
    }

    
/**
     * Render the stored options.
     *
     * @return void
     */
    
public function render() {
        foreach ( 
$this->opts as $opt ) {
            
WP_CLI::line$opt );
        }
    }
}
<?
php

namespace WP_CLI;

/**
 * Class Autoloader.
 *
 * This is a custom autoloader to replace the functionality that we would
 * normally get through the autoloader generated by Composer.
 *
 * We need this separate autoloader for the bootstrapping process, which happens
 * before the Composer autoloader(s) could be loaded.
 *
 * @package WP_CLI
 */
class Autoloader {

    
/**
     * Array containing the registered namespace structures
     *
     * @var array
     */
    
protected $namespaces = [];

    
/**
     * Destructor for the Autoloader class.
     *
     * The destructor automatically unregisters the autoload callback function
     * with the SPL autoload system.
     */
    
public function __destruct() {
        
$this->unregister();
    }

    
/**
     * Registers the autoload callback with the SPL autoload system.
     */
    
public function register() {
        
spl_autoload_register( [ $this'autoload' ] );
    }

    
/**
     * Unregisters the autoload callback with the SPL autoload system.
     */
    
public function unregister() {
        
spl_autoload_unregister( [ $this'autoload' ] );
    }

    
/**
     * Add a specific namespace structure with our custom autoloader.
     *
     * @param string  $root        Root namespace name.
     * @param string  $base_dir    Directory containing the class files.
     * @param string  $prefix      Prefix to be added before the class.
     * @param string  $suffix      Suffix to be added after the class.
     * @param boolean $lowercase   Whether the class should be changed to
     *                             lowercase.
     * @param boolean $underscores Whether the underscores should be changed to
     *                             hyphens.
     *
     * @return self
     */
    
public function add_namespace(
        
$root,
        
$base_dir,
        
$prefix '',
        
$suffix '.php',
        
$lowercase false,
        
$underscores false
    
) {
        
$this->namespaces[] = [
            
'root'        => $this->normalize_root( (string) $root ),
            
'base_dir'    => $this->add_trailing_slash( (string) $base_dir ),
            
'prefix'      => (string) $prefix,
            
'suffix'      => (string) $suffix,
            
'lowercase'   => (bool) $lowercase,
            
'underscores' => (bool) $underscores,
        ];

        return 
$this;
    }

    
/**
     * The autoload function that gets registered with the SPL Autoloader
     * system.
     *
     * @param string $class The class that got requested by the spl_autoloader.
     */
    
public function autoload$class ) {

        
// Iterate over namespaces to find a match.
        
foreach ( $this->namespaces as $namespace ) {

            
// Move on if the object does not belong to the current namespace.
            
if ( !== strpos$class$namespace['root'] ) ) {
                continue;
            }

            
// Remove namespace root level to correspond with root filesystem, and
            // replace the namespace separator "\" by the system-dependent directory separator.
            
$filename str_replace(
                [ 
$namespace['root'], '\\' ],
                [ 
''DIRECTORY_SEPARATOR ],
                
$class
            
);

            
// Remove a leading backslash from the class name.
            
$filename $this->remove_leading_backslash$filename );

            
// Change to lower case if requested.
            
if ( $namespace['lowercase'] ) {
                
$filename strtolower$filename );
            }

            
// Change underscores into hyphens if requested.
            
if ( $namespace['underscores'] ) {
                
$filename str_replace'_''-'$filename );
            }

            
// Add base_dir, prefix and suffix.
            
$filepath $namespace['base_dir']
                . 
$namespace['prefix']
                . 
$filename
                
$namespace['suffix'];

            
// Throw an exception if the file does not exist or is not readable.
            
if ( is_readable$filepath ) ) {
                require_once 
$filepath;
            }
        }
    }

    
/**
     * Normalize a namespace root.
     *
     * @param string $root Namespace root that needs to be normalized.
     *
     * @return string Normalized namespace root.
     */
    
protected function normalize_root$root ) {
        
$root $this->remove_leading_backslash$root );

        return 
$this->add_trailing_backslash$root );
    }

    
/**
     * Remove a leading backslash from a string.
     *
     * @param string $string String to remove the leading backslash from.
     *
     * @return string Modified string.
     */
    
protected function remove_leading_backslash$string ) {
        return 
ltrim$string'\\' );
    }

    
/**
     * Make sure a string ends with a trailing backslash.
     *
     * @param string $string String to check the trailing backslash of.
     *
     * @return string Modified string.
     */
    
protected function add_trailing_backslash$string ) {
        return 
rtrim$string'\\' ) . '\\';
    }

    
/**
     * Make sure a string ends with a trailing slash.
     *
     * @param string $string String to check the trailing slash of.
     *
     * @return string Modified string.
     */
    
protected function add_trailing_slash$string ) {
        return 
rtrim$string'/\\' ) . '/';
    }
}
<?
php

namespace WP_CLI;

use 
Mustangostang\Spyc;

/**
 * Parse command attributes from its PHPdoc.
 * Used to determine execution characteristics (arguments, etc.).
 */
class DocParser {

    
/**
     * PHPdoc command for the command.
     *
     * @var string
     */
    
protected $doc_comment;

    
/**
     * @param string $doc_comment
     */
    
public function __construct$doc_comment ) {
        
/* Make sure we have a known line ending in document */
        
$doc_comment       str_replace"\r\n""\n"$doc_comment );
        
$this->doc_comment self::remove_decorations$doc_comment );
    }

    
/**
     * Remove unused cruft from PHPdoc comment.
     *
     * @param string $comment PHPdoc comment.
     * @return string
     */
    
private static function remove_decorations$comment ) {
        
$comment preg_replace'|^/\*\*[\r\n]+|'''$comment );
        
$comment preg_replace'|\n[\t ]*\*/$|'''$comment );
        
$comment preg_replace'|^[\t ]*\* ?|m'''$comment );

        return 
$comment;
    }

    
/**
     * Get the command's short description (e.g. summary).
     *
     * @return string
     */
    
public function get_shortdesc() {
        if ( ! 
preg_match'|^([^@][^\n]+)\n*|'$this->doc_comment$matches ) ) {
            return 
'';
        }

        return 
$matches[1];
    }

    
/**
     * Get the command's full description
     *
     * @return string
     */
    
public function get_longdesc() {
        
$shortdesc $this->get_shortdesc();
        if ( ! 
$shortdesc ) {
            return 
'';
        }

        
$longdesc substr$this->doc_commentstrlen$shortdesc ) );

        
$lines = [];
        foreach ( 
explode"\n"$longdesc ) as $line ) {
            if ( 
=== strpos$line'@' ) ) {
                break;
            }

            
$lines[] = $line;
        }

        return 
trimimplode"\n"$lines ) );
    }

    
/**
     * Get the value for a given tag (e.g. "@alias" or "@subcommand")
     *
     * @param string $name Name for the tag, without '@'
     * @return string
     */
    
public function get_tag$name ) {
        if ( 
preg_match'|^@' $name '\s+([a-z-_0-9]+)|m'$this->doc_comment$matches ) ) {
            return 
$matches[1];
        }

        return 
'';
    }

    
/**
     * Get the command's synopsis.
     *
     * @return string
     */
    
public function get_synopsis() {
        if ( ! 
preg_match'|^@synopsis\s+(.+)|m'$this->doc_comment$matches ) ) {
            return 
'';
        }

        return 
$matches[1];
    }

    
/**
     * Get the description for a given argument.
     *
     * @param string $name Argument's doc name.
     * @return string
     */
    
public function get_arg_desc$name ) {

        if ( 
preg_match"/\[?<{$name}>.+\n: (.+?)(\n|$)/"$this->doc_comment$matches ) ) {
            return 
$matches[1];
        }

        return 
'';

    }

    
/**
     * Get the arguments for a given argument.
     *
     * @param string $name Argument's doc name.
     * @return mixed|null
     */
    
public function get_arg_args$name ) {
        return 
$this->get_arg_or_param_args"/^\[?<{$name}>.*/" );
    }

    
/**
     * Get the description for a given parameter.
     *
     * @param string $key Parameter's key.
     * @return string
     */
    
public function get_param_desc$key ) {

        if ( 
preg_match"/\[?--{$key}=.+\n: (.+?)(\n|$)/"$this->doc_comment$matches ) ) {
            return 
$matches[1];
        }

        return 
'';
    }

    
/**
     * Get the arguments for a given parameter.
     *
     * @param string $key Parameter's key.
     * @return mixed|null
     */
    
public function get_param_args$key ) {
        return 
$this->get_arg_or_param_args"/^\[?--{$key}=.*/" );
    }

    
/**
     * Get the args for an arg or param
     *
     * @param string $regex Pattern to match against
     * @return array|null Interpreted YAML document, or null.
     */
    
private function get_arg_or_param_args$regex ) {
        
$bits       explode"\n"$this->doc_comment );
        
$within_arg false;
        
$within_doc false;
        
$document   = [];
        foreach ( 
$bits as $bit ) {
            if ( 
preg_match$regex$bit ) ) {
                
$within_arg true;
            }

            if ( 
$within_arg && $within_doc && '---' === $bit ) {
                
$within_doc false;
            }

            if ( 
$within_arg && ! $within_doc && '---' === $bit ) {
                
$within_doc true;
            }

            if ( 
$within_doc ) {
                
$document[] = $bit;
            }

            if ( 
$within_arg && '' === $bit ) {
                
$within_arg false;
                break;
            }
        }

        if ( 
$document ) {
            return 
Spyc::YAMLLoadStringimplode"\n"$document ) );
        }
        return 
null;
    }

}
<?
php

namespace WP_CLI\Bootstrap;

use 
WP_CLI;
use 
WP_CLI\Context;
use 
WP_CLI\ContextManager;

/**
 * Class InitializeContexts.
 *
 * @package WP_CLI\Bootstrap
 */
final class InitializeContexts implements BootstrapStep {

    
/**
     * Process this single bootstrapping step.
     *
     * @param BootstrapState $state Contextual state to pass into the step.
     *
     * @return BootstrapState Modified state to pass to the next step.
     */
    
public function processBootstrapState $state ) {
        
$context_manager = new ContextManager();

        
$contexts = [
            
Context::CLI      => new Context\Cli(),
            
Context::ADMIN    => new Context\Admin(),
            
Context::FRONTEND => new Context\Frontend(),
            
Context::AUTO     => new Context\Auto$context_manager ),
        ];

        
$contexts WP_CLI::do_hook'before_registering_contexts'$contexts );

        foreach ( 
$contexts as $name => $implementation ) {
            
$context_manager->register_context$name$implementation );
        }

        
$state->setValue'context_manager'$context_manager );

        return 
$state;
    }
}
<?
php

namespace WP_CLI\Bootstrap;

/**
 * Class LaunchRunner.
 *
 * Kick off the Runner object that starts the actual commands.
 *
 * @package WP_CLI\Bootstrap
 */
final class LaunchRunner implements BootstrapStep {

    
/**
     * Process this single bootstrapping step.
     *
     * @param BootstrapState $state Contextual state to pass into the step.
     *
     * @return BootstrapState Modified state to pass to the next step.
     */
    
public function processBootstrapState $state ) {
        
$runner = new RunnerInstance();

        
$runner()->register_context_manager(
            
$state->getValue'context_manager' )
        );

        
$runner()->start();

        return 
$state;
    }
}
<?
php

namespace WP_CLI\Bootstrap;

/**
 * Class BootstrapState.
 *
 * Represents the state that is passed from one bootstrap step to the next.
 *
 * @package WP_CLI\Bootstrap
 *
 * Maintain BC: Changing the method names in this class breaks autoload interactions between Phar
 * & framework/commands you use outside of Phar (like when running the Phar WP inside of a command folder).
 * @phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
 */
class BootstrapState {

    
/**
     * Whether the command currently being run is "protected".
     *
     * This means that the command should not be allowed to break due to
     * extension code.
     */
    
const IS_PROTECTED_COMMAND 'is_protected_command';

    
/**
     * Internal storage of the state values.
     *
     * @var array
     */
    
private $state = [];

    
/**
     * Get the state value for a given key.
     *
     * @param string $key      Key to get the state from.
     * @param mixed  $fallback Fallback value to use if the key is not defined.
     *
     * @return mixed
     */
    
public function getValue$key$fallback null ) {
        return 
array_key_exists$key$this->state )
            ? 
$this->state$key ]
            : 
$fallback;
    }

    
/**
     * Set the state value for a given key.
     *
     * @param string $key   Key to set the state for.
     * @param mixed  $value Value to set the state for the given key to.
     *
     * @return void
     */
    
public function setValue$key$value ) {
        
$this->state$key ] = $value;
    }
}
<?
php

namespace WP_CLI\Bootstrap;

/**
 * Class DeclareAbstractBaseCommand.
 *
 * Declares the abstract `WP_CLI_Command` base class.
 *
 * @package WP_CLI\Bootstrap
 */
final class DeclareAbstractBaseCommand implements BootstrapStep {

    
/**
     * Process this single bootstrapping step.
     *
     * @param BootstrapState $state Contextual state to pass into the step.
     *
     * @return BootstrapState Modified state to pass to the next step.
     */
    
public function processBootstrapState $state ) {
        require_once 
WP_CLI_ROOT '/php/class-wp-cli-command.php';

        return 
$state;
    }
}
<?
php

namespace WP_CLI\Bootstrap;

/**
 * Class DeclareMainClass.
 *
 * Declares the main `WP_CLI` class.
 *
 * @package WP_CLI\Bootstrap
 */
final class DeclareMainClass implements BootstrapStep {

    
/**
     * Process this single bootstrapping step.
     *
     * @param BootstrapState $state Contextual state to pass into the step.
     *
     * @return BootstrapState Modified state to pass to the next step.
     */
    
public function processBootstrapState $state ) {
        require_once 
WP_CLI_ROOT '/php/class-wp-cli.php';

        return 
$state;
    }
}
<?
php

namespace WP_CLI\Bootstrap;

/**
 * Class LoadDispatcher.
 *
 * Loads the dispatcher that will dispatch command names to file locations.
 *
 * @package WP_CLI\Bootstrap
 */
final class LoadDispatcher implements BootstrapStep {

    
/**
     * Process this single bootstrapping step.
     *
     * @param BootstrapState $state Contextual state to pass into the step.
     *
     * @return BootstrapState Modified state to pass to the next step.
     */
    
public function processBootstrapState $state ) {
        require_once 
WP_CLI_ROOT '/php/dispatcher.php';

        return 
$state;
    }
}
<?
php

namespace WP_CLI\Bootstrap;

/**
 * Class InitializeColorization.
 *
 * Initialize the colorization through the `WP_CLI\Runner` object.
 *
 * @package WP_CLI\Bootstrap
 */
final class InitializeColorization implements BootstrapStep {

    
/**
     * Process this single bootstrapping step.
     *
     * @param BootstrapState $state Contextual state to pass into the step.
     *
     * @return BootstrapState Modified state to pass to the next step.
     */
    
public function processBootstrapState $state ) {
        
$runner = new RunnerInstance();
        
$runner()->init_colorization();

        return 
$state;
    }
}
<?
php

namespace WP_CLI\Bootstrap;

/**
 * Class ConfigureRunner.
 *
 * Initialize the configuration for the `WP_CLI\Runner` object.
 *
 * @package WP_CLI\Bootstrap
 */
final class ConfigureRunner implements BootstrapStep {

    
/**
     * Process this single bootstrapping step.
     *
     * @param BootstrapState $state Contextual state to pass into the step.
     *
     * @return BootstrapState Modified state to pass to the next step.
     */
    
public function processBootstrapState $state ) {
        
$runner = new RunnerInstance();
        
$runner()->init_config();

        return 
$state;
    }
}
<?
php

namespace WP_CLI\Bootstrap;

/**
 * Class DefineProtectedCommands.
 *
 * Define the commands that are "protected", meaning that they shouldn't be able
 * to break due to extension code.
 *
 * @package WP_CLI\Bootstrap
 */
final class DefineProtectedCommands implements BootstrapStep {

    
/**
     * Process this single bootstrapping step.
     *
     * @param BootstrapState $state Contextual state to pass into the step.
     *
     * @return BootstrapState Modified state to pass to the next step.
     */
    
public function processBootstrapState $state ) {
        
$commands        $this->get_protected_commands();
        
$current_command $this->get_current_command();

        foreach ( 
$commands as $command ) {
            if ( 
=== strpos$current_command$command ) ) {
                
$state->setValueBootstrapState::IS_PROTECTED_COMMANDtrue );
            }
        }

        return 
$state;
    }

    
/**
     * Get the list of protected commands.
     *
     * @return array
     */
    
private function get_protected_commands() {
        return [
            
'cli info',
            
'package',
        ];
    }

    
/**
     * Get the current command as a string.
     *
     * @return string Current command to be executed.
     */
    
private function get_current_command() {
        
$runner = new RunnerInstance();

        return 
implode' ', (array) $runner()->arguments );
    }
}
<?
php

namespace WP_CLI\Bootstrap;

/**
 * Class DeclareFallbackFunctions.
 *
 * Declares functions that might have been disabled but are required.
 *
 * @package WP_CLI\Bootstrap
 */
final class DeclareFallbackFunctions implements BootstrapStep {
    
/**
     * Process this single bootstrapping step.
     *
     * @param BootstrapState $state Contextual state to pass into the step.
     *
     * @return BootstrapState Modified state to pass to the next step.
     */
    
public function processBootstrapState $state ) {
        include 
__DIR__ '/../../fallback-functions.php';

        return 
$state;
    }
}
<?
php

namespace WP_CLI\Bootstrap;

use 
WP_CLI;

/**
 * Class LoadExtraCommand.
 *
 * Loads a command that was passed through the `--exec=<php-code>` option.
 *
 * @package WP_CLI\Bootstrap
 */
final class LoadExecCommand implements BootstrapStep {

    
/**
     * Process this single bootstrapping step.
     *
     * @param BootstrapState $state Contextual state to pass into the step.
     *
     * @return BootstrapState Modified state to pass to the next step.
     */
    
public function processBootstrapState $state ) {
        if ( 
$state->getValueBootstrapState::IS_PROTECTED_COMMANDfalse ) ) {
            return 
$state;
        }

        
$runner = new RunnerInstance();
        if ( ! isset( 
$runner()->config['exec'] ) ) {
            return 
$state;
        }

        foreach ( 
$runner()->config['exec'] as $php_code ) {
            eval( 
$php_code ); // phpcs:ignore Squiz.PHP.Eval.Discouraged
        
}

        return 
$state;
    }
}
<?
php

namespace WP_CLI\Bootstrap;

/**
 * Interface BootstrapStep.
 *
 * Represents a single bootstrapping step that can be processed.
 *
 * @package WP_CLI\Bootstrap
 */
interface BootstrapStep {

    
/**
     * Process this single bootstrapping step.
     *
     * @param BootstrapState $state Contextual state to pass into the step.
     *
     * @return BootstrapState Modified state to pass to the next step.
     */
    
public function processBootstrapState $state );
}
<?
php

namespace WP_CLI\Bootstrap;

use 
WP_CLI;
use 
WP_CLI\Utils;

/**
 * Class LoadRequiredCommand.
 *
 * Loads a command that was passed through the `--require=<command>` option.
 *
 * @package WP_CLI\Bootstrap
 */
final class LoadRequiredCommand implements BootstrapStep {

    
/**
     * Process this single bootstrapping step.
     *
     * @param BootstrapState $state Contextual state to pass into the step.
     *
     * @return BootstrapState Modified state to pass to the next step.
     */
    
public function processBootstrapState $state ) {
        if ( 
$state->getValueBootstrapState::IS_PROTECTED_COMMANDfalse ) ) {
            return 
$state;
        }

        
$runner = new RunnerInstance();
        if ( ! isset( 
$runner()->config['require'] ) ) {
            return 
$state;
        }

        foreach ( 
$runner()->config['require'] as $path ) {
            if ( ! 
file_exists$path ) ) {
                
$context        '';
                
$required_files $runner()->get_required_files();
                foreach ( [ 
'global''project''runtime' ] as $scope ) {
                    if ( 
in_array$path$required_files$scope ], true ) ) {
                        switch ( 
$scope ) {
                            case 
'global':
                                
$context ' (from global ' Utils\basename$runner()->get_global_config_path() ) . ')';
                                break;
                            case 
'project':
                                
$context ' (from project\'s ' Utils\basename$runner()->get_project_config_path() ) . ')';
                                break;
                            case 
'runtime':
                                
$context ' (from runtime argument)';
                                break;
                        }
                        break;
                    }
                }
                
WP_CLI::errorsprintf"Required file '%s' doesn't exist%s."Utils\basename$path ), $context ) );
            }
            
Utils\load_file$path );
            
WP_CLI::debug'Required file from config: ' $path'bootstrap' );
        }

        return 
$state;
    }
}
<?
php

namespace WP_CLI\Bootstrap;

/**
 * Class LoadUtilityFunctions.
 *
 * Loads the functions available through `WP_CLI\Utils`.
 *
 * @package WP_CLI\Bootstrap
 */
final class LoadUtilityFunctions implements BootstrapStep {

    
/**
     * Process this single bootstrapping step.
     *
     * @param BootstrapState $state Contextual state to pass into the step.
     *
     * @return BootstrapState Modified state to pass to the next step.
     */
    
public function processBootstrapState $state ) {
        require_once 
WP_CLI_ROOT '/php/utils.php';

        return 
$state;
    }
}
<?
php

namespace WP_CLI\Bootstrap;

use 
Exception;
use 
WP_CLI;

/**
 * Abstract class AutoloaderStep.
 *
 * Abstract base class for steps that include an autoloader.
 *
 * @package WP_CLI\Bootstrap
 */
abstract class AutoloaderStep implements BootstrapStep {

    
/**
     * Store state for subclasses to have access.
     *
     * @var BootstrapState
     */
    
protected $state;

    
/**
     * Process this single bootstrapping step.
     *
     * @param BootstrapState $state Contextual state to pass into the step.
     *
     * @return BootstrapState Modified state to pass to the next step.
     */
    
public function processBootstrapState $state ) {
        
$this->state $state;

        
$found_autoloader false;
        
$autoloader_paths $this->get_autoloader_paths();

        if ( 
false === $autoloader_paths ) {
            
// Skip this autoloading step.
            
return $state;
        }

        foreach ( 
$autoloader_paths as $autoloader_path ) {
            if ( 
is_readable$autoloader_path ) ) {
                try {
                    
WP_CLI::debug(
                        
sprintf(
                            
'Loading detected autoloader: %s',
                            
$autoloader_path
                        
),
                        
'bootstrap'
                    
);
                    require 
$autoloader_path;
                    
$found_autoloader true;
                } catch ( 
Exception $exception ) {
                    
WP_CLI::warning(
                        
"Failed to load autoloader '{$autoloader_path}'. Reason: "
                        
$exception->getMessage()
                    );
                }
            }
        }

        if ( ! 
$found_autoloader ) {
            
$this->handle_failure();
        }

        return 
$this->state;
    }

    
/**
     * Get the name of the custom vendor folder as set in `composer.json`.
     *
     * @return string|false Name of the custom vendor folder or false if none.
     */
    
protected function get_custom_vendor_folder() {
        
$maybe_composer_json WP_CLI_ROOT '/../../../composer.json';
        if ( ! 
is_readable$maybe_composer_json ) ) {
            return 
false;
        }

        
$composer json_decodefile_get_contents$maybe_composer_json ) );

        if ( ! empty( 
$composer->config )
            && ! empty( 
$composer->config->{'vendor-dir'} )
        ) {
            return 
$composer->config->{'vendor-dir'};
        }

        return 
false;
    }

    
/**
     * Handle the failure to find an autoloader.
     *
     * @return void
     */
    
protected function handle_failure() { }

    
/**
     * Get the autoloader paths to scan for an autoloader.
     *
     * @return string[]|false Array of strings with autoloader paths, or false
     *                        to skip.
     */
    
abstract protected function get_autoloader_paths();
}
<?
php

namespace WP_CLI\Bootstrap;

use 
DirectoryIterator;
use 
Exception;
use 
WP_CLI;

/**
 * Class RegisterFrameworkCommands.
 *
 * Register the commands that are directly included with the framework.
 *
 * @package WP_CLI\Bootstrap
 */
final class RegisterFrameworkCommands implements BootstrapStep {

    
/**
     * Process this single bootstrapping step.
     *
     * @param BootstrapState $state Contextual state to pass into the step.
     *
     * @return BootstrapState Modified state to pass to the next step.
     */
    
public function processBootstrapState $state ) {
        
$cmd_dir WP_CLI_ROOT '/php/commands';

        
$iterator = new DirectoryIterator$cmd_dir );

        foreach ( 
$iterator as $filename ) {
            if ( 
'.php' !== substr$filename, - ) ) {
                continue;
            }

            try {
                
WP_CLI::debug(
                    
sprintf(
                        
'Adding framework command: %s',
                        
"$cmd_dir/$filename"
                    
),
                    
'bootstrap'
                
);

                include_once 
"$cmd_dir/$filename";
            } catch ( 
Exception $exception ) {
                
WP_CLI::warning(
                    
"Could not add command {$cmd_dir}/{$filename}. Reason: " $exception->getMessage()
                );
            }
        }

        return 
$state;
    }
}
<?
php

namespace WP_CLI\Bootstrap;

use 
DirectoryIterator;

/**
 * Class InitializeLogger.
 *
 * Initialize the logger through the `WP_CLI\Runner` object.
 *
 * @package WP_CLI\Bootstrap
 */
final class InitializeLogger implements BootstrapStep {

    
/**
     * Process this single bootstrapping step.
     *
     * @param BootstrapState $state Contextual state to pass into the step.
     *
     * @return BootstrapState Modified state to pass to the next step.
     */
    
public function processBootstrapState $state ) {
        
$this->declare_loggers();
        
$runner = new RunnerInstance();
        
$runner()->init_logger();

        return 
$state;
    }

    
/**
     * Load the class declarations for the loggers.
     */
    
private function declare_loggers() {
        
$logger_dir WP_CLI_ROOT '/php/WP_CLI/Loggers';
        
$iterator   = new DirectoryIterator$logger_dir );

        
// Make sure the base class is declared first.
        
include_once "$logger_dir/Base.php";

        foreach ( 
$iterator as $filename ) {
            if ( 
'.php' !== substr$filename, - ) ) {
                continue;
            }

            include_once 
"$logger_dir/$filename";
        }
    }
}
<?
php

namespace WP_CLI\Bootstrap;

use 
WP_CLI\Autoloader;

/**
 * Class IncludeFrameworkAutoloader.
 *
 * Loads the framework autoloader through an autolaoder separate from the
 * Composer one, to avoid coupling the loading of the framework with bundled
 * commands.
 *
 * This only contains classes for the framework.
 *
 * @package WP_CLI\Bootstrap
 */
final class IncludeFrameworkAutoloader implements BootstrapStep {

    
/**
     * Process this single bootstrapping step.
     *
     * @param BootstrapState $state Contextual state to pass into the step.
     *
     * @return BootstrapState Modified state to pass to the next step.
     */
    
public function processBootstrapState $state ) {
        if ( ! 
class_exists'WP_CLI\Autoloader' ) ) {
            require_once 
WP_CLI_ROOT '/php/WP_CLI/Autoloader.php';
        }

        
$autoloader = new Autoloader();

        
$mappings = [
            
'WP_CLI'                   => WP_CLI_ROOT '/php/WP_CLI',
            
'cli'                      => WP_CLI_VENDOR_DIR '/wp-cli/php-cli-tools/lib/cli',
            
'Requests'                 => WP_CLI_VENDOR_DIR '/rmccue/requests/library/Requests',
            
'Symfony\Component\Finder' => WP_CLI_VENDOR_DIR '/symfony/finder/',
        ];

        foreach ( 
$mappings as $namespace => $folder ) {
            
$autoloader->add_namespace(
                
$namespace,
                
$folder
            
);
        }

        include_once 
WP_CLI_VENDOR_DIR '/rmccue/requests/library/Requests.php';
        include_once 
WP_CLI_VENDOR_DIR '/wp-cli/mustangostang-spyc/Spyc.php';

        
$autoloader->register();

        return 
$state;
    }
}
<?
php

namespace WP_CLI\Bootstrap;

use 
WP_CLI;
use 
WP_CLI\Utils;

/**
 * Class RegisterDeferredCommands.
 *
 * Registers the deferred commands that for which no parent was registered yet.
 * This is necessary, because we can have sub-commands that have no direct
 * parent, like `wp network meta`.
 *
 * @package WP_CLI\Bootstrap
 */
final class RegisterDeferredCommands implements BootstrapStep {

    
/**
     * Process this single bootstrapping step.
     *
     * @param BootstrapState $state Contextual state to pass into the step.
     *
     * @return BootstrapState Modified state to pass to the next step.
     */
    
public function processBootstrapState $state ) {

        
// Process deferred command additions for external packages.
        
$this->add_deferred_commands();

        
// Process deferred command additions for commands added through
        // plugins.
        
WP_CLI::add_hook(
            
'before_run_command',
            [ 
$this'add_deferred_commands' ]
        );

        return 
$state;
    }

    
/**
     * Add deferred commands that are still waiting to be processed.
     */
    
public function add_deferred_commands() {
        
$deferred_additions WP_CLI::get_deferred_additions();

        foreach ( 
$deferred_additions as $name => $addition ) {
            
$addition_data = [];
            foreach ( 
$addition as $addition_key => $addition_value ) {
                
// Describe the callable as a string instead of directly printing it
                // for better debug info.
                
if ( 'callable' === $addition_key ) {
                    
$addition_value Utils\describe_callable$addition_value );

                } elseif ( 
is_array$addition_value ) ) {
                    
$addition_value json_encode$addition_value );
                }

                
$addition_data[] = sprintf(
                    
'%s: %s',
                    
$addition_key,
                    
$addition_value
                
);
            }

            
WP_CLI::debug(
                
sprintf(
                    
'Adding deferred command: %s (%s)',
                    
$name,
                    
implode', '$addition_data )
                ),
                
'bootstrap'
            
);

            
WP_CLI::add_command(
                
$name,
                
$addition['callable'],
                
$addition['args']
            );
        }
    }
}
<?
php

namespace WP_CLI\Bootstrap;

use 
WP_CLI;

/**
 * Class IncludeFallbackAutoloader.
 *
 * Loads the fallback autoloader that is provided through the `composer.json`
 * file.
 *
 * @package WP_CLI\Bootstrap
 */
final class IncludeFallbackAutoloader extends AutoloaderStep {

    
/**
     * Get the autoloader paths to scan for an autoloader.
     *
     * @return string[]|false Array of strings with autoloader paths, or false
     *                        to skip.
     */
    
protected function get_autoloader_paths() {
        
$autoloader_paths = [
            
WP_CLI_VENDOR_DIR '/autoload.php',
        ];

        
$custom_vendor $this->get_custom_vendor_folder();
        if ( 
false !== $custom_vendor ) {
            
array_unshift(
                
$autoloader_paths,
                
WP_CLI_ROOT '/../../../' $custom_vendor '/autoload.php'
            
);
        }

        
WP_CLI::debug(
            
sprintf(
                
'Fallback autoloader paths: %s',
                
implode', '$autoloader_paths )
            ),
            
'bootstrap'
        
);

        return 
$autoloader_paths;
    }
}
<?
php

namespace WP_CLI\Bootstrap;

use 
WP_CLI;

/**
 * Class IncludePackageAutoloader.
 *
 * Loads the package autoloader that includes all the external packages.
 *
 * @package WP_CLI\Bootstrap
 */
final class IncludePackageAutoloader extends AutoloaderStep {

    
/**
     * Get the autoloader paths to scan for an autoloader.
     *
     * @return string[]|false Array of strings with autoloader paths, or false
     *                        to skip.
     */
    
protected function get_autoloader_paths() {
        if ( 
$this->state->getValueBootstrapState::IS_PROTECTED_COMMANDfalse ) ) {
            return 
false;
        }

        
$runner        = new RunnerInstance();
        
$skip_packages $runner()->config['skip-packages'];
        if ( 
true === $skip_packages ) {
            
WP_CLI::debug'Skipped loading packages.''bootstrap' );

            return 
false;
        }

        
$autoloader_path $runner()->get_packages_dir_path() . 'vendor/autoload.php';

        if ( 
is_readable$autoloader_path ) ) {
            
WP_CLI::debug(
                
'Loading packages from: ' $autoloader_path,
                
'bootstrap'
            
);

            return [
                
$autoloader_path,
            ];
        }

        return 
false;
    }

    
/**
     * Handle the failure to find an autoloader.
     *
     * @return void
     */
    
protected function handle_failure() {
        
WP_CLI::debug'No package autoload found to load.''bootstrap' );
    }
}
<?
php

namespace WP_CLI\Bootstrap;

use 
WP_CLI;
use 
WP_CLI\Runner;

/**
 * Class RunnerInstance.
 *
 * Convenience class for steps that make use of the `WP_CLI\Runner` object.
 *
 * @package WP_CLI\Bootstrap
 */
final class RunnerInstance {

    
/**
     * Return an instance of the `WP_CLI\Runner` object.
     *
     * Includes necessary class files first as needed.
     *
     * @return Runner
     */
    
public function __invoke() {
        if ( ! 
class_exists'WP_CLI\Runner' ) ) {
            require_once 
WP_CLI_ROOT '/php/WP_CLI/Runner.php';
        }

        if ( ! 
class_exists'WP_CLI\Configurator' ) ) {
            require_once 
WP_CLI_ROOT '/php/WP_CLI/Configurator.php';
        }

        return 
WP_CLI::get_runner();
    }
}
<?
php

namespace WP_CLI\Fetchers;

use 
WP_CLI;
use 
WP_CLI\ExitException;

/**
 * Fetch a WordPress entity for use in a subcommand.
 */
abstract class Base {

    
/**
     * The message to display when an item is not found.
     *
     * @var string
     */
    
protected $msg;

    
/**
     * @param string $arg The raw CLI argument.
     * @return mixed|false The item if found; false otherwise.
     */
    
abstract public function get$arg );

    
/**
     * Like get(), but calls WP_CLI::error() instead of returning false.
     *
     * @param string $arg The raw CLI argument.
     * @return mixed The item if found.
     * @throws ExitException If the item is not found.
     */
    
public function get_check$arg ) {
        
$item $this->get$arg );

        if ( ! 
$item ) {
            
WP_CLI::errorsprintf$this->msg$arg ) );
        }

        return 
$item;
    }

    
/**
     * Get multiple items.
     *
     * @param array $args The raw CLI arguments.
     * @return array The list of found items.
     */
    
public function get_many$args ) {
        
$items = [];

        foreach ( 
$args as $arg ) {
            
$item $this->get$arg );

            if ( 
$item ) {
                
$items[] = $item;
            } else {
                
WP_CLI::warningsprintf$this->msg$arg ) );
            }
        }

        return 
$items;
    }
}

<?
php

namespace WP_CLI\Fetchers;

use 
WP_Post;

/**
 * Fetch a WordPress post based on one of its attributes.
 */
class Post extends Base {

    
/**
     * The message to display when an item is not found.
     *
     * @var string
     */
    
protected $msg 'Could not find the post with ID %d.';

    
/**
     * Get a post object by ID
     *
     * @param string $arg The raw CLI argument.
     * @return WP_Post|array|false The item if found; false otherwise.
     */
    
public function get$arg ) {
        
$post get_post$arg );

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

        return 
$post;
    }
}

<?
php

namespace WP_CLI\Fetchers;

use 
WP_User;

/**
 * Fetch a WordPress user based on one of its attributes.
 */
class User extends Base {

    
/**
     * The message to display when an item is not found.
     *
     * @var string
     */
    
protected $msg "Invalid user ID, email or login: '%s'";

    
/**
     * Get a user object by one of its identifying attributes.
     *
     * @param string $arg The raw CLI argument.
     * @return WP_User|false The item if found; false otherwise.
     */
    
public function get$arg ) {

        if ( 
is_numeric$arg ) ) {
            
$user get_user_by'id'$arg );
        } elseif ( 
is_email$arg ) ) {
            
$user get_user_by'email'$arg );
            
// Logins can be emails.
            
if ( ! $user ) {
                
$user get_user_by'login'$arg );
            }
        } else {
            
$user get_user_by'login'$arg );
        }

        return 
$user;
    }
}

<?
php

namespace WP_CLI\Fetchers;

use 
WP_Comment;

/**
 * Fetch a WordPress comment based on one of its attributes.
 */
class Comment extends Base {

    
/**
     * The message to display when an item is not found.
     *
     * @var string
     */
    
protected $msg 'Could not find the comment with ID %d.';

    
/**
     * Get a comment object by ID
     *
     * @param string $arg The raw CLI argument.
     * @return WP_Comment|array|false The item if found; false otherwise.
     */
    
public function get$arg ) {
        
$comment_id = (int) $arg;
        
$comment    get_comment$comment_id );

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

        return 
$comment;
    }
}

<?
php

namespace WP_CLI\Fetchers;

/**
 * Fetch a WordPress site based on one of its attributes.
 */
class Site extends Base {

    
/**
     * The message to display when an item is not found.
     *
     * @var string
     */
    
protected $msg 'Could not find the site with ID %d.';

    
/**
     * Get a site object by ID
     *
     * @param int $site_id
     * @return object|false
     */
    
public function get$site_id ) {
        return 
$this->get_site$site_id );
    }

    
/**
     * Get site (blog) data for a given id.
     *
     * @param string $arg The raw CLI argument.
     * @return array|false The item if found; false otherwise.
     */
    
private function get_site$arg ) {
        global 
$wpdb;

        
// Load site data
        
$site $wpdb->get_row(
            
$wpdb->prepare(
                
"SELECT * FROM {$wpdb->blogs} WHERE blog_id = %d",
                
$arg
            
)
        );

        if ( ! empty( 
$site ) ) {
            
// Only care about domain and path which are set here
            
return $site;
        }

        return 
false;
    }
}
<?
php

namespace WP_CLI\Iterators;

use 
Iterator;

/**
 * Iterates over results of a query, split into many queries via LIMIT and OFFSET
 *
 * @source https://gist.github.com/4060005
 */
class Query implements Iterator {

    private 
$chunk_size;
    private 
$query       '';
    private 
$count_query '';

    private 
$global_index     0;
    private 
$index_in_results 0;
    private 
$results          = [];
    private 
$row_count        0;
    private 
$offset           0;
    private 
$db               null;
    private 
$depleted         false;

    
/**
     * Creates a new query iterator
     *
     * This will loop over all users, but will retrieve them 100 by 100:
     * <code>
     * foreach( new Iterators\Query( 'SELECT * FROM users', 100 ) as $user ) {
     *     tickle( $user );
     * }
     * </code>
     *
     * @param string $query The query as a string. It shouldn't include any LIMIT clauses
     * @param int $chunk_size How many rows to retrieve at once; default value is 500 (optional)
     */
    
public function __construct$query$chunk_size 500 ) {
        
$this->query $query;

        
$this->count_query preg_replace'/^.*? FROM /''SELECT COUNT(*) FROM '$query1$replacements );
        if ( 
!== $replacements ) {
            
$this->count_query '';
        }

        
$this->chunk_size $chunk_size;

        
$this->db $GLOBALS['wpdb'];
    }

    
/**
     * Reduces the offset when the query row count shrinks
     *
     * In cases where the iterated rows are being updated such that they will no
     * longer be returned by the original query, the offset must be reduced to
     * iterate over all remaining rows.
     */
    
private function adjust_offset_for_shrinking_result_set() {
        if ( empty( 
$this->count_query ) ) {
            return;
        }

        
$row_count $this->db->get_var$this->count_query );

        if ( 
$row_count $this->row_count ) {
            
$this->offset -= $this->row_count $row_count;
        }

        
$this->row_count $row_count;
    }

    private function 
load_items_from_db() {
        
$this->adjust_offset_for_shrinking_result_set();

        
$query         $this->query sprintf' LIMIT %d OFFSET %d'$this->chunk_size$this->offset );
        
$this->results $this->db->get_results$query );

        if ( ! 
$this->results ) {
            if ( 
$this->db->last_error ) {
                throw new 
Exception'Database error: ' $this->db->last_error );
            }

            return 
false;
        }

        
$this->offset += $this->chunk_size;
        return 
true;
    }

    public function 
current() {
        return 
$this->results$this->index_in_results ];
    }

    public function 
key() {
        return 
$this->global_index;
    }

    public function 
next() {
        
$this->index_in_results++;
        
$this->global_index++;
    }

    public function 
rewind() {
        
$this->results          = [];
        
$this->global_index     0;
        
$this->index_in_results 0;
        
$this->offset           0;
        
$this->depleted         false;
    }

    public function 
valid() {
        if ( 
$this->depleted ) {
            return 
false;
        }

        if ( ! isset( 
$this->results$this->index_in_results ] ) ) {
            
$items_loaded $this->load_items_from_db();

            if ( ! 
$items_loaded ) {
                
$this->rewind();
                
$this->depleted true;
                return 
false;
            }

            
$this->index_in_results 0;
        }

        return 
true;
    }
}

<?
php

namespace WP_CLI\Iterators;

use 
IteratorIterator;

/**
 * Applies one or more callbacks to an item before returning it.
 */
class Transform extends IteratorIterator {

    private 
$transformers = [];

    public function 
add_transform$fn ) {
        
$this->transformers[] = $fn;
    }

    public function 
current() {
        
$value parent::current();

        foreach ( 
$this->transformers as $fn ) {
            
$value call_user_func$fn$value );
        }

        return 
$value;
    }
}

<?
php

namespace WP_CLI\Iterators;

/**
 * @source https://gist.github.com/4060005
 */
class Table extends Query {

    
/**
     * Creates an iterator over a database table.
     *
     * <code>
     * foreach( new Iterators\Table( array( 'table' => $wpdb->posts, 'fields' => array( 'ID', 'post_content' ) ) ) as $post ) {
     *     count_words_for( $post->ID, $post->post_content );
     * }
     * </code>
     *
     * <code>
     * foreach( new Iterators\Table( array( 'table' => $wpdb->posts, 'where' => 'ID = 8 OR post_status = "publish"' ) ) as $post ) {
     *     …
     * }
     * </code>
     *
     * <code>
     * foreach( new PostIterator( array( 'table' => $wpdb->posts, 'where' => array( 'post_status' => 'publish', 'post_date_gmt BETWEEN x AND y' ) ) ) as $post ) {
     *     …
     * }
     * </code>
     *
     * @param array $args Supported arguments:
     *      table – the name of the database table
     *      fields – an array of columns to get from the table, '*' is a valid value and the default
     *      where – conditions for filtering rows. Supports two formats:
     *              = string – this will be the where clause
     *              = array – each element is treated as a condition if it's positional, or as column => value if
     *                it's a key/value pair. In the latter case the value is automatically quoted and escaped
     *      append - add arbitrary extra SQL
     */
    
public function __construct$args = [] ) {
        
$defaults = [
            
'fields'     => '*',
            
'where'      => [],
            
'append'     => '',
            
'table'      => null,
            
'chunk_size' => 500,
        ];
        
$table    $args['table'];
        
$args     array_merge$defaults$args );

        
$fields     self::build_fields$args['fields'] );
        
$conditions self::build_where_conditions$args['where'] );
        
$where_sql  $conditions " WHERE $conditions'';
        
$query      "SELECT $fields FROM `$table$where_sql {$args['append']}";

        
parent::__construct$query$args['chunk_size'] );
    }

    private static function 
build_fields$fields ) {
        if ( 
'*' === $fields ) {
            return 
$fields;
        }

        return 
implode(
            
', ',
            
array_map(
                function ( 
$v ) {
                    return 
"`$v`";
                },
                
$fields
            
)
        );
    }

    private static function 
build_where_conditions$where ) {
        global 
$wpdb;
        if ( 
is_array$where ) ) {
            
$conditions = [];
            foreach ( 
$where as $key => $value ) {
                if ( 
is_array$value ) ) {
                    
$conditions[] = $key ' IN (' esc_sqlimplode','$value ) ) . ')';
                } elseif ( 
is_numeric$key ) ) {
                    
$conditions[] = $value;
                } else {
                    
$conditions[] = $key $wpdb->prepare' = %s'$value );
                }
            }
            
$where implode' AND '$conditions );
        }
        return 
$where;
    }
}

<?
php

namespace WP_CLI\Iterators;

use 
Countable;
use 
Iterator;
use 
SplFileObject;
use 
WP_CLI;

/**
 * Allows incrementally reading and parsing lines from a CSV file.
 */
class CSV implements CountableIterator {

    const 
ROW_SIZE 4096;

    private 
$filename;
    private 
$file_pointer;

    private 
$delimiter;
    private 
$columns;

    private 
$current_index;
    private 
$current_element;

    public function 
__construct$filename$delimiter ',' ) {
        
$this->filename     $filename;
        
$this->file_pointer fopen$filename'rb' );
        if ( ! 
$this->file_pointer ) {
            
WP_CLI::errorsprintf'Could not open file: %s'$filename ) );
        }

        
$this->delimiter $delimiter;
    }

    public function 
rewind() {
        
rewind$this->file_pointer );

        
$this->columns fgetcsv$this->file_pointerself::ROW_SIZE$this->delimiter );

        
$this->current_index = -1;
        
$this->next();
    }

    public function 
current() {
        return 
$this->current_element;
    }

    public function 
key() {
        return 
$this->current_index;
    }

    public function 
next() {
        
$this->current_element false;

        while ( 
true ) {
            
$str fgets$this->file_pointer );

            if ( 
false === $str ) {
                break;
            }

            
$row str_getcsv$str$this->delimiter );

            
$element = [];
            foreach ( 
$this->columns as $i => $key ) {
                if ( isset( 
$row$i ] ) ) {
                    
$element$key ] = $row$i ];
                }
            }

            if ( ! empty( 
$element ) ) {
                
$this->current_element $element;
                
$this->current_index++;

                break;
            }
        }
    }

    public function 
count() {
        
$file = new SplFileObject$this->filename'r' );
        
$file->seekPHP_INT_MAX );
        return 
$file->key() + 1;
    }

    public function 
valid() {
        return 
is_array$this->current_element );
    }
}

<?
php

namespace WP_CLI\Iterators;

use 
RuntimeException;

class 
Exception extends RuntimeException {}

<?
php

namespace WP_CLI;

use 
DirectoryIterator;
use 
Exception;
use 
PharData;
use 
RecursiveDirectoryIterator;
use 
RecursiveIteratorIterator;
use 
WP_CLI;
use 
ZipArchive;

/**
 * Extract a provided archive file.
 */
class Extractor {

    
/**
     * Extract the archive file to a specific destination.
     *
     * @param string $dest
     */
    
public static function extract$tarball_or_zip$dest ) {
        if ( 
preg_match'/\.zip$/'$tarball_or_zip ) ) {
            return 
self::extract_zip$tarball_or_zip$dest );
        }

        if ( 
preg_match'/\.tar\.gz$/'$tarball_or_zip ) ) {
            return 
self::extract_tarball$tarball_or_zip$dest );
        }

        throw new 
Exception"Extraction only supported for '.zip' and '.tar.gz' file types." );
    }

    
/**
     * Extract a ZIP file to a specific destination.
     *
     * @param string $zipfile
     * @param string $dest
     */
    
private static function extract_zip$zipfile$dest ) {
        if ( ! 
class_exists'ZipArchive' ) ) {
            throw new 
Exception'Extracting a zip file requires ZipArchive.' );
        }

        
// Ensure the destination folder exists or can be created.
        
if ( ! self::ensure_dir_exists$dest ) ) {
            throw new 
Exception"Could not create folder '{$dest}'." );
        }

        if ( ! 
file$zipfile )
            || ! 
is_readable$zipfile )
            || 
filesize$zipfile ) <= ) {
            throw new 
Exception"Invalid zip file '{$zipfile}'." );
        }

        
$zip = new ZipArchive();
        
$res $zip->open$zipfile );

        if ( 
true === $res ) {
            
$name    Utils\basename$zipfile );
            
$tempdir Utils\get_temp_dir()
                        . 
uniqid'wp-cli-extract-zipfile-'true )
                        . 
"-{$name}";

            
$zip->extractTo$tempdir );
            
$zip->close();

            
self::copy_overwrite_files(
                
self::get_first_subfolder$tempdir ),
                
$dest
            
);

            
self::rmdir$tempdir );
        } else {
            throw new 
Exception(
                
sprintf(
                    
"ZipArchive failed to unzip '%s': %s.",
                    
$zipfile,
                    
self::zip_error_msg$res )
                )
            );
        }
    }

    
/**
     * Extract a tarball to a specific destination.
     *
     * @param string $tarball
     * @param string $dest
     */
    
private static function extract_tarball$tarball$dest ) {
        
// Ensure the destination folder exists or can be created.
        
if ( ! self::ensure_dir_exists$dest ) ) {
            throw new 
Exception"Could not create folder '{$dest}'." );
        }

        if ( 
class_exists'PharData' ) ) {
            try {
                
$phar    = new PharData$tarball );
                
$name    Utils\basename$tarball );
                
$tempdir Utils\get_temp_dir()
                            . 
uniqid'wp-cli-extract-tarball-'true )
                            . 
"-{$name}";

                
$phar->extractTo$tempdir );

                
self::copy_overwrite_files(
                    
self::get_first_subfolder$tempdir ),
                    
$dest
                
);

                
self::rmdir$tempdir );
                return;
            } catch ( 
Exception $e ) {
                
WP_CLI::warning(
                    
"PharData failed, falling back to 'tar xz' ("
                    
$e->getMessage() . ')'
                
);
                
// Fall through to trying `tar xz` below.
            
}
        }

        
// Ensure relative paths cannot be misinterpreted as hostnames.
        // Prepending `./` will force tar to interpret it as a filesystem path.
        
if ( self::path_is_relative$tarball ) ) {
            
$tarball "./{$tarball}";
        }

        if ( ! 
file$tarball )
            || ! 
is_readable$tarball )
            || 
filesize$tarball ) <= ) {
            throw new 
Exception"Invalid zip file '{$tarball}'." );
        }

        
// Note: directory must exist for tar --directory to work.
        
$cmd Utils\esc_cmd(
            
'tar xz --strip-components=1 --directory=%s -f %s',
            
$dest,
            
$tarball
        
);

        
$process_run WP_CLI::launch(
            
$cmd,
            
false /*exit_on_error*/,
            
true /*return_detailed*/
        
);

        if ( 
!== $process_run->return_code ) {
            throw new 
Exception(
                
sprintf(
                    
'Failed to execute `%s`: %s.',
                    
$cmd,
                    
self::tar_error_msg$process_run )
                )
            );
        }
    }

    
/**
     * Copy files from source directory to destination directory. Source
     * directory must exist.
     *
     * @param string $source
     * @param string $dest
     */
    
public static function copy_overwrite_files$source$dest ) {
        
$iterator = new RecursiveIteratorIterator(
            new 
RecursiveDirectoryIterator(
                
$source,
                
RecursiveDirectoryIterator::SKIP_DOTS
            
),
            
RecursiveIteratorIterator::SELF_FIRST
        
);

        
$error 0;

        if ( ! 
is_dir$dest ) ) {
            
mkdir$dest0777true );
        }

        foreach ( 
$iterator as $item ) {

            
$dest_path $dest DIRECTORY_SEPARATOR $iterator->getSubPathName();

            if ( 
$item->isDir() ) {
                if ( ! 
is_dir$dest_path ) ) {
                    
mkdir$dest_path );
                }
            } else {
                if ( 
file_exists$dest_path ) && is_writable$dest_path ) ) {
                    
copy$item$dest_path );
                } elseif ( ! 
file_exists$dest_path ) ) {
                    
copy$item$dest_path );
                } else {
                    
$error 1;
                    
WP_CLI::warning"Unable to copy '" $iterator->getSubPathName() . "' to current directory." );
                }
            }
        }

        if ( 
$error ) {
            throw new 
Exception'There was an error overwriting existing files.' );
        }
    }

    
/**
     * Delete all files and directories recursively from directory. Directory
     * must exist.
     *
     * @param string $dir
     */
    
public static function rmdir$dir ) {
        
$files = new RecursiveIteratorIterator(
            new 
RecursiveDirectoryIterator(
                
$dir,
                
RecursiveDirectoryIterator::SKIP_DOTS
            
),
            
RecursiveIteratorIterator::CHILD_FIRST
        
);

        foreach ( 
$files as $fileinfo ) {
            
$todo $fileinfo->isDir() ? 'rmdir' 'unlink';
            
$path $fileinfo->getRealPath();
            if ( 
!== strpos$path$fileinfo->getRealPath() ) ) {
                
WP_CLI::warning(
                    
"Temporary file or folder to be removed was found outside of temporary folder, aborting removal: '{$path}'"
                
);
            }
            
$todo$path );
        }
        
rmdir$dir );
    }

    
/**
     * Return formatted ZipArchive error message from error code.
     *
     * @param int $error_code
     * @return string|int The error message corresponding to the specified
     *                    code, if found; Other wise the same error code,
     *                    unmodified.
     */
    
public static function zip_error_msg$error_code ) {
        
// From https://github.com/php/php-src/blob/php-5.3.0/ext/zip/php_zip.c#L2623-L2646.
        
static $zip_err_msgs = [
            
ZipArchive::ER_OK          => 'No error',
            
ZipArchive::ER_MULTIDISK   => 'Multi-disk zip archives not supported',
            
ZipArchive::ER_RENAME      => 'Renaming temporary file failed',
            
ZipArchive::ER_CLOSE       => 'Closing zip archive failed',
            
ZipArchive::ER_SEEK        => 'Seek error',
            
ZipArchive::ER_READ        => 'Read error',
            
ZipArchive::ER_WRITE       => 'Write error',
            
ZipArchive::ER_CRC         => 'CRC error',
            
ZipArchive::ER_ZIPCLOSED   => 'Containing zip archive was closed',
            
ZipArchive::ER_NOENT       => 'No such file',
            
ZipArchive::ER_EXISTS      => 'File already exists',
            
ZipArchive::ER_OPEN        => 'Can\'t open file',
            
ZipArchive::ER_TMPOPEN     => 'Failure to create temporary file',
            
ZipArchive::ER_ZLIB        => 'Zlib error',
            
ZipArchive::ER_MEMORY      => 'Malloc failure',
            
ZipArchive::ER_CHANGED     => 'Entry has been changed',
            
ZipArchive::ER_COMPNOTSUPP => 'Compression method not supported',
            
ZipArchive::ER_EOF         => 'Premature EOF',
            
ZipArchive::ER_INVAL       => 'Invalid argument',
            
ZipArchive::ER_NOZIP       => 'Not a zip archive',
            
ZipArchive::ER_INTERNAL    => 'Internal error',
            
ZipArchive::ER_INCONS      => 'Zip archive inconsistent',
            
ZipArchive::ER_REMOVE      => 'Can\'t remove file',
            
ZipArchive::ER_DELETED     => 'Entry has been deleted',
        ];

        if ( isset( 
$zip_err_msgs$error_code ] ) ) {
            return 
sprintf(
                
'%s (%d)',
                
$zip_err_msgs$error_code ],
                
$error_code
            
);
        }
        return 
$error_code;
    }

    
/**
     * Return formatted error message from ProcessRun of tar command.
     *
     * @param Processrun $process_run
     * @return string|int The error message of the process, if available;
     *                    otherwise the return code.
     */
    
public static function tar_error_msg$process_run ) {
        
$stderr trim$process_run->stderr );
        
$nl_pos strpos$stderr"\n" );
        if ( 
false !== $nl_pos ) {
            
$stderr trimsubstr$stderr0$nl_pos ) );
        }
        if ( 
$stderr ) {
            return 
sprintf'%s (%d)'$stderr$process_run->return_code );
        }
        return 
$process_run->return_code;
    }

    
/**
     * Return the first subfolder within a given path.
     *
     * Falls back to the provided path if no subfolder was detected.
     *
     * @param string $path Path to find the first subfolder in.
     * @return string First subfolder, or same as $path if none found.
     */
    
private static function get_first_subfolder$path ) {
        
$iterator = new DirectoryIterator$path );

        foreach ( 
$iterator as $fileinfo ) {
            if ( 
$fileinfo->isDir() && ! $fileinfo->isDot() ) {
                return 
"{$path}/{$fileinfo->getFilename()}";
            }
        }

        return 
$path;
    }

    
/**
     * Ensure directory exists.
     *
     * @param string $dir Directory to ensure the existence of.
     * @return bool Whether the existence could be asserted.
     */
    
private static function ensure_dir_exists$dir ) {
        if ( ! 
is_dir$dir ) ) {
            if ( ! @
mkdir$dir0777true ) ) {
                
$error error_get_last();
                
WP_CLI::warning(
                    
sprintf(
                        
"Failed to create directory '%s': %s.",
                        
$dir,
                        
$error['message']
                    )
                );
                return 
false;
            }
        }

        return 
true;
    }

    
/**
     * Check whether a path is relative-
     *
     * @param string $path Path to check.
     * @return bool Whether the path is relative.
     */
    
private static function path_is_relative$path ) {
        if ( 
'' === $path ) {
            return 
true;
        }

        
// Strip scheme.
        
$scheme_position strpos$path'://' );
        if ( 
false !== $scheme_position ) {
            
$path substr$path$scheme_position );
        }

        
// UNIX root "/" or "\" (Windows style).
        
if ( '/' === $path[0] || '\\' === $path[0] ) {
            return 
false;
        }

        
// Windows root.
        
if ( strlen$path ) > && ctype_alpha$path[0] ) && ':' === $path[1] ) {

            
// Special case: only drive letter, like "C:".
            
if ( === strlen$path ) ) {
                return 
false;
            }

            
// Regular Windows path starting with drive letter, like "C:/ or "C:\".
            
if ( '/' === $path[2] || '\\' === $path[2] ) {
                return 
false;
            }
        }

        return 
true;
    }
}
<?
php

namespace WP_CLI;

use 
Mustangostang\Spyc;

/**
 * Handles file- and runtime-based configuration values.
 *
 * @package WP_CLI
 */
class Configurator {

    
/**
     * Configurator argument specification.
     *
     * @var array
     */
    
private $spec;

    
/**
     * Values for keys defined in Configurator spec.
     *
     * @var array
     */
    
private $config = [];

    
/**
     * Extra config values not specified in spec.
     *
     * @var array
     */
    
private $extra_config = [];

    
/**
     * Any aliases defined in config files.
     *
     * @var array
     */
    
private $aliases = [];

    
/**
     * Regex pattern used to define an alias.
     *
     * @var string
     */
    
const ALIAS_REGEX '^@[A-Za-z0-9-_\.\-]+$';

    
/**
     * Arguments that can be used in an alias.
     *
     * @var array
     */
    
private static $alias_spec = [
        
'user',
        
'url',
        
'path',
        
'ssh',
        
'http',
    ];

    
/**
     * @param string $path Path to config spec file.
     */
    
public function __construct$path ) {
        
$this->spec = include $path;

        
$defaults = [
            
'runtime'  => false,
            
'file'     => false,
            
'synopsis' => '',
            
'default'  => null,
            
'multiple' => false,
        ];

        foreach ( 
$this->spec as $key => &$details ) {
            
$details array_merge$defaults$details );

            
$this->config$key ] = $details['default'];
        }
    }

    
/**
     * Get declared configuration values as an array.
     *
     * @return array
     */
    
public function to_array() {
        return [ 
$this->config$this->extra_config ];
    }

    
/**
     * Get configuration specification, i.e. list of accepted keys.
     *
     * @return array
     */
    
public function get_spec() {
        return 
$this->spec;
    }

    
/**
     * Get any aliases defined in config files.
     *
     * @return array
     */
    
public function get_aliases() {
        
$runtime_alias getenv'WP_CLI_RUNTIME_ALIAS' );
        if ( 
false !== $runtime_alias ) {
            
$returned_aliases = [];
            foreach ( 
json_decode$runtime_aliastrue ) as $key => $value ) {
                if ( 
preg_match'#' self::ALIAS_REGEX '#'$key ) ) {
                    
$returned_aliases$key ] = [];
                    foreach ( 
self::$alias_spec as $i ) {
                        if ( isset( 
$value$i ] ) ) {
                            
$returned_aliases$key ][ $i ] = $value$i ];
                        }
                    }
                }
            }
            return 
$returned_aliases;
        }

        return 
$this->aliases;
    }

    
/**
     * Splits a list of arguments into positional, associative and config.
     *
     * @param array(string) $arguments
     * @return array(array)
     */
    
public function parse_args$arguments ) {
        list( 
$positional_args$mixed_args$global_assoc$local_assoc ) = self::extract_assoc$arguments );
        list( 
$assoc_args$runtime_config )                               = $this->unmix_assoc_args$mixed_args$global_assoc$local_assoc );
        return [ 
$positional_args$assoc_args$runtime_config ];
    }

    
/**
     * Splits positional args from associative args.
     *
     * @param array $arguments
     * @return array(array)
     */
    
public static function extract_assoc$arguments ) {
        
$positional_args = [];
        
$assoc_args      = [];
        
$global_assoc    = [];
        
$local_assoc     = [];

        foreach ( 
$arguments as $arg ) {
            
$positional null;
            
$assoc_arg  null;

            if ( 
preg_match'|^--no-([^=]+)$|'$arg$matches ) ) {
                
$assoc_arg = [ $matches[1], false ];
            } elseif ( 
preg_match'|^--([^=]+)$|'$arg$matches ) ) {
                
$assoc_arg = [ $matches[1], true ];
            } elseif ( 
preg_match'|^--([^=]+)=(.*)|s'$arg$matches ) ) {
                
$assoc_arg = [ $matches[1], $matches[2] ];
            } else {
                
$positional $arg;
            }

            if ( ! 
is_null$assoc_arg ) ) {
                
$assoc_args[] = $assoc_arg;
                if ( 
count$positional_args ) ) {
                    
$local_assoc[] = $assoc_arg;
                } else {
                    
$global_assoc[] = $assoc_arg;
                }
            } elseif ( ! 
is_null$positional ) ) {
                
$positional_args[] = $positional;
            }
        }

        return [ 
$positional_args$assoc_args$global_assoc$local_assoc ];
    }

    
/**
     * Separate runtime parameters from command-specific parameters.
     *
     * @param array $mixed_args
     * @return array
     */
    
private function unmix_assoc_args$mixed_args$global_assoc = [], $local_assoc = [] ) {
        
$assoc_args     = [];
        
$runtime_config = [];

        if ( 
getenv'WP_CLI_STRICT_ARGS_MODE' ) ) {
            foreach ( 
$global_assoc as $tmp ) {
                list( 
$key$value ) = $tmp;
                if ( isset( 
$this->spec$key ] ) && false !== $this->spec$key ]['runtime'] ) {
                    
$this->assoc_arg_to_runtime_config$key$value$runtime_config );
                }
            }
            foreach ( 
$local_assoc as $tmp ) {
                
$assoc_args$tmp[0] ] = $tmp[1];
            }
        } else {
            foreach ( 
$mixed_args as $tmp ) {
                list( 
$key$value ) = $tmp;

                if ( isset( 
$this->spec$key ] ) && false !== $this->spec$key ]['runtime'] ) {
                    
$this->assoc_arg_to_runtime_config$key$value$runtime_config );
                } else {
                    
$assoc_args$key ] = $value;
                }
            }
        }

        return [ 
$assoc_args$runtime_config ];
    }

    
/**
     * Handle turning an $assoc_arg into a runtime arg.
     */
    
private function assoc_arg_to_runtime_config$key$value, &$runtime_config ) {
        
$details $this->spec$key ];
        if ( isset( 
$details['deprecated'] ) ) {
            
fwriteSTDERR"WP-CLI: The --{$key} global parameter is deprecated. {$details['deprecated']}\n" );
        }

        if ( 
$details['multiple'] ) {
            
$runtime_config$key ][] = $value;
        } else {
            
$runtime_config$key ] = $value;
        }
    }

    
/**
     * Load a YAML file of parameters into scope.
     *
     * @param string $path Path to YAML file.
     */
    
public function merge_yml$path$current_alias null ) {
        
$yaml self::load_yml$path );
        if ( ! empty( 
$yaml['_']['inherit'] ) ) {
            
$this->merge_yml$yaml['_']['inherit'], $current_alias );
        }
        
// Prepare the base path for absolutized alias paths.
        
$yml_file_dir $path dirname$path ) : false;
        foreach ( 
$yaml as $key => $value ) {
            if ( 
preg_match'#' self::ALIAS_REGEX '#'$key ) ) {
                
$this->aliases$key ] = [];
                
$is_alias              false;
                foreach ( 
self::$alias_spec as $i ) {
                    if ( isset( 
$value$i ] ) ) {
                        if ( 
'path' === $i && ! isset( $value['ssh'] ) ) {
                            
self::absolutize$value$i ], $yml_file_dir );
                        }
                        
$this->aliases$key ][ $i ] = $value$i ];
                        
$is_alias                    true;
                    }
                }
                
// If it's not an alias, it might be a group of aliases.
                
if ( ! $is_alias && is_array$value ) ) {
                    
$alias_group = [];
                    foreach ( 
$value as $k ) {
                        if ( 
preg_match'#' self::ALIAS_REGEX '#'$k ) ) {
                            
$alias_group[] = $k;
                        }
                    }
                    
$this->aliases$key ] = $alias_group;
                }
            } elseif ( ! isset( 
$this->spec$key ] ) || false === $this->spec$key ]['file'] ) {
                if ( isset( 
$this->extra_config$key ] )
                    && ! empty( 
$yaml['_']['merge'] )
                    && 
is_array$this->extra_config$key ] )
                    && 
is_array$value ) ) {
                    
$this->extra_config$key ] = array_merge$this->extra_config$key ], $value );
                } else {
                    
$this->extra_config$key ] = $value;
                }
            } elseif ( 
$this->spec$key ]['multiple'] ) {
                
self::arrayify$value );
                
$this->config$key ] = array_merge$this->config$key ], $value );
            } else {
                if ( 
$current_alias && in_array$keyself::$alias_spectrue ) ) {
                    continue;
                }
                
$this->config$key ] = $value;
            }
        }
    }

    
/**
     * Merge an array of values into the configurator config.
     *
     * @param array $config
     */
    
public function merge_array$config ) {
        foreach ( 
$this->spec as $key => $details ) {
            if ( 
false !== $details['runtime'] && isset( $config$key ] ) ) {
                
$value $config$key ];

                if ( 
'require' === $key ) {
                    
$value Utils\expand_globs$value );
                }

                if ( 
$details['multiple'] ) {
                    
self::arrayify$value );
                    
$this->config$key ] = array_merge$this->config$key ], $value );
                } else {
                    
$this->config$key ] = $value;
                }
            }
        }
    }

    
/**
     * Load values from a YAML file.
     *
     * @param string $yml_file Path to the YAML file
     * @return array Declared configuration values
     */
    
private static function load_yml$yml_file ) {
        if ( ! 
$yml_file ) {
            return [];
        }

        
$config Spyc::YAMLLoad$yml_file );

        
// Make sure config-file-relative paths are made absolute.
        
$yml_file_dir dirname$yml_file );

        if ( isset( 
$config['path'] ) ) {
            
self::absolutize$config['path'], $yml_file_dir );
        }

        if ( isset( 
$config['require'] ) ) {
            
self::arrayify$config['require'] );
            
$config['require'] = Utils\expand_globs$config['require'] );
            foreach ( 
$config['require'] as &$path ) {
                
self::absolutize$path$yml_file_dir );
            }
        }

        
// Backwards compat
        // Command 'core config' was moved to 'config create'.
        
if ( isset( $config['core config'] ) ) {
            
$config['config create'] = $config['core config'];
            unset( 
$config['core config'] );
        }
        
// Command 'checksum core' was moved to 'core verify-checksums'.
        
if ( isset( $config['checksum core'] ) ) {
            
$config['core verify-checksums'] = $config['checksum core'];
            unset( 
$config['checksum core'] );
        }
        
// Command 'checksum plugin' was moved to 'plugin verify-checksums'.
        
if ( isset( $config['checksum plugin'] ) ) {
            
$config['plugin verify-checksums'] = $config['checksum plugin'];
            unset( 
$config['checksum plugin'] );
        }

        return 
$config;
    }

    
/**
     * Conform a variable to an array.
     *
     * @param mixed $val A string or an array
     */
    
private static function arrayify( &$val ) {
        
$val = (array) $val;
    }

    
/**
     * Make a path absolute.
     *
     * @param string $path Path to file.
     * @param string $base Base path to prepend.
     */
    
private static function absolutize( &$path$base ) {
        if ( ! empty( 
$path ) && ! Utils\is_path_absolute$path ) ) {
            
$path $base DIRECTORY_SEPARATOR $path;
        }
    }

}
<?
php

namespace WP_CLI;

use 
Composer\DependencyResolver\Rule;
use 
Composer\EventDispatcher\EventSubscriberInterface;
use 
Composer\Installer\PackageEvent;
use 
Composer\Installer\PackageEvents;
use 
WP_CLI;

/**
 * A Composer Event subscriber so we can keep track of what's happening inside Composer
 */
class PackageManagerEventSubscriber implements EventSubscriberInterface {

    
/**
     * Get subscribed events.
     *
     * @return array
     */
    
public static function getSubscribedEvents() {

        return [
            
PackageEvents::PRE_PACKAGE_INSTALL  => 'pre_install',
            
PackageEvents::POST_PACKAGE_INSTALL => 'post_install',
        ];
    }

    
/**
     * Pre-install operation message.
     *
     * @param \Composer\Installer\PackageEvent $event
     *
     * @return void
     */
    
public static function pre_installPackageEvent $event ) {
        
$operation_message $event->getOperation()->__toString();
        
WP_CLI::log' - ' $operation_message );
    }

    
/**
     * Post-install operation log.
     *
     * @param \Composer\Installer\PackageEvent $event
     *
     * @return void
     */
    
public static function post_installPackageEvent $event ) {

        
$operation $event->getOperation();

        
// getReason() was removed in Composer v2 without replacement.
        
if ( ! method_exists$operation'getReason' ) ) {
            return;
        }

        
$reason $operation->getReason();
        if ( 
$reason instanceof Rule ) {

            switch ( 
$reason->getReason() ) {

                case 
Rule::RULE_PACKAGE_CONFLICT:
                case 
Rule::RULE_PACKAGE_SAME_NAME:
                case 
Rule::RULE_PACKAGE_REQUIRES:
                    
$composer_error $reason->getPrettyString$event->getPool() );
                    break;

            }

            if ( ! empty( 
$composer_error ) ) {
                
WP_CLI::logsprintf' - Warning: %s'$composer_error ) );
            }
        }

    }

}
<?
php

namespace WP_CLI;

use 
RuntimeException;

/**
 * Class WpOrgApi.
 *
 * This is an abstraction of the WordPress.org API.
 *
 * @see https://codex.wordpress.org/WordPress.org_API
 *
 * @package WP_CLI
 */
final class WpOrgApi {

    
/**
     * WordPress.org API root URL.
     *
     * @var string
     */
    
const API_ROOT 'https://api.wordpress.org';

    
/**
     * WordPress.org API root URL.
     *
     * @var string
     */
    
const DOWNLOADS_ROOT 'https://downloads.wordpress.org';

    
/**
     * Core checksums endpoint.
     *
     * @see https://codex.wordpress.org/WordPress.org_API#Checksum
     *
     * @var string
     */
    
const CORE_CHECKSUMS_ENDPOINT self::API_ROOT '/core/checksums/1.0/';

    
/**
     * Plugin checksums endpoint.
     *
     * @var string
     */
    
const PLUGIN_CHECKSUMS_ENDPOINT self::DOWNLOADS_ROOT '/plugin-checksums/';

    
/**
     * Plugin info endpoint.
     *
     * @var string
     */
    
const PLUGIN_INFO_ENDPOINT self::API_ROOT '/plugins/info/1.2/';

    
/**
     * Theme info endpoint.
     *
     * @var string
     */
    
const THEME_INFO_ENDPOINT self::API_ROOT '/themes/info/1.2/';

    
/**
     * Salt endpoint.
     *
     * @see https://codex.wordpress.org/WordPress.org_API#Secret_Key
     *
     * @var string
     */
    
const SALT_ENDPOINT self::API_ROOT '/secret-key/1.1/salt/';

    
/**
     * Version check endpoint.
     *
     * @see https://codex.wordpress.org/WordPress.org_API#Version_Check
     *
     * @var string
     */
    
const VERSION_CHECK_ENDPOINT self::API_ROOT '/core/version-check/1.7/';

    
/**
     * Options to pass onto the Requests library for executing the remote calls.
     *
     * @var array
     */
    
private $options;

    
/**
     * WpOrgApi constructor.
     *
     * @param array $options Associative array of options to pass to the API abstraction.
     */
    
public function __construct$options = [] ) {
        
$this->options $options;
    }

    
/**
     * Gets the checksums for the given version of WordPress core.
     *
     * @param string $version Version string to query.
     * @param string $locale  Optional. Locale to query. Defaults to 'en_US'.
     * @return bool|array False on failure. An array of checksums on success.
     * @throws RuntimeException If the remote request fails.
     */
    
public function get_core_checksums$version$locale 'en_US' ) {
        
$url sprintf(
            
'%s?%s',
            
self::CORE_CHECKSUMS_ENDPOINT,
            
http_build_querycompact'version''locale' ), null'&' )
        );

        
$response $this->json_get_request$url );

        if (
            ! 
is_array$response )
            || ! isset( 
$response['checksums'] )
            || ! 
is_array$response['checksums'] )
        ) {
            return 
false;
        }

        return 
$response['checksums'];
    }

    
/**
     * Gets a core version check.
     *
     * @param string $locale Optional. Locale to request a version check for. Defaults to 'en_US'.
     * @return array|false False on failure. Associative array of the offer on success.
     * @throws RuntimeException If the remote request failed.
     */
    
public function get_core_version_check$locale 'en_US' ) {
        
$url sprintf(
            
'%s?%s',
            
self::VERSION_CHECK_ENDPOINT,
            
http_build_querycompact'locale' ), null'&' )
        );

        
$response $this->json_get_request$url );

        if ( ! 
is_array$response ) ) {
            return 
false;
        }

        return 
$response;
    }

    
/**
     * Gets a download offer.
     *
     * @param string $locale Optional. Locale to request an offer from. Defaults to 'en_US'.
     * @return array|false False on failure. Associative array of the offer on success.
     * @throws RuntimeException If the remote request failed.
     */
    
public function get_core_download_offer$locale 'en_US' ) {
        
$response $this->get_core_version_check$locale );

        if (
            ! 
is_array$response )
            || ! isset( 
$response['offers'] )
            || ! 
is_array$response['offers'] )
        ) {
            return 
false;
        }

        
$offer $response['offers'][0];

        if ( ! 
array_key_exists'locale'$offer ) || $locale !== $offer['locale'] ) {
            return 
false;
        }

        return 
$offer;
    }

    
/**
     * Gets the checksums for the given version of plugin.
     *
     * @param string $plugin  Plugin slug to query.
     * @param string $version Version string to query.
     * @return bool|array False on failure. An array of checksums on success.
     * @throws RuntimeException If the remote request fails.
     */
    
public function get_plugin_checksums$plugin$version ) {
        
$url sprintf(
            
'%s%s/%s.json',
            
self::PLUGIN_CHECKSUMS_ENDPOINT,
            
$plugin,
            
$version
        
);

        
$response $this->json_get_request$url );

        if (
            ! 
is_array$response )
            || ! isset( 
$response['files'] )
            || ! 
is_array$response['files'] )
        ) {
            return 
false;
        }

        return 
$response['files'];
    }

    
/**
     * Gets a plugin's info.
     *
     * @param string $plugin Plugin slug to query.
     * @param string $locale Optional. Locale to request info for. Defaults to 'en_US'.
     * @return array|false False on failure. Associative array of the offer on success.
     * @throws RuntimeException If the remote request failed.
     */
    
public function get_plugin_info$plugin$locale 'en_US' ) {
        
$action  'plugin_information';
        
$request = [
            
'locale' => $locale,
            
'slug'   => $plugin,
        ];

        
$url sprintf(
            
'%s?%s',
            
self::PLUGIN_INFO_ENDPOINT,
            
http_build_querycompact'action''request' ), null'&' )
        );

        
$response $this->json_get_request$url );

        if ( ! 
is_array$response ) ) {
            return 
false;
        }

        return 
$response;
    }

    
/**
     * Gets a theme's info.
     *
     * @param string $theme  Theme slug to query.
     * @param string $locale Optional. Locale to request info for. Defaults to 'en_US'.
     * @return array|false False on failure. Associative array of the offer on success.
     * @throws RuntimeException If the remote request failed.
     */
    
public function get_theme_info$theme$locale 'en_US' ) {
        
$action  'theme_information';
        
$request = [
            
'locale' => $locale,
            
'slug'   => $theme,
        ];

        
$url sprintf(
            
'%s?%s',
            
self::THEME_INFO_ENDPOINT,
            
http_build_querycompact'action''request' ), null'&' )
        );

        
$response $this->json_get_request$url );

        if ( ! 
is_array$response ) ) {
            return 
false;
        }

        return 
$response;
    }

    
/**
     * Gets a set of salts in the format required by `wp-config.php`.
     *
     * @return bool|string False on failure. A string of PHP define() statements on success.
     * @throws RuntimeException If the remote request fails.
     */
    
public function get_salts() {
        return 
$this->get_requestself::SALT_ENDPOINT );
    }

    
/**
     * Execute a remote GET request.
     *
     * @param string $url     URL to execute the GET request on.
     * @param array  $headers Optional. Associative array of headers.
     * @param array  $options Optional. Associative array of options.
     * @return mixed|false False on failure. Decoded JSON on success.
     * @throws RuntimeException If the JSON could not be decoded.
     */
    
private function json_get_request$url$headers = [], $options = [] ) {
        
$headers array_merge(
            [
                
'Accept' => 'application/json',
            ],
            
$headers
        
);

        
$response $this->get_request$url$headers$options );

        if ( 
false === $response ) {
            return 
$response;
        }

        
$data json_decode$responsetrue );

        if ( 
JSON_ERROR_NONE !== json_last_error() ) {
            throw new 
RuntimeException'Failed to decode JSON: ' json_last_error_msg() );
        }

        return 
$data;
    }

    
/**
     * Execute a remote GET request.
     *
     * @param string $url     URL to execute the GET request on.
     * @param array  $headers Optional. Associative array of headers.
     * @param array  $options Optional. Associative array of options.
     * @return string|false False on failure. Response body string on success.
     * @throws RuntimeException If the remote request fails.
     */
    
private function get_request$url$headers = [], $options = [] ) {
        
$options array_merge(
            
$this->options,
            [
                
'halt_on_error' => false,
            ],
            
$options
        
);

        
$response Utils\http_request'GET'$urlnull$headers$options );

        if (
            ! 
$response->success
            
|| 200 > (int) $response->status_code
            
|| 300 <= $response->status_code
        
) {
            throw new 
RuntimeException(
                
"Couldn't fetch response from {$url} (HTTP code {$response->status_code})."
            
);
        }

        return 
trim$response->body );
    }
}
<?
php

namespace WP_CLI;

/**
 * Results of an executed command.
 */
class ProcessRun {

    
/**
     * The full command executed by the system.
     *
     * @var string
     */
    
public $command;

    
/**
     * Captured output from the process' STDOUT.
     *
     * @var string
     */
    
public $stdout;

    
/**
     * Captured output from the process' STDERR.
     *
     * @var string
     */
    
public $stderr;

    
/**
     * The path of the working directory for the process or NULL if not specified.
     *
     * This defaults to current working directory.
     *
     * @var string|null
     */
    
public $cwd;

    
/**
     * Environment variables set for this process.
     *
     * @var array
     */
    
public $env;

    
/**
     * Exit code of the process.
     *
     * @var int
     */
    
public $return_code;

    
/**
     * The run time of the process.
     *
     * @var float
     */
    
public $run_time;

    
/**
     * @param array $props Properties of executed command.
     */
    
public function __construct$props ) {
        foreach ( 
$props as $key => $value ) {
            
$this->$key $value;
        }
    }

    
/**
     * Return properties of executed command as a string.
     *
     * @return string
     */
    
public function __toString() {
        
$out  "$ $this->command\n";
        
$out .= "$this->stdout\n$this->stderr";
        
$out .= "cwd: $this->cwd\n";
        
$out .= "run time: $this->run_time\n";
        
$out .= "exit status: $this->return_code";

        return 
$out;
    }
}
<?
php

namespace WP_CLI\Dispatcher;

use 
Closure;
use 
ReflectionClass;
use 
ReflectionFunction;
use 
ReflectionMethod;
use 
WP_CLI;
use 
WP_CLI\DocParser;
use 
WP_CLI\Utils;

/**
 * Creates CompositeCommand or Subcommand instances.
 *
 * @package WP_CLI
 */
class CommandFactory {

    
// Cache of file contents, indexed by filename. Only used if opcache.save_comments is disabled.
    
private static $file_contents = [];

    
/**
     * Create a new CompositeCommand (or Subcommand if class has __invoke())
     *
     * @param string $name Represents how the command should be invoked
     * @param string $callable A subclass of WP_CLI_Command, a function, or a closure
     * @param mixed $parent The new command's parent Composite (or Root) command
     */
    
public static function create$name$callable$parent ) {

        if ( ( 
is_object$callable ) && ( $callable instanceof Closure ) )
            || ( 
is_string$callable ) && function_exists$callable ) ) ) {
            
$reflection = new ReflectionFunction$callable );
            
$command    self::create_subcommand$parent$name$callable$reflection );
        } elseif ( 
is_array$callable ) && ( is_callable$callable ) || Utils\is_valid_class_and_method_pair$callable ) ) ) {
            
$reflection = new ReflectionClass$callable[0] );
            
$command    self::create_subcommand(
                
$parent,
                
$name,
                [ 
$callable[0], $callable[1] ],
                
$reflection->getMethod$callable[1] )
            );
        } else {
            
$reflection = new ReflectionClass$callable );
            if ( 
$reflection->isSubclassOf'\WP_CLI\Dispatcher\CommandNamespace' ) ) {
                
$command self::create_namespace$parent$name$callable );
            } elseif ( 
$reflection->hasMethod'__invoke' ) ) {
                
$class   is_object$callable ) ? $callable $reflection->name;
                
$command self::create_subcommand(
                    
$parent,
                    
$name,
                    [ 
$class'__invoke' ],
                    
$reflection->getMethod'__invoke' )
                );
            } else {
                
$command self::create_composite_command$parent$name$callable );
            }
        }

        return 
$command;
    }

    
/**
     * Clear the file contents cache.
     */
    
public static function clear_file_contents_cache() {
        
self::$file_contents = [];
    }

    
/**
     * Create a new Subcommand instance.
     *
     * @param mixed $parent The new command's parent Composite command
     * @param string|bool $name Represents how the command should be invoked.
     * If false, will be determined from the documented subject, represented by `$reflection`.
     * @param mixed $callable A callable function or closure, or class name and method
     * @param object $reflection Reflection instance, for doc parsing
     */
    
private static function create_subcommand$parent$name$callable$reflection ) {
        
$doc_comment self::get_doc_comment$reflection );
        
$docparser   = new DocParser$doc_comment );

        if ( 
is_array$callable ) ) {
            if ( ! 
$name ) {
                
$name $docparser->get_tag'subcommand' );
            }

            if ( ! 
$name ) {
                
$name $reflection->name;
            }
        }
        if ( ! 
$doc_comment ) {
            
WP_CLI::debugnull === $doc_comment "Failed to get doc comment for {$name}." "No doc comment for {$name}."'commandfactory' );
        }

        
$when_invoked = function ( $args$assoc_args ) use ( $callable ) {
            if ( 
is_array$callable ) ) {
                
$callable[0] = is_object$callable[0] ) ? $callable[0] : new $callable[0]();
                
call_user_func( [ $callable[0], $callable[1] ], $args$assoc_args );
            } else {
                
call_user_func$callable$args$assoc_args );
            }
        };

        return new 
Subcommand$parent$name$docparser$when_invoked );
    }

    
/**
     * Create a new Composite command instance.
     *
     * @param mixed $parent The new command's parent Root or Composite command
     * @param string $name Represents how the command should be invoked
     * @param mixed $callable
     */
    
private static function create_composite_command$parent$name$callable ) {
        
$reflection  = new ReflectionClass$callable );
        
$doc_comment self::get_doc_comment$reflection );
        if ( ! 
$doc_comment ) {
            
WP_CLI::debugnull === $doc_comment "Failed to get doc comment for {$name}." "No doc comment for {$name}."'commandfactory' );
        }
        
$docparser = new DocParser$doc_comment );

        
$container = new CompositeCommand$parent$name$docparser );

        foreach ( 
$reflection->getMethods() as $method ) {
            if ( ! 
self::is_good_method$method ) ) {
                continue;
            }

            
$class      is_object$callable ) ? $callable $reflection->name;
            
$subcommand self::create_subcommand$containerfalse, [ $class$method->name ], $method );

            
$subcommand_name $subcommand->get_name();

            
$container->add_subcommand$subcommand_name$subcommand );
        }

        return 
$container;
    }

    
/**
     * Create a new command namespace instance.
     *
     * @param mixed $parent The new namespace's parent Root or Composite command.
     * @param string $name Represents how the command should be invoked
     * @param mixed $callable
     */
    
private static function create_namespace$parent$name$callable ) {
        
$reflection  = new ReflectionClass$callable );
        
$doc_comment self::get_doc_comment$reflection );
        if ( ! 
$doc_comment ) {
            
WP_CLI::debugnull === $doc_comment "Failed to get doc comment for {$name}." "No doc comment for {$name}."'commandfactory' );
        }
        
$docparser = new DocParser$doc_comment );

        return new 
CommandNamespace$parent$name$docparser );
    }

    
/**
     * Check whether a method is actually callable.
     *
     * @param ReflectionMethod $method
     * @return bool
     */
    
private static function is_good_method$method ) {
        return 
$method->isPublic() && ! $method->isStatic() && !== strpos$method->getName(), '__' );
    }

    
/**
     * Gets the document comment. Caters for PHP directive `opcache.save comments` being disabled.
     *
     * @param ReflectionMethod|ReflectionClass|ReflectionFunction $reflection Reflection instance.
     * @return string|false|null Doc comment string if any, false if none (same as `Reflection*::getDocComment()`), null if error.
     */
    
private static function get_doc_comment$reflection ) {
        
$contents    null;
        
$doc_comment $reflection->getDocComment();

        if ( 
false !== $doc_comment || ! ( ini_get'opcache.enable_cli' ) && ! ini_get'opcache.save_comments' ) ) ) { // phpcs:ignore PHPCompatibility.IniDirectives.NewIniDirectives
            // Either have doc comment, or no doc comment and save comments enabled - standard situation.
            
if ( ! getenv'WP_CLI_TEST_GET_DOC_COMMENT' ) ) {
                return 
$doc_comment;
            }
        }

        
$filename $reflection->getFileName();

        if ( isset( 
self::$file_contents$filename ] ) ) {
            
$contents self::$file_contents$filename ];
        } elseif ( 
is_readable$filename ) ) {
            
$contents file_get_contents$filename );
            if ( 
is_string$contents ) && '' !== $contents ) {
                
$contents                         explode"\n"$contents );
                
self::$file_contents$filename ] = $contents;
            }
        }

        if ( ! empty( 
$contents ) ) {
            return 
self::extract_last_doc_commentimplode"\n"array_slice$contents0$reflection->getStartLine() ) ) );
        }

        
WP_CLI::debug"Could not read contents for filename '{$filename}'."'commandfactory' );
        return 
null;
    }

    
/**
     * Returns the last doc comment if any in `$content`.
     *
     * @param string $content The content, which should end at the class or function declaration.
     * @return string|bool The last doc comment if any, or false if none.
     */
    
private static function extract_last_doc_comment$content ) {
        
$content         trim$content );
        
$comment_end_pos strrpos$content'*/' );
        if ( 
false === $comment_end_pos ) {
            return 
false;
        }
        
// Make sure comment end belongs to this class/function.
        
if ( preg_match_all'/(?:^|[\s;}])(?:class|function)\s+/'substr$content$comment_end_pos ), $dummy /*needed for PHP 5.3*/ ) > ) {
            return 
false;
        }
        
$content           substr$content0$comment_end_pos );
        
$comment_start_pos strrpos$content'/**' );
        if ( 
false === $comment_start_pos || ( $comment_start_pos ) === $comment_end_pos ) {
            return 
false;
        }
        
// Make sure comment start belongs to this comment end.
        
$comment_end2_pos strpossubstr$content$comment_start_pos ), '*/' );
        if ( 
false !== $comment_end2_pos && ( $comment_start_pos $comment_end2_pos ) < $comment_end_pos ) {
            return 
false;
        }
        
// Allow for '/**' within doc comment.
        
$subcontent substr$content0$comment_start_pos );
        while ( 
false !== ( $comment_start2_pos strrpos$subcontent'/**' ) ) && false === strpos$subcontent'*/'$comment_start2_pos ) ) {
            
$comment_start_pos $comment_start2_pos;
            
$subcontent        substr$subcontent0$comment_start_pos );
        }
        return 
substr$content$comment_start_pos$comment_end_pos );
    }
}
<?
php

namespace WP_CLI\Dispatcher;

use 
WP_CLI;

/**
 * Adds a command namespace without actual functionality.
 *
 * This is meant to provide the means to attach meta information to a namespace
 * when there's no actual command needed.
 *
 * In case a real command gets registered for the same name, it replaces the
 * command namespace.
 *
 * @package WP_CLI
 */
class CommandNamespace extends CompositeCommand {

    
/**
     * Show the usage for all subcommands contained
     * by the composite command.
     */
    
public function show_usage() {
        
$methods $this->get_subcommands();

        
$i     0;
        
$count 0;

        foreach ( 
$methods as $subcommand ) {
            
$prefix = ( === $i ) ? 'usage: ' '   or: ';
            
$i++;

            if ( 
\WP_CLI::get_runner()->is_command_disabled$subcommand ) ) {
                continue;
            }

            
\WP_CLI::line$subcommand->get_usage$prefix ) );
            
$count++;
        }

        
$cmd_name implode' 'array_sliceget_path$this ), ) );
        
$message  $count 0
            
"See 'wp help $cmd_name <command>' for more information on a specific command."
            
"The namespace $cmd_name does not contain any usable commands in the current context.";

        
\WP_CLI::line();
        
\WP_CLI::line$message );

    }
}
<?
php

namespace WP_CLI\Dispatcher;

use 
WP_CLI;
use 
WP_CLI\DocParser;
use 
WP_CLI\Utils;

/**
 * A non-leaf node in the command tree.
 * Contains one or more Subcommands.
 *
 * @package WP_CLI
 */
class CompositeCommand {

    protected 
$name;
    protected 
$shortdesc;
    protected 
$synopsis;
    protected 
$docparser;

    protected 
$parent;
    protected 
$subcommands = [];

    
/**
     * Instantiate a new CompositeCommand
     *
     * @param mixed $parent Parent command (either Root or Composite)
     * @param string $name Represents how command should be invoked
     * @param DocParser $docparser
     */
    
public function __construct$parent$name$docparser ) {
        
$this->parent $parent;

        
$this->name $name;

        
$this->shortdesc $docparser->get_shortdesc();
        
$this->longdesc  $docparser->get_longdesc();
        
$this->docparser $docparser;

        
$when_to_invoke $docparser->get_tag'when' );
        if ( 
$when_to_invoke ) {
            
WP_CLI::get_runner()->register_early_invoke$when_to_invoke$this );
        }
    }

    
/**
     * Get the parent composite (or root) command
     *
     * @return mixed
     */
    
public function get_parent() {
        return 
$this->parent;
    }

    
/**
     * Add a named subcommand to this composite command's
     * set of contained subcommands.
     *
     * @param string                      $name     Represents how subcommand should be invoked
     * @param Subcommand|CompositeCommand $command  Cub-command to add.
     * @param bool                        $override Optional. Whether to override an existing subcommand of the same
     *                                              name.
     */
    
public function add_subcommand$name$command$override true ) {
        if ( 
$override || ! array_key_exists$name$this->subcommands ) ) {
            
$this->subcommands$name ] = $command;
        }
    }

    
/**
     * Remove a named subcommand from this composite command's set of contained
     * subcommands
     *
     * @param string $name Represents how subcommand should be invoked
     */
    
public function remove_subcommand$name ) {
        if ( isset( 
$this->subcommands$name ] ) ) {
            unset( 
$this->subcommands$name ] );
        }
    }


    
/**
     * Composite commands always contain subcommands.
     *
     * @return true
     */
    
public function can_have_subcommands() {
        return 
true;
    }

    
/**
     * Get the subcommands contained by this composite
     * command.
     *
     * @return array
     */
    
public function get_subcommands() {
        
ksort$this->subcommands );

        return 
$this->subcommands;
    }

    
/**
     * Get the name of this composite command.
     *
     * @return string
     */
    
public function get_name() {
        return 
$this->name;
    }

    
/**
     * Get the short description for this composite
     * command.
     *
     * @return string
     */
    
public function get_shortdesc() {
        return 
$this->shortdesc;
    }

    
/**
     * Set the short description for this composite command.
     *
     * @param string $shortdesc
     */
    
public function set_shortdesc$shortdesc ) {
        
$this->shortdesc Utils\normalize_eols$shortdesc );
    }

    
/**
     * Get the long description for this composite
     * command.
     *
     * @return string
     */
    
public function get_longdesc() {
        return 
$this->longdesc $this->get_global_params();
    }

    
/**
     * Set the long description for this composite command
     *
     * @param string $longdesc
     */
    
public function set_longdesc$longdesc ) {
        
$this->longdesc Utils\normalize_eols$longdesc );
    }

    
/**
     * Get the synopsis for this composite command.
     * As a collection of subcommands, the composite
     * command is only intended to invoke those
     * subcommands.
     *
     * @return string
     */
    
public function get_synopsis() {
        return 
'<command>';
    }

    
/**
     * Get the usage for this composite command.
     *
     * @return string
     */
    
public function get_usage$prefix ) {
        return 
sprintf(
            
'%s%s %s',
            
$prefix,
            
implode' 'get_path$this ) ),
            
$this->get_synopsis()
        );
    }

    
/**
     * Show the usage for all subcommands contained
     * by the composite command.
     */
    
public function show_usage() {
        
$methods $this->get_subcommands();

        
$i 0;

        foreach ( 
$methods as $subcommand ) {
            
$prefix = ( === $i ) ? 'usage: ' '   or: ';
            
$i++;

            if ( 
WP_CLI::get_runner()->is_command_disabled$subcommand ) ) {
                continue;
            }

            
WP_CLI::line$subcommand->get_usage$prefix ) );
        }

        
$cmd_name implode' 'array_sliceget_path$this ), ) );

        
WP_CLI::line();
        
WP_CLI::line"See 'wp help $cmd_name <command>' for more information on a specific command." );
    }

    
/**
     * When a composite command is invoked, it shows usage
     * docs for its subcommands.
     *
     * @param array $args
     * @param array $assoc_args
     * @param array $extra_args
     */
    
public function invoke$args$assoc_args$extra_args ) {
        
$this->show_usage();
    }

    
/**
     * Given supplied arguments, find a contained
     * subcommand
     *
     * @param array $args
     * @return Subcommand|false
     */
    
public function find_subcommand( &$args ) {
        
$name array_shift$args );

        
$subcommands $this->get_subcommands();

        if ( ! isset( 
$subcommands$name ] ) ) {
            
$aliases self::get_aliases$subcommands );

            if ( isset( 
$aliases$name ] ) ) {
                
$name $aliases$name ];
            }
        }

        if ( ! isset( 
$subcommands$name ] ) ) {
            return 
false;
        }

        return 
$subcommands$name ];
    }

    
/**
     * Get any registered aliases for this composite command's
     * subcommands.
     *
     * @param array $subcommands
     * @return array
     */
    
private static function get_aliases$subcommands ) {
        
$aliases = [];

        foreach ( 
$subcommands as $name => $subcommand ) {
            
$alias $subcommand->get_alias();
            if ( 
$alias ) {
                
$aliases$alias ] = $name;
            }
        }

        return 
$aliases;
    }

    
/**
     * Composite commands can only be known by one name.
     *
     * @return false
     */
    
public function get_alias() {
        return 
false;
    }

    
/***
     * Get the list of global parameters
     *
     * @param string $root_command whether to include or not root command specific description
     * @return string
     */
    
protected function get_global_params$root_command false ) {
        
$binding                 = [];
        
$binding['root_command'] = $root_command;

        if ( ! 
$this->can_have_subcommands() || ( is_object$this->parent ) && get_class$this->parent ) === 'WP_CLI\Dispatcher\CompositeCommand' ) ) {
            
$binding['is_subcommand'] = true;
        }

        foreach ( 
WP_CLI::get_configurator()->get_spec() as $key => $details ) {
            if ( 
false === $details['runtime'] ) {
                continue;
            }

            if ( isset( 
$details['deprecated'] ) ) {
                continue;
            }

            if ( isset( 
$details['hidden'] ) ) {
                continue;
            }

            if ( 
true === $details['runtime'] ) {
                
$synopsis "--[no-]$key";
            } else {
                
$synopsis "--$key$details['runtime'];
            }

            
// Check if global parameters synopsis should be displayed or not.
            
if ( 'true' !== getenv'WP_CLI_SUPPRESS_GLOBAL_PARAMS' ) ) {
                
$binding['parameters'][]   = [
                    
'synopsis' => $synopsis,
                    
'desc'     => $details['desc'],
                ];
                
$binding['has_parameters'] = true;
            }
        }

        if ( 
$this->get_subcommands() ) {
            
$binding['has_subcommands'] = true;
        }

        return 
Utils\mustache_render'man-params.mustache'$binding );
    }
}
<?
php

namespace WP_CLI\Dispatcher;

use 
WP_CLI\Utils;

/**
 * The root node in the command tree.
 *
 * @package WP_CLI
 */
class RootCommand extends CompositeCommand {

    public function 
__construct() {
        
$this->parent false;

        
$this->name 'wp';

        
$this->shortdesc 'Manage WordPress through the command-line.';
    }

    
/**
     * Get the human-readable long description.
     *
     * @return string
     */
    
public function get_longdesc() {
        return 
$this->get_global_paramstrue );
    }

    
/**
     * Find a subcommand registered on the root
     * command.
     *
     * @param array $args
     * @return Subcommand|false
     */
    
public function find_subcommand( &$args ) {
        
$command array_shift$args );

        
Utils\load_command$command );

        if ( ! isset( 
$this->subcommands$command ] ) ) {
            return 
false;
        }

        return 
$this->subcommands$command ];
    }
}
<?
php

namespace WP_CLI\Dispatcher;

use 
WP_CLI;
use 
WP_CLI\DocParser;
use 
WP_CLI\SynopsisParser;
use 
WP_CLI\SynopsisValidator;
use 
WP_CLI\Utils;

/**
 * A leaf node in the command tree.
 *
 * @package WP_CLI
 */
class Subcommand extends CompositeCommand {

    private 
$alias;

    private 
$when_invoked;

    public function 
__construct$parent$name$docparser$when_invoked ) {
        
parent::__construct$parent$name$docparser );

        
$this->when_invoked $when_invoked;

        
$this->alias $docparser->get_tag'alias' );

        
$this->synopsis $docparser->get_synopsis();
        if ( ! 
$this->synopsis && $this->longdesc ) {
            
$this->synopsis self::extract_synopsis$this->longdesc );
        }
    }

    
/**
     * Extract the synopsis from PHPdoc string.
     *
     * @param string $longdesc Command docs via PHPdoc
     * @return string
     */
    
private static function extract_synopsis$longdesc ) {
        
preg_match_all'/(.+?)[\r\n]+:/'$longdesc$matches );
        return 
implode' '$matches[1] );
    }

    
/**
     * Subcommands can't have subcommands because they
     * represent code to be executed.
     *
     * @return bool
     */
    
public function can_have_subcommands() {
        return 
false;
    }

    
/**
     * Get the synopsis string for this subcommand.
     * A synopsis defines what runtime arguments are
     * expected, useful to humans and argument validation.
     *
     * @return string
     */
    
public function get_synopsis() {
        return 
$this->synopsis;
    }

    
/**
     * Set the synopsis string for this subcommand.
     *
     * @param string $synopsis
     */
    
public function set_synopsis$synopsis ) {
        
$this->synopsis $synopsis;
    }

    
/**
     * If an alias is set, grant access to it.
     * Aliases permit subcommands to be instantiated
     * with a secondary identity.
     *
     * @return string
     */
    
public function get_alias() {
        return 
$this->alias;
    }

    
/**
     * Print the usage details to the end user.
     *
     * @param string $prefix
     */
    
public function show_usage$prefix 'usage: ' ) {
        
\WP_CLI::line$this->get_usage$prefix ) );
    }

    
/**
     * Get the usage of the subcommand as a formatted string.
     *
     * @param string $prefix
     * @return string
     */
    
public function get_usage$prefix ) {
        return 
sprintf(
            
'%s%s %s',
            
$prefix,
            
implode' 'get_path$this ) ),
            
$this->get_synopsis()
        );
    }

    
/**
     * Wrapper for CLI Tools' prompt() method.
     *
     * @param string $question
     * @param string $default
     * @return string|false
     */
    
private function prompt$question$default ) {

        
$question .= ': ';
        if ( 
function_exists'readline' ) ) {
            return 
readline$question );
        }

        echo 
$question;

        
$ret stream_get_lineSTDIN1024"\n" );
        if ( 
Utils\is_windows() && "\r" === substr$ret, -) ) {
            
$ret substr$ret0, -);
        }
        return 
$ret;
    }

    
/**
     * Interactively prompt the user for input
     * based on defined synopsis and passed arguments.
     *
     * @param array $args
     * @param array $assoc_args
     * @return array
     */
    
private function prompt_args$args$assoc_args ) {

        
$synopsis $this->get_synopsis();

        if ( ! 
$synopsis ) {
            return [ 
$args$assoc_args ];
        }

        
// To skip the already provided positional arguments, we need to count
        // how many we had already received.
        
$arg_index 0;

        
$spec array_filter(
            
SynopsisParser::parse$synopsis ),
            function( 
$spec_arg ) use ( $args$assoc_args, &$arg_index ) {
                switch ( 
$spec_arg['type'] ) {
                    case 
'positional':
                        
// Only prompt for the positional arguments that are not
                        // yet provided, based purely on number.
                        
return $arg_index++ >= count$args );
                    case 
'generic':
                        
// Always prompt for generic arguments.
                        
return true;
                    case 
'assoc':
                    case 
'flag':
                    default:
                        
// Prompt for the specific flags that were not provided
                        // yet, based on name.
                        
return ! isset( $assoc_args$spec_arg['name'] ] );
                }
            }
        );

        
$spec array_values$spec );

        
$prompt_args WP_CLI::get_config'prompt' );
        if ( 
true !== $prompt_args ) {
            
$prompt_args explode','$prompt_args );
        }

        
// 'positional' arguments are positional (aka zero-indexed)
        // so $args needs to be reset before prompting for new arguments
        
$args = [];

        foreach ( 
$spec as $key => $spec_arg ) {

            
// When prompting for specific arguments (e.g. --prompt=user_pass),
            // ignore all arguments that don't match.
            
if ( is_array$prompt_args ) ) {
                if ( 
'assoc' !== $spec_arg['type'] ) {
                    continue;
                }
                if ( ! 
in_array$spec_arg['name'], $prompt_argstrue ) ) {
                    continue;
                }
            }

            
$current_prompt = ( $key ) . '/' count$spec ) . ' ';
            
$default        $spec_arg['optional'] ? '' false;

            
// 'generic' permits arbitrary key=value (e.g. [--<field>=<value>] )
            
if ( 'generic' === $spec_arg['type'] ) {

                list( 
$key_token$value_token ) = explode'='$spec_arg['token'] );

                
$repeat false;
                do {
                    if ( ! 
$repeat ) {
                        
$key_prompt $current_prompt $key_token;
                    } else {
                        
$key_prompt str_repeat' 'strlen$current_prompt ) ) . $key_token;
                    }

                    
$key $this->prompt$key_prompt$default );
                    if ( 
false === $key ) {
                        return [ 
$args$assoc_args ];
                    }

                    if ( 
$key ) {
                        
$key_prompt_count strlen$key_prompt ) - strlen$value_token ) - 1;
                        
$value_prompt     str_repeat' '$key_prompt_count ) . '=' $value_token;

                        
$value $this->prompt$value_prompt$default );
                        if ( 
false === $value ) {
                            return [ 
$args$assoc_args ];
                        }

                        
$assoc_args$key ] = $value;

                        
$repeat true;
                    } else {
                        
$repeat false;
                    }
                } while ( 
$repeat );

            } else {
                
$prompt $current_prompt $spec_arg['token'];
                if ( 
'flag' === $spec_arg['type'] ) {
                    
$prompt .= ' (Y/n)';
                }

                
$response $this->prompt$prompt$default );
                if ( 
false === $response ) {
                    return [ 
$args$assoc_args ];
                }

                if ( 
$response ) {
                    switch ( 
$spec_arg['type'] ) {
                        case 
'positional':
                            if ( 
$spec_arg['repeating'] ) {
                                
$response explode' '$response );
                            } else {
                                
$response = [ $response ];
                            }
                            
$args array_merge$args$response );
                            break;
                        case 
'assoc':
                            
$assoc_args$spec_arg['name'] ] = $response;
                            break;
                        case 
'flag':
                            if ( 
'Y' === strtoupper$response ) ) {
                                
$assoc_args$spec_arg['name'] ] = true;
                            }
                            break;
                    }
                }
            }
        }

        return [ 
$args$assoc_args ];
    }

    
/**
     * Validate the supplied arguments to the command.
     * Throws warnings or errors if arguments are missing
     * or invalid.
     *
     * @param array $args
     * @param array $assoc_args
     * @param array $extra_args
     * @return array list of invalid $assoc_args keys to unset
     */
    
private function validate_args$args$assoc_args$extra_args ) {
        
$synopsis $this->get_synopsis();
        if ( ! 
$synopsis ) {
            return [ [], 
$args$assoc_args$extra_args ];
        }

        
$validator = new SynopsisValidator$synopsis );

        
$cmd_path implode' 'get_path$this ) );
        foreach ( 
$validator->get_unknown() as $token ) {
            
\WP_CLI::warning(
                
sprintf(
                    
'The `%s` command has an invalid synopsis part: %s',
                    
$cmd_path,
                    
$token
                
)
            );
        }

        if ( ! 
$validator->enough_positionals$args ) ) {
            
$this->show_usage();
            exit( 
);
        }

        
$unknown_positionals $validator->unknown_positionals$args );
        if ( ! empty( 
$unknown_positionals ) ) {
            
\WP_CLI::error(
                
'Too many positional arguments: ' .
                
implode' '$unknown_positionals )
            );
        }

        
$synopsis_spec SynopsisParser::parse$synopsis );
        
$i             0;
        
$errors        = [
            
'fatal'   => [],
            
'warning' => [],
        ];
        
$mock_doc      = [ $this->get_shortdesc(), '' ];
        
$mock_doc      array_merge$mock_docexplode"\n"$this->get_longdesc() ) );
        
$mock_doc      '/**' PHP_EOL '* ' implodePHP_EOL '* '$mock_doc ) . PHP_EOL '*/';
        
$docparser     = new DocParser$mock_doc );
        foreach ( 
$synopsis_spec as $spec ) {
            if ( 
'positional' === $spec['type'] ) {
                
$spec_args $docparser->get_arg_args$spec['name'] );
                if ( ! isset( 
$args$i ] ) ) {
                    if ( isset( 
$spec_args['default'] ) ) {
                        
$args$i ] = $spec_args['default'];
                    }
                }
                if ( isset( 
$spec_args['options'] ) ) {
                    if ( ! empty( 
$spec['repeating'] ) ) {
                        do {
                            
// phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict -- This is a loose comparison by design.
                            
if ( isset( $args$i ] ) && ! in_array$args$i ], $spec_args['options'] ) ) {
                                
\WP_CLI::error'Invalid value specified for positional arg.' );
                            }
                            
$i++;
                        } while ( isset( 
$args$i ] ) );
                    } else {
                        
// phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict -- This is a loose comparison by design.
                        
if ( isset( $args$i ] ) && ! in_array$args$i ], $spec_args['options'] ) ) {
                            
\WP_CLI::error'Invalid value specified for positional arg.' );
                        }
                    }
                }
                
$i++;
            } elseif ( 
'assoc' === $spec['type'] ) {
                
$spec_args $docparser->get_param_args$spec['name'] );
                if ( ! isset( 
$assoc_args$spec['name'] ] ) && ! isset( $extra_args$spec['name'] ] ) ) {
                    if ( isset( 
$spec_args['default'] ) ) {
                        
$assoc_args$spec['name'] ] = $spec_args['default'];
                    }
                }
                if ( isset( 
$assoc_args$spec['name'] ] ) && isset( $spec_args['options'] ) ) {
                    
$value   $assoc_args$spec['name'] ];
                    
$options $spec_args['options'];
                    
// phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict -- This is a loose comparison by design.
                    
if ( ! in_array$value$options ) ) {
                        
// Try whether it might be a comma-separated list of multiple values.
                        
$values array_map'trim'explode','$value ) );
                        
$count  count$values );
                        if (
                            
$count 1
                            
&&
                            
count(
                                
array_filter(
                                    
$values,
                                    static function ( 
$value ) use ( $options ) {
                                        return 
in_array$value$optionstrue );
                                    }
                                )
                            ) === 
$count
                        
) {
                            continue;
                        }
                        
$errors['fatal'][ $spec['name'] ] = "Invalid value specified for '{$spec['name']}'";
                    }
                }
            }
        }

        list( 
$returned_errors$to_unset ) = $validator->validate_assoc(
            
array_merge\WP_CLI::get_config(), $extra_args$assoc_args )
        );
        foreach ( [ 
'fatal''warning' ] as $error_type ) {
            
$errors$error_type ] = array_merge$errors$error_type ], $returned_errors$error_type ] );
        }

        if ( 
'help' !== $this->name ) {
            foreach ( 
$validator->unknown_assoc$assoc_args ) as $key ) {
                
$suggestion    Utils\get_suggestion(
                    
$key,
                    
$this->get_parameters$synopsis_spec ),
                    
$threshold 2
                
);

                
$errors['fatal'][] = sprintf(
                    
'unknown --%s parameter%s',
                    
$key,
                    ! empty( 
$suggestion ) ? PHP_EOL "Did you mean '--{$suggestion}'?" ''
                
);
            }
        }

        if ( ! empty( 
$errors['fatal'] ) ) {
            
$out 'Parameter errors:';
            foreach ( 
$errors['fatal'] as $key => $error ) {
                
$out .= "\n {$error}";
                
$desc $docparser->get_param_desc$key );
                if ( 
'' !== $desc ) {
                    
$out .= " ({$desc})";
                }
            }

            
\WP_CLI::error$out );
        }

        
array_map'\\WP_CLI::warning'$errors['warning'] );

        return [ 
$to_unset$args$assoc_args$extra_args ];
    }

    
/**
     * Invoke the subcommand with the supplied arguments.
     * Given a --prompt argument, interactively request input
     * from the end user.
     *
     * @param array $args
     * @param array $assoc_args
     */
    
public function invoke$args$assoc_args$extra_args ) {
        static 
$prompted_once false;

        if ( 
'help' !== $this->name ) {
            if ( 
\WP_CLI::get_config'prompt' ) && ! $prompted_once ) {
                list( 
$_args$assoc_args ) = $this->prompt_args$args$assoc_args );
                
$args                       array_merge$args$_args );
                
$prompted_once              true;
            }
        }

        
$extra_positionals = [];
        foreach ( 
$extra_args as $k => $v ) {
            if ( 
is_numeric$k ) ) {
                if ( ! isset( 
$args$k ] ) ) {
                    
$extra_positionals$k ] = $v;
                }
                unset( 
$extra_args$k ] );
            }
        }
        
$args += $extra_positionals;

        list( 
$to_unset$args$assoc_args$extra_args ) = $this->validate_args$args$assoc_args$extra_args );

        foreach ( 
$to_unset as $key ) {
            unset( 
$assoc_args$key ] );
        }

        
$path   get_path$this->get_parent() );
        
$parent implode' 'array_slice$path) );
        
$cmd    $this->name;
        if ( 
$parent ) {
            
WP_CLI::do_hook"before_invoke:{$parent});
            
$cmd $parent ' ' $cmd;
        }
        
WP_CLI::do_hook"before_invoke:{$cmd});

        
// Check if `--prompt` arg passed or not.
        
if ( $prompted_once ) {
            
// Unset empty args.
            
$actual_args $assoc_args;
            foreach ( 
$actual_args as $key ) {
                if ( empty( 
$actual_args$key ] ) ) {
                    unset( 
$actual_args$key ] );
                }
            }

            
WP_CLI::log(
                
sprintf(
                    
'wp %s %s',
                    
$cmd,
                    
ltrimUtils\assoc_args_to_str$actual_args ), ' ' )
                )
            );
        }

        
call_user_func$this->when_invoked$argsarray_merge$extra_args$assoc_args ) );

        if ( 
$parent ) {
            
WP_CLI::do_hook"after_invoke:{$parent});
        }
        
WP_CLI::do_hook"after_invoke:{$cmd});
    }

    
/**
     * Get an array of parameter names, by merging the command-specific and the
     * global parameters.
     *
     * @param array  $spec Optional. Specification of the current command.
     *
     * @return array Array of parameter names
     */
    
private function get_parameters$spec = [] ) {
        
$local_parameters  array_column$spec'name' );
        
$global_parameters array_column(
            
SynopsisParser::parse$this->get_global_params() ),
            
'name'
        
);

        return 
array_uniquearray_merge$local_parameters$global_parameters ) );
    }
}
<?
php

namespace WP_CLI\Dispatcher;

use 
WP_CLI;

/**
 * Controls whether adding of a command should be completed or not.
 *
 * This is needed because we can't reliably pass scalar values by reference
 * through the hooks mechanism. An object is always passed by reference.
 *
 * @package WP_CLI
 */
final class CommandAddition {

    
/**
     * Whether the command addition was aborted or not.
     *
     * @var bool
     */
    
private $abort false;

    
/**
     * Reason for which the addition was aborted.
     *
     * @var string
     */
    
private $reason '';

    
/**
     * Abort the current command addition.
     *
     * @param string $reason Reason as to why the addition was aborted.
     */
    
public function abort$reason '' ) {
        
$this->abort  true;
        
$this->reason = (string) $reason;
    }

    
/**
     * Check whether the command addition was aborted.
     *
     * @return bool
     */
    
public function was_aborted() {
        return 
$this->abort;
    }

    
/**
     * Get the reason as to why the addition was aborted.
     *
     * @return string
     */
    
public function get_reason() {
        return 
$this->reason;
    }
}
<?
php

namespace WP_CLI;

/**
 * Escape route for not doing anything.
 */
final class NoOp {

    public function 
__set$key$value ) {
        
// do nothing
    
}

    public function 
__call$method$args ) {
        
// do nothing
    
}
}

<?
php
/*
 * 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.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the MIT license. For more information, see
 * <https://www.doctrine-project.org>.
 */

namespace WP_CLI;

/**
 * Doctrine inflector has static methods for inflecting text.
 *
 * The methods in these classes are from several different sources collected
 * across several different php projects and several different authors. The
 * original author names and emails are not known.
 *
 * Pluralize & Singularize implementation are borrowed from CakePHP with some modifications.
 *
 * @link   www.doctrine-project.org
 * @since  1.0
 * @author Konsta Vesterinen <[email protected]>
 * @author Jonathan H. Wage <[email protected]>
 */
class Inflector {

    
/**
     * Plural inflector rules.
     *
     * @var array
     */
    
private static $plural = [
        
'rules'       => [
            
'/(s)tatus$/i'                         => '\1\2tatuses',
            
'/(quiz)$/i'                           => '\1zes',
            
'/^(ox)$/i'                            => '\1\2en',
            
'/([m|l])ouse$/i'                      => '\1ice',
            
'/(matr|vert|ind)(ix|ex)$/i'           => '\1ices',
            
'/(x|ch|ss|sh)$/i'                     => '\1es',
            
'/([^aeiouy]|qu)y$/i'                  => '\1ies',
            
'/(hive)$/i'                           => '\1s',
            
'/(?:([^f])fe|([lr])f)$/i'             => '\1\2ves',
            
'/sis$/i'                              => 'ses',
            
'/([ti])um$/i'                         => '\1a',
            
'/(p)erson$/i'                         => '\1eople',
            
'/(m)an$/i'                            => '\1en',
            
'/(c)hild$/i'                          => '\1hildren',
            
'/(f)oot$/i'                           => '\1eet',
            
'/(buffal|her|potat|tomat|volcan)o$/i' => '\1\2oes',
            
'/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$/i' => '\1i',
            
'/us$/i'                               => 'uses',
            
'/(alias)$/i'                          => '\1es',
            
'/(analys|ax|cris|test|thes)is$/i'     => '\1es',
            
'/s$/'                                 => 's',
            
'/^$/'                                 => '',
            
'/$/'                                  => 's',
        ],
        
'uninflected' => [
            
'.*[nrlm]ese',
            
'.*deer',
            
'.*fish',
            
'.*measles',
            
'.*ois',
            
'.*pox',
            
'.*sheep',
            
'people',
            
'cookie',
        ],
        
'irregular'   => [
            
'atlas'        => 'atlases',
            
'axe'          => 'axes',
            
'beef'         => 'beefs',
            
'brother'      => 'brothers',
            
'cafe'         => 'cafes',
            
'chateau'      => 'chateaux',
            
'child'        => 'children',
            
'cookie'       => 'cookies',
            
'corpus'       => 'corpuses',
            
'cow'          => 'cows',
            
'criterion'    => 'criteria',
            
'curriculum'   => 'curricula',
            
'demo'         => 'demos',
            
'domino'       => 'dominoes',
            
'echo'         => 'echoes',
            
'foot'         => 'feet',
            
'fungus'       => 'fungi',
            
'ganglion'     => 'ganglions',
            
'genie'        => 'genies',
            
'genus'        => 'genera',
            
'graffito'     => 'graffiti',
            
'hippopotamus' => 'hippopotami',
            
'hoof'         => 'hoofs',
            
'human'        => 'humans',
            
'iris'         => 'irises',
            
'leaf'         => 'leaves',
            
'loaf'         => 'loaves',
            
'man'          => 'men',
            
'medium'       => 'media',
            
'memorandum'   => 'memoranda',
            
'money'        => 'monies',
            
'mongoose'     => 'mongooses',
            
'motto'        => 'mottoes',
            
'move'         => 'moves',
            
'mythos'       => 'mythoi',
            
'niche'        => 'niches',
            
'nucleus'      => 'nuclei',
            
'numen'        => 'numina',
            
'occiput'      => 'occiputs',
            
'octopus'      => 'octopuses',
            
'opus'         => 'opuses',
            
'ox'           => 'oxen',
            
'penis'        => 'penises',
            
'person'       => 'people',
            
'plateau'      => 'plateaux',
            
'runner-up'    => 'runners-up',
            
'sex'          => 'sexes',
            
'soliloquy'    => 'soliloquies',
            
'son-in-law'   => 'sons-in-law',
            
'syllabus'     => 'syllabi',
            
'testis'       => 'testes',
            
'thief'        => 'thieves',
            
'tooth'        => 'teeth',
            
'tornado'      => 'tornadoes',
            
'trilby'       => 'trilbys',
            
'turf'         => 'turfs',
            
'volcano'      => 'volcanoes',
        ],
    ];

    
/**
     * Singular inflector rules.
     *
     * @var array
     */
    
private static $singular = [
        
'rules'       => [
            
'/(s)tatuses$/i'                         => '\1\2tatus',
            
'/^(.*)(menu)s$/i'                       => '\1\2',
            
'/(quiz)zes$/i'                          => '\\1',
            
'/(matr)ices$/i'                         => '\1ix',
            
'/(vert|ind)ices$/i'                     => '\1ex',
            
'/^(ox)en/i'                             => '\1',
            
'/(alias)(es)*$/i'                       => '\1',
            
'/(buffal|her|potat|tomat|volcan)oes$/i' => '\1o',
            
'/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us',
            
'/([ftw]ax)es/i'                         => '\1',
            
'/(analys|ax|cris|test|thes)es$/i'       => '\1is',
            
'/(shoe|slave)s$/i'                      => '\1',
            
'/(o)es$/i'                              => '\1',
            
'/ouses$/'                               => 'ouse',
            
'/([^a])uses$/'                          => '\1us',
            
'/([m|l])ice$/i'                         => '\1ouse',
            
'/(x|ch|ss|sh)es$/i'                     => '\1',
            
'/(m)ovies$/i'                           => '\1\2ovie',
            
'/(s)eries$/i'                           => '\1\2eries',
            
'/([^aeiouy]|qu)ies$/i'                  => '\1y',
            
'/([lr])ves$/i'                          => '\1f',
            
'/(tive)s$/i'                            => '\1',
            
'/(hive)s$/i'                            => '\1',
            
'/(drive)s$/i'                           => '\1',
            
'/([^fo])ves$/i'                         => '\1fe',
            
'/(^analy)ses$/i'                        => '\1sis',
            
'/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis',
            
'/([ti])a$/i'                            => '\1um',
            
'/(p)eople$/i'                           => '\1\2erson',
            
'/(m)en$/i'                              => '\1an',
            
'/(c)hildren$/i'                         => '\1\2hild',
            
'/(f)eet$/i'                             => '\1oot',
            
'/(n)ews$/i'                             => '\1\2ews',
            
'/eaus$/'                                => 'eau',
            
'/^(.*us)$/'                             => '\\1',
            
'/s$/i'                                  => '',
        ],
        
'uninflected' => [
            
'.*[nrlm]ese',
            
'.*deer',
            
'.*fish',
            
'.*measles',
            
'.*ois',
            
'.*pox',
            
'.*sheep',
            
'.*ss',
        ],
        
'irregular'   => [
            
'criteria' => 'criterion',
            
'curves'   => 'curve',
            
'emphases' => 'emphasis',
            
'foes'     => 'foe',
            
'hoaxes'   => 'hoax',
            
'media'    => 'medium',
            
'neuroses' => 'neurosis',
            
'waves'    => 'wave',
            
'oases'    => 'oasis',
        ],
    ];

    
/**
     * Words that should not be inflected.
     *
     * @var array
     */
    
private static $uninflected = [
        
'Amoyese',
        
'bison',
        
'Borghese',
        
'bream',
        
'breeches',
        
'britches',
        
'buffalo',
        
'cantus',
        
'carp',
        
'chassis',
        
'clippers',
        
'cod',
        
'coitus',
        
'Congoese',
        
'contretemps',
        
'corps',
        
'debris',
        
'diabetes',
        
'djinn',
        
'eland',
        
'elk',
        
'equipment',
        
'Faroese',
        
'flounder',
        
'Foochowese',
        
'gallows',
        
'Genevese',
        
'Genoese',
        
'Gilbertese',
        
'graffiti',
        
'headquarters',
        
'herpes',
        
'hijinks',
        
'Hottentotese',
        
'information',
        
'innings',
        
'jackanapes',
        
'Kiplingese',
        
'Kongoese',
        
'Lucchese',
        
'mackerel',
        
'Maltese',
        
'.*?media',
        
'mews',
        
'moose',
        
'mumps',
        
'Nankingese',
        
'news',
        
'nexus',
        
'Niasese',
        
'Pekingese',
        
'Piedmontese',
        
'pincers',
        
'Pistoiese',
        
'pliers',
        
'Portuguese',
        
'proceedings',
        
'rabies',
        
'rice',
        
'rhinoceros',
        
'salmon',
        
'Sarawakese',
        
'scissors',
        
'sea[- ]bass',
        
'series',
        
'Shavese',
        
'shears',
        
'siemens',
        
'species',
        
'staff',
        
'swine',
        
'testes',
        
'trousers',
        
'trout',
        
'tuna',
        
'Vermontese',
        
'Wenchowese',
        
'whiting',
        
'wildebeest',
        
'Yengeese',
    ];

    
/**
     * Method cache array.
     *
     * @var array
     */
    
private static $cache = [];

    
/**
     * The initial state of Inflector so reset() works.
     *
     * @var array
     */
    
private static $initial_state = [];

    
/**
     * Converts a word into the format for a Doctrine table name. Converts 'ModelName' to 'model_name'.
     *
     * @param string $word The word to tableize.
     *
     * @return string The tableized word.
     */
    
public static function tableize$word ) {
        return 
strtolowerpreg_replace'~(?<=\\w)([A-Z])~''_$1'$word ) );
    }

    
/**
     * Converts a word into the format for a Doctrine class name. Converts 'table_name' to 'TableName'.
     *
     * @param string $word The word to classify.
     *
     * @return string The classified word.
     */
    
public static function classify$word ) {
        return 
str_replace' '''ucwordsstrtr$word'_-''  ' ) ) );
    }

    
/**
     * Camelizes a word. This uses the classify() method and turns the first character to lowercase.
     *
     * @param string $word The word to camelize.
     *
     * @return string The camelized word.
     */
    
public static function camelize$word ) {
        return 
lcfirstself::classify$word ) );
    }

    
/**
     * Uppercases words with configurable delimeters between words.
     *
     * Takes a string and capitalizes all of the words, like PHP's built-in
     * ucwords function.  This extends that behavior, however, by allowing the
     * word delimeters to be configured, rather than only separating on
     * whitespace.
     *
     * Here is an example:
     * <code>
     * <?php
     * $string = 'top-o-the-morning to all_of_you!';
     * echo \Doctrine\Common\Inflector\Inflector::ucwords($string);
     * // Top-O-The-Morning To All_of_you!
     *
     * echo \Doctrine\Common\Inflector\Inflector::ucwords($string, '-_ ');
     * // Top-O-The-Morning To All_Of_You!
     * ?>
     * </code>
     *
     * @param string $string The string to operate on.
     * @param string $delimiters A list of word separators.
     *
     * @return string The string with all delimeter-separated words capitalized.
     */
    
public static function ucwords$string$delimiters " \n\t\r\0\x0B-" ) {
        return 
preg_replace_callback(
            
'/[^' preg_quote$delimiters'/' ) . ']+/',
            function( 
$matches ) {
                return 
ucfirst$matches[0] );
            },
            
$string
        
);
    }

    
/**
     * Clears Inflectors inflected value caches, and resets the inflection
     * rules to the initial values.
     *
     * @return void
     */
    
public static function reset() {
        if ( empty( 
self::$initial_state ) ) {
            
self::$initial_state get_class_vars'Inflector' );

            return;
        }

        foreach ( 
self::$initial_state as $key => $val ) {
            if ( 
'initial_state' !== $key ) {
                
self::${$key} = $val;
            }
        }
    }

    
/**
     * Adds custom inflection $rules, of either 'plural' or 'singular' $type.
     *
     * ### Usage:
     *
     * {{{
     * Inflector::rules('plural', array('/^(inflect)or$/i' => '\1ables'));
     * Inflector::rules('plural', array(
     *     'rules' => array('/^(inflect)ors$/i' => '\1ables'),
     *     'uninflected' => array('dontinflectme'),
     *     'irregular' => array('red' => 'redlings')
     * ));
     * }}}
     *
     * @param string  $type  The type of inflection, either 'plural' or 'singular'
     * @param array   $rules An array of rules to be added.
     * @param boolean $reset If true, will unset default inflections for all
     *                       new rules that are being defined in $rules.
     *
     * @return void
     */
    
public static function rules$type$rules$reset false ) {
        foreach ( 
$rules as $rule => $pattern ) {
            if ( ! 
is_array$pattern ) ) {
                continue;
            }

            if ( 
$reset ) {
                
self::${$type}[ $rule ] = $pattern;
            } else {
                
self::${$type}[ $rule ] = ( 'uninflected' === $rule )
                    ? 
array_merge$patternself::${$type}[ $rule ] )
                    : 
$pattern self::${$type}[ $rule ];
            }

            unset( 
$rules$rule ], self::${$type}[ 'cache' ucfirst$rule ) ] );

            if ( isset( 
self::${$type}['merged'][ $rule ] ) ) {
                unset( 
self::${$type}['merged'][ $rule ] );
            }

            if ( 
'plural' === $type ) {
                
self::$cache['pluralize'] = [];
                
self::$cache['tableize']  = [];
            } elseif ( 
'singular' === $type ) {
                
self::$cache['singularize'] = [];
            }
        }

        
self::${$type}['rules'] = $rules self::${$type}['rules'];
    }

    
/**
     * Returns a word in plural form.
     *
     * @param string $word The word in singular form.
     *
     * @return string The word in plural form.
     */
    
public static function pluralize$word ) {
        if ( isset( 
self::$cache['pluralize'][ $word ] ) ) {
            return 
self::$cache['pluralize'][ $word ];
        }

        if ( ! isset( 
self::$plural['merged']['irregular'] ) ) {
            
self::$plural['merged']['irregular'] = self::$plural['irregular'];
        }

        if ( ! isset( 
self::$plural['merged']['uninflected'] ) ) {
            
self::$plural['merged']['uninflected'] = array_mergeself::$plural['uninflected'], self::$uninflected );
        }

        if ( ! isset( 
self::$plural['cacheUninflected'] ) || ! isset( self::$plural['cacheIrregular'] ) ) {
            
self::$plural['cacheUninflected'] = '(?:' implode'|'self::$plural['merged']['uninflected'] ) . ')';
            
self::$plural['cacheIrregular']   = '(?:' implode'|'array_keysself::$plural['merged']['irregular'] ) ) . ')';
        }

        if ( 
preg_match'/(.*)\\b(' self::$plural['cacheIrregular'] . ')$/i'$word$regs ) ) {
            
self::$cache['pluralize'][ $word ] = $regs[1] . substr$word0) . substrself::$plural['merged']['irregular'][ strtolower$regs[2] ) ], );

            return 
self::$cache['pluralize'][ $word ];
        }

        if ( 
preg_match'/^(' self::$plural['cacheUninflected'] . ')$/i'$word$regs ) ) {
            
self::$cache['pluralize'][ $word ] = $word;

            return 
$word;
        }

        foreach ( 
self::$plural['rules'] as $rule => $replacement ) {
            if ( 
preg_match$rule$word ) ) {
                
self::$cache['pluralize'][ $word ] = preg_replace$rule$replacement$word );

                return 
self::$cache['pluralize'][ $word ];
            }
        }
    }

    
/**
     * Returns a word in singular form.
     *
     * @param string $word The word in plural form.
     *
     * @return string The word in singular form.
     */
    
public static function singularize$word ) {
        if ( isset( 
self::$cache['singularize'][ $word ] ) ) {
            return 
self::$cache['singularize'][ $word ];
        }

        if ( ! isset( 
self::$singular['merged']['uninflected'] ) ) {
            
self::$singular['merged']['uninflected'] = array_merge(
                
self::$singular['uninflected'],
                
self::$uninflected
            
);
        }

        if ( ! isset( 
self::$singular['merged']['irregular'] ) ) {
            
self::$singular['merged']['irregular'] = array_merge(
                
self::$singular['irregular'],
                
array_flipself::$plural['irregular'] )
            );
        }

        if ( ! isset( 
self::$singular['cacheUninflected'] ) || ! isset( self::$singular['cacheIrregular'] ) ) {
            
self::$singular['cacheUninflected'] = '(?:' join'|'self::$singular['merged']['uninflected'] ) . ')';
            
self::$singular['cacheIrregular']   = '(?:' join'|'array_keysself::$singular['merged']['irregular'] ) ) . ')';
        }

        if ( 
preg_match'/(.*)\\b(' self::$singular['cacheIrregular'] . ')$/i'$word$regs ) ) {
            
self::$cache['singularize'][ $word ] = $regs[1] . substr$word0) . substrself::$singular['merged']['irregular'][ strtolower$regs[2] ) ], );

            return 
self::$cache['singularize'][ $word ];
        }

        if ( 
preg_match'/^(' self::$singular['cacheUninflected'] . ')$/i'$word$regs ) ) {
            
self::$cache['singularize'][ $word ] = $word;

            return 
$word;
        }

        foreach ( 
self::$singular['rules'] as $rule => $replacement ) {
            if ( 
preg_match$rule$word ) ) {
                
self::$cache['singularize'][ $word ] = preg_replace$rule$replacement$word );

                return 
self::$cache['singularize'][ $word ];
            }
        }

        
self::$cache['singularize'][ $word ] = $word;

        return 
$word;
    }
}
<?
php

namespace WP_CLI;

use 
RuntimeException;

/**
 * Run a system process, and learn what happened.
 */
class Process {
    
/**
     * @var string The full command to execute by the system.
     */
    
private $command;

    
/**
     * @var string|null The path of the working directory for the process or NULL if not specified (defaults to current working directory).
     */
    
private $cwd;

    
/**
     * @var array Environment variables to set when running the command.
     */
    
private $env;

    
/**
     * @var array Descriptor spec for `proc_open()`.
     */
    
private static $descriptors = [
        
=> STDIN,
        
=> [ 'pipe''w' ],
        
=> [ 'pipe''w' ],
    ];

    
/**
     * @var bool Whether to log run time info or not.
     */
    
public static $log_run_times false;

    
/**
     * @var array Array of process run time info, keyed by process command, each a 2-element array containing run time and run count.
     */
    
public static $run_times = [];

    
/**
     * @param string $command Command to execute.
     * @param string $cwd Directory to execute the command in.
     * @param array $env Environment variables to set when running the command.
     *
     * @return Process
     */
    
public static function create$command$cwd null$env = [] ) {
        
$proc = new self();

        
$proc->command $command;
        
$proc->cwd     $cwd;
        
$proc->env     $env;

        return 
$proc;
    }

    private function 
__construct() {}

    
/**
     * Run the command.
     *
     * @return ProcessRun
     */
    
public function run() {
        
$start_time microtimetrue );

        
$pipes = [];
        
$proc  Utils\proc_open_compat$this->commandself::$descriptors$pipes$this->cwd$this->env );

        
$stdout stream_get_contents$pipes[1] );
        
fclose$pipes[1] );

        
$stderr stream_get_contents$pipes[2] );
        
fclose$pipes[2] );

        
$return_code proc_close$proc );

        
$run_time microtimetrue ) - $start_time;

        if ( 
self::$log_run_times ) {
            if ( ! isset( 
self::$run_times$this->command ] ) ) {
                
self::$run_times$this->command ] = [ 0];
            }
            
self::$run_times$this->command ][0] += $run_time;
            
self::$run_times$this->command ][1]++;
        }

        return new 
ProcessRun(
            [
                
'stdout'      => $stdout,
                
'stderr'      => $stderr,
                
'return_code' => $return_code,
                
'command'     => $this->command,
                
'cwd'         => $this->cwd,
                
'env'         => $this->env,
                
'run_time'    => $run_time,
            ]
        );
    }

    
/**
     * Run the command, but throw an Exception on error.
     *
     * @return ProcessRun
     */
    
public function run_check() {
        
$r $this->run();

        if ( 
$r->return_code ) {
            throw new 
RuntimeException$r );
        }

        return 
$r;
    }

    
/**
     * Run the command, but throw an Exception on error.
     * Same as `run_check()` above, but checks the correct stderr.
     *
     * @return ProcessRun
     */
    
public function run_check_stderr() {
        
$r $this->run();

        if ( 
$r->return_code || ! empty( $r->stderr ) ) {
            throw new 
RuntimeException$r );
        }

        return 
$r;
    }
}
<?
php

/*
 * This file is heavily inspired and use code from Composer(getcomposer.org),
 * in particular Composer/Cache and Composer/Util/FileSystem from 1.0.0-alpha7
 *
 * The original code and this file are both released under MIT license.
 *
 * The copyright holders of the original code are:
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 */

namespace WP_CLI;

use 
DateTime;
use 
Exception;
use 
Symfony\Component\Finder\Finder;
use 
WP_CLI;

/**
 * Reads/writes to a filesystem cache
 */
class FileCache {

    
/**
     * @var string cache path
     */
    
protected $root;
    
/**
     * @var bool
     */
    
protected $enabled true;
    
/**
     * @var int files time to live
     */
    
protected $ttl;
    
/**
     * @var int max total size
     */
    
protected $max_size;
    
/**
     * @var string key allowed chars (regex class)
     */
    
protected $whitelist;

    
/**
     * @param string $cache_dir  location of the cache
     * @param int    $ttl        cache files default time to live (expiration)
     * @param int    $max_size   max total cache size
     * @param string $whitelist  List of characters that are allowed in path names (used in a regex character class)
     */
    
public function __construct$cache_dir$ttl$max_size$whitelist 'a-z0-9._-' ) {
        
$this->root      Utils\trailingslashit$cache_dir );
        
$this->ttl       = (int) $ttl;
        
$this->max_size  = (int) $max_size;
        
$this->whitelist $whitelist;

        if ( ! 
$this->ensure_dir_exists$this->root ) ) {
            
$this->enabled false;
        }

    }

    
/**
     * Cache is enabled
     *
     * @return bool
     */
    
public function is_enabled() {
        return 
$this->enabled;
    }

    
/**
     * Cache root
     *
     * @return string
     */
    
public function get_root() {
        return 
$this->root;
    }


    
/**
     * Check if a file is in cache and return its filename
     *
     * @param string $key cache key
     * @param int    $ttl time to live
     * @return bool|string filename or false
     */
    
public function has$key$ttl null ) {
        if ( ! 
$this->enabled ) {
            return 
false;
        }

        
$filename $this->filename$key );

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

        
// Use ttl param or global ttl.
        
if ( null === $ttl ) {
            
$ttl $this->ttl;
        } elseif ( 
$this->ttl ) {
            
$ttl min( (int) $ttl$this->ttl );
        } else {
            
$ttl = (int) $ttl;
        }

        
//
        
if ( $ttl && ( filemtime$filename ) + $ttl ) < time() ) {
            if ( 
$this->ttl && $ttl >= $this->ttl ) {
                
unlink$filename );
            }
            return 
false;
        }

        return 
$filename;
    }

    
/**
     * Write to cache file
     *
     * @param string $key      cache key
     * @param string $contents file contents
     * @return bool
     */
    
public function write$key$contents ) {
        
$filename $this->prepare_write$key );

        if ( 
$filename ) {
            return 
file_put_contents$filename$contents ) && touch$filename );
        }

        return 
false;
    }

    
/**
     * Read from cache file
     *
     * @param string $key cache key
     * @param int    $ttl time to live
     * @return bool|string file contents or false
     */
    
public function read$key$ttl null ) {
        
$filename $this->has$key$ttl );

        if ( 
$filename ) {
            return 
file_get_contents$filename );
        }

        return 
false;
    }

    
/**
     * Copy a file into the cache
     *
     * @param string $key    cache key
     * @param string $source source filename
     * @return bool
     */
    
public function import$key$source ) {
        
$filename $this->prepare_write$key );

        if ( 
$filename ) {
            return 
copy$source$filename ) && touch$filename );
        }

        return 
false;
    }

    
/**
     * Copy a file out of the cache
     *
     * @param string $key    cache key
     * @param string $target target filename
     * @param int    $ttl    time to live
     * @return bool
     */
    
public function export$key$target$ttl null ) {
        
$filename $this->has$key$ttl );

        if ( 
$filename && $this->ensure_dir_existsdirname$target ) ) ) {
            return 
copy$filename$target );
        }

        return 
false;
    }

    
/**
     * Remove file from cache
     *
     * @param string $key cache key
     * @return bool
     */
    
public function remove$key ) {
        if ( ! 
$this->enabled ) {
            return 
false;
        }

        
$filename $this->filename$key );

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

        return 
false;
    }

    
/**
     * Clean cache based on time to live and max size
     *
     * @return bool
     */
    
public function clean() {
        if ( ! 
$this->enabled ) {
            return 
false;
        }

        
$ttl      $this->ttl;
        
$max_size $this->max_size;

        
// Unlink expired files.
        
if ( $ttl ) {
            try {
                
$expire = new DateTime();
                
$expire->modify'-' $ttl ' seconds' );

                
$finder $this->get_finder()->date'until ' $expire->format'Y-m-d H:i:s' ) );
                foreach ( 
$finder as $file ) {
                    
unlink$file->getRealPath() );
                }
            } catch ( 
Exception $e ) {
                
WP_CLI::error$e->getMessage() );
            }
        }

        
// Unlink older files if max cache size is exceeded.
        
if ( $max_size ) {
            
$files array_reverseiterator_to_array$this->get_finder()->sortByAccessedTime()->getIterator() ) );
            
$total 0;

            foreach ( 
$files as $file ) {
                if ( ( 
$total $file->getSize() ) <= $max_size ) {
                    
$total += $file->getSize();
                } else {
                    
unlink$file->getRealPath() );
                }
            }
        }

        return 
true;
    }

    
/**
     * Remove all cached files.
     *
     * @return bool
     */
    
public function clear() {
        if ( ! 
$this->enabled ) {
            return 
false;
        }

        
$finder $this->get_finder();

        foreach ( 
$finder as $file ) {
            
unlink$file->getRealPath() );
        }

        return 
true;
    }

    
/**
     * Remove all cached files except for the newest version of one.
     *
     * @return bool
     */
    
public function prune() {
        if ( ! 
$this->enabled ) {
            return 
false;
        }

        
/** @var Finder $finder */
        
$finder $this->get_finder()->sortByName();

        
$files_to_delete = [];

        foreach ( 
$finder as $file ) {
            
$pieces    explode'-'$file->getBasename$file->getExtension() ) );
            
$timestamp end$pieces );

            
// No way to compare versions, do nothing.
            
if ( ! is_numeric$timestamp ) ) {
                continue;
            }

            
$basename_without_timestamp str_replace'-' $timestamp''$file->getBasename() );

            
// There's a file with an older timestamp, delete it.
            
if ( isset( $files_to_delete$basename_without_timestamp ] ) ) {
                
unlink$files_to_delete$basename_without_timestamp ] );
            }

            
$files_to_delete$basename_without_timestamp ] = $file->getRealPath();
        }

        return 
true;
    }

    
/**
     * Ensure directory exists
     *
     * @param string $dir directory
     * @return bool
     */
    
protected function ensure_dir_exists$dir ) {
        if ( ! 
is_dir$dir ) ) {
            
// Disable the cache if a null device like /dev/null is being used.
            
if ( preg_match'{(^|[\\\\/])(\$null|nul|NUL|/dev/null)([\\\\/]|$)}'$dir ) ) {
                return 
false;
            }

            if ( ! @
mkdir$dir0777true ) ) {
                
$message "Failed to create directory '{$dir}'";
                
$error   error_get_last();
                if ( 
is_array$error ) && array_key_exists'message'$error ) ) {
                    
$message .= ": {$error['message']}";
                }
                
WP_CLI::warning"{$message}." );
                return 
false;
            }
        }

        return 
true;
    }

    
/**
     * Prepare cache write
     *
     * @param string $key cache key
     * @return bool|string filename or false
     */
    
protected function prepare_write$key ) {
        if ( ! 
$this->enabled ) {
            return 
false;
        }

        
$filename $this->filename$key );

        if ( ! 
$this->ensure_dir_existsdirname$filename ) ) ) {
            return 
false;
        }

        return 
$filename;
    }

    
/**
     * Validate cache key
     *
     * @param string $key cache key
     * @return string relative filename
     */
    
protected function validate_key$key ) {
        
$url_parts Utils\parse_url$key, -1false );
        if ( 
array_key_exists'path'$url_parts ) && ! empty( $url_parts['scheme'] ) ) { // is url
            
$parts   = [ 'misc' ];
            
$parts[] = $url_parts['scheme'] .
                ( empty( 
$url_parts['host'] ) ? '' '-' $url_parts['host'] ) .
                ( empty( 
$url_parts['port'] ) ? '' '-' $url_parts['port'] );
            
$parts[] = substr$url_parts['path'], ) .
                ( empty( 
$url_parts['query'] ) ? '' '-' $url_parts['query'] );
        } else {
            
$key   str_replace'\\''/'$key );
            
$parts explode'/'ltrim$key ) );
        }

        
$parts preg_replace"#[^{$this->whitelist}]#i"'-'$parts );

        return 
implode'/'$parts );
    }

    
/**
     * Filename from key
     *
     * @param string $key
     * @return string filename
     */
    
protected function filename$key ) {
        return 
$this->root $this->validate_key$key );
    }

    
/**
     * Get a Finder that iterates in cache root only the files
     *
     * @return Finder
     */
    
protected function get_finder() {
        return 
Finder::create()->in$this->root )->files();
    }
}
<?
php

namespace WP_CLI;

use 
Composer\IO\NullIO;
use 
WP_CLI;

/**
 * A Composer IO class so we can provide some level of interactivity from WP-CLI
 */
class ComposerIO extends NullIO {

    
/**
     * {@inheritDoc}
     */
    
public function isVerbose() {
        return 
true;
    }

    
/**
     * {@inheritDoc}
     */
    
public function write$messages$newline true$verbosity self::NORMAL ) {
        
self::output_clean_message$messages );
    }

    
/**
     * {@inheritDoc}
     */
    
public function writeError$messages$newline true$verbosity self::NORMAL ) {
        
self::output_clean_message$messages );
    }

    private static function 
output_clean_message$messages ) {
        
$messages = (array) preg_replace'#<(https?)([^>]+)>#''$1$2'$messages );
        foreach ( 
$messages as $message ) {
            
// phpcs:ignore WordPress.WP.AlternativeFunctions.strip_tags_strip_tags
            
WP_CLI::logstrip_tagstrim$message ) ) );
        }
    }

}
<?
php

// Utilities that depend on WordPress code.

namespace WP_CLI\Utils;

use 
ReflectionClass;
use 
ReflectionParameter;
use 
WP_CLI;
use 
WP_CLI\UpgraderSkin;

function 
wp_not_installed() {
    global 
$wpdb$table_prefix;
    if ( ! 
is_blog_installed() && ! defined'WP_INSTALLING' ) ) {
        
$tables         $wpdb->get_col"SHOW TABLES LIKE '%_options'" );
        
$found_prefixes = [];
        if ( 
count$tables ) ) {
            foreach ( 
$tables as $table ) {
                
$maybe_prefix substr$table0, - strlen'options' ) );
                if ( 
$maybe_prefix !== $table_prefix ) {
                    
$found_prefixes[] = $maybe_prefix;
                }
            }
        }
        if ( 
count$found_prefixes ) ) {
            
$prefix_list   implode', '$found_prefixes );
            
$install_label count$found_prefixes ) > 'installations' 'installation';
            
WP_CLI::error(
                
"The site you have requested is not installed.\n" .
                
"Your table prefix is '{$table_prefix}'. Found {$install_label} with table prefix: {$prefix_list}.\n" .
                
'Or, run `wp core install` to create database tables.'
            
);
        } else {
            
WP_CLI::error(
                
"The site you have requested is not installed.\n" .
                
'Run `wp core install` to create database tables.'
            
);
        }
    }
}

// phpcs:disable WordPress.PHP.IniSet -- Intentional & correct usage.
function wp_debug_mode() {
    if ( 
WP_CLI::get_config'debug' ) ) {
        if ( ! 
defined'WP_DEBUG' ) ) {
            
define'WP_DEBUG'true );
        }

        
error_reportingE_ALL & ~E_DEPRECATED & ~E_STRICT );
    } else {
        if ( 
WP_DEBUG ) {
            
error_reportingE_ALL );

            if ( 
WP_DEBUG_DISPLAY ) {
                
ini_set'display_errors');
            } elseif ( 
null !== WP_DEBUG_DISPLAY ) {
                
ini_set'display_errors');
            }

            if ( 
in_arraystrtolower( (string) WP_DEBUG_LOG ), [ 'true''1' ], true ) ) {
                
$log_path WP_CONTENT_DIR '/debug.log';
            } elseif ( 
is_stringWP_DEBUG_LOG ) ) {
                
$log_path WP_DEBUG_LOG;
            } else {
                
$log_path false;
            }

            if ( 
false !== $log_path ) {
                
ini_set'log_errors');
                
ini_set'error_log'$log_path );
            }
        } else {
            
error_reportingE_CORE_ERROR E_CORE_WARNING E_COMPILE_ERROR E_ERROR E_WARNING E_PARSE E_USER_ERROR E_USER_WARNING E_RECOVERABLE_ERROR );
        }

        if ( 
defined'XMLRPC_REQUEST' ) || defined'REST_REQUEST' ) || ( defined'DOING_AJAX' ) && DOING_AJAX ) ) {
            
ini_set'display_errors');
        }
    }

    
// XDebug already sends errors to STDERR.
    
ini_set'display_errors'function_exists'xdebug_debug_zval' ) ? false 'STDERR' );
}
// phpcs:enable

function replace_wp_die_handler() {
    
\remove_filter'wp_die_handler''_default_wp_die_handler' );
    
\add_filter(
        
'wp_die_handler',
        function() {
            return 
__NAMESPACE__ '\\wp_die_handler';
        }
    );
}

function 
wp_die_handler$message ) {
    if ( 
$message instanceof \WP_Error ) {
        
$message $message->get_error_message();
    }

    
$message wp_clean_error_message$message );

    
WP_CLI::error$message );
}

/**
 * Clean HTML error message so suitable for text display.
 */
function wp_clean_error_message$message ) {
    
$original_message trim$message );
    
$message          $original_message;
    if ( 
preg_match'|^\<h1>(.+?)</h1>|'$original_message$matches ) ) {
        
$message $matches[1] . '.';
    }
    if ( 
preg_match'|\<p>(.+?)</p>|'$original_message$matches ) ) {
        
$message .= ' ' $matches[1];
    }

    
$search_replace = [
        
'<code>'  => '`',
        
'</code>' => '`',
    ];
    
$message        str_replacearray_keys$search_replace ), array_values$search_replace ), $message );
    
$message        namespace\strip_tags$message );
    
$message        html_entity_decode$messageENT_COMPAT'UTF-8' );

    return 
$message;
}

function 
wp_redirect_handler$url ) {
    
WP_CLI::warning'Some code is trying to do a URL redirect. Backtrace:' );

    
ob_start();
    
debug_print_backtrace();
    
fwriteSTDERRob_get_clean() );

    return 
$url;
}

function 
maybe_require$since$path ) {
    if ( 
wp_version_compare$since'>=' ) ) {
        require 
$path;
    }
}

function 
get_upgrader$class$insecure false ) {
    if ( ! 
class_exists'\WP_Upgrader' ) ) {
        require_once 
ABSPATH 'wp-admin/includes/class-wp-upgrader.php';
    }

    
$uses_insecure_flag false;

    
$reflection = new ReflectionClass$class );
    if ( 
$reflection ) {
        
$constructor $reflection->getConstructor();
        if ( 
$constructor ) {
            
$arguments $constructor->getParameters();
            
/** @var ReflectionParameter $argument */
            
foreach ( $arguments as $argument ) {
                if ( 
'insecure' === $argument->name ) {
                    
$uses_insecure_flag true;
                    break;
                }
            }
        }
    }

    if ( 
$uses_insecure_flag ) {
        return new 
$class( new UpgraderSkin(), $insecure );
    } else {
        return new 
$class( new UpgraderSkin() );
    }
}

/**
 * Converts a plugin basename back into a friendly slug.
 */
function get_plugin_name$basename ) {
    if ( 
false === strpos$basename'/' ) ) {
        
$name basename$basename'.php' );
    } else {
        
$name dirname$basename );
    }

    return 
$name;
}

function 
is_plugin_skipped$file ) {
    
$name get_plugin_namestr_replaceWP_PLUGIN_DIR '/'''$file ) );

    
$skipped_plugins WP_CLI::get_runner()->config['skip-plugins'];
    if ( 
true === $skipped_plugins ) {
        return 
true;
    }

    if ( ! 
is_array$skipped_plugins ) ) {
        
$skipped_plugins explode','$skipped_plugins );
    }

    return 
in_array$namearray_filter$skipped_plugins ), true );
}

function 
get_theme_name$path ) {
    return 
basename$path );
}

function 
is_theme_skipped$path ) {
    
$name get_theme_name$path );

    
$skipped_themes WP_CLI::get_runner()->config['skip-themes'];
    if ( 
true === $skipped_themes ) {
        return 
true;
    }

    if ( ! 
is_array$skipped_themes ) ) {
        
$skipped_themes explode','$skipped_themes );
    }

    return 
in_array$namearray_filter$skipped_themes ), true );
}

/**
 * Register the sidebar for unused widgets.
 * Core does this in /wp-admin/widgets.php, which isn't helpful.
 */
function wp_register_unused_sidebar() {

    
register_sidebar(
        [
            
'name'          => __'Inactive Widgets' ),
            
'id'            => 'wp_inactive_widgets',
            
'class'         => 'inactive-sidebar',
            
'description'   => __'Drag widgets here to remove them from the sidebar but keep their settings.' ),
            
'before_widget' => '',
            
'after_widget'  => '',
            
'before_title'  => '',
            
'after_title'   => '',
        ]
    );

}

/**
 * Attempts to determine which object cache is being used.
 *
 * Note that the guesses made by this function are based on the WP_Object_Cache classes
 * that define the 3rd party object cache extension. Changes to those classes could render
 * problems with this function's ability to determine which object cache is being used.
 *
 * @return string
 */
function wp_get_cache_type() {
    global 
$_wp_using_ext_object_cache$wp_object_cache;

    if ( ! empty( 
$_wp_using_ext_object_cache ) ) {
        
// Test for Memcached PECL extension memcached object cache (https://github.com/tollmanz/wordpress-memcached-backend)
        
if ( isset( $wp_object_cache->) && $wp_object_cache->instanceof \Memcached ) {
            
$message 'Memcached';

            
// Test for Memcache PECL extension memcached object cache (https://wordpress.org/extend/plugins/memcached/)
        
} elseif ( isset( $wp_object_cache->mc ) ) {
            
$is_memcache true;
            foreach ( 
$wp_object_cache->mc as $bucket ) {
                if ( ! 
$bucket instanceof \Memcache && ! $bucket instanceof \Memcached ) {
                    
$is_memcache false;
                }
            }

            if ( 
$is_memcache ) {
                
$message 'Memcache';
            }

            
// Test for Xcache object cache (https://plugins.svn.wordpress.org/xcache/trunk/object-cache.php)
        
} elseif ( $wp_object_cache instanceof \XCache_Object_Cache ) {
            
$message 'Xcache';

            
// Test for WinCache object cache (https://wordpress.org/extend/plugins/wincache-object-cache-backend/)
        
} elseif ( class_exists'WinCache_Object_Cache' ) ) {
            
$message 'WinCache';

            
// Test for APC object cache (https://wordpress.org/extend/plugins/apc/)
        
} elseif ( class_exists'APC_Object_Cache' ) ) {
            
$message 'APC';

            
// Test for WP Redis (https://wordpress.org/plugins/wp-redis/)
        
} elseif ( isset( $wp_object_cache->redis ) && $wp_object_cache->redis instanceof \Redis ) {
            
$message 'Redis';

            
// Test for Redis Object Cache (https://wordpress.org/plugins/redis-cache/)
        
} elseif ( method_exists$wp_object_cache'redis_instance' ) && method_exists$wp_object_cache'redis_status' ) ) {
            
$message 'Redis';

            
// Test for WP LCache Object cache (https://github.com/lcache/wp-lcache)
        
} elseif ( isset( $wp_object_cache->lcache ) && $wp_object_cache->lcache instanceof \LCache\Integrated ) {
            
$message 'WP LCache';

        } elseif ( 
function_exists'w3_instance' ) ) {
            
$config  w3_instance'W3_Config' );
            
$message 'Unknown';

            if ( 
$config->get_boolean'objectcache.enabled' ) ) {
                
$message 'W3TC ' $config->get_string'objectcache.engine' );
            }
        } else {
            
$message 'Unknown';
        }
    } else {
        
$message 'Default';
    }
    return 
$message;
}

/**
 * Clear WordPress internal object caches.
 *
 * In long-running scripts, the internal caches on `$wp_object_cache` and `$wpdb`
 * can grow to consume gigabytes of memory. Periodically calling this utility
 * can help with memory management.
 *
 * @access public
 * @category System
 * @deprecated 1.5.0
 */
function wp_clear_object_cache() {
    global 
$wpdb$wp_object_cache;

    
$wpdb->queries = [];

    if ( ! 
is_object$wp_object_cache ) ) {
        return;
    }

    
// The following are Memcached (Redux) plugin specific (see https://core.trac.wordpress.org/ticket/31463).
    
if ( isset( $wp_object_cache->group_ops ) ) {
        
$wp_object_cache->group_ops = [];
    }
    if ( isset( 
$wp_object_cache->stats ) ) {
        
$wp_object_cache->stats = [];
    }
    if ( isset( 
$wp_object_cache->memcache_debug ) ) {
        
$wp_object_cache->memcache_debug = [];
    }
    
// Used by `WP_Object_Cache` also.
    
if ( isset( $wp_object_cache->cache ) ) {
        
$wp_object_cache->cache = [];
    }
}

/**
 * Get a set of tables in the database.
 *
 * Interprets common command-line options into a resolved set of table names.
 *
 * @param array $args Provided table names, or tables with wildcards.
 * @param array $assoc_args Optional flags for groups of tables (e.g. --network)
 * @return array
 */
function wp_get_table_names$args$assoc_args = [] ) {
    global 
$wpdb;

    
$tables = [];

    
// Abort if incompatible args supplied.
    
if ( get_flag_value$assoc_args'base-tables-only' ) && get_flag_value$assoc_args'views-only' ) ) {
        
WP_CLI::error'You cannot supply --base-tables-only and --views-only at the same time.' );
    }

    
// Pre-load tables SQL query with Views restriction if needed.
    
if ( get_flag_value$assoc_args'base-tables-only' ) ) {
        
$tables_sql 'SHOW FULL TABLES WHERE Table_Type = "BASE TABLE"';

    } elseif ( 
get_flag_value$assoc_args'views-only' ) ) {
        
$tables_sql 'SHOW FULL TABLES WHERE Table_Type = "VIEW"';

    }

    if ( 
get_flag_value$assoc_args'all-tables' ) ) {
        if ( empty( 
$tables_sql ) ) {
            
$tables_sql 'SHOW TABLES';
        }

        
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query is safe, see above.
        
$tables $wpdb->get_col$tables_sql);

    } elseif ( 
get_flag_value$assoc_args'all-tables-with-prefix' ) ) {
        if ( empty( 
$tables_sql ) ) {
            
$tables_sql $wpdb->prepare'SHOW TABLES LIKE %s'esc_like$wpdb->get_blog_prefix() ) . '%' );
        } else {
            
$tables_sql .= sprintf" AND %s LIKE '%s'"esc_sql_ident'Tables_in_' $wpdb->dbname ), esc_like$wpdb->get_blog_prefix() ) . '%' );
        }

        
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query is prepared, see above.
        
$tables $wpdb->get_col$tables_sql);

    } else {
        
$scope get_flag_value$assoc_args'scope''all' );

        
// Note: BC change 1.5.0, taking scope into consideration for network also.
        
if ( get_flag_value$assoc_args'network' ) && is_multisite() ) {
            
$network_global_scope in_array$scope, [ 'all''global''ms_global' ], true ) ? ( 'all' === $scope 'global' $scope ) : '';
            
$wp_tables            array_values$wpdb->tables$network_global_scope ) );
            if ( 
in_array$scope, [ 'all''blog' ], true ) ) {
                
// Do directly for compat with old WP versions. Note: private, deleted, archived sites are not excluded.
                
$blog_ids $wpdb->get_col"SELECT blog_id FROM $wpdb->blogs WHERE site_id = $wpdb->siteid);
                foreach ( 
$blog_ids as $blog_id ) {
                    
$wp_tables array_merge$wp_tablesarray_values$wpdb->tables'blog'true /*prefix*/$blog_id ) ) );
                }
            }
        } else {
            
$wp_tables array_values$wpdb->tables$scope ) );
        }

        if ( ! 
global_terms_enabled() ) {
            
// Only include sitecategories when it's actually enabled.
            
$wp_tables array_valuesarray_diff$wp_tables, [ $wpdb->sitecategories ] ) );
        }

        
// Note: BC change 1.5.0, tables are sorted (via TABLES view).
        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- uses esc_sql_ident() and $wpdb->_escape().
        
$tables $wpdb->get_colsprintf"SHOW TABLES WHERE %s IN ('%s')"esc_sql_ident'Tables_in_' $wpdb->dbname ), implode"', '"$wpdb->_escape$wp_tables ) ) ) );

        if ( 
get_flag_value$assoc_args'base-tables-only' ) || get_flag_value$assoc_args'views-only' ) ) {
            
// Apply Views restriction args if needed.
            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query is prepared, see above.
            
$views_query_tables $wpdb->get_col$tables_sql);
            
$tables             array_intersect$tables$views_query_tables );
        }
    }

    
// Filter by `$args`.
    
if ( $args ) {
        
$args_tables = [];
        foreach ( 
$args as $arg ) {
            if ( 
false !== strpos$arg'*' ) || false !== strpos$arg'?' ) ) {
                
$args_tables array_merge(
                    
$args_tables,
                    
array_filter(
                        
$tables,
                        function ( 
$v ) use ( $arg ) {
                            return 
fnmatch$arg$v );
                        }
                    )
                );
            } else {
                
$args_tables[] = $arg;
            }
        }
        
$args_tables array_valuesarray_unique$args_tables ) );
        
$tables      array_valuesarray_intersect$tables$args_tables ) );
        if ( empty( 
$tables ) ) {
            
WP_CLI::errorsprintf"Couldn't find any tables matching: %s"implode' '$args ) ) );
        }
    }

    return 
$tables;
}

/**
 * Failsafe use of the WordPress wp_strip_all_tags() function.
 *
 * Automatically falls back to strip_tags() function if the WP function is not
 * available.
 *
 * @param string $string String to strip the tags from.
 * @return string String devoid of tags.
 */
function strip_tags$string ) {
    if ( 
function_exists'wp_strip_all_tags' ) ) {
        return 
\wp_strip_all_tags$string );
    }

    
$string preg_replace(
        
'@<(script|style)[^>]*?>.*?</\\1>@si',
        
'',
        
$string
    
);

    
// phpcs:ignore WordPress.WP.AlternativeFunctions.strip_tags_strip_tags -- Fallback.
    
$string \strip_tags$string );

    return 
trim$string );
}
<?
php

/**
 * Copied from https://github.com/ramsey/array_column
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright Copyright (c) Ben Ramsey (https://benramsey.com)
 * @license https://opensource.org/licenses/MIT MIT
 *
 * @phpcs:disable -- Code from external source. Left as-is for easier compare.
 */
if (!function_exists('array_column')) {
    
/**
     * Returns the values from a single column of the input array, identified by
     * the $columnKey.
     *
     * Optionally, you may provide an $indexKey to index the values in the returned
     * array by the values from the $indexKey column in the input array.
     *
     * @param array $input A multi-dimensional array (record set) from which to pull
     *                     a column of values.
     * @param mixed $columnKey The column of values to return. This value may be the
     *                         integer key of the column you wish to retrieve, or it
     *                         may be the string key name for an associative array.
     * @param mixed $indexKey (Optional.) The column to use as the index/keys for
     *                        the returned array. This value may be the integer key
     *                        of the column, or it may be the string key name.
     * @return array
     */
    
function array_column($input null$columnKey null$indexKey null)
    {
        
// Using func_get_args() in order to check for proper number of
        // parameters and trigger errors exactly as the built-in array_column()
        // does in PHP 5.5.
        
$argc func_num_args();
        
$params func_get_args();

        if (
$argc 2) {
            
trigger_error("array_column() expects at least 2 parameters, {$argc} given"E_USER_WARNING);
            return 
null;
        }

        if (!
is_array($params[0])) {
            
trigger_error(
                
'array_column() expects parameter 1 to be array, ' gettype($params[0]) . ' given',
                
E_USER_WARNING
            
);
            return 
null;
        }

        if (!
is_int($params[1])
            && !
is_float($params[1])
            && !
is_string($params[1])
            && 
$params[1] !== null
            
&& !(is_object($params[1]) && method_exists($params[1], '__toString'))
        ) {
            
trigger_error('array_column(): The column key should be either a string or an integer'E_USER_WARNING);
            return 
false;
        }

        if (isset(
$params[2])
            && !
is_int($params[2])
            && !
is_float($params[2])
            && !
is_string($params[2])
            && !(
is_object($params[2]) && method_exists($params[2], '__toString'))
        ) {
            
trigger_error('array_column(): The index key should be either a string or an integer'E_USER_WARNING);
            return 
false;
        }

        
$paramsInput $params[0];
        
$paramsColumnKey = ($params[1] !== null) ? (string) $params[1] : null;

        
$paramsIndexKey null;
        if (isset(
$params[2])) {
            if (
is_float($params[2]) || is_int($params[2])) {
                
$paramsIndexKey = (int) $params[2];
            } else {
                
$paramsIndexKey = (string) $params[2];
            }
        }

        
$resultArray = [];

        foreach (
$paramsInput as $row) {
            
$key $value null;
            
$keySet $valueSet false;

            if (
$paramsIndexKey !== null && array_key_exists($paramsIndexKey$row)) {
                
$keySet true;
                
$key = (string) $row[$paramsIndexKey];
            }

            if (
$paramsColumnKey === null) {
                
$valueSet true;
                
$value $row;
            } elseif (
is_array($row) && array_key_exists($paramsColumnKey$row)) {
                
$valueSet true;
                
$value $row[$paramsColumnKey];
            }

            if (
$valueSet) {
                if (
$keySet) {
                    
$resultArray[$key] = $value;
                } else {
                    
$resultArray[] = $value;
                }
            }

        }

        return 
$resultArray;
    }

}
// phpcs:enable
<?php

/**
 * This file contains fallback functions that might have been disabled but are required nevertheless.
 */

if ( PHP_MAJOR_VERSION >= && ! function_exists'ini_set' ) ) {
    function 
ini_set$option$value ) {
        return 
false;
    }
}
<?
php
/**
 * A modified version of wp-settings.php, tailored for CLI use.
 *
 * Note: This is not being used anymore from WordPress 4.6-alpha-37575 onwards.
 *
 * @phpcs:disable WordPress.WP.GlobalVariablesOverride -- Setting the globals is the point of this file.
 * @phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- These are WP native constants which are needed.
 * @phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hook calls in this file are to WP native hooks.
 * @phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Ensuring native WP variables are available.
 */

use WP_CLI\Utils;

/**
 * Stores the location of the WordPress directory of functions, classes, and core content.
 *
 * @since 1.0.0
 */
define'WPINC''wp-includes' );

// Include files required for initialization.
require ABSPATH WPINC '/load.php';
require 
ABSPATH WPINC '/default-constants.php';
require 
ABSPATH WPINC '/plugin.php';

/*
 * These can't be directly globalized in version.php. When updating,
 * we're including version.php from another installation and don't want
 * these values to be overridden if already set.
 */
global $wp_version$wp_db_version$tinymce_version$required_php_version$required_mysql_version$wp_local_package;
require 
ABSPATH WPINC '/version.php';

/**
 * If not already configured, `$blog_id` will default to 1 in a single site
 * configuration. In multisite, it will be overridden by default in ms-settings.php.
 *
 * @global int $blog_id
 * @since 2.0.0
 */
global $blog_id;

// Set initial default constants including WP_MEMORY_LIMIT, WP_MAX_MEMORY_LIMIT, WP_DEBUG, WP_CONTENT_DIR and WP_CACHE.
wp_initial_constants();

// Check for the required PHP version and for the MySQL extension or a database drop-in.
wp_check_php_mysql_versions();

// Disable magic quotes at runtime. Magic quotes are added using wpdb later in wp-settings.php.
// phpcs:disable PHPCompatibility.IniDirectives.RemovedIniDirectives,WordPress.PHP.IniSet.Risky
ini_set'magic_quotes_runtime');
ini_set'magic_quotes_sybase');
// phpc:enable PHPCompatibility.IniDirectives.RemovedIniDirectives,WordPress.PHP.IniSet

// WordPress calculates offsets from UTC.
// phpcs:ignore WordPress.DateTime.RestrictedFunctions.timezone_change_date_default_timezone_set
date_default_timezone_set'UTC' );

// Turn register_globals off.
wp_unregister_GLOBALS();

// Standardize $_SERVER variables across setups.
wp_fix_server_vars();

// Check if we're in maintenance mode.
// WP-CLI: run enable_maintenance_mode filter early for compat with < WP 4.6
/**
 * Filters whether to enable maintenance mode.
 *
 * This filter runs before it can be used by plugins. It is designed for
 * non-web runtimes. If this filter returns true, maintenance mode will be
 * active and the request will end. If false, the request will be allowed to
 * continue processing even if maintenance mode should be active.
 *
 * @since 4.6.0
 *
 * @param bool $enable_checks Whether to enable maintenance mode. Default true.
 * @param int  $upgrading     The timestamp set in the .maintenance file.
 */
if ( apply_filters'enable_maintenance_mode'true ) ) {
    
wp_maintenance();
}

// Start loading timer.
timer_start();

// WP-CLI: run enable_wp_debug_mode_checks filter early for compat with < WP 4.6
/**
 * Filters whether to allow the debug mode check to occur.
 *
 * This filter runs before it can be used by plugins. It is designed for
 * non-web run-times. Returning false causes the `WP_DEBUG` and related
 * constants to not be checked and the default php values for errors
 * will be used unless you take care to update them yourself.
 *
 * @since 4.6.0
 *
 * @param bool $enable_debug_mode Whether to enable debug mode checks to occur. Default true.
 */
if ( apply_filters'enable_wp_debug_mode_checks'true ) ) {
    
wp_debug_mode();
}

/**
 * Filters whether to enable loading of the advanced-cache.php drop-in.
 *
 * This filter runs before it can be used by plugins. It is designed for non-web
 * run-times. If false is returned, advance-cache.php will never be loaded.
 *
 * @since 4.6.0
 *
 * @param bool $enable_advanced_cache Whether to enable loading advanced-cache.php (if present).
 *                                    Default true.
 */
if ( WP_CACHE && apply_filters'enable_loading_advanced_cache_dropin'true ) ) {
    
// For an advanced caching plugin to use. Uses a static drop-in because you would only want one.
    
if ( file_existsWP_CONTENT_DIR '/advanced-cache.php' ) ) {
        include 
WP_CONTENT_DIR '/advanced-cache.php';
    }
}

// Define WP_LANG_DIR if not set.
wp_set_lang_dir();

// Load early WordPress files.
require ABSPATH WPINC '/compat.php';
require 
ABSPATH WPINC '/functions.php';
require 
ABSPATH WPINC '/class-wp.php';
require 
ABSPATH WPINC '/class-wp-error.php';
require 
ABSPATH WPINC '/pomo/mo.php';

// Include the wpdb class and, if present, a db.php database drop-in.
require_wp_db();

// WP-CLI: Handle db error ourselves, instead of waiting for dead_db()
global $wpdb;
if ( ! empty( 
$wpdb->error ) ) {
    
wp_die$wpdb->error );
}

// Set the database table prefix and the format specifiers for database table columns.
$GLOBALS['table_prefix'] = $table_prefix;
wp_set_wpdb_vars();

// Start the WordPress object cache, or an external object cache if the drop-in is present.
wp_start_object_cache();

// Attach the default filters.
require ABSPATH WPINC '/default-filters.php';

// Initialize multisite if enabled.
if ( is_multisite() ) {
    
Utils\maybe_require'4.6-alpha-37575'ABSPATH WPINC '/class-wp-site-query.php' );
    
Utils\maybe_require'4.6-alpha-37896'ABSPATH WPINC '/class-wp-network-query.php' );
    require 
ABSPATH WPINC '/ms-blogs.php';
    require 
ABSPATH WPINC '/ms-settings.php';
} elseif ( ! 
defined'MULTISITE' ) ) {
    
define'MULTISITE'false );
}

register_shutdown_function'shutdown_action_hook' );

// Stop most of WordPress from being loaded if we just want the basics.
if ( SHORTINIT ) {
    return 
false;
}

// Load the L10n library.
require_once ABSPATH WPINC '/l10n.php';

// WP-CLI: Permit Utils\wp_not_installed() to run on < WP 4.0
apply_filters'nocache_headers', [] );

// Run the installer if WordPress is not installed.
wp_not_installed();

// Load most of WordPress.
require ABSPATH WPINC '/class-wp-walker.php';
require 
ABSPATH WPINC '/class-wp-ajax-response.php';
require 
ABSPATH WPINC '/formatting.php';
require 
ABSPATH WPINC '/capabilities.php';
Utils\maybe_require'4.4-beta4-35719'ABSPATH WPINC '/class-wp-roles.php' );
Utils\maybe_require'4.4-beta4-35719'ABSPATH WPINC '/class-wp-role.php' );
Utils\maybe_require'4.4-beta4-35719'ABSPATH WPINC '/class-wp-user.php' );
require 
ABSPATH WPINC '/query.php';
Utils\maybe_require'3.7-alpha-25139'ABSPATH WPINC '/date.php' );
require 
ABSPATH WPINC '/theme.php';
require 
ABSPATH WPINC '/class-wp-theme.php';
require 
ABSPATH WPINC '/template.php';
require 
ABSPATH WPINC '/user.php';
Utils\maybe_require'4.4-beta4-35719'ABSPATH WPINC '/class-wp-user-query.php' );
Utils\maybe_require'4.0'ABSPATH WPINC '/session.php' );
require 
ABSPATH WPINC '/meta.php';
Utils\maybe_require'4.4-beta4-35719'ABSPATH WPINC '/class-wp-meta-query.php' );
Utils\maybe_require'4.5-alpha-35776'ABSPATH WPINC '/class-wp-metadata-lazyloader.php' );
require 
ABSPATH WPINC '/general-template.php';
require 
ABSPATH WPINC '/link-template.php';
require 
ABSPATH WPINC '/author-template.php';
require 
ABSPATH WPINC '/post.php';
Utils\maybe_require'4.4-beta4-35719'ABSPATH WPINC '/class-walker-page.php' );
Utils\maybe_require'4.4-beta4-35719'ABSPATH WPINC '/class-walker-page-dropdown.php' );
Utils\maybe_require'4.6-alpha-37890'ABSPATH WPINC '/class-wp-post-type.php' );
Utils\maybe_require'4.4-beta4-35719'ABSPATH WPINC '/class-wp-post.php' );
require 
ABSPATH WPINC '/post-template.php';
Utils\maybe_require'3.6-alpha-23451'ABSPATH WPINC '/revision.php' );
Utils\maybe_require'3.6-alpha-23451'ABSPATH WPINC '/post-formats.php' );
require 
ABSPATH WPINC '/post-thumbnail-template.php';
require 
ABSPATH WPINC '/category.php';
Utils\maybe_require'4.4-beta4-35719'ABSPATH WPINC '/class-walker-category.php' );
Utils\maybe_require'4.4-beta4-35719'ABSPATH WPINC '/class-walker-category-dropdown.php' );
require 
ABSPATH WPINC '/category-template.php';
require 
ABSPATH WPINC '/comment.php';
Utils\maybe_require'4.4-beta4-35719'ABSPATH WPINC '/class-wp-comment.php' );
Utils\maybe_require'4.4-beta4-35719'ABSPATH WPINC '/class-wp-comment-query.php' );
Utils\maybe_require'4.4-beta4-35719'ABSPATH WPINC '/class-walker-comment.php' );
require 
ABSPATH WPINC '/comment-template.php';
require 
ABSPATH WPINC '/rewrite.php';
Utils\maybe_require'4.4-beta4-35719'ABSPATH WPINC '/class-wp-rewrite.php' );
require 
ABSPATH WPINC '/feed.php';
require 
ABSPATH WPINC '/bookmark.php';
require 
ABSPATH WPINC '/bookmark-template.php';
require 
ABSPATH WPINC '/kses.php';
require 
ABSPATH WPINC '/cron.php';
require 
ABSPATH WPINC '/deprecated.php';
require 
ABSPATH WPINC '/script-loader.php';
require 
ABSPATH WPINC '/taxonomy.php';
Utils\maybe_require'4.4-beta4-35719'ABSPATH WPINC '/class-wp-term.php' );
Utils\maybe_require'4.6-alpha-37575'ABSPATH WPINC '/class-wp-term-query.php' );
Utils\maybe_require'4.4-beta4-35719'ABSPATH WPINC '/class-wp-tax-query.php' );
require 
ABSPATH WPINC '/update.php';
require 
ABSPATH WPINC '/canonical.php';
require 
ABSPATH WPINC '/shortcodes.php';
Utils\maybe_require'4.4-beta4-35719'ABSPATH WPINC '/embed.php' );
require 
ABSPATH WPINC '/class-wp-embed.php';
require 
ABSPATH WPINC '/media.php';
Utils\maybe_require'4.4-alpha-34903'ABSPATH WPINC '/class-wp-oembed-controller.php' );
require 
ABSPATH WPINC '/http.php';
require_once 
ABSPATH WPINC '/class-http.php';
Utils\maybe_require'4.4-beta4-35719'ABSPATH WPINC '/class-wp-http-streams.php' );
Utils\maybe_require'4.4-beta4-35719'ABSPATH WPINC '/class-wp-http-curl.php' );
Utils\maybe_require'4.4-beta4-35719'ABSPATH WPINC '/class-wp-http-proxy.php' );
Utils\maybe_require'4.4-beta4-35719'ABSPATH WPINC '/class-wp-http-cookie.php' );
Utils\maybe_require'4.4-beta4-35719'ABSPATH WPINC '/class-wp-http-encoding.php' );
Utils\maybe_require'4.4-beta4-35719'ABSPATH WPINC '/class-wp-http-response.php' );
Utils\maybe_require'4.6-alpha-37438'ABSPATH WPINC '/class-wp-http-requests-response.php' );
require 
ABSPATH WPINC '/widgets.php';
Utils\maybe_require'4.4-beta4-35719'ABSPATH WPINC '/class-wp-widget.php' );
Utils\maybe_require'4.4-beta4-35719'ABSPATH WPINC '/class-wp-widget-factory.php' );
require 
ABSPATH WPINC '/nav-menu.php';
require 
ABSPATH WPINC '/nav-menu-template.php';
require 
ABSPATH WPINC '/admin-bar.php';
Utils\maybe_require'4.4-alpha-34928'ABSPATH WPINC '/rest-api.php' );
Utils\maybe_require'4.4-beta4-35719'ABSPATH WPINC '/rest-api/class-wp-rest-server.php' );
Utils\maybe_require'4.4-beta4-35719'ABSPATH WPINC '/rest-api/class-wp-rest-response.php' );
Utils\maybe_require'4.4-beta4-35719'ABSPATH WPINC '/rest-api/class-wp-rest-request.php' );

// Load multisite-specific files.
if ( is_multisite() ) {
    require 
ABSPATH WPINC '/ms-functions.php';
    require 
ABSPATH WPINC '/ms-default-filters.php';
    require 
ABSPATH WPINC '/ms-deprecated.php';
}

// Define constants that rely on the API to obtain the default value.
// Define must-use plugin directory constants, which may be overridden in the sunrise.php drop-in.
wp_plugin_directory_constants();

$symlinked_plugins_supported function_exists'wp_register_plugin_realpath' );
if ( 
$symlinked_plugins_supported ) {
    
$GLOBALS['wp_plugin_paths'] = [];
}

// Load must-use plugins.
foreach ( wp_get_mu_plugins() as $mu_plugin ) {
    include_once 
$mu_plugin;
}
unset( 
$mu_plugin );

// Load network activated plugins.
if ( is_multisite() ) {
    foreach ( 
wp_get_active_network_plugins() as $network_plugin ) {
        if ( 
$symlinked_plugins_supported ) {
            
wp_register_plugin_realpath$network_plugin );
        }
        include_once 
$network_plugin;
    }
    unset( 
$network_plugin );
}

do_action'muplugins_loaded' );

if ( 
is_multisite() ) {
    
ms_cookie_constants();
}

// Define constants after multisite is loaded. Cookie-related constants may be overridden in ms_network_cookies().
wp_cookie_constants();

// Define and enforce our SSL constants.
wp_ssl_constants();

// Create common globals.
require ABSPATH WPINC '/vars.php';

// Make taxonomies and posts available to plugins and themes.
// @plugin authors: warning: these get registered again on the init hook.
create_initial_taxonomies();
create_initial_post_types();

// Register the default theme directory root
register_theme_directoryget_theme_root() );

// Load active plugins.
foreach ( wp_get_active_and_valid_plugins() as $plugin ) {
    if ( 
$symlinked_plugins_supported ) {
        
wp_register_plugin_realpath$plugin );
    }
    include_once 
$plugin;
}
unset( 
$plugin$symlinked_plugins_supported );

// Load pluggable functions.
require ABSPATH WPINC '/pluggable.php';
require 
ABSPATH WPINC '/pluggable-deprecated.php';

// Set internal encoding.
wp_set_internal_encoding();

// Run wp_cache_postload() if object cache is enabled and the function exists.
if ( WP_CACHE && function_exists'wp_cache_postload' ) ) {
    
wp_cache_postload();
}

do_action'plugins_loaded' );

// Define constants which affect functionality if not already defined.
wp_functionality_constants();

if ( ! 
function_exists'get_magic_quotes_gpc' ) ) {
    
// Provide compat fallback for newer PHP version (7.4+) on older WordPress core versions.
    
function get_magic_quotes_gpc() {
        return 
false;
    }
}

// Add magic quotes and set up $_REQUEST ( $_GET + $_POST )
wp_magic_quotes();

do_action'sanitize_comment_cookies' );

/**
 * WordPress Query object
 * @global object $wp_the_query
 * @since 2.0.0
 */
$GLOBALS['wp_the_query'] = new WP_Query();

/**
 * Holds the reference to @see $wp_the_query
 * Use this global for WordPress queries
 * @global object $wp_query
 * @since 1.5.0
 */
$GLOBALS['wp_query'] = $GLOBALS['wp_the_query'];

/**
 * Holds the WordPress Rewrite object for creating pretty URLs
 * @global object $wp_rewrite
 * @since 1.5.0
 */
$GLOBALS['wp_rewrite'] = new WP_Rewrite();

/**
 * WordPress Object
 * @global object $wp
 * @since 2.0.0
 */
$GLOBALS['wp'] = new WP();

/**
 * WordPress Widget Factory Object
 * @global object $wp_widget_factory
 * @since 2.8.0
 */
$GLOBALS['wp_widget_factory'] = new WP_Widget_Factory();

/**
 * WordPress User Roles
 * @global object $wp_roles
 * @since 2.0.0
 */
$GLOBALS['wp_roles'] = new WP_Roles();

do_action'setup_theme' );

// Define the template related constants.
wp_templating_constants();

// Load the default text localization domain.
load_default_textdomain();

$locale      get_locale();
$locale_file WP_LANG_DIR "/$locale.php";
if ( ( 
=== validate_file$locale ) ) && is_readable$locale_file ) ) {
    require 
$locale_file;
}
unset( 
$locale_file );

// Pull in locale data after loading text domain.
require_once ABSPATH WPINC '/locale.php';

/**
 * WordPress Locale object for loading locale domain date and various strings.
 * @global object $wp_locale
 * @since 2.1.0
 */
$GLOBALS['wp_locale'] = new WP_Locale();

// Load the functions for the active theme, for both parent and child theme if applicable.
// phpcs:disable WordPress.WP.DiscouragedConstants.STYLESHEETPATHUsageFound,WordPress.WP.DiscouragedConstants.TEMPLATEPATHUsageFound
global $pagenow;
if ( ! 
defined'WP_INSTALLING' ) || 'wp-activate.php' === $pagenow ) {
    if ( 
TEMPLATEPATH !== STYLESHEETPATH && file_existsSTYLESHEETPATH '/functions.php' ) ) {
        include 
STYLESHEETPATH '/functions.php';
    }
    if ( 
file_existsTEMPLATEPATH '/functions.php' ) ) {
        include 
TEMPLATEPATH '/functions.php';
    }
}
// phpcs:enable WordPress.WP.DiscouragedConstants

do_action'after_setup_theme' );

// Set up current user.
$GLOBALS['wp']->init();

/**
 * Most of WP is loaded at this stage, and the user is authenticated. WP continues
 * to load on the init hook that follows (e.g. widgets), and many plugins instantiate
 * themselves on it for all sorts of reasons (e.g. they need a user, a taxonomy, etc.).
 *
 * If you wish to plug an action once WP is loaded, use the wp_loaded hook below.
 */
do_action'init' );

// Check site status
# if ( is_multisite() ) {  // WP-CLI
if ( is_multisite() && ! defined'WP_INSTALLING' ) ) {
    
$file ms_site_check();
    if ( 
true !== $file ) {
        require 
$file;
        die();
    }
    unset( 
$file );
}

/**
 * This hook is fired once WP, all plugins, and the theme are fully loaded and instantiated.
 *
 * AJAX requests should use wp-admin/admin-ajax.php. admin-ajax.php can handle requests for
 * users not logged in.
 *
 * @link https://codex.wordpress.org/AJAX_in_Plugins
 *
 * @since 3.0.0
 */
do_action'wp_loaded' );
<?
php

use cli\Colors;
use 
Mustangostang\Spyc;
use 
WP_CLI\Configurator;
use 
WP_CLI\Dispatcher;
use 
WP_CLI\Dispatcher\CommandAddition;
use 
WP_CLI\Dispatcher\CommandFactory;
use 
WP_CLI\Dispatcher\CommandNamespace;
use 
WP_CLI\Dispatcher\CompositeCommand;
use 
WP_CLI\Dispatcher\RootCommand;
use 
WP_CLI\DocParser;
use 
WP_CLI\ExitException;
use 
WP_CLI\FileCache;
use 
WP_CLI\Loggers\Execution;
use 
WP_CLI\Process;
use 
WP_CLI\ProcessRun;
use 
WP_CLI\Runner;
use 
WP_CLI\SynopsisParser;
use 
WP_CLI\Utils;
use 
WP_CLI\WpHttpCacheManager;

/**
 * Various utilities for WP-CLI commands.
 */
class WP_CLI {

    private static 
$logger;

    private static 
$hooks = [];

    private static 
$hooks_passed = [];

    private static 
$capture_exit false;

    private static 
$deferred_additions = [];

    
/**
     * Set the logger instance.
     *
     * @param object $logger Logger instance to set.
     */
    
public static function set_logger$logger ) {
        
self::$logger $logger;
    }

    
/**
     * Get the logger instance.
     *
     * @return object $logger Logger instance.
     */
    
public static function get_logger() {
        return 
self::$logger;
    }

    
/**
     * Get the Configurator instance
     *
     * @return Configurator
     */
    
public static function get_configurator() {
        static 
$configurator;

        if ( ! 
$configurator ) {
            
$configurator = new ConfiguratorWP_CLI_ROOT '/php/config-spec.php' );
        }

        return 
$configurator;
    }

    public static function 
get_root_command() {
        static 
$root;

        if ( ! 
$root ) {
            
$root = new RootCommand();
        }

        return 
$root;
    }

    public static function 
get_runner() {
        static 
$runner;

        if ( ! 
$runner ) {
            
$runner = new Runner();
        }

        return 
$runner;
    }

    
/**
     * @return FileCache
     */
    
public static function get_cache() {
        static 
$cache;

        if ( ! 
$cache ) {
            
$home     Utils\get_home_dir();
            
$dir      getenv'WP_CLI_CACHE_DIR' ) ? : "$home/.wp-cli/cache";
            
$ttl      getenv'WP_CLI_CACHE_EXPIRY' ) ? : 15552000;
            
$max_size getenv'WP_CLI_CACHE_MAX_SIZE' ) ? : 314572800;
            
// 6 months, 300mb
            
$cache = new FileCache$dir$ttl$max_size );

            
// Clean older files on shutdown with 1/50 probability.
            // phpcs:ignore WordPress.WP.AlternativeFunctions.rand_mt_rand -- no crypto and WP not loaded.
            
if ( === mt_rand050 ) ) {
                
register_shutdown_function(
                    function () use ( 
$cache ) {
                        
$cache->clean();
                    }
                );
            }
        }

        return 
$cache;
    }

    
/**
     * Set the context in which WP-CLI should be run
     */
    
public static function set_url$url ) {
        
self::debug'Set URL: ' $url'bootstrap' );
        
$url_parts Utils\parse_url$url );
        
self::set_url_params$url_parts );
    }

    private static function 
set_url_params$url_parts ) {
        
$f = function( $key ) use ( $url_parts ) {
            return 
Utils\get_flag_value$url_parts$key'' );
        };

        if ( isset( 
$url_parts['host'] ) ) {
            if ( isset( 
$url_parts['scheme'] ) && 'https' === strtolower$url_parts['scheme'] ) ) {
                
$_SERVER['HTTPS'] = 'on';
            }

            
$_SERVER['HTTP_HOST'] = $url_parts['host'];
            if ( isset( 
$url_parts['port'] ) ) {
                
$_SERVER['HTTP_HOST'] .= ':' $url_parts['port'];
            }

            
$_SERVER['SERVER_NAME'] = $url_parts['host'];
        }

        
$_SERVER['REQUEST_URI']  = $f'path' ) . ( isset( $url_parts['query'] ) ? '?' $url_parts['query'] : '' );
        
$_SERVER['SERVER_PORT']  = Utils\get_flag_value$url_parts'port''80' );
        
$_SERVER['QUERY_STRING'] = $f'query' );
    }

    
/**
     * @return WpHttpCacheManager
     */
    
public static function get_http_cache_manager() {
        static 
$http_cacher;

        if ( ! 
$http_cacher ) {
            
$http_cacher = new WpHttpCacheManagerself::get_cache() );
        }

        return 
$http_cacher;
    }

    
/**
     * Colorize a string for output.
     *
     * Yes, you can change the color of command line text too. For instance,
     * here's how `WP_CLI::success()` colorizes "Success: "
     *
     * ```
     * WP_CLI::colorize( "%GSuccess:%n " )
     * ```
     *
     * Uses `\cli\Colors::colorize()` to transform color tokens to display
     * settings. Choose from the following tokens (and note 'reset'):
     *
     * * %y => ['color' => 'yellow'],
     * * %g => ['color' => 'green'],
     * * %b => ['color' => 'blue'],
     * * %r => ['color' => 'red'],
     * * %p => ['color' => 'magenta'],
     * * %m => ['color' => 'magenta'],
     * * %c => ['color' => 'cyan'],
     * * %w => ['color' => 'grey'],
     * * %k => ['color' => 'black'],
     * * %n => ['color' => 'reset'],
     * * %Y => ['color' => 'yellow', 'style' => 'bright'],
     * * %G => ['color' => 'green', 'style' => 'bright'],
     * * %B => ['color' => 'blue', 'style' => 'bright'],
     * * %R => ['color' => 'red', 'style' => 'bright'],
     * * %P => ['color' => 'magenta', 'style' => 'bright'],
     * * %M => ['color' => 'magenta', 'style' => 'bright'],
     * * %C => ['color' => 'cyan', 'style' => 'bright'],
     * * %W => ['color' => 'grey', 'style' => 'bright'],
     * * %K => ['color' => 'black', 'style' => 'bright'],
     * * %N => ['color' => 'reset', 'style' => 'bright'],
     * * %3 => ['background' => 'yellow'],
     * * %2 => ['background' => 'green'],
     * * %4 => ['background' => 'blue'],
     * * %1 => ['background' => 'red'],
     * * %5 => ['background' => 'magenta'],
     * * %6 => ['background' => 'cyan'],
     * * %7 => ['background' => 'grey'],
     * * %0 => ['background' => 'black'],
     * * %F => ['style' => 'blink'],
     * * %U => ['style' => 'underline'],
     * * %8 => ['style' => 'inverse'],
     * * %9 => ['style' => 'bright'],
     * * %_ => ['style' => 'bright']
     *
     * @access public
     * @category Output
     *
     * @param string $string String to colorize for output, with color tokens.
     * @return string Colorized string.
     */
    
public static function colorize$string ) {
        return 
Colors::colorize$stringself::get_runner()->in_color() );
    }

    
/**
     * Schedule a callback to be executed at a certain point.
     *
     * Hooks conceptually are very similar to WordPress actions. WP-CLI hooks
     * are typically called before WordPress is loaded.
     *
     * WP-CLI hooks include:
     *
     * * `before_add_command:<command>` - Before the command is added.
     * * `after_add_command:<command>` - After the command was added.
     * * `before_invoke:<command>` - Just before a command is invoked.
     * * `after_invoke:<command>` - Just after a command is invoked.
     * * `find_command_to_run_pre` - Just before WP-CLI finds the command to run.
     * * `before_registering_contexts` (1) - Before the contexts are registered.
     * * `before_wp_load` - Just before the WP load process begins.
     * * `before_wp_config_load` - After wp-config.php has been located.
     * * `after_wp_config_load` - After wp-config.php has been loaded into scope.
     * * `after_wp_load` - Just after the WP load process has completed.
     * * `before_run_command` (3) - Just before the command is executed.
     *
     * The parentheses behind the hook name denote the number of arguments
     * being passed into the hook. For such hooks, the callback should return
     * the first argument again, making them work like a WP filter.
     *
     * WP-CLI commands can create their own hooks with `WP_CLI::do_hook()`.
     *
     * If additional arguments are passed through the `WP_CLI::do_hook()` call,
     * these will be passed on to the callback provided by `WP_CLI::add_hook()`.
     *
     * ```
     * # `wp network meta` confirms command is executing in multisite context.
     * WP_CLI::add_command( 'network meta', 'Network_Meta_Command', array(
     *    'before_invoke' => function () {
     *        if ( !is_multisite() ) {
     *            WP_CLI::error( 'This is not a multisite installation.' );
     *        }
     *    }
     * ) );
     * ```
     *
     * @access public
     * @category Registration
     *
     * @param string $when Identifier for the hook.
     * @param mixed $callback Callback to execute when hook is called.
     * @return null
     */
    
public static function add_hook$when$callback ) {
        if ( 
array_key_exists$whenself::$hooks_passed ) ) {
            
self::debug(
                
sprintf(
                    
'Immediately invoking on passed hook "%s": %s',
                    
$when,
                    
Utils\describe_callable$callback )
                ),
                
'hooks'
            
);
            
call_user_func_array$callback, (array) self::$hooks_passed$when ] );
        }

        
self::$hooks$when ][] = $callback;
    }

    
/**
     * Execute callbacks registered to a given hook.
     *
     * See `WP_CLI::add_hook()` for details on WP-CLI's internal hook system.
     * Commands can provide and call their own hooks.
     *
     * @access public
     * @category Registration
     *
     * @param string $when    Identifier for the hook.
     * @param mixed  ...$args Optional. Arguments that will be passed onto the
     *                        callback provided by `WP_CLI::add_hook()`.
     * @return null|mixed Returns the first optional argument if optional
     *                    arguments were passed, otherwise returns null.
     */
    
public static function do_hook$when, ...$args ) {
        
self::$hooks_passed$when ] = $args;

        
$has_args count$args ) > 0;

        if ( ! isset( 
self::$hooks$when ] ) ) {
            if ( 
$has_args ) {
                return 
$args[0];
            }

            return 
null;
        }

        
self::debug(
            
sprintf(
                
'Processing hook "%s" with %d callbacks',
                
$when,
                
countself::$hooks$when ] )
            ),
            
'hooks'
        
);

        foreach ( 
self::$hooks$when ] as $callback ) {
            
self::debug(
                
sprintf(
                    
'On hook "%s": %s',
                    
$when,
                    
Utils\describe_callable$callback )
                ),
                
'hooks'
            
);

            if ( 
$has_args ) {
                
$return_value $callback( ...$args );
                if ( isset( 
$return_value ) ) {
                    
$args[0] = $return_value;
                }
            } else {
                
$callback();
            }
        }

        if ( 
$has_args ) {
            return 
$args[0];
        }

        return 
null;
    }

    
/**
     * Add a callback to a WordPress action or filter.
     *
     * `add_action()` without needing access to `add_action()`. If WordPress is
     * already loaded though, you should use `add_action()` (and `add_filter()`)
     * instead.
     *
     * @access public
     * @category Registration
     *
     * @param string $tag Named WordPress action or filter.
     * @param mixed $function_to_add Callable to execute when the action or filter is evaluated.
     * @param integer $priority Priority to add the callback as.
     * @param integer $accepted_args Number of arguments to pass to callback.
     * @return true
     */
    
public static function add_wp_hook$tag$function_to_add$priority 10$accepted_args ) {
        global 
$wp_filter$merged_filters;

        if ( 
function_exists'add_filter' ) ) {
            
add_filter$tag$function_to_add$priority$accepted_args );
        } else {
            
$idx self::wp_hook_build_unique_id$tag$function_to_add$priority );

            
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- This is intentional & the purpose of this function.
            
$wp_filter$tag ][ $priority ][ $idx ] = [
                
'function'      => $function_to_add,
                
'accepted_args' => $accepted_args,
            ];
            unset( 
$merged_filters$tag ] );
        }

        return 
true;
    }

    
/**
     * Build Unique ID for storage and retrieval.
     *
     * Essentially _wp_filter_build_unique_id() without needing access to _wp_filter_build_unique_id()
     */
    
private static function wp_hook_build_unique_id$tag$function$priority ) {
        global 
$wp_filter;
        static 
$filter_id_count 0;

        if ( 
is_string$function ) ) {
            return 
$function;
        }

        if ( 
is_object$function ) ) {
            
// Closures are currently implemented as objects
            
$function = [ $function'' ];
        } else {
            
$function = (array) $function;
        }

        if ( 
is_object$function[0] ) ) {
            
// Object Class Calling
            
if ( function_exists'spl_object_hash' ) ) {
                return 
spl_object_hash$function[0] ) . $function[1];
            }

            
$obj_idx get_class$function[0] ) . $function[1];
            if ( ! isset( 
$function[0]->wp_filter_id ) ) {
                if ( 
false === $priority ) {
                    return 
false;
                }
                
$obj_idx                  .= isset( $wp_filter$tag ][ $priority ] ) ? count( (array) $wp_filter$tag ][ $priority ] ) : $filter_id_count;
                
$function[0]->wp_filter_id $filter_id_count;
                ++
$filter_id_count;
            } else {
                
$obj_idx .= $function[0]->wp_filter_id;
            }

            return 
$obj_idx;
        }

        if ( 
is_string$function[0] ) ) {
            
// Static Calling
            
return $function[0] . '::' $function[1];
        }
    }

    
/**
     * Register a command to WP-CLI.
     *
     * WP-CLI supports using any callable class, function, or closure as a
     * command. `WP_CLI::add_command()` is used for both internal and
     * third-party command registration.
     *
     * Command arguments are parsed from PHPDoc by default, but also can be
     * supplied as an optional third argument during registration.
     *
     * ```
     * # Register a custom 'foo' command to output a supplied positional param.
     * #
     * # $ wp foo bar --append=qux
     * # Success: bar qux
     *
     * /**
     *  * My awesome closure command
     *  *
     *  * <message>
     *  * : An awesome message to display
     *  *
     *  * --append=<message>
     *  * : An awesome message to append to the original message.
     *  *
     *  * @when before_wp_load
     *  *\/
     * $foo = function( $args, $assoc_args ) {
     *     WP_CLI::success( $args[0] . ' ' . $assoc_args['append'] );
     * };
     * WP_CLI::add_command( 'foo', $foo );
     * ```
     *
     * @access public
     * @category Registration
     *
     * @param string   $name Name for the command (e.g. "post list" or "site empty").
     * @param callable $callable Command implementation as a class, function or closure.
     * @param array    $args {
     *    Optional. An associative array with additional registration parameters.
     *
     *    @type callable $before_invoke Callback to execute before invoking the command.
     *    @type callable $after_invoke  Callback to execute after invoking the command.
     *    @type string   $shortdesc     Short description (80 char or less) for the command.
     *    @type string   $longdesc      Description of arbitrary length for examples, etc.
     *    @type string   $synopsis      The synopsis for the command (string or array).
     *    @type string   $when          Execute callback on a named WP-CLI hook (e.g. before_wp_load).
     *    @type bool     $is_deferred   Whether the command addition had already been deferred.
     * }
     * @return bool True on success, false if deferred, hard error if registration failed.
     */
    
public static function add_command$name$callable$args = [] ) {
        
// Bail immediately if the WP-CLI executable has not been run.
        
if ( ! defined'WP_CLI' ) ) {
            return 
false;
        }

        
$valid false;
        if ( 
is_callable$callable ) ) {
            
$valid true;
        } elseif ( 
is_string$callable ) && class_exists( (string) $callable ) ) {
            
$valid true;
        } elseif ( 
is_object$callable ) ) {
            
$valid true;
        } elseif ( 
Utils\is_valid_class_and_method_pair$callable ) ) {
            
$valid true;
        }
        if ( ! 
$valid ) {
            if ( 
is_array$callable ) ) {
                
$callable[0] = is_object$callable[0] ) ? get_class$callable[0] ) : $callable[0];
                
$callable    = [ $callable[0], $callable[1] ];
            }
            
self::errorsprintf'Callable %s does not exist, and cannot be registered as `wp %s`.'json_encode$callable ), $name ) );
        }

        
$addition = new CommandAddition();
        
self::do_hook"before_add_command:{$name}"$addition );

        if ( 
$addition->was_aborted() ) {
            
self::warning"Aborting the addition of the command '{$name}' with reason: {$addition->get_reason()}." );
            return 
false;
        }

        foreach ( [ 
'before_invoke''after_invoke' ] as $when ) {
            if ( isset( 
$args$when ] ) ) {
                
self::add_hook"{$when}:{$name}"$args$when ] );
            }
        }

        
$path preg_split'/\s+/'$name );

        
$leaf_name array_pop$path );

        
$command self::get_root_command();

        while ( ! empty( 
$path ) ) {
            
$subcommand_name $path[0];
            
$parent          implode' '$path );
            
$subcommand      $command->find_subcommand$path );

            
// Parent not found. Defer addition or create an empty container as
            // needed.
            
if ( ! $subcommand ) {
                if ( isset( 
$args['is_deferred'] ) && $args['is_deferred'] ) {
                    
$subcommand = new CompositeCommand(
                        
$command,
                        
$subcommand_name,
                        new 
DocParser'' )
                    );

                    
self::debug(
                        
"Adding empty container for deferred command: {$name}",
                        
'commands'
                    
);

                    
$command->add_subcommand$subcommand_name$subcommand );
                } else {
                    
self::debug"Deferring command: {$name}"'commands' );

                    
self::defer_command_addition(
                        
$name,
                        
$parent,
                        
$callable,
                        
$args
                    
);

                    return 
false;
                }
            }

            
$command $subcommand;
        }

        
$leaf_command CommandFactory::create$leaf_name$callable$command );

        
// Only add a command namespace if the command itself does not exist yet.
        
if ( $leaf_command instanceof CommandNamespace
            
&& array_key_exists$leaf_name$command->get_subcommands() ) ) {
            return 
false;
        }

        
// Reattach commands attached to namespace to real command.
        
$subcommand_name  = (array) $leaf_name;
        
$existing_command $command->find_subcommand$subcommand_name );
        if ( 
$existing_command instanceof CompositeCommand && $existing_command->can_have_subcommands() ) {
            if ( 
$leaf_command instanceof CommandNamespace || ! $leaf_command->can_have_subcommands() ) {
                
$command_to_keep $existing_command;
            } else {
                
$command_to_keep $leaf_command;
            }

            
self::merge_sub_commands$command_to_keep$existing_command$leaf_command );
        }

        
/** @var Dispatcher\Subcommand|Dispatcher\CompositeCommand|Dispatcher\CommandNamespace $leaf_command */

        
if ( ! $command->can_have_subcommands() ) {
            throw new 
Exception(
                
sprintf(
                    
"'%s' can't have subcommands.",
                    
implode' 'get_path$command ) )
                )
            );
        }

        if ( isset( 
$args['shortdesc'] ) ) {
            
$leaf_command->set_shortdesc$args['shortdesc'] );
        }

        if ( isset( 
$args['longdesc'] ) ) {
            
$leaf_command->set_longdesc$args['longdesc'] );
        }

        if ( isset( 
$args['synopsis'] ) ) {
            if ( 
is_string$args['synopsis'] ) ) {
                
$leaf_command->set_synopsis$args['synopsis'] );
            } elseif ( 
is_array$args['synopsis'] ) ) {
                
$synopsis SynopsisParser::render$args['synopsis'] );
                
$leaf_command->set_synopsis$synopsis );
                
$long_desc '';
                
$bits      explode' '$synopsis );
                foreach ( 
$args['synopsis'] as $key => $arg ) {
                    
$long_desc .= $bits$key ] . "\n";
                    if ( ! empty( 
$arg['description'] ) ) {
                        
$long_desc .= ': ' $arg['description'] . "\n";
                    }
                    
$yamlify = [];
                    foreach ( [ 
'default''options' ] as $key ) {
                        if ( isset( 
$arg$key ] ) ) {
                            
$yamlify$key ] = $arg$key ];
                        }
                    }
                    if ( ! empty( 
$yamlify ) ) {
                        
$long_desc .= Spyc::YAMLDump$yamlify );
                        
$long_desc .= '---' "\n";
                    }
                    
$long_desc .= "\n";
                }
                if ( ! empty( 
$long_desc ) ) {
                    
$long_desc rtrim$long_desc"\r\n" );
                    
$long_desc '## OPTIONS' "\n\n" $long_desc;
                    if ( ! empty( 
$args['longdesc'] ) ) {
                        
$long_desc .= "\n\n" ltrim$args['longdesc'], "\r\n" );
                    }
                    
$leaf_command->set_longdesc$long_desc );
                }
            }
        }

        if ( isset( 
$args['when'] ) ) {
            
self::get_runner()->register_early_invoke$args['when'], $leaf_command );
        }

        if ( ! empty( 
$parent ) ) {
            
$sub_command trimstr_replace$parent''$name ) );
            
self::debug"Adding command: {$sub_command} in {$parent} Namespace"'commands' );
        } else {
            
self::debug"Adding command: {$name}"'commands' );
        }

        
$command->add_subcommand$leaf_name$leaf_command );

        
self::do_hook"after_add_command:{$name});
        return 
true;
    }

    
/**
     * Merge the sub-commands of two commands into a single command to keep.
     *
     * @param CompositeCommand $command_to_keep Command to merge the sub commands into. This is typically one of the
     *                                          two others.
     * @param CompositeCommand $old_command     Command that was already registered.
     * @param CompositeCommand $new_command     New command that is being added.
     */
    
private static function merge_sub_commands(
        
CompositeCommand $command_to_keep,
        
CompositeCommand $old_command,
        
CompositeCommand $new_command
    
) {
        foreach ( 
$old_command->get_subcommands() as $subname => $subcommand ) {
            
$command_to_keep->add_subcommand$subname$subcommandfalse );
        }

        foreach ( 
$new_command->get_subcommands() as $subname => $subcommand ) {
            
$command_to_keep->add_subcommand$subname$subcommandtrue );
        }
    }

    
/**
     * Defer command addition for a sub-command if the parent command is not yet
     * registered.
     *
     * @param string $name     Name for the sub-command.
     * @param string $parent   Name for the parent command.
     * @param string $callable Command implementation as a class, function or closure.
     * @param array  $args     Optional. See `WP_CLI::add_command()` for details.
     */
    
private static function defer_command_addition$name$parent$callable$args = [] ) {
        
$args['is_deferred']               = true;
        
self::$deferred_additions$name ] = [
            
'parent'   => $parent,
            
'callable' => $callable,
            
'args'     => $args,
        ];
        
self::add_hook(
            
"after_add_command:$parent",
            function () use ( 
$name ) {
                
$deferred_additions WP_CLI::get_deferred_additions();

                if ( ! 
array_key_exists$name$deferred_additions ) ) {
                    return;
                }

                
$callable $deferred_additions$name ]['callable'];
                
$args     $deferred_additions$name ]['args'];
                
WP_CLI::remove_deferred_addition$name );

                
WP_CLI::add_command$name$callable$args );
            }
        );
    }

    
/**
     * Get the list of outstanding deferred command additions.
     *
     * @return array Array of outstanding command additions.
     */
    
public static function get_deferred_additions() {
        return 
self::$deferred_additions;
    }

    
/**
     * Remove a command addition from the list of outstanding deferred additions.
     */
    
public static function remove_deferred_addition$name ) {
        if ( ! 
array_key_exists$nameself::$deferred_additions ) ) {
            
self::warning"Trying to remove a non-existent command addition '{$name}'." );
        }

        unset( 
self::$deferred_additions$name ] );
    }

    
/**
     * Display informational message without prefix, and ignore `--quiet`.
     *
     * Message is written to STDOUT. `WP_CLI::log()` is typically recommended;
     * `WP_CLI::line()` is included for historical compat.
     *
     * @access public
     * @category Output
     *
     * @param string $message Message to display to the end user.
     * @return null
     */
    
public static function line$message '' ) {
        echo 
$message "\n";
    }

    
/**
     * Display informational message without prefix.
     *
     * Message is written to STDOUT, or discarded when `--quiet` flag is supplied.
     *
     * ```
     * # `wp cli update` lets user know of each step in the update process.
     * WP_CLI::log( sprintf( 'Downloading from %s...', $download_url ) );
     * ```
     *
     * @access public
     * @category Output
     *
     * @param string $message Message to write to STDOUT.
     */
    
public static function log$message ) {
        if ( 
null === self::$logger ) {
            return;
        }

        
self::$logger->info$message );
    }

    
/**
     * Display success message prefixed with "Success: ".
     *
     * Success message is written to STDOUT.
     *
     * Typically recommended to inform user of successful script conclusion.
     *
     * ```
     * # wp rewrite flush expects 'rewrite_rules' option to be set after flush.
     * flush_rewrite_rules( \WP_CLI\Utils\get_flag_value( $assoc_args, 'hard' ) );
     * if ( ! get_option( 'rewrite_rules' ) ) {
     *     WP_CLI::warning( "Rewrite rules are empty." );
     * } else {
     *     WP_CLI::success( 'Rewrite rules flushed.' );
     * }
     * ```
     *
     * @access public
     * @category Output
     *
     * @param string $message Message to write to STDOUT.
     * @return null
     */
    
public static function success$message ) {
        if ( 
null === self::$logger ) {
            return;
        }

        
self::$logger->success$message );
    }

    
/**
     * Display debug message prefixed with "Debug: " when `--debug` is used.
     *
     * Debug message is written to STDERR, and includes script execution time.
     *
     * Helpful for optionally showing greater detail when needed. Used throughout
     * WP-CLI bootstrap process for easier debugging and profiling.
     *
     * ```
     * # Called in `WP_CLI\Runner::set_wp_root()`.
     * private static function set_wp_root( $path ) {
     *     define( 'ABSPATH', Utils\trailingslashit( $path ) );
     *     WP_CLI::debug( 'ABSPATH defined: ' . ABSPATH );
     *     $_SERVER['DOCUMENT_ROOT'] = realpath( $path );
     * }
     *
     * # Debug details only appear when `--debug` is used.
     * # $ wp --debug
     * # [...]
     * # Debug: ABSPATH defined: /srv/www/wordpress-develop.dev/src/ (0.225s)
     * ```
     *
     * @access public
     * @category Output
     *
     * @param string|WP_Error|Exception|Throwable $message Message to write to STDERR.
     * @param string|bool $group Organize debug message to a specific group.
     * Use `false` to not group the message.
     * @return null
     */
    
public static function debug$message$group false ) {
        static 
$storage = [];

        if ( ! 
self::$logger ) {
            
$storage[] = [ $message$group ];
            return;
        }

        if ( ! empty( 
$storage ) && self::$logger ) {
            foreach ( 
$storage as $entry ) {
                list( 
$stored_message$stored_group ) = $entry;
                
self::$logger->debugself::error_to_string$stored_message ), $stored_group );
            }
            
$storage = [];
        }

        
self::$logger->debugself::error_to_string$message ), $group );
    }

    
/**
     * Display warning message prefixed with "Warning: ".
     *
     * Warning message is written to STDERR.
     *
     * Use instead of `WP_CLI::debug()` when script execution should be permitted
     * to continue.
     *
     * ```
     * # `wp plugin activate` skips activation when plugin is network active.
     * $status = $this->get_status( $plugin->file );
     * // Network-active is the highest level of activation status
     * if ( 'active-network' === $status ) {
     *   WP_CLI::warning( "Plugin '{$plugin->name}' is already network active." );
     *   continue;
     * }
     * ```
     *
     * @access public
     * @category Output
     *
     * @param string|WP_Error|Exception|Throwable $message Message to write to STDERR.
     * @return null
     */
    
public static function warning$message ) {
        if ( 
null === self::$logger ) {
            return;
        }

        
self::$logger->warningself::error_to_string$message ) );
    }

    
/**
     * Display error message prefixed with "Error: " and exit script.
     *
     * Error message is written to STDERR. Defaults to halting script execution
     * with return code 1.
     *
     * Use `WP_CLI::warning()` instead when script execution should be permitted
     * to continue.
     *
     * ```
     * # `wp cache flush` considers flush failure to be a fatal error.
     * if ( false === wp_cache_flush() ) {
     *     WP_CLI::error( 'The object cache could not be flushed.' );
     * }
     * ```
     *
     * @access public
     * @category Output
     *
     * @param string|WP_Error|Exception|Throwable $message Message to write to STDERR.
     * @param boolean|integer            $exit    True defaults to exit(1).
     * @return null
     */
    
public static function error$message$exit true ) {
        if ( 
null !== self::$logger && ! isset( self::get_runner()->assoc_args['completions'] ) ) {
            
self::$logger->errorself::error_to_string$message ) );
        }

        
$return_code false;
        if ( 
true === $exit ) {
            
$return_code 1;
        } elseif ( 
is_int$exit ) && $exit >= ) {
            
$return_code $exit;
        }

        if ( 
$return_code ) {
            if ( 
self::$capture_exit ) {
                throw new 
ExitExceptionnull$return_code );
            }
            exit( 
$return_code );
        }
    }

    
/**
     * Halt script execution with a specific return code.
     *
     * Permits script execution to be overloaded by `WP_CLI::runcommand()`
     *
     * @access public
     * @category Output
     *
     * @param integer $return_code
     */
    
public static function halt$return_code ) {
        if ( 
self::$capture_exit ) {
            throw new 
ExitExceptionnull$return_code );
        }
        exit( 
$return_code );
    }

    
/**
     * Display a multi-line error message in a red box. Doesn't exit script.
     *
     * Error message is written to STDERR.
     *
     * @access public
     * @category Output
     *
     * @param array $message_lines Multi-line error message to be displayed.
     */
    
public static function error_multi_line$message_lines ) {
        if ( 
null === self::$logger ) {
            return;
        }

        if ( ! isset( 
self::get_runner()->assoc_args['completions'] ) && is_array$message_lines ) ) {
            
self::$logger->error_multi_linearray_map( [ __CLASS__'error_to_string' ], $message_lines ) );
        }
    }

    
/**
     * Ask for confirmation before running a destructive operation.
     *
     * If 'y' is provided to the question, the script execution continues. If
     * 'n' or any other response is provided to the question, script exits.
     *
     * ```
     * # `wp db drop` asks for confirmation before dropping the database.
     *
     * WP_CLI::confirm( "Are you sure you want to drop the database?", $assoc_args );
     * ```
     *
     * @access public
     * @category Input
     *
     * @param string $question Question to display before the prompt.
     * @param array $assoc_args Skips prompt if 'yes' is provided.
     */
    
public static function confirm$question$assoc_args = [] ) {
        if ( ! 
Utils\get_flag_value$assoc_args'yes' ) ) {
            
fwriteSTDOUT$question ' [y/n] ' );

            
$answer strtolowertrimfgetsSTDIN ) ) );

            if ( 
'y' !== $answer ) {
                exit;
            }
        }
    }

    
/**
     * Read value from a positional argument or from STDIN.
     *
     * @param array $args The list of positional arguments.
     * @param int $index At which position to check for the value.
     *
     * @return string
     */
    
public static function get_value_from_arg_or_stdin$args$index ) {
        if ( isset( 
$args$index ] ) ) {
            
$raw_value $args$index ];
        } else {
            
// We don't use file_get_contents() here because it doesn't handle
            // Ctrl-D properly, when typing in the value interactively.
            
$raw_value '';
            while ( 
false !== ( $line fgetsSTDIN ) ) ) {
                
$raw_value .= $line;
            }
        }

        return 
$raw_value;
    }

    
/**
     * Read a value, from various formats.
     *
     * @access public
     * @category Input
     *
     * @param mixed $raw_value
     * @param array $assoc_args
     */
    
public static function read_value$raw_value$assoc_args = [] ) {
        if ( 
Utils\get_flag_value$assoc_args'format' ) === 'json' ) {
            
$value json_decode$raw_valuetrue );
            if ( 
null === $value ) {
                
self::errorsprintf'Invalid JSON: %s'$raw_value ) );
            }
        } else {
            
$value $raw_value;
        }

        return 
$value;
    }

    
/**
     * Display a value, in various formats
     *
     * @param mixed $value Value to display.
     * @param array $assoc_args Arguments passed to the command, determining format.
     */
    
public static function print_value$value$assoc_args = [] ) {
        if ( 
Utils\get_flag_value$assoc_args'format' ) === 'json' ) {
            
$value json_encode$value );
        } elseif ( 
Utils\get_flag_value$assoc_args'format' ) === 'yaml' ) {
            
$value Spyc::YAMLDump$value2);
        } elseif ( 
is_array$value ) || is_object$value ) ) {
            
$value var_export$valuetrue );
        }

        echo 
$value "\n";
    }

    
/**
     * Convert a WP_Error or Exception into a string
     *
     * @param string|WP_Error|Exception|Throwable $errors
     * @throws InvalidArgumentException
     *
     * @return string
     */
    
public static function error_to_string$errors ) {
        if ( 
is_string$errors ) ) {
            return 
$errors;
        }

        
// Only json_encode() the data when it needs it
        
$render_data = function( $data ) {
            if ( 
is_array$data ) || is_object$data ) ) {
                return 
json_encode$data );
            }

            return 
'"' $data '"';
        };

        if ( 
$errors instanceof WP_Error ) {
            foreach ( 
$errors->get_error_messages() as $message ) {
                if ( 
$errors->get_error_data() ) {
                    return 
$message ' ' $render_data$errors->get_error_data() );
                }

                return 
$message;
            }
        }

        
// PHP 7+: internal and user exceptions must implement Throwable interface.
        // PHP 5: internal and user exceptions must extend Exception class.
        
if ( interface_exists'Throwable' ) && ( $errors instanceof Throwable ) || ( $errors instanceof Exception ) ) {
            return 
get_class$errors ) . ': ' $errors->getMessage();
        }

        throw new 
InvalidArgumentException(
            
sprintf(
                
"Unsupported argument type passed to WP_CLI::error_to_string(): '%s'",
                
gettype$errors )
            )
        );
    }

    
/**
     * Launch an arbitrary external process that takes over I/O.
     *
     * ```
     * # `wp core download` falls back to the `tar` binary when PharData isn't available
     * if ( ! class_exists( 'PharData' ) ) {
     *     $cmd = "tar xz --strip-components=1 --directory=%s -f $tarball";
     *     WP_CLI::launch( Utils\esc_cmd( $cmd, $dest ) );
     *     return;
     * }
     * ```
     *
     * @access public
     * @category Execution
     *
     * @param string $command External process to launch.
     * @param boolean $exit_on_error Whether to exit if the command returns an elevated return code.
     * @param boolean $return_detailed Whether to return an exit status (default) or detailed execution results.
     * @return int|ProcessRun The command exit status, or a ProcessRun object for full details.
     */
    
public static function launch$command$exit_on_error true$return_detailed false ) {
        
Utils\check_proc_available'launch' );

        
$proc    Process::create$command );
        
$results $proc->run();

        if ( -
=== $results->return_code ) {
            
self::warning"Spawned process returned exit code {$results->return_code}, which could be caused by a custom compiled version of PHP that uses the --enable-sigchild option." );
        }

        if ( 
$results->return_code && $exit_on_error ) {
            exit( 
$results->return_code );
        }

        if ( 
$return_detailed ) {
            return 
$results;
        }

        return 
$results->return_code;
    }

    
/**
     * Run a WP-CLI command in a new process reusing the current runtime arguments.
     *
     * Use `WP_CLI::runcommand()` instead, which is easier to use and works better.
     *
     * Note: While this command does persist a limited set of runtime arguments,
     * it *does not* persist environment variables. Practically speaking, WP-CLI
     * packages won't be loaded when using WP_CLI::launch_self() because the
     * launched process doesn't have access to the current process $HOME.
     *
     * @access public
     * @category Execution
     *
     * @param string $command WP-CLI command to call.
     * @param array $args Positional arguments to include when calling the command.
     * @param array $assoc_args Associative arguments to include when calling the command.
     * @param bool $exit_on_error Whether to exit if the command returns an elevated return code.
     * @param bool $return_detailed Whether to return an exit status (default) or detailed execution results.
     * @param array $runtime_args Override one or more global args (path,url,user,allow-root)
     * @return int|ProcessRun The command exit status, or a ProcessRun instance
     */
    
public static function launch_self$command$args = [], $assoc_args = [], $exit_on_error true$return_detailed false$runtime_args = [] ) {
        
$reused_runtime_args = [
            
'path',
            
'url',
            
'user',
            
'allow-root',
        ];

        foreach ( 
$reused_runtime_args as $key ) {
            if ( isset( 
$runtime_args$key ] ) ) {
                
$assoc_args$key ] = $runtime_args$key ];
                continue;
            }

            
$value self::get_runner()->config$key ];
            if ( 
$value ) {
                
$assoc_args$key ] = $value;
            }
        }

        
$php_bin escapeshellargUtils\get_php_binary() );

        
$script_path $GLOBALS['argv'][0];

        if ( 
getenv'WP_CLI_CONFIG_PATH' ) ) {
            
$config_path getenv'WP_CLI_CONFIG_PATH' );
        } else {
            
$config_path Utils\get_home_dir() . '/.wp-cli/config.yml';
        }
        
$config_path escapeshellarg$config_path );

        
$args       implode' 'array_map'escapeshellarg'$args ) );
        
$assoc_args Utils\assoc_args_to_str$assoc_args );

        
$full_command "WP_CLI_CONFIG_PATH={$config_path} {$php_bin} {$script_path} {$command} {$args} {$assoc_args}";

        return 
self::launch$full_command$exit_on_error$return_detailed );
    }

    
/**
     * Get the path to the PHP binary used when executing WP-CLI.
     *
     * Environment values permit specific binaries to be indicated.
     *
     * Note: moved to Utils, left for BC.
     *
     * @access public
     * @category System
     *
     * @return string
     */
    
public static function get_php_binary() {
        return 
Utils\get_php_binary();
    }

    
/**
     * Confirm that a global configuration parameter does exist.
     *
     * @access public
     * @category Input
     *
     * @param string $key Config parameter key to check.
     *
     * @return bool
     */
    
public static function has_config$key ) {
        return 
array_key_exists$keyself::get_runner()->config );
    }

    
/**
     * Get values of global configuration parameters.
     *
     * Provides access to `--path=<path>`, `--url=<url>`, and other values of
     * the [global configuration parameters](https://wp-cli.org/config/).
     *
     * ```
     * WP_CLI::log( 'The --url=<url> value is: ' . WP_CLI::get_config( 'url' ) );
     * ```
     *
     * @access public
     * @category Input
     *
     * @param string $key Get value for a specific global configuration parameter.
     * @return mixed
     */
    
public static function get_config$key null ) {
        if ( 
null === $key ) {
            return 
self::get_runner()->config;
        }

        if ( ! isset( 
self::get_runner()->config$key ] ) ) {
            
self::warning"Unknown config option '$key'." );
            return 
null;
        }

        return 
self::get_runner()->config$key ];
    }

    
/**
     * Run a WP-CLI command.
     *
     * Launches a new child process to run a specified WP-CLI command.
     * Optionally:
     *
     * * Run the command in an existing process.
     * * Prevent halting script execution on error.
     * * Capture and return STDOUT, or full details about command execution.
     * * Parse JSON output if the command rendered it.
     *
     * ```
     * $options = array(
     *   'return'     => true,   // Return 'STDOUT'; use 'all' for full object.
     *   'parse'      => 'json', // Parse captured STDOUT to JSON array.
     *   'launch'     => false,  // Reuse the current process.
     *   'exit_error' => true,   // Halt script execution on error.
     * );
     * $plugins = WP_CLI::runcommand( 'plugin list --format=json', $options );
     * ```
     *
     * @access public
     * @category Execution
     *
     * @param string $command WP-CLI command to run, including arguments.
     * @param array  $options Configuration options for command execution.
     * @return mixed
     */
    
public static function runcommand$command$options = [] ) {
        
$defaults   = [
            
'launch'     => true// Launch a new process, or reuse the existing.
            
'exit_error' => true// Exit on error by default.
            
'return'     => false// Capture and return output, or render in realtime.
            
'parse'      => false// Parse returned output as a particular format.
        
];
        
$options    array_merge$defaults$options );
        
$launch     $options['launch'];
        
$exit_error $options['exit_error'];
        
$return     $options['return'];
        
$parse      $options['parse'];
        
$retval     null;
        if ( 
$launch ) {
            
Utils\check_proc_available'launch option' );

            
$descriptors = [
                
=> STDIN,
                
=> STDOUT,
                
=> STDERR,
            ];

            if ( 
$return ) {
                
$descriptors = [
                    
=> STDIN,
                    
=> [ 'pipe''w' ],
                    
=> [ 'pipe''w' ],
                ];
            }

            
$php_bin     escapeshellargUtils\get_php_binary() );
            
$script_path $GLOBALS['argv'][0];

            
// Persist runtime arguments unless they've been specified otherwise.
            
$configurator self::get_configurator();
            
$argv         array_slice$GLOBALS['argv'], );

            list( 
$ignore1$ignore2$runtime_config ) = $configurator->parse_args$argv );
            foreach ( 
$runtime_config as $k => $v ) {
                if ( 
preg_match"|^--{$k}=?$|"$command ) ) {
                    unset( 
$runtime_config$k ] );
                }
            }
            
$runtime_config Utils\assoc_args_to_str$runtime_config );

            
$runcommand "{$php_bin} {$script_path} {$runtime_config} {$command}";

            
$pipes = [];
            
$proc  Utils\proc_open_compat$runcommand$descriptors$pipesgetcwd() );

            if ( 
$return ) {
                
$stdout stream_get_contents$pipes[1] );
                
fclose$pipes[1] );
                
$stderr stream_get_contents$pipes[2] );
                
fclose$pipes[2] );
            }
            
$return_code proc_close$proc );
            if ( -
=== $return_code ) {
                
self::warning'Spawned process returned exit code -1, which could be caused by a custom compiled version of PHP that uses the --enable-sigchild option.' );
            } elseif ( 
$return_code && $exit_error ) {
                exit( 
$return_code );
            }
            if ( 
true === $return || 'stdout' === $return ) {
                
$retval trim$stdout );
            } elseif ( 
'stderr' === $return ) {
                
$retval trim$stderr );
            } elseif ( 
'return_code' === $return ) {
                
$retval $return_code;
            } elseif ( 
'all' === $return ) {
                
$retval = (object) [
                    
'stdout'      => trim$stdout ),
                    
'stderr'      => trim$stderr ),
                    
'return_code' => $return_code,
                ];
            }
        } else {
            
$configurator                               self::get_configurator();
            
$argv                                       Utils\parse_str_to_argv$command );
            list( 
$args$assoc_args$runtime_config ) = $configurator->parse_args$argv );
            if ( 
$return ) {
                
$existing_logger self::$logger;
                
self::$logger    = new Execution();
                
self::$logger->ob_start();
            }
            if ( ! 
$exit_error ) {
                
self::$capture_exit true;
            }
            try {
                
self::get_runner()->run_command(
                    
$args,
                    
$assoc_args,
                    [
                        
'back_compat_conversions' => true,
                    ]
                );
                
$return_code 0;
            } catch ( 
ExitException $e ) {
                
$return_code $e->getCode();
            }
            if ( 
$return ) {
                
$execution_logger self::$logger;
                
$execution_logger->ob_end();
                
self::$logger $existing_logger;
                
$stdout       $execution_logger->stdout;
                
$stderr       $execution_logger->stderr;
                if ( 
true === $return || 'stdout' === $return ) {
                    
$retval trim$stdout );
                } elseif ( 
'stderr' === $return ) {
                    
$retval trim$stderr );
                } elseif ( 
'return_code' === $return ) {
                    
$retval $return_code;
                } elseif ( 
'all' === $return ) {
                    
$retval = (object) [
                        
'stdout'      => trim$stdout ),
                        
'stderr'      => trim$stderr ),
                        
'return_code' => $return_code,
                    ];
                }
            }
            if ( ! 
$exit_error ) {
                
self::$capture_exit false;
            }
        }
        if ( ( 
true === $return || 'stdout' === $return )
            && 
'json' === $parse ) {
            
$retval json_decode$retvaltrue );
        }
        return 
$retval;
    }

    
/**
     * Run a given command within the current process using the same global
     * parameters.
     *
     * Use `WP_CLI::runcommand()` instead, which is easier to use and works better.
     *
     * To run a command using a new process with the same global parameters,
     * use WP_CLI::launch_self(). To run a command using a new process with
     * different global parameters, use WP_CLI::launch().
     *
     * ```
     * ob_start();
     * WP_CLI::run_command( array( 'cli', 'cmd-dump' ) );
     * $ret = ob_get_clean();
     * ```
     *
     * @access public
     * @category Execution
     *
     * @param array $args Positional arguments including command name.
     * @param array $assoc_args
     */
    
public static function run_command$args$assoc_args = [] ) {
        
self::get_runner()->run_command$args$assoc_args );
    }



    
// DEPRECATED STUFF

    
public static function add_man_dir() {
        
trigger_error'WP_CLI::add_man_dir() is deprecated. Add docs inline.'E_USER_WARNING );
    }

    
// back-compat
    
public static function out$str ) {
        
fwriteSTDOUT$str );
    }

    
// back-compat
    // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid -- Deprecated method.
    
public static function addCommand$name$class ) {
        
trigger_error(
            
sprintf(
                
'wp %s: %s is deprecated. use WP_CLI::add_command() instead.',
                
$name,
                
__FUNCTION__
            
),
            
E_USER_WARNING
        
);
        
self::add_command$name$class );
    }
}
<?
php

// Utilities that do NOT depend on WordPress code.

namespace WP_CLI\Utils;

use 
ArrayIterator;
use 
cli;
use 
cli\progress\Bar;
use 
cli\Shell;
use 
Closure;
use 
Composer\Semver\Comparator;
use 
Composer\Semver\Semver;
use 
Exception;
use 
Mustache_Engine;
use 
ReflectionFunction;
use 
Requests;
use 
Requests_Exception;
use 
RuntimeException;
use 
WP_CLI;
use 
WP_CLI\ExitException;
use 
WP_CLI\Formatter;
use 
WP_CLI\Inflector;
use 
WP_CLI\Iterators\Transform;
use 
WP_CLI\NoOp;
use 
WP_CLI\Process;

const 
PHAR_STREAM_PREFIX 'phar://';

/**
 * Regular expression pattern to match __FILE__ and __DIR__ constants.
 *
 * We try to be smart and only replace the constants when they are not within quotes.
 * Regular expressions being stateless, this is probably not 100% correct for edge cases.
 *
 * @see https://regex101.com/r/9hXp5d/11
 * @see https://stackoverflow.com/a/171499/933065
 *
 * @var string
 */
const FILE_DIR_PATTERN '%(?>#.*?$)|(?>//.*?$)|(?>/\*.*?\*/)|(?>\'(?:(?=(\\\\?))\1.)*?\')|(?>"(?:(?=(\\\\?))\2.)*?")|(?<file>\b__FILE__\b)|(?<dir>\b__DIR__\b)%ms';

function 
inside_phar() {
    return 
=== strposWP_CLI_ROOTPHAR_STREAM_PREFIX );
}

// Files that need to be read by external programs have to be extracted from the Phar archive.
function extract_from_phar$path ) {
    if ( ! 
inside_phar() ) {
        return 
$path;
    }

    
$fname basename$path );

    
$tmp_path get_temp_dir() . uniqid'wp-cli-extract-from-phar-'true ) . "-$fname";

    
copy$path$tmp_path );

    
register_shutdown_function(
        function() use ( 
$tmp_path ) {
            if ( 
file_exists$tmp_path ) ) {
                
unlink$tmp_path );
            }
        }
    );

    return 
$tmp_path;
}

function 
load_dependencies() {
    if ( 
inside_phar() ) {
        if ( 
file_existsWP_CLI_ROOT '/vendor/autoload.php' ) ) {
            require 
WP_CLI_ROOT '/vendor/autoload.php';
        } elseif ( 
file_existsdirnamedirnameWP_CLI_ROOT ) ) . '/autoload.php' ) ) {
            require 
dirnamedirnameWP_CLI_ROOT ) ) . '/autoload.php';
        }
        return;
    }

    
$has_autoload false;

    foreach ( 
get_vendor_paths() as $vendor_path ) {
        if ( 
file_exists$vendor_path '/autoload.php' ) ) {
            require 
$vendor_path '/autoload.php';
            
$has_autoload true;
            break;
        }
    }

    if ( ! 
$has_autoload ) {
        
fwriteSTDERR"Internal error: Can't find Composer autoloader.\nTry running: composer install\n" );
        exit( 
);
    }
}

function 
get_vendor_paths() {
    
$vendor_paths        = [
        
WP_CLI_ROOT '/../../../vendor',  // Part of a larger project / installed via Composer (preferred).
        
WP_CLI_ROOT '/vendor',           // Top-level project / installed as Git clone.
    
];
    
$maybe_composer_json WP_CLI_ROOT '/../../../composer.json';
    if ( 
file_exists$maybe_composer_json ) && is_readable$maybe_composer_json ) ) {
        
$composer json_decodefile_get_contents$maybe_composer_json ) );
        if ( ! empty( 
$composer->config ) && ! empty( $composer->config->{'vendor-dir'} ) ) {
            
array_unshift$vendor_pathsWP_CLI_ROOT '/../../../' $composer->config->{'vendor-dir'} );
        }
    }
    return 
$vendor_paths;
}

// Using require() directly inside a class grants access to private methods to the loaded code.
function load_file$path ) {
    require_once 
$path;
}

function 
load_command$name ) {
    
$path WP_CLI_ROOT "/php/commands/$name.php";

    if ( 
is_readable$path ) ) {
        include_once 
$path;
    }
}

/**
 * Like array_map(), except it returns a new iterator, instead of a modified array.
 *
 * Example:
 *
 *     $arr = array('Football', 'Socker');
 *
 *     $it = iterator_map($arr, 'strtolower', function($val) {
 *       return str_replace('foo', 'bar', $val);
 *     });
 *
 *     foreach ( $it as $val ) {
 *       var_dump($val);
 *     }
 *
 * @param array|object $it Either a plain array or another iterator.
 * @param callback     $fn The function to apply to an element.
 * @return object An iterator that applies the given callback(s).
 */
function iterator_map$it$fn ) {
    if ( 
is_array$it ) ) {
        
$it = new ArrayIterator$it );
    }

    if ( ! 
method_exists$it'add_transform' ) ) {
        
$it = new Transform$it );
    }

    foreach ( 
array_slicefunc_get_args(), ) as $fn ) {
        
$it->add_transform$fn );
    }

    return 
$it;
}

/**
 * Search for file by walking up the directory tree until the first file is found or until $stop_check($dir) returns true.
 *
 * @param string|array $files      The files (or file) to search for.
 * @param string|null  $dir        The directory to start searching from; defaults to CWD.
 * @param callable     $stop_check Function which is passed the current dir each time a directory level is traversed.
 * @return null|string Null if the file was not found.
 */
function find_file_upward$files$dir null$stop_check null ) {
    
$files = (array) $files;
    if ( 
is_null$dir ) ) {
        
$dir getcwd();
    }
    while ( 
is_readable$dir ) ) {
        
// Stop walking up when the supplied callable returns true being passed the $dir
        
if ( is_callable$stop_check ) && call_user_func$stop_check$dir ) ) {
            return 
null;
        }

        foreach ( 
$files as $file ) {
            
$path $dir DIRECTORY_SEPARATOR $file;
            if ( 
file_exists$path ) ) {
                return 
$path;
            }
        }

        
$parent_dir dirname$dir );
        if ( empty( 
$parent_dir ) || $parent_dir === $dir ) {
            break;
        }
        
$dir $parent_dir;
    }
    return 
null;
}

function 
is_path_absolute$path ) {
    
// Windows
    
if ( isset( $path[1] ) && ':' === $path[1] ) {
        return 
true;
    }

    return 
'/' === $path[0];
}

/**
 * Composes positional arguments into a command string.
 *
 * @param array $args Positional arguments to compose.
 * @return string
 */
function args_to_str$args ) {
    return 
' ' implode' 'array_map'escapeshellarg'$args ) );
}

/**
 * Composes associative arguments into a command string.
 *
 * @param array $assoc_args Associative arguments to compose.
 * @return string
 */
function assoc_args_to_str$assoc_args ) {
    
$str '';

    foreach ( 
$assoc_args as $key => $value ) {
        if ( 
true === $value ) {
            
$str .= " --$key";
        } elseif ( 
is_array$value ) ) {
            foreach ( 
$value as $v ) {
                
$str .= assoc_args_to_str(
                    [
                        
$key => $v,
                    ]
                );
            }
        } else {
            
$str .= " --$key=" escapeshellarg$value );
        }
    }

    return 
$str;
}

/**
 * Given a template string and an arbitrary number of arguments,
 * returns the final command, with the parameters escaped.
 */
function esc_cmd$cmd ) {
    if ( 
func_num_args() < ) {
        
trigger_error'esc_cmd() requires at least two arguments.'E_USER_WARNING );
    }

    
$args func_get_args();

    
$cmd array_shift$args );

    return 
vsprintf$cmdarray_map'escapeshellarg'$args ) );
}

/**
 * Gets path to WordPress configuration.
 *
 * @return string
 */
function locate_wp_config() {
    static 
$path;

    if ( 
null === $path ) {
        
$path false;

        if ( 
getenv'WP_CONFIG_PATH' ) && file_existsgetenv'WP_CONFIG_PATH' ) ) ) {
            
$path getenv'WP_CONFIG_PATH' );
        } elseif ( 
file_existsABSPATH 'wp-config.php' ) ) {
            
$path ABSPATH 'wp-config.php';
        } elseif ( 
file_existsdirnameABSPATH ) . '/wp-config.php' ) && ! file_existsdirnameABSPATH ) . '/wp-settings.php' ) ) {
            
$path dirnameABSPATH ) . '/wp-config.php';
        }

        if ( 
$path ) {
            
$path realpath$path );
        }
    }

    return 
$path;
}

function 
wp_version_compare$since$operator ) {
    
$wp_version str_replace'-src'''$GLOBALS['wp_version'] );
    
$since      str_replace'-src'''$since );
    return 
version_compare$wp_version$since$operator );
}

/**
 * Render a collection of items as an ASCII table, JSON, CSV, YAML, list of ids, or count.
 *
 * Given a collection of items with a consistent data structure:
 *
 * ```
 * $items = array(
 *     array(
 *         'key'   => 'foo',
 *         'value'  => 'bar',
 *     )
 * );
 * ```
 *
 * Render `$items` as an ASCII table:
 *
 * ```
 * WP_CLI\Utils\format_items( 'table', $items, array( 'key', 'value' ) );
 *
 * # +-----+-------+
 * # | key | value |
 * # +-----+-------+
 * # | foo | bar   |
 * # +-----+-------+
 * ```
 *
 * Or render `$items` as YAML:
 *
 * ```
 * WP_CLI\Utils\format_items( 'yaml', $items, array( 'key', 'value' ) );
 *
 * # ---
 * # -
 * #   key: foo
 * #   value: bar
 * ```
 *
 * @access public
 * @category Output
 *
 * @param string       $format Format to use: 'table', 'json', 'csv', 'yaml', 'ids', 'count'.
 * @param array        $items  An array of items to output.
 * @param array|string $fields Named fields for each item of data. Can be array or comma-separated list.
 */
function format_items$format$items$fields ) {
    
$assoc_args compact'format''fields' );
    
$formatter  = new Formatter$assoc_args );
    
$formatter->display_items$items );
}

/**
 * Write data as CSV to a given file.
 *
 * @access public
 *
 * @param resource $fd      File descriptor.
 * @param array    $rows    Array of rows to output.
 * @param array    $headers List of CSV columns (optional).
 */
function write_csv$fd$rows$headers = [] ) {
    if ( ! empty( 
$headers ) ) {
        
fputcsv$fd$headers );
    }

    foreach ( 
$rows as $row ) {
        if ( ! empty( 
$headers ) ) {
            
$row pick_fields$row$headers );
        }

        
fputcsv$fdarray_values$row ) );
    }
}

/**
 * Pick fields from an associative array or object.
 *
 * @param  array|object $item    Associative array or object to pick fields from.
 * @param  array        $fields  List of fields to pick.
 * @return array
 */
function pick_fields$item$fields ) {
    
$values = [];

    if ( 
is_object$item ) ) {
        foreach ( 
$fields as $field ) {
            
$values$field ] = isset( $item->$field ) ? $item->$field null;
        }
    } else {
        foreach ( 
$fields as $field ) {
            
$values$field ] = isset( $item$field ] ) ? $item$field ] : null;
        }
    }

    return 
$values;
}

/**
 * Launch system's $EDITOR for the user to edit some text.
 *
 * @access public
 * @category Input
 *
 * @param string $input Some form of text to edit (e.g. post content).
 * @param string $title Title to display in the editor.
 * @param string $ext   Extension to use with the temp file.
 * @return string|bool  Edited text, if file is saved from editor; false, if no change to file.
 */
function launch_editor_for_input$input$title 'WP-CLI'$ext 'tmp' ) {

    
check_proc_available'launch_editor_for_input' );

    
$tmpdir get_temp_dir();

    do {
        
$tmpfile  basename$title );
        
$tmpfile  preg_replace'|\.[^.]*$|'''$tmpfile );
        
$tmpfile .= '-' substrmd5mt_rand() ), 0); // phpcs:ignore WordPress.WP.AlternativeFunctions.rand_mt_rand -- no crypto and WP not loaded.
        
$tmpfile  $tmpdir $tmpfile '.' $ext;
        
$fp       fopen$tmpfile'xb' );
        if ( ! 
$fp && is_writable$tmpdir ) && file_exists$tmpfile ) ) {
            
$tmpfile '';
            continue;
        }
        if ( 
$fp ) {
            
fclose$fp );
        }
    } while ( ! 
$tmpfile );

    if ( ! 
$tmpfile ) {
        
WP_CLI::error'Error creating temporary file.' );
    }

    
file_put_contents$tmpfile$input );

    
$editor getenv'EDITOR' );
    if ( ! 
$editor ) {
        
$editor is_windows() ? 'notepad' 'vi';
    }

    
$descriptorspec = [ STDINSTDOUTSTDERR ];
    
$process        proc_open_compat"$editor " escapeshellarg$tmpfile ), $descriptorspec$pipes );
    
$r              proc_close$process );
    if ( 
$r ) {
        exit( 
$r );
    }

    
$output file_get_contents$tmpfile );

    
unlink$tmpfile );

    if ( 
$output === $input ) {
        return 
false;
    }

    return 
$output;
}

/**
 * @param string $raw_host MySQL host string, as defined in wp-config.php.
 *
 * @return array
 */
function mysql_host_to_cli_args$raw_host ) {
    
$assoc_args = [];

    
/**
     * If the host string begins with 'p:' for a persistent db connection,
     * replace 'p:' with nothing.
     */
    
if ( substr$raw_host0) === 'p:' ) {
        
$raw_host substr_replace$raw_host''0);
    }

    
$host_parts explode':'$raw_host );
    if ( 
count$host_parts ) === ) {
        list( 
$assoc_args['host'], $extra ) = $host_parts;
        
$extra                              trim$extra );
        if ( 
is_numeric$extra ) ) {
            
$assoc_args['port']     = (int) $extra;
            
$assoc_args['protocol'] = 'tcp';
        } elseif ( 
'' !== $extra ) {
            
$assoc_args['socket'] = $extra;
        }
    } else {
        
$assoc_args['host'] = $raw_host;
    }

    return 
$assoc_args;
}

/**
 * Run a MySQL command and optionally return the output.
 *
 * @since v2.5.0 Deprecated $descriptors argument.
 *
 * @param string $cmd           Command to run.
 * @param array  $assoc_args    Associative array of arguments to use.
 * @param mixed  $_             Deprecated. Former $descriptors argument.
 * @param bool   $send_to_shell Optional. Whether to send STDOUT and STDERR
 *                              immediately to the shell. Defaults to true.
 * @param bool   $interactive   Optional. Whether MySQL is meant to be
 *                              executed as an interactive process. Defaults
 *                              to false.
 *
 * @return array {
 *     Associative array containing STDOUT and STDERR output.
 *
 *     @type string $stdout    Output that was sent to STDOUT.
 *     @type string $stderr    Output that was sent to STDERR.
 *     @type int    $exit_code Exit code of the process.
 * }
 */
function run_mysql_command$cmd$assoc_args$_ null$send_to_shell true$interactive false ) {
    
check_proc_available'run_mysql_command' );

    
$descriptors = ( $interactive || $send_to_shell ) ?
        [
            
=> STDIN,
            
=> STDOUT,
            
=> STDERR,
        ] :
        [
            
=> STDIN,
            
=> [ 'pipe''w' ],
            
=> [ 'pipe''w' ],
        ];

    
$stdout '';
    
$stderr '';
    
$pipes  = [];

    if ( isset( 
$assoc_args['host'] ) ) {
        
// phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysql_host_to_cli_args -- Misidentified as PHP native MySQL function.
        
$assoc_args array_merge$assoc_argsmysql_host_to_cli_args$assoc_args['host'] ) );
    }

    if ( isset( 
$assoc_args['pass'] ) ) {
        
$old_password getenv'MYSQL_PWD' );
        
putenv'MYSQL_PWD=' $assoc_args['pass'] );
        unset( 
$assoc_args['pass'] );
    }

    
$final_cmd force_env_on_nix_systems$cmd ) . assoc_args_to_str$assoc_args );

    
WP_CLI::debug'Final MySQL command: ' $final_cmd'db' );
    
$process proc_open_compat$final_cmd$descriptors$pipes );

    if ( isset( 
$old_password ) ) {
        
putenv'MYSQL_PWD=' $old_password );
    }

    if ( ! 
$process ) {
        
WP_CLI::debug'Failed to create a valid process using proc_open_compat()''db' );
        exit( 
);
    }

    if ( 
is_resource$process ) && ! $send_to_shell && ! $interactive ) {
        
$stdout stream_get_contents$pipes[1] );
        
$stderr stream_get_contents$pipes[2] );

        
fclose$pipes[1] );
        
fclose$pipes[2] );
    }

    
$exit_code proc_close$process );

    if ( 
$exit_code && ( $send_to_shell || $interactive ) ) {
        exit( 
$exit_code );
    }

    return [
        
$stdout,
        
$stderr,
        
$exit_code,
    ];
}

/**
 * Render PHP or other types of files using Mustache templates.
 *
 * IMPORTANT: Automatic HTML escaping is disabled!
 */
function mustache_render$template_name$data = [] ) {
    if ( ! 
file_exists$template_name ) ) {
        
$template_name WP_CLI_ROOT "/templates/$template_name";
    }

    
$template file_get_contents$template_name );

    
$mustache = new Mustache_Engine(
        [
            
'escape' => function ( $val ) {
                return 
$val; },
        ]
    );

    return 
$mustache->render$template$data );
}

/**
 * Create a progress bar to display percent completion of a given operation.
 *
 * Progress bar is written to STDOUT, and disabled when command is piped. Progress
 * advances with `$progress->tick()`, and completes with `$progress->finish()`.
 * Process bar also indicates elapsed time and expected total time.
 *
 * ```
 * # `wp user generate` ticks progress bar each time a new user is created.
 * #
 * # $ wp user generate --count=500
 * # Generating users  22 % [=======>                             ] 0:05 / 0:23
 *
 * $progress = \WP_CLI\Utils\make_progress_bar( 'Generating users', $count );
 * for ( $i = 0; $i < $count; $i++ ) {
 *     // uses wp_insert_user() to insert the user
 *     $progress->tick();
 * }
 * $progress->finish();
 * ```
 *
 * @access public
 * @category Output
 *
 * @param string  $message  Text to display before the progress bar.
 * @param integer $count    Total number of ticks to be performed.
 * @param int     $interval Optional. The interval in milliseconds between updates. Default 100.
 * @return cli\progress\Bar|NoOp
 */
function make_progress_bar$message$count$interval 100 ) {
    if ( 
Shell::isPiped() ) {
        return new 
NoOp();
    }

    return new 
Bar$message$count$interval );
}

/**
 * Helper function to use wp_parse_url when available or fall back to PHP's
 * parse_url if not.
 *
 * Additionally, this adds 'http://' to the URL if no scheme was found.
 *
 * @param string $url             The URL to parse.
 * @param int    $component       Optional. The specific component to retrieve.
 *                                Use one of the PHP predefined constants to
 *                                specify which one. Defaults to -1 (= return
 *                                all parts as an array).
 * @param bool   $auto_add_scheme Optional. Automatically add an http:// scheme if
 *                                none was found. Defaults to true.
 * @return mixed False on parse failure; Array of URL components on success;
 *               When a specific component has been requested: null if the
 *               component doesn't exist in the given URL; a string or - in the
 *               case of PHP_URL_PORT - integer when it does. See parse_url()'s
 *               return values.
 */
function parse_url$url$component = - 1$auto_add_scheme true ) {
    if (
        
function_exists'wp_parse_url' )
        && (
            -
=== $component
            
|| wp_version_compare'4.7''>=' )
        )
    ) {
        
$url_parts wp_parse_url$url$component );
    } else {
        
// phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url -- Fallback.
        
$url_parts \parse_url$url$component );
    }

    
// phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url -- Own version based on WP one.
    
if ( $auto_add_scheme && ! parse_url$urlPHP_URL_SCHEMEfalse ) ) {
        
// phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url -- Own version based on WP one.
        
$url_parts parse_url'http://' $url$componentfalse );
    }

    return 
$url_parts;
}

/**
 * Check if we're running in a Windows environment (cmd.exe).
 *
 * @return bool
 */
function is_windows() {
    
$test_is_windows getenv'WP_CLI_TEST_IS_WINDOWS' );
    return 
false !== $test_is_windows ? (bool) $test_is_windows strtouppersubstrPHP_OS0) ) === 'WIN';
}

/**
 * Replace magic constants in some PHP source code.
 *
 * Replaces the __FILE__ and __DIR__ magic constants with the values they are
 * supposed to represent at runtime.
 *
 * @param string $source The PHP code to manipulate.
 * @param string $path The path to use instead of the magic constants.
 * @return string Adapted PHP code.
 */
function replace_path_consts$source$path ) {
    
// Solve issue with Windows allowing single quotes in account names.
    
$file addslashes$path );

    if ( 
file_exists$file ) ) {
        
$file realpath$file );
    }

    
$dir dirname$file );

    
// Replace __FILE__ and __DIR__ constants with value of $file or $dir.
    
return preg_replace_callback(
        
FILE_DIR_PATTERN,
        static function ( 
$matches ) use ( $file$dir ) {
            if ( ! empty( 
$matches['file'] ) ) {
                return 
"'{$file}'";
            }

            if ( ! empty( 
$matches['dir'] ) ) {
                return 
"'{$dir}'";
            }

            return 
$matches[0];
        },
        
$source
    
);
}

/**
 * Make a HTTP request to a remote URL.
 *
 * Wraps the Requests HTTP library to ensure every request includes a cert.
 *
 * ```
 * # `wp core download` verifies the hash for a downloaded WordPress archive
 *
 * $md5_response = Utils\http_request( 'GET', $download_url . '.md5' );
 * if ( 20 != substr( $md5_response->status_code, 0, 2 ) ) {
 *      WP_CLI::error( "Couldn't access md5 hash for release (HTTP code {$response->status_code})" );
 * }
 * ```
 *
 * @access public
 *
 * @param string $method  HTTP method (GET, POST, DELETE, etc.).
 * @param string $url     URL to make the HTTP request to.
 * @param array  $headers Add specific headers to the request.
 * @param array  $options {
 *     Optional. An associative array of additional request options.
 *
 *     @type bool $halt_on_error Whether or not command execution should be halted on error. Default: true
 *     @type bool|string $verify A boolean to use enable/disable SSL verification
 *                               or string absolute path to CA cert to use.
 *                               Defaults to detected CA cert bundled with the Requests library.
 *     @type bool $insecure      Whether to retry automatically without certificate validation.
 * }
 * @return object
 * @throws RuntimeException If the request failed.
 * @throws ExitException If the request failed and $halt_on_error is true.
 */
function http_request$method$url$data null$headers = [], $options = [] ) {

    if ( ! 
class_exists'Requests_Hooks' ) ) {
        
// Autoloader for the Requests library has not been registered yet.
        
Requests::register_autoloader();
    }

    
$insecure      = isset( $options['insecure'] ) && (bool) $options['insecure'];
    
$halt_on_error = ! isset( $options['halt_on_error'] ) || (bool) $options['halt_on_error'];
    unset( 
$options['halt_on_error'] );

    if ( ! isset( 
$options['verify'] ) ) {
        
// 'curl.cainfo' enforces the CA file to use, otherwise fallback to system-wide defaults then use the embedded CA file.
        
$options['verify'] = ! empty( ini_get'curl.cainfo' ) ) ? ini_get'curl.cainfo' ) : true;
    }

    try {
        try {
            return 
Requests::request$url$headers$data$method$options );
        } catch ( 
Requests_Exception $ex ) {
            if ( 
true !== $options['verify'] || 'curlerror' !== $ex->getType() || curl_errno$ex->getData() ) !== CURLE_SSL_CACERT ) {
                throw 
$ex;
            }

            
$options['verify'] = get_default_cacert$halt_on_error );

            return 
Requests::request$url$headers$data$method$options );
        }
    } catch ( 
Requests_Exception $ex ) {
        
// CURLE_SSL_CACERT_BADFILE only defined for PHP >= 7.
        
if (
            ! 
$insecure
            
||
            
'curlerror' !== $ex->getType()
            ||
            ! 
in_arraycurl_errno$ex->getData() ), [ CURLE_SSL_CONNECT_ERRORCURLE_SSL_CERTPROBLEM77 /*CURLE_SSL_CACERT_BADFILE*/ ], true )
        ) {
            
$error_msg sprintf"Failed to get url '%s': %s."$url$ex->getMessage() );
            if ( 
$halt_on_error ) {
                
WP_CLI::error$error_msg );
            }
            throw new 
RuntimeException$error_msgnull$ex );
        }

        
$warning sprintf(
            
"Re-trying without verify after failing to get verified url '%s' %s.",
            
$url,
            
$ex->getMessage()
        );
        
WP_CLI::warning$warning );

        
// Disable certificate validation for the next try.
        
$options['verify'] = false;

        try {
            return 
Requests::request$url$headers$data$method$options );
        } catch ( 
Requests_Exception $ex ) {
            
$error_msg sprintf"Failed to get non-verified url '%s' %s."$url$ex->getMessage() );
            if ( 
$halt_on_error ) {
                
WP_CLI::error$error_msg );
            }
            throw new 
RuntimeException$error_msgnull$ex );
        }
    }
}

/**
 * Gets the full path to the default CA cert.
 *
 * @param bool $halt_on_error Whether or not command execution should be halted on error. Default: false
 * @return string Absolute path to the default CA cert.
 * @throws RuntimeException If unable to locate the cert.
 * @throws ExitException If unable to locate the cert and $halt_on_error is true.
 */
function get_default_cacert$halt_on_error false ) {
    
$cert_path '/rmccue/requests/library/Requests/Transport/cacert.pem';
    
$error_msg 'Cannot find SSL certificate.';

    if ( 
inside_phar() ) {
        
// cURL can't read Phar archives
        
return extract_from_pharWP_CLI_VENDOR_DIR $cert_path );
    }

    foreach ( 
get_vendor_paths() as $vendor_path ) {
        if ( 
file_exists$vendor_path $cert_path ) ) {
            return 
$vendor_path $cert_path;
        }
    }

    if ( 
$halt_on_error ) {
        
WP_CLI::error$error_msg );
    }

    throw new 
RuntimeException$error_msg );
}

/**
 * Increments a version string using the "x.y.z-pre" format.
 *
 * Can increment the major, minor or patch number by one.
 * If $new_version == "same" the version string is not changed.
 * If $new_version is not a known keyword, it will be used as the new version string directly.
 *
 * @param string $current_version
 * @param string $new_version
 * @return string
 */
function increment_version$current_version$new_version ) {
    
// split version assuming the format is x.y.z-pre
    
$current_version    explode'-'$current_version);
    
$current_version[0] = explode'.'$current_version[0] );

    switch ( 
$new_version ) {
        case 
'same':
            
// do nothing
            
break;

        case 
'patch':
            
$current_version[0][2]++;

            
$current_version = [ $current_version[0] ]; // Drop possible pre-release info.
            
break;

        case 
'minor':
            
$current_version[0][1]++;
            
$current_version[0][2] = 0;

            
$current_version = [ $current_version[0] ]; // Drop possible pre-release info.
            
break;

        case 
'major':
            
$current_version[0][0]++;
            
$current_version[0][1] = 0;
            
$current_version[0][2] = 0;

            
$current_version = [ $current_version[0] ]; // Drop possible pre-release info.
            
break;

        default: 
// not a keyword
            
$current_version = [ [ $new_version ] ];
            break;
    }

    
// Reconstruct version string.
    
$current_version[0] = implode'.'$current_version[0] );
    
$current_version    implode'-'$current_version );

    return 
$current_version;
}

/**
 * Compare two version strings to get the named semantic version.
 *
 * @access public
 *
 * @param string $new_version
 * @param string $original_version
 * @return string 'major', 'minor', 'patch'
 */
function get_named_sem_ver$new_version$original_version ) {

    if ( ! 
Comparator::greaterThan$new_version$original_version ) ) {
        return 
'';
    }

    
$parts explode'-'$original_version );
    
$bits  explode'.'$parts[0] );
    
$major $bits[0];
    if ( isset( 
$bits[1] ) ) {
        
$minor $bits[1];
    }
    if ( isset( 
$bits[2] ) ) {
        
$patch $bits[2];
    }

    if ( isset( 
$minor ) && Semver::satisfies$new_version"{$major}.{$minor}.x" ) ) {
        return 
'patch';
    }

    if ( 
Semver::satisfies$new_version"{$major}.x.x" ) ) {
        return 
'minor';
    }

    return 
'major';
}

/**
 * Return the flag value or, if it's not set, the $default value.
 *
 * Because flags can be negated (e.g. --no-quiet to negate --quiet), this
 * function provides a safer alternative to using
 * `isset( $assoc_args['quiet'] )` or similar.
 *
 * @access public
 * @category Input
 *
 * @param array  $assoc_args Arguments array.
 * @param string $flag       Flag to get the value.
 * @param mixed  $default    Default value for the flag. Default: NULL.
 * @return mixed
 */
function get_flag_value$assoc_args$flag$default null ) {
    return isset( 
$assoc_args$flag ] ) ? $assoc_args$flag ] : $default;
}

/**
 * Get the home directory.
 *
 * @access public
 * @category System
 *
 * @return string
 */
function get_home_dir() {
    
$home getenv'HOME' );
    if ( ! 
$home ) {
        
// In Windows $HOME may not be defined
        
$home getenv'HOMEDRIVE' ) . getenv'HOMEPATH' );
    }

    return 
rtrim$home'/\\' );
}

/**
 * Appends a trailing slash.
 *
 * @access public
 * @category System
 *
 * @param string $string What to add the trailing slash to.
 * @return string String with trailing slash added.
 */
function trailingslashit$string ) {
    return 
rtrim$string'/\\' ) . '/';
}

/**
 * Normalize a filesystem path.
 *
 * On Windows systems, replaces backslashes with forward slashes
 * and forces upper-case drive letters.
 * Allows for two leading slashes for Windows network shares, but
 * ensures that all other duplicate slashes are reduced to a single one.
 * Ensures upper-case drive letters on Windows systems.
 *
 * @access public
 * @category System
 *
 * @param string $path Path to normalize.
 * @return string Normalized path.
 */
function normalize_path$path ) {
    
$path str_replace'\\''/'$path );
    
$path preg_replace'|(?<=.)/+|''/'$path );
    if ( 
':' === substr$path1) ) {
        
$path ucfirst$path );
    }
    return 
$path;
}


/**
 * Convert Windows EOLs to *nix.
 *
 * @param string $str String to convert.
 * @return string String with carriage return / newline pairs reduced to newlines.
 */
function normalize_eols$str ) {
    return 
str_replace"\r\n""\n"$str );
}

/**
 * Get the system's temp directory. Warns user if it isn't writable.
 *
 * @access public
 * @category System
 *
 * @return string
 */
function get_temp_dir() {
    static 
$temp '';

    if ( 
$temp ) {
        return 
$temp;
    }

    
// `sys_get_temp_dir()` introduced PHP 5.2.1. Will always return something.
    
$temp trailingslashitsys_get_temp_dir() );

    if ( ! 
is_writable$temp ) ) {
        
WP_CLI::warning"Temp directory isn't writable: {$temp});
    }

    return 
$temp;
}

/**
 * Parse a SSH url for its host, port, and path.
 *
 * Similar to parse_url(), but adds support for defined SSH aliases.
 *
 * ```
 * host OR host/path/to/wordpress OR host:port/path/to/wordpress
 * ```
 *
 * @access public
 *
 * @return mixed
 */
function parse_ssh_url$url$component = -) {
    
preg_match'#^((docker|docker\-compose|ssh|vagrant):)?(([^@:]+)@)?([^:/~]+)(:([\d]*))?((/|~)(.+))?$#'$url$matches );
    
$bits = [];
    foreach ( [
        
=> 'scheme',
        
=> 'user',
        
=> 'host',
        
=> 'port',
        
=> 'path',
    ] as 
$i => $key ) {
        if ( ! empty( 
$matches$i ] ) ) {
            
$bits$key ] = $matches$i ];
        }
    }

    
// Find the hostname from `vagrant ssh-config` automatically.
    
if ( preg_match'/^vagrant:?/'$url ) ) {
        if ( 
'vagrant' === $bits['host'] && empty( $bits['scheme'] ) ) {
            
$bits['scheme'] = 'vagrant';
            
$bits['host']   = '';
        }
    }

    switch ( 
$component ) {
        case 
PHP_URL_SCHEME:
            return isset( 
$bits['scheme'] ) ? $bits['scheme'] : null;
        case 
PHP_URL_USER:
            return isset( 
$bits['user'] ) ? $bits['user'] : null;
        case 
PHP_URL_HOST:
            return isset( 
$bits['host'] ) ? $bits['host'] : null;
        case 
PHP_URL_PATH:
            return isset( 
$bits['path'] ) ? $bits['path'] : null;
        case 
PHP_URL_PORT:
            return isset( 
$bits['port'] ) ? $bits['port'] : null;
        default:
            return 
$bits;
    }
}

/**
 * Report the results of the same operation against multiple resources.
 *
 * @access public
 * @category Input
 *
 * @param string       $noun      Resource being affected (e.g. plugin).
 * @param string       $verb      Type of action happening to the noun (e.g. activate).
 * @param integer      $total     Total number of resource being affected.
 * @param integer      $successes Number of successful operations.
 * @param integer      $failures  Number of failures.
 * @param null|integer $skips     Optional. Number of skipped operations. Default null (don't show skips).
 */
function report_batch_operation_results$noun$verb$total$successes$failures$skips null ) {
    
$plural_noun           $noun 's';
    
$past_tense_verb       past_tense_verb$verb );
    
$past_tense_verb_upper ucfirst$past_tense_verb );
    if ( 
$failures ) {
        
$failed_skipped_message null === $skips '' " ({$failures} failed" . ( $skips ", {$skips} skipped" '' ) . ')';
        if ( 
$successes ) {
            
WP_CLI::error"Only {$past_tense_verb} {$successes} of {$total} {$plural_noun}{$failed_skipped_message}." );
        } else {
            
WP_CLI::error"No {$plural_noun} {$past_tense_verb}{$failed_skipped_message}." );
        }
    } else {
        
$skipped_message $skips " ({$skips} skipped)" '';
        if ( 
$successes || $skips ) {
            
WP_CLI::success"{$past_tense_verb_upper} {$successes} of {$total} {$plural_noun}{$skipped_message}." );
        } else {
            
$message $total ucfirst$plural_noun ) : ucfirst$noun );
            
WP_CLI::success"{$message} already {$past_tense_verb}." );
        }
    }
}

/**
 * Parse a string of command line arguments into an $argv-esqe variable.
 *
 * @access public
 * @category Input
 *
 * @param string $arguments
 * @return array
 */
function parse_str_to_argv$arguments ) {
    
preg_match_all'/(?:--[^\s=]+=(["\'])((\\{2})*|(?:[^\1]+?[^\\\\](\\{2})*))\1|--[^\s=]+=[^\s]+|--[^\s=]+|(["\'])((\\{2})*|(?:[^\5]+?[^\\\\](\\{2})*))\5|[^\s]+)/'$arguments$matches );
    
$argv = isset( $matches[0] ) ? $matches[0] : [];
    return 
array_map(
        static function ( 
$arg ) {
            foreach ( [ 
'"'"'" ] as $char ) {
                if ( 
substr$arg0) === $char && substr$arg, -) === $char ) {
                    
$arg substr$arg1, -);
                    break;
                }
            }
                return 
$arg;
        },
        
$argv
    
);
}

/**
 * Locale-independent version of basename()
 *
 * @access public
 *
 * @param string $path
 * @param string $suffix
 * @return string
 */
function basename$path$suffix '' ) {
    
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.urlencode_urlencode -- Format required by wordpress.org API.
    
return urldecode\basenamestr_replace( [ '%2F''%5C' ], '/'urlencode$path ) ), $suffix ) );
}

/**
 * Checks whether the output of the current script is a TTY or a pipe / redirect
 *
 * Returns true if STDOUT output is being redirected to a pipe or a file; false is
 * output is being sent directly to the terminal.
 *
 * If an env variable SHELL_PIPE exists, returned result depends on its
 * value. Strings like 1, 0, yes, no, that validate to booleans are accepted.
 *
 * To enable ASCII formatting even when the shell is piped, use the
 * ENV variable SHELL_PIPE=0.
 *
 * @access public
 *
 * @return bool
 */
function isPiped() { // phpcs:ignore WordPress.NamingConventions.ValidFunctionName.FunctionNameInvalid -- Renaming would break BC.
    
$shell_pipe getenv'SHELL_PIPE' );

    if ( 
false !== $shell_pipe ) {
        return 
filter_var$shell_pipeFILTER_VALIDATE_BOOLEAN );
    }

    return 
function_exists'posix_isatty' ) && ! posix_isattySTDOUT );
}

/**
 * Expand within paths to their matching paths.
 *
 * Has no effect on paths which do not use glob patterns.
 *
 * @param string|array $paths Single path as a string, or an array of paths.
 * @param int          $flags Optional. Flags to pass to glob. Defaults to GLOB_BRACE.
 * @return array Expanded paths.
 */
function expand_globs$paths$flags 'default' ) {
    
// Compatibility for systems without GLOB_BRACE.
    
$glob_func 'glob';
    if ( 
'default' === $flags ) {
        if ( ! 
defined'GLOB_BRACE' ) || getenv'WP_CLI_TEST_EXPAND_GLOBS_NO_GLOB_BRACE' ) ) {
            
$glob_func 'WP_CLI\Utils\glob_brace';
        } else {
            
$flags GLOB_BRACE;
        }
    }

    
$expanded = [];

    foreach ( (array) 
$paths as $path ) {
        
$matching = [ $path ];

        if ( 
preg_match'/[' preg_quote'*?[]{}!''/' ) . ']/'$path ) ) {
            
$matching $glob_func$path$flags ) ?: [];
        }
        
$expanded array_merge$expanded$matching );
    }

    return 
array_valuesarray_unique$expanded ) );
}

/**
 * Simulate a `glob()` with the `GLOB_BRACE` flag set. For systems (eg Alpine Linux) built against a libc library (eg https://www.musl-libc.org/) that lacks it.
 * Copied and adapted from Zend Framework's `Glob::fallbackGlob()` and Glob::nextBraceSub()`.
 *
 * Zend Framework (https://framework.zend.com/)
 *
 * @link      https://github.com/zendframework/zf2 for the canonical source repository
 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (https://www.zend.com)
 * @license   https://framework.zend.com/license/new-bsd New BSD License
 *
 * @param string $pattern     Filename pattern.
 * @param void   $dummy_flags Not used.
 * @return array Array of paths.
 */
function glob_brace$pattern$dummy_flags null ) {

    static 
$next_brace_sub;
    if ( ! 
$next_brace_sub ) {
        
// Find the end of the subpattern in a brace expression.
        
$next_brace_sub = function ( $pattern$current ) {
            
$length strlen$pattern );
            
$depth  0;

            while ( 
$current $length ) {
                if ( 
'\\' === $pattern$current ] ) {
                    if ( ++
$current === $length ) {
                        break;
                    }
                    
$current++;
                } else {
                    if ( ( 
'}' === $pattern$current ] && === $depth-- ) || ( ',' === $pattern$current ] && === $depth ) ) {
                        break;
                    }

                    if ( 
'{' === $pattern$current++ ] ) {
                        
$depth++;
                    }
                }
            }

            return 
$current $length $current null;
        };
    }

    
$length strlen$pattern );

    
// Find first opening brace.
    
for ( $begin 0$begin $length$begin++ ) {
        if ( 
'\\' === $pattern$begin ] ) {
            
$begin++;
        } elseif ( 
'{' === $pattern$begin ] ) {
            break;
        }
    }

    
// Find comma or matching closing brace.
    
$next $next_brace_sub$pattern$begin );
    if ( 
null === $next ) {
        return 
glob$pattern );
    }

    
$rest $next;

    
// Point `$rest` to matching closing brace.
    
while ( '}' !== $pattern$rest ] ) {
        
$rest $next_brace_sub$pattern$rest );
        if ( 
null === $rest ) {
            return 
glob$pattern );
        }
    }

    
$paths = [];
    
$p     $begin 1;

    
// For each comma-separated subpattern.
    
do {
        
$subpattern substr$pattern0$begin )
                    . 
substr$pattern$p$next $p )
                    . 
substr$pattern$rest );

        
$result glob_brace$subpattern );
        if ( ! empty( 
$result ) ) {
            
$paths array_merge$paths$result );
        }

        if ( 
'}' === $pattern$next ] ) {
            break;
        }

        
$p    $next 1;
        
$next $next_brace_sub$pattern$p );
    } while ( 
null !== $next );

    return 
array_valuesarray_unique$paths ) );
}

/**
 * Get the closest suggestion for a mis-typed target term amongst a list of
 * options.
 *
 * Uses the Levenshtein algorithm to calculate the relative "distance" between
 * terms.
 *
 * If the "distance" to the closest term is higher than the threshold, an empty
 * string is returned.
 *
 * @param string $target    Target term to get a suggestion for.
 * @param array  $options   Array with possible options.
 * @param int    $threshold Threshold above which to return an empty string.
 * @return string
 */
function get_suggestion$target, array $options$threshold ) {

    
$suggestion_map = [
        
'add'        => 'create',
        
'check'      => 'check-update',
        
'capability' => 'cap',
        
'clear'      => 'flush',
        
'decrement'  => 'decr',
        
'del'        => 'delete',
        
'directory'  => 'dir',
        
'exec'       => 'eval',
        
'exec-file'  => 'eval-file',
        
'increment'  => 'incr',
        
'language'   => 'locale',
        
'lang'       => 'locale',
        
'new'        => 'create',
        
'number'     => 'count',
        
'remove'     => 'delete',
        
'regen'      => 'regenerate',
        
'rep'        => 'replace',
        
'repl'       => 'replace',
        
'trash'      => 'delete',
        
'v'          => 'version',
    ];

    if ( 
array_key_exists$target$suggestion_map ) && in_array$suggestion_map$target ], $optionstrue ) ) {
        return 
$suggestion_map$target ];
    }

    if ( empty( 
$options ) ) {
        return 
'';
    }
    foreach ( 
$options as $option ) {
        
$distance               levenshtein$option$target );
        
$levenshtein$option ] = $distance;
    }

    
// Sort known command strings by distance to user entry.
    
asort$levenshtein );

    
// Fetch the closest command string.
    
reset$levenshtein );
    
$suggestion key$levenshtein );

    
// Only return a suggestion if below a given threshold.
    
return $levenshtein$suggestion ] <= $threshold && $suggestion !== $target
        
? (string) $suggestion
        
'';
}

/**
 * Get a Phar-safe version of a path.
 *
 * For paths inside a Phar, this strips the outer filesystem's location to
 * reduce the path to what it needs to be within the Phar archive.
 *
 * Use the __FILE__ or __DIR__ constants as a starting point.
 *
 * @param string $path An absolute path that might be within a Phar.
 * @return string A Phar-safe version of the path.
 */
function phar_safe_path$path ) {

    if ( ! 
inside_phar() ) {
        return 
$path;
    }

    return 
str_replace(
        
PHAR_STREAM_PREFIX WP_CLI_PHAR_PATH '/',
        
PHAR_STREAM_PREFIX,
        
$path
    
);
}

/**
 * Maybe prefix command string with "/usr/bin/env".
 * Removes (if there) if Windows, adds (if not there) if not.
 *
 * @param string $command
 * @return string
 */
function force_env_on_nix_systems$command ) {
    
$env_prefix     '/usr/bin/env ';
    
$env_prefix_len strlen$env_prefix );
    if ( 
is_windows() ) {
        if ( 
=== strncmp$command$env_prefix$env_prefix_len ) ) {
            
$command substr$command$env_prefix_len );
        }
    } elseif ( 
!== strncmp$command$env_prefix$env_prefix_len ) ) {
        
$command $env_prefix $command;
    }
    return 
$command;
}

/**
 * Check that `proc_open()` and `proc_close()` haven't been disabled.
 *
 * @param string $context Optional. If set will appear in error message. Default null.
 * @param bool   $return  Optional. If set will return false rather than error out. Default false.
 * @return bool
 */
function check_proc_available$context null$return false ) {
    if ( ! 
function_exists'proc_open' ) || ! function_exists'proc_close' ) ) {
        if ( 
$return ) {
            return 
false;
        }
        
$msg 'The PHP functions `proc_open()` and/or `proc_close()` are disabled. Please check your PHP ini directive `disable_functions` or suhosin settings.';
        if ( 
$context ) {
            
WP_CLI::errorsprintf"Cannot do '%s': %s"$context$msg ) );
        } else {
            
WP_CLI::error$msg );
        }
    }
    return 
true;
}

/**
 * Returns past tense of verb, with limited accuracy. Only regular verbs catered for, apart from "reset".
 *
 * @param string $verb Verb to return past tense of.
 * @return string
 */
function past_tense_verb$verb ) {
    static 
$irregular = [
        
'reset' => 'reset',
    ];
    if ( isset( 
$irregular$verb ] ) ) {
        return 
$irregular$verb ];
    }
    
$last substr$verb, -);
    if ( 
'e' === $last ) {
        
$verb substr$verb0, -);
    } elseif ( 
'y' === $last && ! preg_match'/[aeiou]y$/'$verb ) ) {
        
$verb substr$verb0, -) . 'i';
    } elseif ( 
preg_match'/^[^aeiou]*[aeiou][^aeiouhwxy]$/'$verb ) ) {
        
// Rule of thumb that most (all?) one-voweled regular verbs ending in vowel + consonant (excluding "h", "w", "x", "y") double their final consonant - misses many cases (eg "submit").
        
$verb .= $last;
    }
    return 
$verb 'ed';
}

/**
 * Get the path to the PHP binary used when executing WP-CLI.
 *
 * Environment values permit specific binaries to be indicated.
 *
 * @access public
 * @category System
 *
 * @return string
 */
function get_php_binary() {
    
$wp_cli_php_used getenv'WP_CLI_PHP_USED' );
    if ( 
false !== $wp_cli_php_used ) {
        return 
$wp_cli_php_used;
    }

    
$wp_cli_php getenv'WP_CLI_PHP' );
    if ( 
false !== $wp_cli_php ) {
        return 
$wp_cli_php;
    }

    return 
PHP_BINARY;
}

/**
 * Windows compatible `proc_open()`.
 * Works around bug in PHP, and also deals with *nix-like `ENV_VAR=blah cmd` environment variable prefixes.
 *
 * @access public
 *
 * @param string $cmd            Command to execute.
 * @param array  $descriptorspec Indexed array of descriptor numbers and their values.
 * @param array  &$pipes         Indexed array of file pointers that correspond to PHP's end of any pipes that are created.
 * @param string $cwd            Initial working directory for the command.
 * @param array  $env            Array of environment variables.
 * @param array  $other_options  Array of additional options (Windows only).
 * @return resource Command stripped of any environment variable settings.
 */
function proc_open_compat$cmd$descriptorspec, &$pipes$cwd null$env null$other_options null ) {
    if ( 
is_windows() ) {
        
// Need to encompass the whole command in double quotes - PHP bug https://bugs.php.net/bug.php?id=49139
        
$cmd '"' _proc_open_compat_win_env$cmd$env ) . '"';
    }
    return 
proc_open$cmd$descriptorspec$pipes$cwd$env$other_options );
}

/**
 * For use by `proc_open_compat()` only. Separated out for ease of testing. Windows only.
 * Turns *nix-like `ENV_VAR=blah command` environment variable prefixes into stripped `cmd` with prefixed environment variables added to passed in environment array.
 *
 * @access private
 *
 * @param string $cmd Command to execute.
 * @param array &$env Array of existing environment variables. Will be modified if any settings in command.
 * @return string Command stripped of any environment variable settings.
 */
function _proc_open_compat_win_env$cmd, &$env ) {
    if ( 
false !== strpos$cmd'=' ) ) {
        while ( 
preg_match'/^([A-Za-z_][A-Za-z0-9_]*)=("[^"]*"|[^ ]*) /'$cmd$matches ) ) {
            
$cmd substr$cmdstrlen$matches[0] ) );
            if ( 
null === $env ) {
                
$env = [];
            }
            
$env$matches[1] ] = isset( $matches[2][0] ) && '"' === $matches[2][0] ? substr$matches[2], 1, -) : $matches[2];
        }
    }
    return 
$cmd;
}

/**
 * First half of escaping for LIKE special characters % and _ before preparing for MySQL.
 *
 * Use this only before wpdb::prepare() or esc_sql().  Reversing the order is very bad for security.
 *
 * Copied from core "wp-includes/wp-db.php". Avoids dependency on WP 4.4 wpdb.
 *
 * @access public
 *
 * @param string $text The raw text to be escaped. The input typed by the user should have no
 *                     extra or deleted slashes.
 * @return string Text in the form of a LIKE phrase. The output is not SQL safe. Call $wpdb::prepare()
 *                or real_escape next.
 */
function esc_like$text ) {
    return 
addcslashes$text'_%\\' );
}

/**
 * Escapes (backticks) MySQL identifiers (aka schema object names) - i.e. column names, table names, and database/index/alias/view etc names.
 * See https://dev.mysql.com/doc/refman/5.5/en/identifiers.html
 *
 * @param  string|array $idents A single identifier or an array of identifiers.
 * @return string|array An escaped string if given a string, or an array of escaped strings if given an array of strings.
 */
function esc_sql_ident$idents ) {
    
$backtick = static function ( $v ) {
        
// Escape any backticks in the identifier by doubling.
        
return '`' str_replace'`''``'$v ) . '`';
    };
    if ( 
is_string$idents ) ) {
        return 
$backtick$idents );
    }
    return 
array_map$backtick$idents );
}

/**
 * Check whether a given string is a valid JSON representation.
 *
 * @param string $argument       String to evaluate.
 * @param bool   $ignore_scalars Optional. Whether to ignore scalar values.
 *                               Defaults to true.
 * @return bool Whether the provided string is a valid JSON representation.
 */
function is_json$argument$ignore_scalars true ) {
    if ( ! 
is_string$argument ) || '' === $argument ) {
        return 
false;
    }

    if ( 
$ignore_scalars && ! in_array$argument[0], [ '{''[' ], true ) ) {
        return 
false;
    }

    
json_decode$argument$assoc true );

    return 
json_last_error() === JSON_ERROR_NONE;
}

/**
 * Parse known shell arrays included in the $assoc_args array.
 *
 * @param array $assoc_args      Associative array of arguments.
 * @param array $array_arguments Array of argument keys that should receive an
 *                               array through the shell.
 * @return array
 */
function parse_shell_arrays$assoc_args$array_arguments ) {
    if ( empty( 
$assoc_args ) || empty( $array_arguments ) ) {
        return 
$assoc_args;
    }

    foreach ( 
$array_arguments as $key ) {
        if ( 
array_key_exists$key$assoc_args ) && is_json$assoc_args$key ] ) ) {
            
$assoc_args$key ] = json_decode$assoc_args$key ], $assoc true );
        }
    }

    return 
$assoc_args;
}

/**
 * Describe a callable as a string.
 *
 * @param callable $callable The callable to describe.
 * @return string String description of the callable.
 */
function describe_callable$callable ) {
    try {
        if ( 
$callable instanceof Closure ) {
            
$reflection = new ReflectionFunction$callable );

            return 
"Closure in file {$reflection->getFileName()} at line {$reflection->getStartLine()}";
        }

        if ( 
is_array$callable ) ) {
            if ( 
is_object$callable[0] ) ) {
                return 
sprintf(
                    
'%s->%s()',
                    
get_class$callable[0] ),
                    
$callable[1]
                );
            }

            return 
sprintf'%s::%s()'$callable[0], $callable[1] );
        }

        return 
gettype$callable );
    } catch ( 
Exception $exception ) {
        return 
'Callable of unknown type';
    }
}

/**
 * Checks if the given class and method pair is a valid callable.
 *
 * This accommodates changes to `is_callable()` in PHP 8 that mean an array of a
 * classname and instance method is no longer callable.
 *
 * @param array $pair The class and method pair to check.
 * @return bool
 */
function is_valid_class_and_method_pair$pair ) {
    if ( ! 
is_array$pair ) || !== count$pair ) ) {
        return 
false;
    }

    if ( ! 
is_string$pair[0] ) || ! is_string$pair[1] ) ) {
        return 
false;
    }

    if ( ! 
class_exists$pair[0] ) ) {
        return 
false;
    }

    if ( ! 
method_exists$pair[0], $pair[1] ) ) {
        return 
false;
    }

    return 
true;
}

/**
 * Pluralizes a noun in a grammatically correct way.
 *
 * @param string   $noun  Noun to be pluralized. Needs to be in singular form.
 * @param int|null $count Optional. Count of the nouns, to decide whether to
 *                        pluralize. Will pluralize unconditionally if none
 *                        provided.
 * @return string Pluralized noun.
 */
function pluralize$noun$count null ) {
    if ( 
=== $count ) {
        return 
$noun;
    }

    return 
Inflector::pluralize$noun );
}

/**
 * Get the path to the mysql binary.
 *
 * @return string Path to the mysql binary, or an empty string if not found.
 */
function get_mysql_binary_path() {
    static 
$path null;

    if ( 
null === $path ) {
        
$result Process::create'/usr/bin/env which mysql'nullnull )->run();

        if ( 
!== $result->return_code ) {
            
$path '';
        } else {
            
$path trim$result->stdout );
        }
    }

    return 
$path;
}

/**
 * Get the version of the MySQL database.
 *
 * @return string Version of the MySQL database, or an empty string if not
 *                found.
 */
function get_mysql_version() {
    static 
$version null;

    if ( 
null === $version ) {
        
$result Process::create'/usr/bin/env mysql --version'nullnull )->run();

        if ( 
!== $result->return_code ) {
            
$version '';
        } else {
            
$version trim$result->stdout );
        }
    }

    return 
$version;
}

/**
 * Get the SQL modes of the MySQL session.
 *
 * @return string[] Array of SQL modes, or an empty array if they couldn't be
 *                  read.
 */
function get_sql_modes() {
    static 
$sql_modes null;

    if ( 
null === $sql_modes ) {
        
$result Process::create'/usr/bin/env mysql --no-auto-rehash --batch --skip-column-names --execute="SELECT @@SESSION.sql_mode"'nullnull )->run();

        if ( 
!== $result->return_code ) {
            
$sql_modes = [];
        } else {
            
$sql_modes array_filter(
                
array_map(
                    
'trim',
                    
preg_split"/\r\n|\n|\r/"$result->stdout )
                )
            );
        }
    }

    return 
$sql_modes;
}
<?
php

namespace WP_CLI;

use 
WP_CLI\Bootstrap\BootstrapState;
use 
WP_CLI\Bootstrap\BootstrapStep;

/**
 * Get the list of ordered steps that need to be processed to bootstrap WP-CLI.
 *
 * Each entry is a fully qualified class name for a class implementing the
 * `WP_CLI\Bootstrap\BootstrapStep` interface.
 *
 * @return string[]
 */
function get_bootstrap_steps() {
    return [
        
'WP_CLI\Bootstrap\DeclareFallbackFunctions',
        
'WP_CLI\Bootstrap\LoadUtilityFunctions',
        
'WP_CLI\Bootstrap\LoadDispatcher',
        
'WP_CLI\Bootstrap\DeclareMainClass',
        
'WP_CLI\Bootstrap\DeclareAbstractBaseCommand',
        
'WP_CLI\Bootstrap\IncludeFrameworkAutoloader',
        
'WP_CLI\Bootstrap\ConfigureRunner',
        
'WP_CLI\Bootstrap\InitializeColorization',
        
'WP_CLI\Bootstrap\InitializeLogger',
        
'WP_CLI\Bootstrap\DefineProtectedCommands',
        
'WP_CLI\Bootstrap\LoadExecCommand',
        
'WP_CLI\Bootstrap\LoadRequiredCommand',
        
'WP_CLI\Bootstrap\IncludePackageAutoloader',
        
'WP_CLI\Bootstrap\IncludeFallbackAutoloader',
        
'WP_CLI\Bootstrap\RegisterFrameworkCommands',
        
'WP_CLI\Bootstrap\RegisterDeferredCommands',
        
'WP_CLI\Bootstrap\InitializeContexts',
        
'WP_CLI\Bootstrap\LaunchRunner',
    ];
}

/**
 * Register the classes needed for the bootstrap process.
 *
 * The Composer autoloader is not active yet at this point, so we need to use a
 * custom autoloader to fetch the bootstrap classes in a flexible way.
 */
function prepare_bootstrap() {
    require_once 
WP_CLI_ROOT '/php/WP_CLI/Autoloader.php';

    
$autoloader = new Autoloader();

    
$autoloader->add_namespace(
        
'WP_CLI\Bootstrap',
        
WP_CLI_ROOT '/php/WP_CLI/Bootstrap'
    
)->register();
}

/**
 * Initialize and return the bootstrap state to pass from step to step.
 *
 * @return BootstrapState
 */
function initialize_bootstrap_state() {
    return new 
BootstrapState();
}

/**
 * Process the bootstrapping steps.
 *
 * Loops over each of the provided steps, instantiates it and then calls its
 * `process()` method.
 */
function bootstrap() {
    
prepare_bootstrap();
    
$state initialize_bootstrap_state();

    foreach ( 
get_bootstrap_steps() as $step ) {
        
/** @var BootstrapStep $step_instance */
        
$step_instance = new $step();
        
$state         $step_instance->process$state );
    }
}
<?
php

return [
    
'path'              => [
        
'runtime' => '=<path>',
        
'file'    => '<path>',
        
'desc'    => 'Path to the WordPress files.',
    ],

    
'url'               => [
        
'runtime' => '=<url>',
        
'file'    => '<url>',
        
'desc'    => 'Pretend request came from given URL. In multisite, this argument is how the target site is specified.',
    ],

    
'ssh'               => [
        
'runtime' => '=[<scheme>:][<user>@]<host|container>[:<port>][<path>]',
        
'file'    => '[<scheme>:][<user>@]<host|container>[:<port>][<path>]',
        
'desc'    => 'Perform operation against a remote server over SSH (or a container using scheme of "docker", "docker-compose", "vagrant").',
    ],

    
'http'              => [
        
'runtime' => '=<http>',
        
'file'    => '<http>',
        
'desc'    => 'Perform operation against a remote WordPress installation over HTTP.',
    ],

    
'blog'              => [
        
'deprecated' => 'Use --url instead.',
        
'runtime'    => '=<url>',
    ],

    
'user'              => [
        
'runtime' => '=<id|login|email>',
        
'file'    => '<id|login|email>',
        
'desc'    => 'Set the WordPress user.',
    ],

    
'skip-plugins'      => [
        
'runtime' => '[=<plugins>]',
        
'file'    => '<list>',
        
'desc'    => 'Skip loading all plugins, or a comma-separated list of plugins. Note: mu-plugins are still loaded.',
        
'default' => '',
    ],

    
'skip-themes'       => [
        
'runtime' => '[=<themes>]',
        
'file'    => '<list>',
        
'desc'    => 'Skip loading all themes, or a comma-separated list of themes.',
        
'default' => '',
    ],

    
'skip-packages'     => [
        
'runtime' => '',
        
'file'    => '<bool>',
        
'desc'    => 'Skip loading all installed packages.',
        
'default' => false,
    ],

    
'require'           => [
        
'runtime'  => '=<path>',
        
'file'     => '<path>',
        
'desc'     => 'Load PHP file before running the command (may be used more than once).',
        
'multiple' => true,
        
'default'  => [],
    ],

    
'exec'              => [
        
'runtime'  => '=<php-code>',
        
'file'     => '<php-code>',
        
'desc'     => 'Execute PHP code before running the command (may be used more than once).',
        
'multiple' => true,
        
'default'  => [],
    ],

    
'context'           => [
        
'runtime' => '=<context>',
        
'file'    => '<context>',
        
'default' => 'cli',
        
'desc'    => 'Load WordPress in a given context.',
    ],

    
'disabled_commands' => [
        
'file'    => '<list>',
        
'default' => [],
        
'desc'    => '(Sub)commands to disable.',
    ],

    
'color'             => [
        
'runtime' => true,
        
'file'    => '<bool>',
        
'default' => 'auto',
        
'desc'    => 'Whether to colorize the output.',
    ],

    
'debug'             => [
        
'runtime' => '[=<group>]',
        
'file'    => '<group>',
        
'default' => false,
        
'desc'    => 'Show all PHP errors and add verbosity to WP-CLI output. Built-in groups include: bootstrap, commandfactory, and help.',
    ],

    
'prompt'            => [
        
'runtime' => '[=<assoc>]',
        
'file'    => false,
        
'default' => false,
        
'desc'    => 'Prompt the user to enter values for all command arguments, or a subset specified as comma-separated values.',
    ],

    
'quiet'             => [
        
'runtime' => '',
        
'file'    => '<bool>',
        
'default' => false,
        
'desc'    => 'Suppress informational messages.',
    ],

    
'apache_modules'    => [
        
'file'     => '<list>',
        
'desc'     => 'List of Apache Modules that are to be reported as loaded.',
        
'multiple' => true,
        
'default'  => [],
    ],

    
# --allow-root => (NOT RECOMMENDED) Allow wp-cli to run as root. This poses
    # a security risk, so you probably do not want to do this.
    
'allow-root'        => [
        
'file'    => false# Explicit. Just in case the default changes.
        
'runtime' => '',
        
'hidden'  => true,
    ],

];
<?
php

/**
 * Base class for WP-CLI commands
 *
 * @package wp-cli
 */
abstract class WP_CLI_Command {

    public function 
__construct() {}
}

<?
php

namespace WP_CLI\Dispatcher;

/**
 * Get the path to a command, e.g. "core download"
 *
 * @param Subcommand|CompositeCommand $command
 * @return string[]
 */
function get_path$command ) {
    
$path = [];

    do {
        
array_unshift$path$command->get_name() );
    } while ( 
$command $command->get_parent() );

    return 
$path;
}

<?
php

// This file needs to parse without error in PHP < 5.3

if ( 'cli' !== PHP_SAPI ) {
    echo 
"Only CLI access.\n";
    die( -
);
}

if ( 
version_comparePHP_VERSION'5.6.0''<' ) ) {
    
printf"Error: WP-CLI requires PHP %s or newer. You are running version %s.\n"'5.6.0'PHP_VERSION );
    die( -
);
}

define'WP_CLI_ROOT'dirname__DIR__ ) );

require_once 
WP_CLI_ROOT '/php/wp-cli.php';

<?
php

/**
 * Manages the internal WP-CLI cache,.
 *
 * ## EXAMPLES
 *
 *     # Remove all cached files.
 *     $ wp cli cache clear
 *     Success: Cache cleared.
 *
 *     # Remove all cached files except for the newest version of each one.
 *     $ wp cli cache prune
 *     Success: Cache pruned.
 *
 * @when before_wp_load
 */
class CLI_Cache_Command extends WP_CLI_Command {

    
/**
     * Clears the internal cache.
     *
     * ## EXAMPLES
     *
     *     $ wp cli cache clear
     *     Success: Cache cleared.
     *
     * @subcommand clear
     */
    
public function cache_clear() {
        
$cache WP_CLI::get_cache();

        if ( ! 
$cache->is_enabled() ) {
            
WP_CLI::error'Cache directory does not exist.' );
        }

        
$cache->clear();

        
WP_CLI::success'Cache cleared.' );
    }

    
/**
     * Prunes the internal cache.
     *
     * Removes all cached files except for the newest version of each one.
     *
     * ## EXAMPLES
     *
     *     $ wp cli cache prune
     *     Success: Cache pruned.
     *
     * @subcommand prune
     */
    
public function cache_prune() {
        
$cache WP_CLI::get_cache();

        if ( ! 
$cache->is_enabled() ) {
            
WP_CLI::error'Cache directory does not exist.' );
        }

        
$cache->prune();

        
WP_CLI::success'Cache pruned.' );
    }
}
<?
php

/**
 * Retrieves, sets and updates aliases for WordPress Installations.
 */

use Mustangostang\Spyc;
use 
WP_CLI\ExitException;
use 
WP_CLI\Utils;

/**
 * Retrieves, sets and updates aliases for WordPress Installations.
 *
 * Aliases are shorthand references to WordPress installs. For instance,
 * `@dev` could refer to a development install and `@prod` could refer to a production install.
 * This command gives you and option to add, update and delete, the registered aliases you have available.
 *
 * ## EXAMPLES
 *
 *     # List alias information.
 *     $ wp cli alias list
 *     list
 *     ---
 *     @all: Run command against every registered alias.
 *     @local:
 *       user: wpcli
 *       path: /Users/wpcli/sites/testsite
 *
 *     # Get alias information.
 *     $ wp cli alias get @dev
 *     ssh: [email protected]:12345/home/dev/
 *
 *     # Add alias.
 *     $ wp cli alias add @prod --set-ssh=login@host --set-path=/path/to/wordpress/install/ --set-user=wpcli
 *     Success: Added '@prod' alias.
 *
 *     # Update alias.
 *     $ wp cli alias update @prod --set-user=newuser --set-path=/new/path/to/wordpress/install/
 *     Success: Updated 'prod' alias.
 *
 *     # Delete alias.
 *     $ wp cli alias delete @prod
 *     Success: Deleted '@prod' alias.
 *
 * @package wp-cli
 * @when    before_wp_load
 */
class CLI_Alias_Command extends WP_CLI_Command {

    
/**
     * Lists available WP-CLI aliases.
     *
     * ## OPTIONS
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: yaml
     * options:
     *   - yaml
     *   - json
     *   - var_export
     * ---
     *
     * ## EXAMPLES
     *
     *     # List all available aliases.
     *     $ wp cli alias list
     *     ---
     *     @all: Run command against every registered alias.
     *     @prod:
     *       ssh: [email protected]~/webapps/production
     *     @dev:
     *       ssh: [email protected]/srv/www/runcommand.dev
     *     @both:
     *       - @prod
     *       - @dev
     *
     * @subcommand list
     */
    
public function list_$args$assoc_args ) {
        
WP_CLI::print_valueWP_CLI::get_runner()->aliases$assoc_args );
    }

    
/**
     * Gets the value for an alias.
     *
     * ## OPTIONS
     *
     * <key>
     * : Key for the alias.
     *
     * ## EXAMPLES
     *
     *     # Get alias.
     *     $ wp cli alias get @prod
     *     ssh: [email protected]:12345/home/dev/
     */
    
public function get$args$assoc_args ) {
        list( 
$alias ) = $args;

        
$aliases WP_CLI::get_runner()->aliases;

        if ( empty( 
$aliases$alias ] ) ) {
            
WP_CLI::error"No alias found with key '{$alias}'." );
        }

        foreach ( 
$aliases$alias ] as $key => $value ) {
            
WP_CLI::log"{$key}{$value});
        }
    }

    
/**
     * Creates an alias.
     *
     * ## OPTIONS
     *
     * <key>
     * : Key for the alias.
     *
     * [--set-user=<user>]
     * : Set user for alias.
     *
     * [--set-url=<url>]
     * : Set url for alias.
     *
     * [--set-path=<path>]
     * : Set path for alias.
     *
     * [--set-ssh=<ssh>]
     * : Set ssh for alias.
     *
     * [--set-http=<http>]
     * : Set http for alias.
     *
     * [--grouping=<grouping>]
     * : For grouping multiple aliases.
     *
     * [--config=<config>]
     * : Config file to be considered for operations.
     * ---
     * default: global
     * options:
     *   - global
     *   - project
     * ---
     *
     * ## EXAMPLES
     *
     *     # Add alias to global config.
     *     $ wp cli alias add @prod  --set-ssh=login@host --set-path=/path/to/wordpress/install/ --set-user=wpcli
     *     Success: Added '@prod' alias.
     *
     *     # Add alias to project config.
     *     $ wp cli alias add @prod --set-ssh=login@host --set-path=/path/to/wordpress/install/ --set-user=wpcli --config=project
     *     Success: Added '@prod' alias.
     *
     *     # Add group of aliases.
     *     $ wp cli alias add @multiservers --grouping=servera,serverb
     *     Success: Added '@multiservers' alias.
     */
    
public function add$args$assoc_args ) {

        
$config = ( ! empty( $assoc_args['config'] ) ? $assoc_args['config'] : 'global' );

        list( 
$config_path$aliases ) = $this->get_aliases_data$config''true );

        
$this->validate_config_file$config_path );

        
$alias    $args[0];
        
$grouping Utils\get_flag_value$assoc_args'grouping' );

        
$this->validate_input$assoc_args$grouping );

        if ( isset( 
$aliases$alias ] ) ) {
            
WP_CLI::error"Key '{$alias}' exists already." );
        }

        if ( 
null === $grouping ) {
            
$aliases $this->build_aliases$aliases$alias$assoc_argsfalse );
        } else {
            
$aliases $this->build_aliases$aliases$alias$assoc_argstrue$grouping );
        }

        
$this->process_aliases$aliases$alias$config_path'Added' );
    }

    
/**
     * Deletes an alias.
     *
     * ## OPTIONS
     *
     * <key>
     * : Key for the alias.
     *
     * [--config=<config>]
     * : Config file to be considered for operations.
     * ---
     * options:
     *   - global
     *   - project
     * ---
     *
     * ## EXAMPLES
     *
     *     # Delete alias.
     *     $ wp cli alias delete @prod
     *     Success: Deleted '@prod' alias.
     *
     *     # Delete project alias.
     *     $ wp cli alias delete @prod --config=project
     *     Success: Deleted '@prod' alias.
     */
    
public function delete$args$assoc_args ) {

        list( 
$alias ) = $args;

        
$config = ( ! empty( $assoc_args['config'] ) ? $assoc_args['config'] : '' );

        list( 
$config_path$aliases ) = $this->get_aliases_data$config$alias );

        
$this->validate_config_file$config_path );

        if ( empty( 
$aliases$alias ] ) ) {
            
WP_CLI::error"No alias found with key '{$alias}'." );
        }

        unset( 
$aliases$alias ] );
        
$this->process_aliases$aliases$alias$config_path'Deleted' );

    }

    
/**
     * Updates an alias.
     *
     * ## OPTIONS
     *
     * <key>
     * : Key for the alias.
     *
     * [--set-user=<user>]
     * : Set user for alias.
     *
     * [--set-url=<url>]
     * : Set url for alias.
     *
     * [--set-path=<path>]
     * : Set path for alias.
     *
     * [--set-ssh=<ssh>]
     * : Set ssh for alias.
     *
     * [--set-http=<http>]
     * : Set http for alias.
     *
     * [--grouping=<grouping>]
     * : For grouping multiple aliases.
     *
     * [--config=<config>]
     * : Config file to be considered for operations.
     * ---
     * options:
     *   - global
     *   - project
     * ---
     *
     * ## EXAMPLES
     *
     *     # Update alias.
     *     $ wp cli alias update @prod --set-user=newuser --set-path=/new/path/to/wordpress/install/
     *     Success: Updated 'prod' alias.
     *
     *     # Update project alias.
     *     $ wp cli alias update @prod --set-user=newuser --set-path=/new/path/to/wordpress/install/ --config=project
     *     Success: Updated 'prod' alias.
     */
    
public function update$args$assoc_args ) {

        
$config   = ( ! empty( $assoc_args['config'] ) ? $assoc_args['config'] : '' );
        
$alias    $args[0];
        
$grouping Utils\get_flag_value$assoc_args'grouping' );

        list( 
$config_path$aliases ) = $this->get_aliases_data$config$aliastrue );

        
$this->validate_config_file$config_path );

        
$this->validate_input$assoc_args$grouping );

        if ( empty( 
$aliases$alias ] ) ) {
            
WP_CLI::error"No alias found with key '{$alias}'." );
        }

        if ( 
null === $grouping ) {
            
$aliases $this->build_aliases$aliases$alias$assoc_argsfalse''true );
        } else {
            
$aliases $this->build_aliases$aliases$alias$assoc_argstrue$groupingtrue );
        }

        
$this->process_aliases$aliases$alias$config_path'Updated' );
    }

    
/**
     * Get config path and aliases data based on config type.
     *
     * @param string $config             Type of config to get data from.
     * @param string $alias              Alias to be used for Add/Update/Delete.
     * @param bool   $create_config_file Optional. If a config file doesn't exist,
     *                                   should it be created? Defaults to false.
     *
     * @return array Config Path and Aliases in it.
     * @throws ExitException
     */
    
private function get_aliases_data$config$alias$create_config_file false ) {

        
$global_config_path WP_CLI::get_runner()->get_global_config_path$create_config_file );
        
$global_aliases     Spyc::YAMLLoad$global_config_path );

        
$project_config_path WP_CLI::get_runner()->get_project_config_path();
        
$project_aliases     Spyc::YAMLLoad$project_config_path );

        if ( 
'global' === $config ) {
            
$config_path $global_config_path;
            
$aliases     $global_aliases;
        } elseif ( 
'project' === $config ) {
            
$config_path $project_config_path;
            
$aliases     $project_aliases;
        } else {

            
$is_global_alias  array_key_exists$alias$global_aliases );
            
$is_project_alias array_key_exists$alias$project_aliases );

            if ( 
$is_global_alias && $is_project_alias ) {
                
WP_CLI::error"Key '{$alias}' found in more than one path. Please pass --config param." );
            } elseif ( 
$is_global_alias ) {
                
$config_path $global_config_path;
                
$aliases     $global_aliases;
            } else {
                
$config_path $project_config_path;
                
$aliases     $project_aliases;
            }
        }

        return [ 
$config_path$aliases ];

    }

    
/**
     * Check if the config file exists and is writable.
     *
     * @param string $config_path Path to config file.
     *
     * @return void
     */
    
private function validate_config_file$config_path ) {
        if ( ! 
file_exists$config_path ) || ! is_writable$config_path ) ) {
            
WP_CLI::error"Config file does not exist: {$config_path});
        }
    }

    
/**
     * Return aliases array.
     *
     * @param array  $aliases     Current aliases data.
     * @param string $alias       Name of alias.
     * @param array  $assoc_args  Associative arguments.
     * @param bool   $is_grouping Check if its a grouping operation.
     * @param string $grouping    Grouping value.
     * @param bool   $is_update   Is this an update operation?
     *
     * @return mixed
     */
    
private function build_aliases$aliases$alias$assoc_args$is_grouping$grouping ''$is_update false ) {

        if ( 
$is_grouping ) {
            
$valid_assoc_args = [ 'config''grouping' ];
            
$invalid_args     array_diffarray_keys$assoc_args ), $valid_assoc_args );

            
// Check for invalid args.
            
if ( ! empty( $invalid_args ) ) {
                
$args_info implode','$invalid_args );
                
WP_CLI::error"--grouping argument works alone. Found invalid arg(s) '$args_info'." );
            }
        }

        if ( 
$is_update ) {
            
$this->validate_alias_type$aliases$alias$assoc_args$grouping );
        }

        if ( ! 
$is_grouping ) {
            foreach ( 
$assoc_args as $key => $value ) {
                if ( 
strpos$key'set-' ) !== false ) {
                    
$alias_key_info explode'-'$key );
                    
$alias_key      = empty( $alias_key_info[1] ) ? '' $alias_key_info[1];
                    if ( ! empty( 
$alias_key ) && ! empty( $value ) ) {
                        
$aliases$alias ][ $alias_key ] = $value;
                    }
                }
            }
        } else {

            if ( ! empty( 
$grouping ) ) {
                
$group_alias_list  explode','$grouping );
                
$group_alias       array_map(
                    function ( 
$current_alias ) {
                        return 
'@' ltrim$current_alias'@' );
                    },
                    
$group_alias_list
                
);
                
$aliases$alias ] = $group_alias;
            }
        }

        return 
$aliases;
    }

    
/**
     * Validate input of passed arguments.
     *
     * @param array  $assoc_args Arguments array.
     * @param string $grouping   Grouping argument value.
     *
     * @throws ExitException
     */
    
private function validate_input$assoc_args$grouping ) {
        
// Check if valid arguments were passed.
        
$arg_match preg_grep'/^set-(\w+)/i'array_keys$assoc_args ) );

        
// Verify passed-arguments.
        
if ( empty( $grouping ) && empty( $arg_match ) ) {
            
WP_CLI::error'No valid arguments passed.' );
        }

        
// Check whether passed arguments contain value or not.
        
$assoc_arg_values array_filterarray_intersect_key$assoc_argsarray_flip$arg_match ) ) );

        if ( empty( 
$grouping ) && empty( $assoc_arg_values ) ) {
            
WP_CLI::error'No value passed to arguments.' );
        }
    }

    
/**
     * Validate alias type before update.
     *
     * @param array  $aliases    Existing aliases data.
     * @param string $alias      Alias Name.
     * @param array  $assoc_args Arguments array.
     * @param string $grouping   Grouping argument value.
     *
     * @throws ExitException
     */
    
private function validate_alias_type$aliases$alias$assoc_args$grouping ) {

        
$alias_data $aliases$alias ];

        
$group_aliases_match preg_grep'/^@(\w+)/i'$alias_data );
        
$arg_match           preg_grep'/^set-(\w+)/i'array_keys$assoc_args ) );

        if ( ! empty( 
$group_aliases_match ) && ! empty( $arg_match ) ) {
            
WP_CLI::error'Trying to update group alias with invalid arguments.' );
        } elseif ( empty( 
$group_aliases_match ) && ! empty( $grouping ) ) {
            
WP_CLI::error'Trying to update simple alias with invalid --grouping argument.' );
        }
    }

    
/**
     * Save aliases data to config file.
     *
     * @param array  $aliases     Current aliases data.
     * @param string $alias       Name of alias.
     * @param string $config_path Path to config file.
     * @param string $operation   Current operation string fro message.
     */
    
private function process_aliases$aliases$alias$config_path$operation '' ) {
        
// Convert data to YAML string.
        
$yaml_data Spyc::YAMLDump$aliases );

        
// Add data in config file.
        
if ( file_put_contents$config_path$yaml_data ) ) {
            
WP_CLI::success"$operation '{$alias}' alias." );
        }
    }
}
<?
php

use Composer\Semver\Comparator;
use 
WP_CLI\Completions;
use 
WP_CLI\Formatter;
use 
WP_CLI\Process;
use 
WP_CLI\Utils;

/**
 * Reviews current WP-CLI info, checks for updates, or views defined aliases.
 *
 * ## EXAMPLES
 *
 *     # Display the version currently installed.
 *     $ wp cli version
 *     WP-CLI 0.24.1
 *
 *     # Check for updates to WP-CLI.
 *     $ wp cli check-update
 *     Success: WP-CLI is at the latest version.
 *
 *     # Update WP-CLI to the latest stable release.
 *     $ wp cli update
 *     You have version 0.24.0. Would you like to update to 0.24.1? [y/n] y
 *     Downloading from https://github.com/wp-cli/wp-cli/releases/download/v0.24.1/wp-cli-0.24.1.phar...
 *     New version works. Proceeding to replace.
 *     Success: Updated WP-CLI to 0.24.1.
 *
 *     # Clear the internal WP-CLI cache.
 *     $ wp cli cache clear
 *     Success: Cache cleared.
 *
 * @when before_wp_load
 */
class CLI_Command extends WP_CLI_Command {

    private function 
command_to_array$command ) {
        
$dump = [
            
'name'        => $command->get_name(),
            
'description' => $command->get_shortdesc(),
            
'longdesc'    => $command->get_longdesc(),
        ];

        foreach ( 
$command->get_subcommands() as $subcommand ) {
            
$dump['subcommands'][] = $this->command_to_array$subcommand );
        }

        if ( empty( 
$dump['subcommands'] ) ) {
            
$dump['synopsis'] = (string) $command->get_synopsis();
        }

        return 
$dump;
    }

    
/**
     * Prints WP-CLI version.
     *
     * ## EXAMPLES
     *
     *     # Display CLI version.
     *     $ wp cli version
     *     WP-CLI 0.24.1
     */
    
public function version() {
        
WP_CLI::line'WP-CLI ' WP_CLI_VERSION );
    }

    
/**
     * Prints various details about the WP-CLI environment.
     *
     * Helpful for diagnostic purposes, this command shares:
     *
     * * OS information.
     * * Shell information.
     * * PHP binary used.
     * * PHP binary version.
     * * php.ini configuration file used (which is typically different than web).
     * * WP-CLI root dir: where WP-CLI is installed (if non-Phar install).
     * * WP-CLI global config: where the global config YAML file is located.
     * * WP-CLI project config: where the project config YAML file is located.
     * * WP-CLI version: currently installed version.
     *
     * See [config docs](https://wp-cli.org/config/) for more details on global
     * and project config YAML files.
     *
     * ## OPTIONS
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: list
     * options:
     *   - list
     *   - json
     * ---
     *
     * ## EXAMPLES
     *
     *     # Display various data about the CLI environment.
     *     $ wp cli info
     *     OS:  Linux 4.10.0-42-generic #46~16.04.1-Ubuntu SMP Mon Dec 4 15:57:59 UTC 2017 x86_64
     *     Shell:   /usr/bin/zsh
     *     PHP binary:  /usr/bin/php
     *     PHP version: 7.1.12-1+ubuntu16.04.1+deb.sury.org+1
     *     php.ini used:    /etc/php/7.1/cli/php.ini
     *     WP-CLI root dir:    phar://wp-cli.phar
     *     WP-CLI packages dir:    /home/person/.wp-cli/packages/
     *     WP-CLI global config:
     *     WP-CLI project config:
     *     WP-CLI version: 1.5.0
     */
    
public function info$_$assoc_args ) {
        
// php_uname() $mode argument was only added with PHP 7.0+. Fall back to
        // entire string for older versions.
        
$system_os PHP_MAJOR_VERSION 7
            
php_uname()
            : 
sprintf(
                
'%s %s %s %s',
                
php_uname's' ),
                
php_uname'r' ),
                
php_uname'v' ),
                
php_uname'm' )
            );

        
$shell getenv'SHELL' );
        if ( ! 
$shell && Utils\is_windows() ) {
            
$shell getenv'ComSpec' );
        }

        
$php_bin Utils\get_php_binary();

        
$runner WP_CLI::get_runner();

        
$packages_dir $runner->get_packages_dir_path();
        if ( ! 
is_dir$packages_dir ) ) {
            
$packages_dir null;
        }

        if ( 
Utils\get_flag_value$assoc_args'format' ) === 'json' ) {
            
$info = [
                
'system_os'                => $system_os,
                
'shell'                    => $shell,
                
'php_binary_path'          => $php_bin,
                
'php_version'              => PHP_VERSION,
                
'php_ini_used'             => get_cfg_var'cfg_file_path' ),
                
'mysql_binary_path'        => Utils\get_mysql_binary_path(),
                
'mysql_version'            => Utils\get_mysql_version(),
                
'sql_modes'                => Utils\get_sql_modes(),
                
'wp_cli_dir_path'          => WP_CLI_ROOT,
                
'wp_cli_vendor_path'       => WP_CLI_VENDOR_DIR,
                
'wp_cli_phar_path'         => defined'WP_CLI_PHAR_PATH' ) ? WP_CLI_PHAR_PATH '',
                
'wp_cli_packages_dir_path' => $packages_dir,
                
'global_config_path'       => $runner->global_config_path,
                
'project_config_path'      => $runner->project_config_path,
                
'wp_cli_version'           => WP_CLI_VERSION,
            ];

            
WP_CLI::linejson_encode$info ) );
        } else {
            
WP_CLI::line"OS:\t" $system_os );
            
WP_CLI::line"Shell:\t" $shell );
            
WP_CLI::line"PHP binary:\t" $php_bin );
            
WP_CLI::line"PHP version:\t" PHP_VERSION );
            
WP_CLI::line"php.ini used:\t" get_cfg_var'cfg_file_path' ) );
            
WP_CLI::line"MySQL binary:\t" Utils\get_mysql_binary_path() );
            
WP_CLI::line"MySQL version:\t" Utils\get_mysql_version() );
            
WP_CLI::line"SQL modes:\t" implode','Utils\get_sql_modes() ) );
            
WP_CLI::line"WP-CLI root dir:\t" WP_CLI_ROOT );
            
WP_CLI::line"WP-CLI vendor dir:\t" WP_CLI_VENDOR_DIR );
            
WP_CLI::line"WP_CLI phar path:\t" . ( defined'WP_CLI_PHAR_PATH' ) ? WP_CLI_PHAR_PATH '' ) );
            
WP_CLI::line"WP-CLI packages dir:\t" $packages_dir );
            
WP_CLI::line"WP-CLI global config:\t" $runner->global_config_path );
            
WP_CLI::line"WP-CLI project config:\t" $runner->project_config_path );
            
WP_CLI::line"WP-CLI version:\t" WP_CLI_VERSION );
        }
    }

    
/**
     * Checks to see if there is a newer version of WP-CLI available.
     *
     * Queries the Github releases API. Returns available versions if there are
     * updates available, or success message if using the latest release.
     *
     * ## OPTIONS
     *
     * [--patch]
     * : Only list patch updates.
     *
     * [--minor]
     * : Only list minor updates.
     *
     * [--major]
     * : Only list major updates.
     *
     * [--field=<field>]
     * : Prints the value of a single field for each update.
     *
     * [--fields=<fields>]
     * : Limit the output to specific object fields. Defaults to version,update_type,package_url.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - count
     *   - yaml
     * ---
     *
     * ## EXAMPLES
     *
     *     # Check for update.
     *     $ wp cli check-update
     *     Success: WP-CLI is at the latest version.
     *
     *     # Check for update and new version is available.
     *     $ wp cli check-update
     *     +---------+-------------+-------------------------------------------------------------------------------+
     *     | version | update_type | package_url                                                                   |
     *     +---------+-------------+-------------------------------------------------------------------------------+
     *     | 0.24.1  | patch       | https://github.com/wp-cli/wp-cli/releases/download/v0.24.1/wp-cli-0.24.1.phar |
     *     +---------+-------------+-------------------------------------------------------------------------------+
     *
     * @subcommand check-update
     */
    
public function check_update$_$assoc_args ) {
        
$updates $this->get_updates$assoc_args );

        if ( 
$updates ) {
            
$formatter = new Formatter(
                
$assoc_args,
                [ 
'version''update_type''package_url' ]
            );
            
$formatter->display_items$updates );
        } elseif ( empty( 
$assoc_args['format'] ) || 'table' === $assoc_args['format'] ) {
            
$update_type $this->get_update_type_str$assoc_args );
            
WP_CLI::success"WP-CLI is at the latest{$update_type}version." );
        }
    }

    
/**
     * Updates WP-CLI to the latest release.
     *
     * Default behavior is to check the releases API for the newest stable
     * version, and prompt if one is available.
     *
     * Use `--stable` to install or reinstall the latest stable version.
     *
     * Use `--nightly` to install the latest built version of the master branch.
     * While not recommended for production, nightly contains the latest and
     * greatest, and should be stable enough for development and staging
     * environments.
     *
     * Only works for the Phar installation mechanism.
     *
     * ## OPTIONS
     *
     * [--patch]
     * : Only perform patch updates.
     *
     * [--minor]
     * : Only perform minor updates.
     *
     * [--major]
     * : Only perform major updates.
     *
     * [--stable]
     * : Update to the latest stable release. Skips update check.
     *
     * [--nightly]
     * : Update to the latest built version of the master branch. Potentially unstable.
     *
     * [--yes]
     * : Do not prompt for confirmation.
     *
     * [--insecure]
     * : Retry without certificate validation if TLS handshake fails. Note: This makes the request vulnerable to a MITM attack.
     *
     * ## EXAMPLES
     *
     *     # Update CLI.
     *     $ wp cli update
     *     You have version 0.24.0. Would you like to update to 0.24.1? [y/n] y
     *     Downloading from https://github.com/wp-cli/wp-cli/releases/download/v0.24.1/wp-cli-0.24.1.phar...
     *     New version works. Proceeding to replace.
     *     Success: Updated WP-CLI to 0.24.1.
     */
    
public function update$_$assoc_args ) {
        if ( ! 
Utils\inside_phar() ) {
            
WP_CLI::error'You can only self-update Phar files.' );
        }

        
$old_phar realpath$_SERVER['argv'][0] );

        if ( ! 
is_writable$old_phar ) ) {
            
WP_CLI::errorsprintf'%s is not writable by current user.'$old_phar ) );
        } elseif ( ! 
is_writabledirname$old_phar ) ) ) {
            
WP_CLI::errorsprintf'%s is not writable by current user.'dirname$old_phar ) ) );
        }

        if ( 
Utils\get_flag_value$assoc_args'nightly' ) ) {
            
WP_CLI::confirmsprintf'You have version %s. Would you like to update to the latest nightly?'WP_CLI_VERSION ), $assoc_args );
            
$download_url 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli-nightly.phar';
            
$md5_url      'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli-nightly.phar.md5';
        } elseif ( 
Utils\get_flag_value$assoc_args'stable' ) ) {
            
WP_CLI::confirmsprintf'You have version %s. Would you like to update to the latest stable release?'WP_CLI_VERSION ), $assoc_args );
            
$download_url 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar';
            
$md5_url      'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar.md5';
        } else {

            
$updates $this->get_updates$assoc_args );

            if ( empty( 
$updates ) ) {
                
$update_type $this->get_update_type_str$assoc_args );
                
WP_CLI::success"WP-CLI is at the latest{$update_type}version." );
                return;
            }

            
$newest $updates[0];

            
WP_CLI::confirmsprintf'You have version %s. Would you like to update to %s?'WP_CLI_VERSION$newest['version'] ), $assoc_args );

            
$download_url $newest['package_url'];
            
$md5_url      str_replace'.phar''.phar.md5'$download_url );
        }

        
WP_CLI::logsprintf'Downloading from %s...'$download_url ) );

        
$temp Utils\get_temp_dir() . uniqid'wp_'true ) . '.phar';

        
$headers = [];
        
$options = [
            
'timeout'  => 600,  // 10 minutes ought to be enough for everybody.
            
'filename' => $temp,
            
'insecure' => (bool) Utils\get_flag_value$assoc_args'insecure'false ),
        ];

        
Utils\http_request'GET'$download_urlnull$headers$options );

        unset( 
$options['filename'] );
        
$md5_response Utils\http_request'GET'$md5_urlnull$headers$options );
        if ( 
'20' !== substr$md5_response->status_code0) ) {
            
WP_CLI::error"Couldn't access md5 hash for release (HTTP code {$md5_response->status_code})." );
        }
        
$md5_file     md5_file$temp );
        
$release_hash trim$md5_response->body );
        if ( 
$md5_file === $release_hash ) {
            
WP_CLI::log'md5 hash verified: ' $release_hash );
        } else {
            
WP_CLI::error"md5 hash for download ({$md5_file}) is different than the release hash ({$release_hash})." );
        }

        
$allow_root WP_CLI::get_runner()->config['allow-root'] ? '--allow-root' '';
        
$php_binary Utils\get_php_binary();
        
$process    Process::create"{$php_binary} $temp --info {$allow_root});
        
$result     $process->run();
        if ( 
!== $result->return_code || false === stripos$result->stdout'WP-CLI version' ) ) {
            
$multi_line explodePHP_EOL$result->stderr );
            
WP_CLI::error_multi_line$multi_line );
            
WP_CLI::error'The downloaded PHAR is broken, try running wp cli update again.' );
        }

        
WP_CLI::log'New version works. Proceeding to replace.' );

        
$mode fileperms$old_phar ) & 511;

        if ( 
false === chmod$temp$mode ) ) {
            
WP_CLI::errorsprintf'Cannot chmod %s.'$temp ) );
        }

        
class_exists'\cli\Colors' ); // This autoloads \cli\Colors - after we move the file we no longer have access to this class.

        
if ( false === rename$temp$old_phar ) ) {
            
WP_CLI::errorsprintf'Cannot move %s to %s'$temp$old_phar ) );
        }

        if ( 
Utils\get_flag_value$assoc_args'nightly'false ) ) {
            
$updated_version 'the latest nightly release';
        } elseif ( 
Utils\get_flag_value$assoc_args'stable'false ) ) {
            
$updated_version 'the latest stable release';
        } else {
            
$updated_version = isset( $newest['version'] ) ? $newest['version'] : '<not provided>';
        }
        
WP_CLI::successsprintf'Updated WP-CLI to %s.'$updated_version ) );
    }

    
/**
     * Returns update information.
     */
    
private function get_updates$assoc_args ) {
        
$url 'https://api.github.com/repos/wp-cli/wp-cli/releases?per_page=100';

        
$options = [
            
'timeout'  => 30,
            
'insecure' => (bool) Utils\get_flag_value$assoc_args'insecure'false ),
        ];

        
$headers = [
            
'Accept' => 'application/json',
        ];

        
$github_token getenv'GITHUB_TOKEN' );
        if ( 
false !== $github_token ) {
            
$headers['Authorization'] = 'token ' $github_token;
        }

        
$response Utils\http_request'GET'$urlnull$headers$options );

        if ( ! 
$response->success || 200 !== $response->status_code ) {
            
WP_CLI::errorsprintf'Failed to get latest version (HTTP code %d).'$response->status_code ) );
        }

        
$release_data json_decode$response->body );

        
$updates = [
            
'major' => false,
            
'minor' => false,
            
'patch' => false,
        ];
        foreach ( 
$release_data as $release ) {

            
// Get rid of leading "v" if there is one set.
            
$release_version $release->tag_name;
            if ( 
'v' === substr$release_version0) ) {
                
$release_version ltrim$release_version'v' );
            }

            
$update_type Utils\get_named_sem_ver$release_versionWP_CLI_VERSION );
            if ( ! 
$update_type ) {
                continue;
            }

            if ( ! isset( 
$release->assets[0]->browser_download_url ) ) {
                continue;
            }

            if ( ! empty( 
$updates$update_type ] ) && ! Comparator::greaterThan$release_version$updates$update_type ]['version'] ) ) {
                continue;
            }

            
$updates$update_type ] = [
                
'version'     => $release_version,
                
'update_type' => $update_type,
                
'package_url' => $release->assets[0]->browser_download_url,
            ];
        }

        foreach ( 
$updates as $type => $value ) {
            if ( empty( 
$value ) ) {
                unset( 
$updates$type ] );
            }
        }

        foreach ( [ 
'major''minor''patch' ] as $type ) {
            if ( 
true === Utils\get_flag_value$assoc_args$type ) ) {
                return ! empty( 
$updates$type ] ) ? [ $updates$type ] ] : false;
            }
        }

        if ( empty( 
$updates ) && preg_match'#-alpha-(.+)$#'WP_CLI_VERSION$matches ) ) {
            
$version_url 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/NIGHTLY_VERSION';
            
$response    Utils\http_request'GET'$version_urlnull, [], $options );
            if ( ! 
$response->success || 200 !== $response->status_code ) {
                
WP_CLI::errorsprintf'Failed to get current nightly version (HTTP code %d)'$response->status_code ) );
            }
            
$nightly_version trim$response->body );
            if ( 
WP_CLI_VERSION !== $nightly_version ) {
                
$updates['nightly'] = [
                    
'version'     => $nightly_version,
                    
'update_type' => 'nightly',
                    
'package_url' => 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli-nightly.phar',
                ];
            }
        }

        return 
array_values$updates );
    }

    
/**
     * Dumps the list of global parameters, as JSON or in var_export format.
     *
     * ## OPTIONS
     *
     * [--with-values]
     * : Display current values also.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: json
     * options:
     *   - var_export
     *   - json
     * ---
     *
     * ## EXAMPLES
     *
     *     # Dump the list of global parameters.
     *     $ wp cli param-dump --format=var_export
     *     array (
     *       'path' =>
     *       array (
     *         'runtime' => '=<path>',
     *         'file' => '<path>',
     *         'synopsis' => '',
     *         'default' => NULL,
     *         'multiple' => false,
     *         'desc' => 'Path to the WordPress files.',
     *       ),
     *       'url' =>
     *       array (
     *
     * @subcommand param-dump
     */
    
public function param_dump$_$assoc_args ) {
        
$spec WP_CLI::get_configurator()->get_spec();

        if ( 
Utils\get_flag_value$assoc_args'with-values' ) ) {
            
$config WP_CLI::get_configurator()->to_array();
            
// Copy current config values to $spec.
            
foreach ( $spec as $key => $value ) {
                
$current null;
                if ( isset( 
$config[0][ $key ] ) ) {
                    
$current $config[0][ $key ];
                }
                
$spec$key ]['current'] = $current;
            }
        }

        if ( 
'var_export' === Utils\get_flag_value$assoc_args'format' ) ) {
            
var_export$spec );
        } else {
            echo 
json_encode$spec );
        }
    }

    
/**
     * Dumps the list of installed commands, as JSON.
     *
     * ## EXAMPLES
     *
     *     # Dump the list of installed commands.
     *     $ wp cli cmd-dump
     *     {"name":"wp","description":"Manage WordPress through the command-line.","longdesc":"\n\n## GLOBAL PARAMETERS\n\n  --path=<path>\n      Path to the WordPress files.\n\n  --ssh=<ssh>\n      Perform operation against a remote server over SSH (or a container using scheme of "docker" or "docker-compose").\n\n  --url=<url>\n      Pretend request came from given URL. In multisite, this argument is how the target site is specified. \n\n  --user=<id|login|email>\n
     *
     * @subcommand cmd-dump
     */
    
public function cmd_dump() {
        echo 
json_encode$this->command_to_arrayWP_CLI::get_root_command() ) );
    }

    
/**
     * Generates tab completion strings.
     *
     * ## OPTIONS
     *
     * --line=<line>
     * : The current command line to be executed.
     *
     * --point=<point>
     * : The index to the current cursor position relative to the beginning of the command.
     *
     * ## EXAMPLES
     *
     *     # Generate tab completion strings.
     *     $ wp cli completions --line='wp eva' --point=100
     *     eval
     *     eval-file
     */
    
public function completions$_$assoc_args ) {
        
$line  substr$assoc_args['line'], 0$assoc_args['point'] );
        
$compl = new Completions$line );
        
$compl->render();
    }

    
/**
     * Get a string representing the type of update being checked for.
     */
    
private function get_update_type_str$assoc_args ) {
        
$update_type ' ';
        foreach ( [ 
'major''minor''patch' ] as $type ) {
            if ( 
true === Utils\get_flag_value$assoc_args$type ) ) {
                
$update_type ' ' $type ' ';
                break;
            }
        }
        return 
$update_type;
    }

    
/**
     * Detects if a command exists
     *
     * This commands checks if a command is registered with WP-CLI.
     * If the command is found then it returns with exit status 0.
     * If the command doesn't exist, then it will exit with status 1.
     *
     * ## OPTIONS
     * <command_name>...
     * : The command
     *
     * ## EXAMPLES
     *
     *     # The "site delete" command is registered.
     *     $ wp cli has-command "site delete"
     *     $ echo $?
     *     0
     *
     *     # The "foo bar" command is not registered.
     *     $ wp cli has-command "foo bar"
     *     $ echo $?
     *     1
     *
     * @subcommand has-command
     *
     * @when after_wp_load
     */
    
public function has_command$_$assoc_args ) {

        
// If command is input as a string, then explode it into array.
        
$command explode' 'implode' '$_ ) );

        
WP_CLI::haltis_arrayWP_CLI::get_runner()->find_command_to_run$command ) ) ? );
    }
}
<?
php

use cli\Shell;
use 
WP_CLI\Dispatcher;
use 
WP_CLI\Utils;

class 
Help_Command extends WP_CLI_Command {

    
/**
     * Gets help on WP-CLI, or on a specific command.
     *
     * ## OPTIONS
     *
     * [<command>...]
     * : Get help on a specific command.
     *
     * ## EXAMPLES
     *
     *     # get help for `core` command
     *     wp help core
     *
     *     # get help for `core download` subcommand
     *     wp help core download
     */
    
public function __invoke$args$assoc_args ) {
        
$r WP_CLI::get_runner()->find_command_to_run$args );

        if ( 
is_array$r ) ) {
            list( 
$command ) = $r;

            
self::show_help$command );
            exit;
        }
    }

    private static function 
show_help$command ) {
        
$out self::get_initial_markdown$command );

        
// Remove subcommands if in columns - will wordwrap separately.
        
$subcommands       '';
        
$column_subpattern '[ \t]+[^\t]+\t+';
        if ( 
preg_match'/(^## SUBCOMMANDS[^\n]*\n+' $column_subpattern '.+?)(?:^##|\z)/ms'$out$matchesPREG_OFFSET_CAPTURE ) ) {
            
$subcommands        $matches[1][0];
            
$subcommands_header "## SUBCOMMANDS\n";
            
$out                substr_replace$out$subcommands_header$matches[1][1], strlen$subcommands ) );
        }

        
$out .= self::parse_reference_links$command->get_longdesc() );

        
// Definition lists.
        
$out preg_replace_callback'/([^\n]+)\n: (.+?)(\n\n|$)/s', [ __CLASS__'rewrap_param_desc' ], $out );

        
// Ensure lines with no leading whitespace that aren't section headers are indented.
        
$out preg_replace'/^((?! |\t|##).)/m'"\t$1"$out );

        
$tab str_repeat' ');

        
// Need to de-tab for wordwrapping to work properly.
        
$out str_replace"\t"$tab$out );

        
$wordwrap_width Shell::columns();

        
// Wordwrap with indent.
        
$out preg_replace_callback(
            
'/^( *)([^\n]+)\n/m',
            function ( 
$matches ) use ( $wordwrap_width ) {
                return 
$matches[1] . str_replace"\n""\n{$matches[1]}"wordwrap$matches[2], $wordwrap_width strlen$matches[1] ) ) ) . "\n";
            },
            
$out
        
);

        if ( 
$subcommands ) {
            
// Wordwrap with column indent.
            
$subcommands preg_replace_callback(
                
'/^(' $column_subpattern ')([^\n]+)\n/m',
                function ( 
$matches ) use ( $wordwrap_width$tab ) {
                    
// Need to de-tab for wordwrapping to work properly.
                    
$matches[1]  = str_replace"\t"$tab$matches[1] );
                    
$matches[2]  = str_replace"\t"$tab$matches[2] );
                    
$padding_len strlen$matches[1] );
                    
$padding     str_repeat' '$padding_len );
                    return 
$matches[1] . str_replace"\n""\n$padding"wordwrap$matches[2], $wordwrap_width $padding_len ) ) . "\n";
                },
                
$subcommands
            
);

            
// Put subcommands back.
            
$out str_replace$subcommands_header$subcommands$out );
        }

        
// Section headers.
        
$out preg_replace'/^## ([A-Z ]+)/m'WP_CLI::colorize'%9\1%n' ), $out );

        
self::pass_through_pager$out );
    }

    private static function 
rewrap_param_desc$matches ) {
        
$param $matches[1];
        
$desc  self::indent"\t\t"$matches[2] );
        return 
"\t$param\n$desc\n\n";
    }

    private static function 
indent$whitespace$text ) {
        
$lines explode"\n"$text );
        foreach ( 
$lines as &$line ) {
            
$line $whitespace $line;
        }
        return 
implode"\n"$lines );
    }

    private static function 
pass_through_pager$out ) {

        if ( ! 
Utils\check_proc_availablenull /*context*/true /*return*/ ) ) {
            
WP_CLI::line$out );
            
WP_CLI::debug'Warning: check_proc_available() failed in pass_through_pager().''help' );
            return -
1;
        }

        
$pager getenv'PAGER' );
        if ( 
false === $pager ) {
            
$pager Utils\is_windows() ? 'more' 'less -r';
        }

        
// For Windows 7 need to set code page to something other than Unicode (65001) to get around "Not enough memory." error with `more.com` on PHP 7.1+.
        
if ( 'more' === $pager && defined'PHP_WINDOWS_VERSION_MAJOR' ) && PHP_WINDOWS_VERSION_MAJOR 10 && function_exists'sapi_windows_cp_set' ) ) {
            
// Note will also apply to Windows 8 (see https://msdn.microsoft.com/en-us/library/windows/desktop/ms724832.aspx) but probably harmless anyway.
            
$cp getenv'WP_CLI_WINDOWS_CODE_PAGE' ) ?: 1252// Code page 1252 is the most used so probably the most compat.
            // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions -- Wrapped in function_exists() call.
            
sapi_windows_cp_set$cp ); // `sapi_windows_cp_set()` introduced PHP 7.1.
        
}

        
// Convert string to file handle.
        
$fd fopen'php://temp''r+b' );
        
fwrite$fd$out );
        
rewind$fd );

        
$descriptorspec = [
            
=> $fd,
            
=> STDOUT,
            
=> STDERR,
        ];

        return 
proc_closeUtils\proc_open_compat$pager$descriptorspec$pipes ) );
    }

    private static function 
get_initial_markdown$command ) {
        
$name implode' 'Dispatcher\get_path$command ) );

        
$binding = [
            
'name'      => $name,
            
'shortdesc' => $command->get_shortdesc(),
        ];

        
$binding['synopsis'] = "$name " $command->get_synopsis();

        
$alias $command->get_alias();
        if ( 
$alias ) {
            
$binding['alias'] = $alias;
        }

        if ( 
$command->can_have_subcommands() ) {
            
$binding['has-subcommands']['subcommands'] = self::render_subcommands$command );
        }

        return 
Utils\mustache_render'man.mustache'$binding );
    }

    private static function 
render_subcommands$command ) {
        
$subcommands = [];
        foreach ( 
$command->get_subcommands() as $subcommand ) {

            if ( 
WP_CLI::get_runner()->is_command_disabled$subcommand ) ) {
                continue;
            }

            
$subcommands$subcommand->get_name() ] = $subcommand->get_shortdesc();
        }

        
$max_len self::get_max_lenarray_keys$subcommands ) );

        
$lines = [];
        foreach ( 
$subcommands as $name => $desc ) {
            
$lines[] = str_pad$name$max_len ) . "\t\t\t" $desc;
        }

        return 
$lines;
    }

    private static function 
get_max_len$strings ) {
        
$max_len 0;
        foreach ( 
$strings as $str ) {
            
$len strlen$str );
            if ( 
$len $max_len ) {
                
$max_len $len;
            }
        }

        return 
$max_len;
    }

    
/**
     * Parse reference links from longdescription.
     *
     * @param  string $longdesc The longdescription from the `$command->get_longdesc()`.
     * @return string The longdescription which has links as footnote.
     */
    
private static function parse_reference_links$longdesc ) {
        
$description '';
        foreach ( 
explode"\n"$longdesc ) as $line ) {
            if ( 
=== strpos$line'#' ) ) {
                break;
            }
            
$description .= $line "\n";
        }

        
// Fires if it has description text at the head of `$longdesc`.
        
if ( $description ) {
            
$links   = []; // An array of URLs from the description.
            
$pattern '/\[.+?\]\((https?:\/\/.+?)\)/';
            
$newdesc preg_replace_callback(
                
$pattern,
                function( 
$matches ) use ( &$links ) {
                    static 
$count 0;
                    
$count++;
                    
$links[] = $matches[1];
                    return 
str_replace'(' $matches[1] . ')''[' $count ']'$matches[0] );
                },
                
$description
            
);

            
$footnote   '';
            
$link_count count$links );
            for ( 
$i 0$i $link_count$i++ ) {
                
$n         $i 1;
                
$footnote .= '[' $n '] ' $links$i ] . "\n";
            }

            if ( 
$footnote ) {
                
$newdesc  trim$newdesc ) . "\n\n---\n" $footnote;
                
$longdesc str_replacetrim$description ), trim$newdesc ), $longdesc );
            }
        }

        return 
$longdesc;
    }
}
<?
php

if ( ! class_exists'Help_Command' ) ) {
    require_once 
__DIR__ '/src/Help_Command.php';
}

WP_CLI::add_command'help''Help_Command' );
<?
php

if ( ! class_exists'CLI_Command' ) ) {
    require_once 
__DIR__ '/src/CLI_Command.php';
}

if ( ! 
class_exists'CLI_Cache_Command' ) ) {
    require_once 
__DIR__ '/src/CLI_Cache_Command.php';
}

if ( ! 
class_exists'CLI_Alias_Command' ) ) {
    require_once 
__DIR__ '/src/CLI_Alias_Command.php';
}

WP_CLI::add_command'cli''CLI_Command' );

WP_CLI::add_command'cli cache''CLI_Cache_Command' );

WP_CLI::add_command'cli alias''CLI_Alias_Command' );
<?
php

// Store the path to the Phar early on for `Utils\phar-safe-path()` function.
define'WP_CLI_PHAR_PATH'getcwd() );

if ( 
file_exists'phar://wp-cli.phar/php/wp-cli.php' ) ) {
    
define'WP_CLI_ROOT''phar://wp-cli.phar' );
    include 
WP_CLI_ROOT '/php/wp-cli.php';
} elseif ( 
file_exists'phar://wp-cli.phar/vendor/wp-cli/wp-cli/php/wp-cli.php' ) ) {
    
define'WP_CLI_ROOT''phar://wp-cli.phar/vendor/wp-cli/wp-cli' );
    include 
WP_CLI_ROOT '/php/wp-cli.php';
} else {
    echo 
"Couldn't find 'php/wp-cli.php'. Was this Phar built correctly?";
    exit( 
);
}
#!/usr/bin/env php
<?php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2015 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A shell script to create a single-file class cache of the entire Mustache
 * library.
 *
 *     $ bin/build_bootstrap.php
 *
 * ... will create a `mustache.php` bootstrap file in the project directory,
 * containing all Mustache library classes. This file can then be included in
 * your project, rather than requiring the Mustache Autoloader.
 */
$baseDir realpath(dirname(__FILE__) . '/..');

require 
$baseDir '/src/Mustache/Autoloader.php';
Mustache_Autoloader::register();

// delete the old file
$file $baseDir '/mustache.php';
if (
file_exists($file)) {
    
unlink($file);
}

// and load the new one
SymfonyClassCollectionLoader::load(array(
    
'Mustache_Engine',
    
'Mustache_Cache',
    
'Mustache_Cache_AbstractCache',
    
'Mustache_Cache_FilesystemCache',
    
'Mustache_Cache_NoopCache',
    
'Mustache_Compiler',
    
'Mustache_Context',
    
'Mustache_Exception',
    
'Mustache_Exception_InvalidArgumentException',
    
'Mustache_Exception_LogicException',
    
'Mustache_Exception_RuntimeException',
    
'Mustache_Exception_SyntaxException',
    
'Mustache_Exception_UnknownFilterException',
    
'Mustache_Exception_UnknownHelperException',
    
'Mustache_Exception_UnknownTemplateException',
    
'Mustache_HelperCollection',
    
'Mustache_LambdaHelper',
    
'Mustache_Loader',
    
'Mustache_Loader_ArrayLoader',
    
'Mustache_Loader_CascadingLoader',
    
'Mustache_Loader_FilesystemLoader',
    
'Mustache_Loader_InlineLoader',
    
'Mustache_Loader_MutableLoader',
    
'Mustache_Loader_StringLoader',
    
'Mustache_Logger',
    
'Mustache_Logger_AbstractLogger',
    
'Mustache_Logger_StreamLogger',
    
'Mustache_Parser',
    
'Mustache_Template',
    
'Mustache_Tokenizer',
), 
dirname($file), basename($file'.php'));

/**
 * SymfonyClassCollectionLoader.
 *
 * Based heavily on the Symfony ClassCollectionLoader component, with all
 * the unnecessary bits removed.
 *
 * @license http://www.opensource.org/licenses/MIT
 * @author Fabien Potencier <[email protected]>
 */
class SymfonyClassCollectionLoader
{
    private static 
$loaded;

    const 
HEADER = <<<'EOS'
<?php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-%d Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
EOS;

    
/**
     * Loads a list of classes and caches them in one big file.
     *
     * @param array  $classes   An array of classes to load
     * @param string $cacheDir  A cache directory
     * @param string $name      The cache name prefix
     * @param string $extension File extension of the resulting file
     *
     * @throws InvalidArgumentException When class can't be loaded
     */
    
public static function load(array $classes$cacheDir$name$extension '.php')
    {
        
// each $name can only be loaded once per PHP process
        
if (isset(self::$loaded[$name])) {
            return;
        }

        
self::$loaded[$name] = true;

        
$content '';
        foreach (
$classes as $class) {
            if (!
class_exists($class) && !interface_exists($class) && (!function_exists('trait_exists') || !trait_exists($class))) {
                throw new 
InvalidArgumentException(sprintf('Unable to load class "%s"'$class));
            }

            
$r = new ReflectionClass($class);
            
$content .= preg_replace(array('/^\s*<\?php/''/\?>\s*$/'), ''file_get_contents($r->getFileName()));
        }

        
$cache  $cacheDir '/' $name $extension;
        
$header sprintf(self::HEADERstrftime('%Y'));
        
self::writeCacheFile($cache$header substr(self::stripComments('<?php ' $content), 5));
    }

    
/**
     * Writes a cache file.
     *
     * @param string $file    Filename
     * @param string $content Temporary file content
     *
     * @throws RuntimeException when a cache file cannot be written
     */
    
private static function writeCacheFile($file$content)
    {
        
$tmpFile tempnam(dirname($file), basename($file));
        if (
false !== @file_put_contents($tmpFile$content) && @rename($tmpFile$file)) {
            
chmod($file0666 & ~umask());

            return;
        }

        throw new 
RuntimeException(sprintf('Failed to write cache file "%s".'$file));
    }

    
/**
     * Removes comments from a PHP source string.
     *
     * We don't use the PHP php_strip_whitespace() function
     * as we want the content to be readable and well-formatted.
     *
     * @param string $source A PHP string
     *
     * @return string The PHP string with the comments removed
     */
    
private static function stripComments($source)
    {
        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], array(T_COMMENTT_DOC_COMMENT))) {
                
$output .= $token[1];
            }
        }

        
// replace multiple new lines with a single newline
        
$output preg_replace(array('/\s+$/Sm''/\n+/S'), "\n"$output);

        return 
$output;
    }
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Mustache Template Loader interface.
 */
interface Mustache_Loader
{
    
/**
     * Load a Template by name.
     *
     * @throws Mustache_Exception_UnknownTemplateException If a template file is not found
     *
     * @param string $name
     *
     * @return string|Mustache_Source Mustache Template source
     */
    
public function load($name);
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A Mustache implementation in PHP.
 *
 * {@link http://defunkt.github.com/mustache}
 *
 * Mustache is a framework-agnostic logic-less templating language. It enforces separation of view
 * logic from template files. In fact, it is not even possible to embed logic in the template.
 *
 * This is very, very rad.
 *
 * @author Justin Hileman {@link http://justinhileman.com}
 */
class Mustache_Engine
{
    const 
VERSION        '2.14.0';
    const 
SPEC_VERSION   '1.2.2';

    const 
PRAGMA_FILTERS      'FILTERS';
    const 
PRAGMA_BLOCKS       'BLOCKS';
    const 
PRAGMA_ANCHORED_DOT 'ANCHORED-DOT';

    
// Known pragmas
    
private static $knownPragmas = array(
        
self::PRAGMA_FILTERS      => true,
        
self::PRAGMA_BLOCKS       => true,
        
self::PRAGMA_ANCHORED_DOT => true,
    );

    
// Template cache
    
private $templates = array();

    
// Environment
    
private $templateClassPrefix '__Mustache_';
    private 
$cache;
    private 
$lambdaCache;
    private 
$cacheLambdaTemplates false;
    private 
$loader;
    private 
$partialsLoader;
    private 
$helpers;
    private 
$escape;
    private 
$entityFlags ENT_COMPAT;
    private 
$charset 'UTF-8';
    private 
$logger;
    private 
$strictCallables false;
    private 
$pragmas = array();
    private 
$delimiters;

    
// Services
    
private $tokenizer;
    private 
$parser;
    private 
$compiler;

    
/**
     * Mustache class constructor.
     *
     * Passing an $options array allows overriding certain Mustache options during instantiation:
     *
     *     $options = array(
     *         // The class prefix for compiled templates. Defaults to '__Mustache_'.
     *         'template_class_prefix' => '__MyTemplates_',
     *
     *         // A Mustache cache instance or a cache directory string for compiled templates.
     *         // Mustache will not cache templates unless this is set.
     *         'cache' => dirname(__FILE__).'/tmp/cache/mustache',
     *
     *         // Override default permissions for cache files. Defaults to using the system-defined umask. It is
     *         // *strongly* recommended that you configure your umask properly rather than overriding permissions here.
     *         'cache_file_mode' => 0666,
     *
     *         // Optionally, enable caching for lambda section templates. This is generally not recommended, as lambda
     *         // sections are often too dynamic to benefit from caching.
     *         'cache_lambda_templates' => true,
     *
     *         // Customize the tag delimiters used by this engine instance. Note that overriding here changes the
     *         // delimiters used to parse all templates and partials loaded by this instance. To override just for a
     *         // single template, use an inline "change delimiters" tag at the start of the template file:
     *         //
     *         //     {{=<% %>=}}
     *         //
     *         'delimiters' => '<% %>',
     *
     *         // A Mustache template loader instance. Uses a StringLoader if not specified.
     *         'loader' => new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views'),
     *
     *         // A Mustache loader instance for partials.
     *         'partials_loader' => new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views/partials'),
     *
     *         // An array of Mustache partials. Useful for quick-and-dirty string template loading, but not as
     *         // efficient or lazy as a Filesystem (or database) loader.
     *         'partials' => array('foo' => file_get_contents(dirname(__FILE__).'/views/partials/foo.mustache')),
     *
     *         // An array of 'helpers'. Helpers can be global variables or objects, closures (e.g. for higher order
     *         // sections), or any other valid Mustache context value. They will be prepended to the context stack,
     *         // so they will be available in any template loaded by this Mustache instance.
     *         'helpers' => array('i18n' => function ($text) {
     *             // do something translatey here...
     *         }),
     *
     *         // An 'escape' callback, responsible for escaping double-mustache variables.
     *         'escape' => function ($value) {
     *             return htmlspecialchars($buffer, ENT_COMPAT, 'UTF-8');
     *         },
     *
     *         // Type argument for `htmlspecialchars`.  Defaults to ENT_COMPAT.  You may prefer ENT_QUOTES.
     *         'entity_flags' => ENT_QUOTES,
     *
     *         // Character set for `htmlspecialchars`. Defaults to 'UTF-8'. Use 'UTF-8'.
     *         'charset' => 'ISO-8859-1',
     *
     *         // A Mustache Logger instance. No logging will occur unless this is set. Using a PSR-3 compatible
     *         // logging library -- such as Monolog -- is highly recommended. A simple stream logger implementation is
     *         // available as well:
     *         'logger' => new Mustache_Logger_StreamLogger('php://stderr'),
     *
     *         // Only treat Closure instances and invokable classes as callable. If true, values like
     *         // `array('ClassName', 'methodName')` and `array($classInstance, 'methodName')`, which are traditionally
     *         // "callable" in PHP, are not called to resolve variables for interpolation or section contexts. This
     *         // helps protect against arbitrary code execution when user input is passed directly into the template.
     *         // This currently defaults to false, but will default to true in v3.0.
     *         'strict_callables' => true,
     *
     *         // Enable pragmas across all templates, regardless of the presence of pragma tags in the individual
     *         // templates.
     *         'pragmas' => [Mustache_Engine::PRAGMA_FILTERS],
     *     );
     *
     * @throws Mustache_Exception_InvalidArgumentException If `escape` option is not callable
     *
     * @param array $options (default: array())
     */
    
public function __construct(array $options = array())
    {
        if (isset(
$options['template_class_prefix'])) {
            if ((string) 
$options['template_class_prefix'] === '') {
                throw new 
Mustache_Exception_InvalidArgumentException('Mustache Constructor "template_class_prefix" must not be empty');
            }

            
$this->templateClassPrefix $options['template_class_prefix'];
        }

        if (isset(
$options['cache'])) {
            
$cache $options['cache'];

            if (
is_string($cache)) {
                
$mode  = isset($options['cache_file_mode']) ? $options['cache_file_mode'] : null;
                
$cache = new Mustache_Cache_FilesystemCache($cache$mode);
            }

            
$this->setCache($cache);
        }

        if (isset(
$options['cache_lambda_templates'])) {
            
$this->cacheLambdaTemplates = (bool) $options['cache_lambda_templates'];
        }

        if (isset(
$options['loader'])) {
            
$this->setLoader($options['loader']);
        }

        if (isset(
$options['partials_loader'])) {
            
$this->setPartialsLoader($options['partials_loader']);
        }

        if (isset(
$options['partials'])) {
            
$this->setPartials($options['partials']);
        }

        if (isset(
$options['helpers'])) {
            
$this->setHelpers($options['helpers']);
        }

        if (isset(
$options['escape'])) {
            if (!
is_callable($options['escape'])) {
                throw new 
Mustache_Exception_InvalidArgumentException('Mustache Constructor "escape" option must be callable');
            }

            
$this->escape $options['escape'];
        }

        if (isset(
$options['entity_flags'])) {
            
$this->entityFlags $options['entity_flags'];
        }

        if (isset(
$options['charset'])) {
            
$this->charset $options['charset'];
        }

        if (isset(
$options['logger'])) {
            
$this->setLogger($options['logger']);
        }

        if (isset(
$options['strict_callables'])) {
            
$this->strictCallables $options['strict_callables'];
        }

        if (isset(
$options['delimiters'])) {
            
$this->delimiters $options['delimiters'];
        }

        if (isset(
$options['pragmas'])) {
            foreach (
$options['pragmas'] as $pragma) {
                if (!isset(
self::$knownPragmas[$pragma])) {
                    throw new 
Mustache_Exception_InvalidArgumentException(sprintf('Unknown pragma: "%s".'$pragma));
                }
                
$this->pragmas[$pragma] = true;
            }
        }
    }

    
/**
     * Shortcut 'render' invocation.
     *
     * Equivalent to calling `$mustache->loadTemplate($template)->render($context);`
     *
     * @see Mustache_Engine::loadTemplate
     * @see Mustache_Template::render
     *
     * @param string $template
     * @param mixed  $context  (default: array())
     *
     * @return string Rendered template
     */
    
public function render($template$context = array())
    {
        return 
$this->loadTemplate($template)->render($context);
    }

    
/**
     * Get the current Mustache escape callback.
     *
     * @return callable|null
     */
    
public function getEscape()
    {
        return 
$this->escape;
    }

    
/**
     * Get the current Mustache entitity type to escape.
     *
     * @return int
     */
    
public function getEntityFlags()
    {
        return 
$this->entityFlags;
    }

    
/**
     * Get the current Mustache character set.
     *
     * @return string
     */
    
public function getCharset()
    {
        return 
$this->charset;
    }

    
/**
     * Get the current globally enabled pragmas.
     *
     * @return array
     */
    
public function getPragmas()
    {
        return 
array_keys($this->pragmas);
    }

    
/**
     * Set the Mustache template Loader instance.
     *
     * @param Mustache_Loader $loader
     */
    
public function setLoader(Mustache_Loader $loader)
    {
        
$this->loader $loader;
    }

    
/**
     * Get the current Mustache template Loader instance.
     *
     * If no Loader instance has been explicitly specified, this method will instantiate and return
     * a StringLoader instance.
     *
     * @return Mustache_Loader
     */
    
public function getLoader()
    {
        if (!isset(
$this->loader)) {
            
$this->loader = new Mustache_Loader_StringLoader();
        }

        return 
$this->loader;
    }

    
/**
     * Set the Mustache partials Loader instance.
     *
     * @param Mustache_Loader $partialsLoader
     */
    
public function setPartialsLoader(Mustache_Loader $partialsLoader)
    {
        
$this->partialsLoader $partialsLoader;
    }

    
/**
     * Get the current Mustache partials Loader instance.
     *
     * If no Loader instance has been explicitly specified, this method will instantiate and return
     * an ArrayLoader instance.
     *
     * @return Mustache_Loader
     */
    
public function getPartialsLoader()
    {
        if (!isset(
$this->partialsLoader)) {
            
$this->partialsLoader = new Mustache_Loader_ArrayLoader();
        }

        return 
$this->partialsLoader;
    }

    
/**
     * Set partials for the current partials Loader instance.
     *
     * @throws Mustache_Exception_RuntimeException If the current Loader instance is immutable
     *
     * @param array $partials (default: array())
     */
    
public function setPartials(array $partials = array())
    {
        if (!isset(
$this->partialsLoader)) {
            
$this->partialsLoader = new Mustache_Loader_ArrayLoader();
        }

        if (!
$this->partialsLoader instanceof Mustache_Loader_MutableLoader) {
            throw new 
Mustache_Exception_RuntimeException('Unable to set partials on an immutable Mustache Loader instance');
        }

        
$this->partialsLoader->setTemplates($partials);
    }

    
/**
     * Set an array of Mustache helpers.
     *
     * An array of 'helpers'. Helpers can be global variables or objects, closures (e.g. for higher order sections), or
     * any other valid Mustache context value. They will be prepended to the context stack, so they will be available in
     * any template loaded by this Mustache instance.
     *
     * @throws Mustache_Exception_InvalidArgumentException if $helpers is not an array or Traversable
     *
     * @param array|Traversable $helpers
     */
    
public function setHelpers($helpers)
    {
        if (!
is_array($helpers) && !$helpers instanceof Traversable) {
            throw new 
Mustache_Exception_InvalidArgumentException('setHelpers expects an array of helpers');
        }

        
$this->getHelpers()->clear();

        foreach (
$helpers as $name => $helper) {
            
$this->addHelper($name$helper);
        }
    }

    
/**
     * Get the current set of Mustache helpers.
     *
     * @see Mustache_Engine::setHelpers
     *
     * @return Mustache_HelperCollection
     */
    
public function getHelpers()
    {
        if (!isset(
$this->helpers)) {
            
$this->helpers = new Mustache_HelperCollection();
        }

        return 
$this->helpers;
    }

    
/**
     * Add a new Mustache helper.
     *
     * @see Mustache_Engine::setHelpers
     *
     * @param string $name
     * @param mixed  $helper
     */
    
public function addHelper($name$helper)
    {
        
$this->getHelpers()->add($name$helper);
    }

    
/**
     * Get a Mustache helper by name.
     *
     * @see Mustache_Engine::setHelpers
     *
     * @param string $name
     *
     * @return mixed Helper
     */
    
public function getHelper($name)
    {
        return 
$this->getHelpers()->get($name);
    }

    
/**
     * Check whether this Mustache instance has a helper.
     *
     * @see Mustache_Engine::setHelpers
     *
     * @param string $name
     *
     * @return bool True if the helper is present
     */
    
public function hasHelper($name)
    {
        return 
$this->getHelpers()->has($name);
    }

    
/**
     * Remove a helper by name.
     *
     * @see Mustache_Engine::setHelpers
     *
     * @param string $name
     */
    
public function removeHelper($name)
    {
        
$this->getHelpers()->remove($name);
    }

    
/**
     * Set the Mustache Logger instance.
     *
     * @throws Mustache_Exception_InvalidArgumentException If logger is not an instance of Mustache_Logger or Psr\Log\LoggerInterface
     *
     * @param Mustache_Logger|Psr\Log\LoggerInterface $logger
     */
    
public function setLogger($logger null)
    {
        if (
$logger !== null && !($logger instanceof Mustache_Logger || is_a($logger'Psr\\Log\\LoggerInterface'))) {
            throw new 
Mustache_Exception_InvalidArgumentException('Expected an instance of Mustache_Logger or Psr\\Log\\LoggerInterface.');
        }

        if (
$this->getCache()->getLogger() === null) {
            
$this->getCache()->setLogger($logger);
        }

        
$this->logger $logger;
    }

    
/**
     * Get the current Mustache Logger instance.
     *
     * @return Mustache_Logger|Psr\Log\LoggerInterface
     */
    
public function getLogger()
    {
        return 
$this->logger;
    }

    
/**
     * Set the Mustache Tokenizer instance.
     *
     * @param Mustache_Tokenizer $tokenizer
     */
    
public function setTokenizer(Mustache_Tokenizer $tokenizer)
    {
        
$this->tokenizer $tokenizer;
    }

    
/**
     * Get the current Mustache Tokenizer instance.
     *
     * If no Tokenizer instance has been explicitly specified, this method will instantiate and return a new one.
     *
     * @return Mustache_Tokenizer
     */
    
public function getTokenizer()
    {
        if (!isset(
$this->tokenizer)) {
            
$this->tokenizer = new Mustache_Tokenizer();
        }

        return 
$this->tokenizer;
    }

    
/**
     * Set the Mustache Parser instance.
     *
     * @param Mustache_Parser $parser
     */
    
public function setParser(Mustache_Parser $parser)
    {
        
$this->parser $parser;
    }

    
/**
     * Get the current Mustache Parser instance.
     *
     * If no Parser instance has been explicitly specified, this method will instantiate and return a new one.
     *
     * @return Mustache_Parser
     */
    
public function getParser()
    {
        if (!isset(
$this->parser)) {
            
$this->parser = new Mustache_Parser();
        }

        return 
$this->parser;
    }

    
/**
     * Set the Mustache Compiler instance.
     *
     * @param Mustache_Compiler $compiler
     */
    
public function setCompiler(Mustache_Compiler $compiler)
    {
        
$this->compiler $compiler;
    }

    
/**
     * Get the current Mustache Compiler instance.
     *
     * If no Compiler instance has been explicitly specified, this method will instantiate and return a new one.
     *
     * @return Mustache_Compiler
     */
    
public function getCompiler()
    {
        if (!isset(
$this->compiler)) {
            
$this->compiler = new Mustache_Compiler();
        }

        return 
$this->compiler;
    }

    
/**
     * Set the Mustache Cache instance.
     *
     * @param Mustache_Cache $cache
     */
    
public function setCache(Mustache_Cache $cache)
    {
        if (isset(
$this->logger) && $cache->getLogger() === null) {
            
$cache->setLogger($this->getLogger());
        }

        
$this->cache $cache;
    }

    
/**
     * Get the current Mustache Cache instance.
     *
     * If no Cache instance has been explicitly specified, this method will instantiate and return a new one.
     *
     * @return Mustache_Cache
     */
    
public function getCache()
    {
        if (!isset(
$this->cache)) {
            
$this->setCache(new Mustache_Cache_NoopCache());
        }

        return 
$this->cache;
    }

    
/**
     * Get the current Lambda Cache instance.
     *
     * If 'cache_lambda_templates' is enabled, this is the default cache instance. Otherwise, it is a NoopCache.
     *
     * @see Mustache_Engine::getCache
     *
     * @return Mustache_Cache
     */
    
protected function getLambdaCache()
    {
        if (
$this->cacheLambdaTemplates) {
            return 
$this->getCache();
        }

        if (!isset(
$this->lambdaCache)) {
            
$this->lambdaCache = new Mustache_Cache_NoopCache();
        }

        return 
$this->lambdaCache;
    }

    
/**
     * Helper method to generate a Mustache template class.
     *
     * This method must be updated any time options are added which make it so
     * the same template could be parsed and compiled multiple different ways.
     *
     * @param string|Mustache_Source $source
     *
     * @return string Mustache Template class name
     */
    
public function getTemplateClassName($source)
    {
        
// For the most part, adding a new option here should do the trick.
        //
        // Pick a value here which is unique for each possible way the template
        // could be compiled... but not necessarily unique per option value. See
        // escape below, which only needs to differentiate between 'custom' and
        // 'default' escapes.
        //
        // Keep this list in alphabetical order :)
        
$chunks = array(
            
'charset'         => $this->charset,
            
'delimiters'      => $this->delimiters $this->delimiters '{{ }}',
            
'entityFlags'     => $this->entityFlags,
            
'escape'          => isset($this->escape) ? 'custom' 'default',
            
'key'             => ($source instanceof Mustache_Source) ? $source->getKey() : 'source',
            
'pragmas'         => $this->getPragmas(),
            
'strictCallables' => $this->strictCallables,
            
'version'         => self::VERSION,
        );

        
$key json_encode($chunks);

        
// Template Source instances have already provided their own source key. For strings, just include the whole
        // source string in the md5 hash.
        
if (!$source instanceof Mustache_Source) {
            
$key .= "\n" $source;
        }

        return 
$this->templateClassPrefix md5($key);
    }

    
/**
     * Load a Mustache Template by name.
     *
     * @param string $name
     *
     * @return Mustache_Template
     */
    
public function loadTemplate($name)
    {
        return 
$this->loadSource($this->getLoader()->load($name));
    }

    
/**
     * Load a Mustache partial Template by name.
     *
     * This is a helper method used internally by Template instances for loading partial templates. You can most likely
     * ignore it completely.
     *
     * @param string $name
     *
     * @return Mustache_Template
     */
    
public function loadPartial($name)
    {
        try {
            if (isset(
$this->partialsLoader)) {
                
$loader $this->partialsLoader;
            } elseif (isset(
$this->loader) && !$this->loader instanceof Mustache_Loader_StringLoader) {
                
$loader $this->loader;
            } else {
                throw new 
Mustache_Exception_UnknownTemplateException($name);
            }

            return 
$this->loadSource($loader->load($name));
        } catch (
Mustache_Exception_UnknownTemplateException $e) {
            
// If the named partial cannot be found, log then return null.
            
$this->log(
                
Mustache_Logger::WARNING,
                
'Partial not found: "{name}"',
                array(
'name' => $e->getTemplateName())
            );
        }
    }

    
/**
     * Load a Mustache lambda Template by source.
     *
     * This is a helper method used by Template instances to generate subtemplates for Lambda sections. You can most
     * likely ignore it completely.
     *
     * @param string $source
     * @param string $delims (default: null)
     *
     * @return Mustache_Template
     */
    
public function loadLambda($source$delims null)
    {
        if (
$delims !== null) {
            
$source $delims "\n" $source;
        }

        return 
$this->loadSource($source$this->getLambdaCache());
    }

    
/**
     * Instantiate and return a Mustache Template instance by source.
     *
     * Optionally provide a Mustache_Cache instance. This is used internally by Mustache_Engine::loadLambda to respect
     * the 'cache_lambda_templates' configuration option.
     *
     * @see Mustache_Engine::loadTemplate
     * @see Mustache_Engine::loadPartial
     * @see Mustache_Engine::loadLambda
     *
     * @param string|Mustache_Source $source
     * @param Mustache_Cache         $cache  (default: null)
     *
     * @return Mustache_Template
     */
    
private function loadSource($sourceMustache_Cache $cache null)
    {
        
$className $this->getTemplateClassName($source);

        if (!isset(
$this->templates[$className])) {
            if (
$cache === null) {
                
$cache $this->getCache();
            }

            if (!
class_exists($classNamefalse)) {
                if (!
$cache->load($className)) {
                    
$compiled $this->compile($source);
                    
$cache->cache($className$compiled);
                }
            }

            
$this->log(
                
Mustache_Logger::DEBUG,
                
'Instantiating template: "{className}"',
                array(
'className' => $className)
            );

            
$this->templates[$className] = new $className($this);
        }

        return 
$this->templates[$className];
    }

    
/**
     * Helper method to tokenize a Mustache template.
     *
     * @see Mustache_Tokenizer::scan
     *
     * @param string $source
     *
     * @return array Tokens
     */
    
private function tokenize($source)
    {
        return 
$this->getTokenizer()->scan($source$this->delimiters);
    }

    
/**
     * Helper method to parse a Mustache template.
     *
     * @see Mustache_Parser::parse
     *
     * @param string $source
     *
     * @return array Token tree
     */
    
private function parse($source)
    {
        
$parser $this->getParser();
        
$parser->setPragmas($this->getPragmas());

        return 
$parser->parse($this->tokenize($source));
    }

    
/**
     * Helper method to compile a Mustache template.
     *
     * @see Mustache_Compiler::compile
     *
     * @param string|Mustache_Source $source
     *
     * @return string generated Mustache template class code
     */
    
private function compile($source)
    {
        
$name $this->getTemplateClassName($source);

        
$this->log(
            
Mustache_Logger::INFO,
            
'Compiling template to "{className}" class',
            array(
'className' => $name)
        );

        if (
$source instanceof Mustache_Source) {
            
$source $source->getSource();
        }
        
$tree $this->parse($source);

        
$compiler $this->getCompiler();
        
$compiler->setPragmas($this->getPragmas());

        return 
$compiler->compile($source$tree$name, isset($this->escape), $this->charset$this->strictCallables$this->entityFlags);
    }

    
/**
     * Add a log record if logging is enabled.
     *
     * @param int    $level   The logging level
     * @param string $message The log message
     * @param array  $context The log context
     */
    
private function log($level$message, array $context = array())
    {
        if (isset(
$this->logger)) {
            
$this->logger->log($level$message$context);
        }
    }
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Mustache Template rendering Context.
 */
class Mustache_Context
{
    private 
$stack      = array();
    private 
$blockStack = array();

    
/**
     * Mustache rendering Context constructor.
     *
     * @param mixed $context Default rendering context (default: null)
     */
    
public function __construct($context null)
    {
        if (
$context !== null) {
            
$this->stack = array($context);
        }
    }

    
/**
     * Push a new Context frame onto the stack.
     *
     * @param mixed $value Object or array to use for context
     */
    
public function push($value)
    {
        
array_push($this->stack$value);
    }

    
/**
     * Push a new Context frame onto the block context stack.
     *
     * @param mixed $value Object or array to use for block context
     */
    
public function pushBlockContext($value)
    {
        
array_push($this->blockStack$value);
    }

    
/**
     * Pop the last Context frame from the stack.
     *
     * @return mixed Last Context frame (object or array)
     */
    
public function pop()
    {
        return 
array_pop($this->stack);
    }

    
/**
     * Pop the last block Context frame from the stack.
     *
     * @return mixed Last block Context frame (object or array)
     */
    
public function popBlockContext()
    {
        return 
array_pop($this->blockStack);
    }

    
/**
     * Get the last Context frame.
     *
     * @return mixed Last Context frame (object or array)
     */
    
public function last()
    {
        return 
end($this->stack);
    }

    
/**
     * Find a variable in the Context stack.
     *
     * Starting with the last Context frame (the context of the innermost section), and working back to the top-level
     * rendering context, look for a variable with the given name:
     *
     *  * If the Context frame is an associative array which contains the key $id, returns the value of that element.
     *  * If the Context frame is an object, this will check first for a public method, then a public property named
     *    $id. Failing both of these, it will try `__isset` and `__get` magic methods.
     *  * If a value named $id is not found in any Context frame, returns an empty string.
     *
     * @param string $id Variable name
     *
     * @return mixed Variable value, or '' if not found
     */
    
public function find($id)
    {
        return 
$this->findVariableInStack($id$this->stack);
    }

    
/**
     * Find a 'dot notation' variable in the Context stack.
     *
     * Note that dot notation traversal bubbles through scope differently than the regular find method. After finding
     * the initial chunk of the dotted name, each subsequent chunk is searched for only within the value of the previous
     * result. For example, given the following context stack:
     *
     *     $data = array(
     *         'name' => 'Fred',
     *         'child' => array(
     *             'name' => 'Bob'
     *         ),
     *     );
     *
     * ... and the Mustache following template:
     *
     *     {{ child.name }}
     *
     * ... the `name` value is only searched for within the `child` value of the global Context, not within parent
     * Context frames.
     *
     * @param string $id Dotted variable selector
     *
     * @return mixed Variable value, or '' if not found
     */
    
public function findDot($id)
    {
        
$chunks explode('.'$id);
        
$first  array_shift($chunks);
        
$value  $this->findVariableInStack($first$this->stack);

        foreach (
$chunks as $chunk) {
            if (
$value === '') {
                return 
$value;
            }

            
$value $this->findVariableInStack($chunk, array($value));
        }

        return 
$value;
    }

    
/**
     * Find an 'anchored dot notation' variable in the Context stack.
     *
     * This is the same as findDot(), except it looks in the top of the context
     * stack for the first value, rather than searching the whole context stack
     * and starting from there.
     *
     * @see Mustache_Context::findDot
     *
     * @throws Mustache_Exception_InvalidArgumentException if given an invalid anchored dot $id
     *
     * @param string $id Dotted variable selector
     *
     * @return mixed Variable value, or '' if not found
     */
    
public function findAnchoredDot($id)
    {
        
$chunks explode('.'$id);
        
$first  array_shift($chunks);
        if (
$first !== '') {
            throw new 
Mustache_Exception_InvalidArgumentException(sprintf('Unexpected id for findAnchoredDot: %s'$id));
        }

        
$value  $this->last();

        foreach (
$chunks as $chunk) {
            if (
$value === '') {
                return 
$value;
            }

            
$value $this->findVariableInStack($chunk, array($value));
        }

        return 
$value;
    }

    
/**
     * Find an argument in the block context stack.
     *
     * @param string $id
     *
     * @return mixed Variable value, or '' if not found
     */
    
public function findInBlock($id)
    {
        foreach (
$this->blockStack as $context) {
            if (
array_key_exists($id$context)) {
                return 
$context[$id];
            }
        }

        return 
'';
    }

    
/**
     * Helper function to find a variable in the Context stack.
     *
     * @see Mustache_Context::find
     *
     * @param string $id    Variable name
     * @param array  $stack Context stack
     *
     * @return mixed Variable value, or '' if not found
     */
    
private function findVariableInStack($id, array $stack)
    {
        for (
$i count($stack) - 1$i >= 0$i--) {
            
$frame = &$stack[$i];

            switch (
gettype($frame)) {
                case 
'object':
                    if (!(
$frame instanceof Closure)) {
                        
// Note that is_callable() *will not work here*
                        // See https://github.com/bobthecow/mustache.php/wiki/Magic-Methods
                        
if (method_exists($frame$id)) {
                            return 
$frame->$id();
                        }

                        if (isset(
$frame->$id)) {
                            return 
$frame->$id;
                        }

                        if (
$frame instanceof ArrayAccess && isset($frame[$id])) {
                            return 
$frame[$id];
                        }
                    }
                    break;

                case 
'array':
                    if (
array_key_exists($id$frame)) {
                        return 
$frame[$id];
                    }
                    break;
            }
        }

        return 
'';
    }
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Describes a Mustache logger instance.
 *
 * This is identical to the Psr\Log\LoggerInterface.
 *
 * 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 Mustache_Logger
{
    
/**
     * Psr\Log compatible log levels.
     */
    
const EMERGENCY 'emergency';
    const 
ALERT     'alert';
    const 
CRITICAL  'critical';
    const 
ERROR     'error';
    const 
WARNING   'warning';
    const 
NOTICE    'notice';
    const 
INFO      'info';
    const 
DEBUG     'debug';

    
/**
     * System is unusable.
     *
     * @param string $message
     * @param array  $context
     */
    
public function emergency($message, array $context = array());

    
/**
     * Action must be taken immediately.
     *
     * Example: Entire website down, database unavailable, etc. This should
     * trigger the SMS alerts and wake you up.
     *
     * @param string $message
     * @param array  $context
     */
    
public function alert($message, array $context = array());

    
/**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     *
     * @param string $message
     * @param array  $context
     */
    
public function critical($message, array $context = array());

    
/**
     * Runtime errors that do not require immediate action but should typically
     * be logged and monitored.
     *
     * @param string $message
     * @param array  $context
     */
    
public function error($message, array $context = array());

    
/**
     * Exceptional occurrences that are not errors.
     *
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
     * that are not necessarily wrong.
     *
     * @param string $message
     * @param array  $context
     */
    
public function warning($message, array $context = array());

    
/**
     * Normal but significant events.
     *
     * @param string $message
     * @param array  $context
     */
    
public function notice($message, array $context = array());

    
/**
     * Interesting events.
     *
     * Example: User logs in, SQL logs.
     *
     * @param string $message
     * @param array  $context
     */
    
public function info($message, array $context = array());

    
/**
     * Detailed debug information.
     *
     * @param string $message
     * @param array  $context
     */
    
public function debug($message, array $context = array());

    
/**
     * Logs with an arbitrary level.
     *
     * @param mixed  $level
     * @param string $message
     * @param array  $context
     */
    
public function log($level$message, array $context = array());
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Mustache Compiler class.
 *
 * This class is responsible for turning a Mustache token parse tree into normal PHP source code.
 */
class Mustache_Compiler
{
    private 
$pragmas;
    private 
$defaultPragmas = array();
    private 
$sections;
    private 
$blocks;
    private 
$source;
    private 
$indentNextLine;
    private 
$customEscape;
    private 
$entityFlags;
    private 
$charset;
    private 
$strictCallables;

    
/**
     * Compile a Mustache token parse tree into PHP source code.
     *
     * @param string $source          Mustache Template source code
     * @param string $tree            Parse tree of Mustache tokens
     * @param string $name            Mustache Template class name
     * @param bool   $customEscape    (default: false)
     * @param string $charset         (default: 'UTF-8')
     * @param bool   $strictCallables (default: false)
     * @param int    $entityFlags     (default: ENT_COMPAT)
     *
     * @return string Generated PHP source code
     */
    
public function compile($source, array $tree$name$customEscape false$charset 'UTF-8'$strictCallables false$entityFlags ENT_COMPAT)
    {
        
$this->pragmas         $this->defaultPragmas;
        
$this->sections        = array();
        
$this->blocks          = array();
        
$this->source          $source;
        
$this->indentNextLine  true;
        
$this->customEscape    $customEscape;
        
$this->entityFlags     $entityFlags;
        
$this->charset         $charset;
        
$this->strictCallables $strictCallables;

        return 
$this->writeCode($tree$name);
    }

    
/**
     * Enable pragmas across all templates, regardless of the presence of pragma
     * tags in the individual templates.
     *
     * @internal Users should set global pragmas in Mustache_Engine, not here :)
     *
     * @param string[] $pragmas
     */
    
public function setPragmas(array $pragmas)
    {
        
$this->pragmas = array();
        foreach (
$pragmas as $pragma) {
            
$this->pragmas[$pragma] = true;
        }
        
$this->defaultPragmas $this->pragmas;
    }

    
/**
     * Helper function for walking the Mustache token parse tree.
     *
     * @throws Mustache_Exception_SyntaxException upon encountering unknown token types
     *
     * @param array $tree  Parse tree of Mustache tokens
     * @param int   $level (default: 0)
     *
     * @return string Generated PHP source code
     */
    
private function walk(array $tree$level 0)
    {
        
$code '';
        
$level++;
        foreach (
$tree as $node) {
            switch (
$node[Mustache_Tokenizer::TYPE]) {
                case 
Mustache_Tokenizer::T_PRAGMA:
                    
$this->pragmas[$node[Mustache_Tokenizer::NAME]] = true;
                    break;

                case 
Mustache_Tokenizer::T_SECTION:
                    
$code .= $this->section(
                        
$node[Mustache_Tokenizer::NODES],
                        
$node[Mustache_Tokenizer::NAME],
                        isset(
$node[Mustache_Tokenizer::FILTERS]) ? $node[Mustache_Tokenizer::FILTERS] : array(),
                        
$node[Mustache_Tokenizer::INDEX],
                        
$node[Mustache_Tokenizer::END],
                        
$node[Mustache_Tokenizer::OTAG],
                        
$node[Mustache_Tokenizer::CTAG],
                        
$level
                    
);
                    break;

                case 
Mustache_Tokenizer::T_INVERTED:
                    
$code .= $this->invertedSection(
                        
$node[Mustache_Tokenizer::NODES],
                        
$node[Mustache_Tokenizer::NAME],
                        isset(
$node[Mustache_Tokenizer::FILTERS]) ? $node[Mustache_Tokenizer::FILTERS] : array(),
                        
$level
                    
);
                    break;

                case 
Mustache_Tokenizer::T_PARTIAL:
                    
$code .= $this->partial(
                        
$node[Mustache_Tokenizer::NAME],
                        isset(
$node[Mustache_Tokenizer::INDENT]) ? $node[Mustache_Tokenizer::INDENT] : '',
                        
$level
                    
);
                    break;

                case 
Mustache_Tokenizer::T_PARENT:
                    
$code .= $this->parent(
                        
$node[Mustache_Tokenizer::NAME],
                        isset(
$node[Mustache_Tokenizer::INDENT]) ? $node[Mustache_Tokenizer::INDENT] : '',
                        
$node[Mustache_Tokenizer::NODES],
                        
$level
                    
);
                    break;

                case 
Mustache_Tokenizer::T_BLOCK_ARG:
                    
$code .= $this->blockArg(
                        
$node[Mustache_Tokenizer::NODES],
                        
$node[Mustache_Tokenizer::NAME],
                        
$node[Mustache_Tokenizer::INDEX],
                        
$node[Mustache_Tokenizer::END],
                        
$node[Mustache_Tokenizer::OTAG],
                        
$node[Mustache_Tokenizer::CTAG],
                        
$level
                    
);
                    break;

                case 
Mustache_Tokenizer::T_BLOCK_VAR:
                    
$code .= $this->blockVar(
                        
$node[Mustache_Tokenizer::NODES],
                        
$node[Mustache_Tokenizer::NAME],
                        
$node[Mustache_Tokenizer::INDEX],
                        
$node[Mustache_Tokenizer::END],
                        
$node[Mustache_Tokenizer::OTAG],
                        
$node[Mustache_Tokenizer::CTAG],
                        
$level
                    
);
                    break;

                case 
Mustache_Tokenizer::T_COMMENT:
                    break;

                case 
Mustache_Tokenizer::T_ESCAPED:
                case 
Mustache_Tokenizer::T_UNESCAPED:
                case 
Mustache_Tokenizer::T_UNESCAPED_2:
                    
$code .= $this->variable(
                        
$node[Mustache_Tokenizer::NAME],
                        isset(
$node[Mustache_Tokenizer::FILTERS]) ? $node[Mustache_Tokenizer::FILTERS] : array(),
                        
$node[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_ESCAPED,
                        
$level
                    
);
                    break;

                case 
Mustache_Tokenizer::T_TEXT:
                    
$code .= $this->text($node[Mustache_Tokenizer::VALUE], $level);
                    break;

                default:
                    throw new 
Mustache_Exception_SyntaxException(sprintf('Unknown token type: %s'$node[Mustache_Tokenizer::TYPE]), $node);
            }
        }

        return 
$code;
    }

    const 
KLASS '<?php

        class %s extends Mustache_Template
        {
            private $lambdaHelper;%s

            public function renderInternal(Mustache_Context $context, $indent = \'\')
            {
                $this->lambdaHelper = new Mustache_LambdaHelper($this->mustache, $context);
                $buffer = \'\';
        %s

                return $buffer;
            }
        %s
        %s
        }'
;

    const 
KLASS_NO_LAMBDAS '<?php

        class %s extends Mustache_Template
        {%s
            public function renderInternal(Mustache_Context $context, $indent = \'\')
            {
                $buffer = \'\';
        %s

                return $buffer;
            }
        }'
;

    const 
STRICT_CALLABLE 'protected $strictCallables = true;';

    
/**
     * Generate Mustache Template class PHP source.
     *
     * @param array  $tree Parse tree of Mustache tokens
     * @param string $name Mustache Template class name
     *
     * @return string Generated PHP source code
     */
    
private function writeCode($tree$name)
    {
        
$code     $this->walk($tree);
        
$sections implode("\n"$this->sections);
        
$blocks   implode("\n"$this->blocks);
        
$klass    = empty($this->sections) && empty($this->blocks) ? self::KLASS_NO_LAMBDAS self::KLASS;

        
$callable $this->strictCallables $this->prepare(self::STRICT_CALLABLE) : '';

        return 
sprintf($this->prepare($klass0falsetrue), $name$callable$code$sections$blocks);
    }

    const 
BLOCK_VAR '
        $blockFunction = $context->findInBlock(%s);
        if (is_callable($blockFunction)) {
            $buffer .= call_user_func($blockFunction, $context);
        %s}
    '
;

    const 
BLOCK_VAR_ELSE '} else {%s';

    
/**
     * Generate Mustache Template inheritance block variable PHP source.
     *
     * @param array  $nodes Array of child tokens
     * @param string $id    Section name
     * @param int    $start Section start offset
     * @param int    $end   Section end offset
     * @param string $otag  Current Mustache opening tag
     * @param string $ctag  Current Mustache closing tag
     * @param int    $level
     *
     * @return string Generated PHP source code
     */
    
private function blockVar($nodes$id$start$end$otag$ctag$level)
    {
        
$id var_export($idtrue);

        
$else $this->walk($nodes$level);
        if (
$else !== '') {
            
$else sprintf($this->prepare(self::BLOCK_VAR_ELSE$level 1falsetrue), $else);
        }

        return 
sprintf($this->prepare(self::BLOCK_VAR$level), $id$else);
    }

    const 
BLOCK_ARG '%s => array($this, \'block%s\'),';

    
/**
     * Generate Mustache Template inheritance block argument PHP source.
     *
     * @param array  $nodes Array of child tokens
     * @param string $id    Section name
     * @param int    $start Section start offset
     * @param int    $end   Section end offset
     * @param string $otag  Current Mustache opening tag
     * @param string $ctag  Current Mustache closing tag
     * @param int    $level
     *
     * @return string Generated PHP source code
     */
    
private function blockArg($nodes$id$start$end$otag$ctag$level)
    {
        
$key $this->block($nodes);
        
$id var_export($idtrue);

        return 
sprintf($this->prepare(self::BLOCK_ARG$level), $id$key);
    }

    const 
BLOCK_FUNCTION '
        public function block%s($context)
        {
            $indent = $buffer = \'\';%s

            return $buffer;
        }
    '
;

    
/**
     * Generate Mustache Template inheritance block function PHP source.
     *
     * @param array $nodes Array of child tokens
     *
     * @return string key of new block function
     */
    
private function block($nodes)
    {
        
$code $this->walk($nodes0);
        
$key ucfirst(md5($code));

        if (!isset(
$this->blocks[$key])) {
            
$this->blocks[$key] = sprintf($this->prepare(self::BLOCK_FUNCTION0), $key$code);
        }

        return 
$key;
    }

    const 
SECTION_CALL '
        $value = $context->%s(%s);%s
        $buffer .= $this->section%s($context, $indent, $value);
    '
;

    const 
SECTION '
        private function section%s(Mustache_Context $context, $indent, $value)
        {
            $buffer = \'\';

            if (%s) {
                $source = %s;
                $result = (string) call_user_func($value, $source, %s);
                if (strpos($result, \'{{\') === false) {
                    $buffer .= $result;
                } else {
                    $buffer .= $this->mustache
                        ->loadLambda($result%s)
                        ->renderInternal($context);
                }
            } elseif (!empty($value)) {
                $values = $this->isIterable($value) ? $value : array($value);
                foreach ($values as $value) {
                    $context->push($value);
                    %s
                    $context->pop();
                }
            }

            return $buffer;
        }
    '
;

    
/**
     * Generate Mustache Template section PHP source.
     *
     * @param array    $nodes   Array of child tokens
     * @param string   $id      Section name
     * @param string[] $filters Array of filters
     * @param int      $start   Section start offset
     * @param int      $end     Section end offset
     * @param string   $otag    Current Mustache opening tag
     * @param string   $ctag    Current Mustache closing tag
     * @param int      $level
     *
     * @return string Generated section PHP source code
     */
    
private function section($nodes$id$filters$start$end$otag$ctag$level)
    {
        
$source   var_export(substr($this->source$start$end $start), true);
        
$callable $this->getCallable();

        if (
$otag !== '{{' || $ctag !== '}}') {
            
$delimTag var_export(sprintf('{{= %s %s =}}'$otag$ctag), true);
            
$helper sprintf('$this->lambdaHelper->withDelimiters(%s)'$delimTag);
            
$delims ', ' $delimTag;
        } else {
            
$helper '$this->lambdaHelper';
            
$delims '';
        }

        
$key ucfirst(md5($delims "\n" $source));

        if (!isset(
$this->sections[$key])) {
            
$this->sections[$key] = sprintf($this->prepare(self::SECTION), $key$callable$source$helper$delims$this->walk($nodes2));
        }

        
$method  $this->getFindMethod($id);
        
$id      var_export($idtrue);
        
$filters $this->getFilters($filters$level);

        return 
sprintf($this->prepare(self::SECTION_CALL$level), $method$id$filters$key);
    }

    const 
INVERTED_SECTION '
        $value = $context->%s(%s);%s
        if (empty($value)) {
            %s
        }
    '
;

    
/**
     * Generate Mustache Template inverted section PHP source.
     *
     * @param array    $nodes   Array of child tokens
     * @param string   $id      Section name
     * @param string[] $filters Array of filters
     * @param int      $level
     *
     * @return string Generated inverted section PHP source code
     */
    
private function invertedSection($nodes$id$filters$level)
    {
        
$method  $this->getFindMethod($id);
        
$id      var_export($idtrue);
        
$filters $this->getFilters($filters$level);

        return 
sprintf($this->prepare(self::INVERTED_SECTION$level), $method$id$filters$this->walk($nodes$level));
    }

    const 
PARTIAL_INDENT ', $indent . %s';
    const 
PARTIAL '
        if ($partial = $this->mustache->loadPartial(%s)) {
            $buffer .= $partial->renderInternal($context%s);
        }
    '
;

    
/**
     * Generate Mustache Template partial call PHP source.
     *
     * @param string $id     Partial name
     * @param string $indent Whitespace indent to apply to partial
     * @param int    $level
     *
     * @return string Generated partial call PHP source code
     */
    
private function partial($id$indent$level)
    {
        if (
$indent !== '') {
            
$indentParam sprintf(self::PARTIAL_INDENTvar_export($indenttrue));
        } else {
            
$indentParam '';
        }

        return 
sprintf(
            
$this->prepare(self::PARTIAL$level),
            
var_export($idtrue),
            
$indentParam
        
);
    }

    const 
PARENT '
        if ($parent = $this->mustache->loadPartial(%s)) {
            $context->pushBlockContext(array(%s
            ));
            $buffer .= $parent->renderInternal($context, $indent);
            $context->popBlockContext();
        }
    '
;

    const 
PARENT_NO_CONTEXT '
        if ($parent = $this->mustache->loadPartial(%s)) {
            $buffer .= $parent->renderInternal($context, $indent);
        }
    '
;

    
/**
     * Generate Mustache Template inheritance parent call PHP source.
     *
     * @param string $id       Parent tag name
     * @param string $indent   Whitespace indent to apply to parent
     * @param array  $children Child nodes
     * @param int    $level
     *
     * @return string Generated PHP source code
     */
    
private function parent($id$indent, array $children$level)
    {
        
$realChildren array_filter($children, array(__CLASS__'onlyBlockArgs'));

        if (empty(
$realChildren)) {
            return 
sprintf($this->prepare(self::PARENT_NO_CONTEXT$level), var_export($idtrue));
        }

        return 
sprintf(
            
$this->prepare(self::PARENT$level),
            
var_export($idtrue),
            
$this->walk($realChildren$level 1)
        );
    }

    
/**
     * Helper method for filtering out non-block-arg tokens.
     *
     * @param array $node
     *
     * @return bool True if $node is a block arg token
     */
    
private static function onlyBlockArgs(array $node)
    {
        return 
$node[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_BLOCK_ARG;
    }

    const 
VARIABLE '
        $value = $this->resolveValue($context->%s(%s), $context);%s
        $buffer .= %s($value === null ? \'\' : %s);
    '
;

    
/**
     * Generate Mustache Template variable interpolation PHP source.
     *
     * @param string   $id      Variable name
     * @param string[] $filters Array of filters
     * @param bool     $escape  Escape the variable value for output?
     * @param int      $level
     *
     * @return string Generated variable interpolation PHP source
     */
    
private function variable($id$filters$escape$level)
    {
        
$method  $this->getFindMethod($id);
        
$id      = ($method !== 'last') ? var_export($idtrue) : '';
        
$filters $this->getFilters($filters$level);
        
$value   $escape $this->getEscape() : '$value';

        return 
sprintf($this->prepare(self::VARIABLE$level), $method$id$filters$this->flushIndent(), $value);
    }

    const 
FILTER '
        $filter = $context->%s(%s);
        if (!(%s)) {
            throw new Mustache_Exception_UnknownFilterException(%s);
        }
        $value = call_user_func($filter, $value);%s
    '
;

    
/**
     * Generate Mustache Template variable filtering PHP source.
     *
     * @param string[] $filters Array of filters
     * @param int      $level
     *
     * @return string Generated filter PHP source
     */
    
private function getFilters(array $filters$level)
    {
        if (empty(
$filters)) {
            return 
'';
        }

        
$name     array_shift($filters);
        
$method   $this->getFindMethod($name);
        
$filter   = ($method !== 'last') ? var_export($nametrue) : '';
        
$callable $this->getCallable('$filter');
        
$msg      var_export($nametrue);

        return 
sprintf($this->prepare(self::FILTER$level), $method$filter$callable$msg$this->getFilters($filters$level));
    }

    const 
LINE '$buffer .= "\n";';
    const 
TEXT '$buffer .= %s%s;';

    
/**
     * Generate Mustache Template output Buffer call PHP source.
     *
     * @param string $text
     * @param int    $level
     *
     * @return string Generated output Buffer call PHP source
     */
    
private function text($text$level)
    {
        
$indentNextLine = (substr($text, -1) === "\n");
        
$code sprintf($this->prepare(self::TEXT$level), $this->flushIndent(), var_export($texttrue));
        
$this->indentNextLine $indentNextLine;

        return 
$code;
    }

    
/**
     * Prepare PHP source code snippet for output.
     *
     * @param string $text
     * @param int    $bonus          Additional indent level (default: 0)
     * @param bool   $prependNewline Prepend a newline to the snippet? (default: true)
     * @param bool   $appendNewline  Append a newline to the snippet? (default: false)
     *
     * @return string PHP source code snippet
     */
    
private function prepare($text$bonus 0$prependNewline true$appendNewline false)
    {
        
$text = ($prependNewline "\n" '') . trim($text);
        if (
$prependNewline) {
            
$bonus++;
        }
        if (
$appendNewline) {
            
$text .= "\n";
        }

        return 
preg_replace("/\n( {8})?/""\n" str_repeat(' '$bonus 4), $text);
    }

    const 
DEFAULT_ESCAPE 'htmlspecialchars(%s, %s, %s)';
    const 
CUSTOM_ESCAPE  'call_user_func($this->mustache->getEscape(), %s)';

    
/**
     * Get the current escaper.
     *
     * @param string $value (default: '$value')
     *
     * @return string Either a custom callback, or an inline call to `htmlspecialchars`
     */
    
private function getEscape($value '$value')
    {
        if (
$this->customEscape) {
            return 
sprintf(self::CUSTOM_ESCAPE$value);
        }

        return 
sprintf(self::DEFAULT_ESCAPE$valuevar_export($this->entityFlagstrue), var_export($this->charsettrue));
    }

    
/**
     * Select the appropriate Context `find` method for a given $id.
     *
     * The return value will be one of `find`, `findDot`, `findAnchoredDot` or `last`.
     *
     * @see Mustache_Context::find
     * @see Mustache_Context::findDot
     * @see Mustache_Context::last
     *
     * @param string $id Variable name
     *
     * @return string `find` method name
     */
    
private function getFindMethod($id)
    {
        if (
$id === '.') {
            return 
'last';
        }

        if (isset(
$this->pragmas[Mustache_Engine::PRAGMA_ANCHORED_DOT]) && $this->pragmas[Mustache_Engine::PRAGMA_ANCHORED_DOT]) {
            if (
substr($id01) === '.') {
                return 
'findAnchoredDot';
            }
        }

        if (
strpos($id'.') === false) {
            return 
'find';
        }

        return 
'findDot';
    }

    const 
IS_CALLABLE        '!is_string(%s) && is_callable(%s)';
    const 
STRICT_IS_CALLABLE 'is_object(%s) && is_callable(%s)';

    
/**
     * Helper function to compile strict vs lax "is callable" logic.
     *
     * @param string $variable (default: '$value')
     *
     * @return string "is callable" logic
     */
    
private function getCallable($variable '$value')
    {
        
$tpl $this->strictCallables self::STRICT_IS_CALLABLE self::IS_CALLABLE;

        return 
sprintf($tpl$variable$variable);
    }

    const 
LINE_INDENT '$indent . ';

    
/**
     * Get the current $indent prefix to write to the buffer.
     *
     * @return string "$indent . " or ""
     */
    
private function flushIndent()
    {
        if (!
$this->indentNextLine) {
            return 
'';
        }

        
$this->indentNextLine false;

        return 
self::LINE_INDENT;
    }
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Mustache Template filesystem Loader implementation.
 *
 * A FilesystemLoader instance loads Mustache Template source from the filesystem by name:
 *
 *     $loader = new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views');
 *     $tpl = $loader->load('foo'); // equivalent to `file_get_contents(dirname(__FILE__).'/views/foo.mustache');
 *
 * This is probably the most useful Mustache Loader implementation. It can be used for partials and normal Templates:
 *
 *     $m = new Mustache(array(
 *          'loader'          => new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views'),
 *          'partials_loader' => new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views/partials'),
 *     ));
 */
class Mustache_Loader_FilesystemLoader implements Mustache_Loader
{
    private 
$baseDir;
    private 
$extension '.mustache';
    private 
$templates = array();

    
/**
     * Mustache filesystem Loader constructor.
     *
     * Passing an $options array allows overriding certain Loader options during instantiation:
     *
     *     $options = array(
     *         // The filename extension used for Mustache templates. Defaults to '.mustache'
     *         'extension' => '.ms',
     *     );
     *
     * @throws Mustache_Exception_RuntimeException if $baseDir does not exist
     *
     * @param string $baseDir Base directory containing Mustache template files
     * @param array  $options Array of Loader options (default: array())
     */
    
public function __construct($baseDir, array $options = array())
    {
        
$this->baseDir $baseDir;

        if (
strpos($this->baseDir'://') === false) {
            
$this->baseDir realpath($this->baseDir);
        }

        if (
$this->shouldCheckPath() && !is_dir($this->baseDir)) {
            throw new 
Mustache_Exception_RuntimeException(sprintf('FilesystemLoader baseDir must be a directory: %s'$baseDir));
        }

        if (
array_key_exists('extension'$options)) {
            if (empty(
$options['extension'])) {
                
$this->extension '';
            } else {
                
$this->extension '.' ltrim($options['extension'], '.');
            }
        }
    }

    
/**
     * Load a Template by name.
     *
     *     $loader = new Mustache_Loader_FilesystemLoader(dirname(__FILE__).'/views');
     *     $loader->load('admin/dashboard'); // loads "./views/admin/dashboard.mustache";
     *
     * @param string $name
     *
     * @return string Mustache Template source
     */
    
public function load($name)
    {
        if (!isset(
$this->templates[$name])) {
            
$this->templates[$name] = $this->loadFile($name);
        }

        return 
$this->templates[$name];
    }

    
/**
     * Helper function for loading a Mustache file by name.
     *
     * @throws Mustache_Exception_UnknownTemplateException If a template file is not found
     *
     * @param string $name
     *
     * @return string Mustache Template source
     */
    
protected function loadFile($name)
    {
        
$fileName $this->getFileName($name);

        if (
$this->shouldCheckPath() && !file_exists($fileName)) {
            throw new 
Mustache_Exception_UnknownTemplateException($name);
        }

        return 
file_get_contents($fileName);
    }

    
/**
     * Helper function for getting a Mustache template file name.
     *
     * @param string $name
     *
     * @return string Template file name
     */
    
protected function getFileName($name)
    {
        
$fileName $this->baseDir '/' $name;
        if (
substr($fileNamestrlen($this->extension)) !== $this->extension) {
            
$fileName .= $this->extension;
        }

        return 
$fileName;
    }

    
/**
     * Only check if baseDir is a directory and requested templates are files if
     * baseDir is using the filesystem stream wrapper.
     *
     * @return bool Whether to check `is_dir` and `file_exists`
     */
    
protected function shouldCheckPath()
    {
        return 
strpos($this->baseDir'://') === false || strpos($this->baseDir'file://') === 0;
    }
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A Mustache Template cascading loader implementation, which delegates to other
 * Loader instances.
 */
class Mustache_Loader_CascadingLoader implements Mustache_Loader
{
    private 
$loaders;

    
/**
     * Construct a CascadingLoader with an array of loaders.
     *
     *     $loader = new Mustache_Loader_CascadingLoader(array(
     *         new Mustache_Loader_InlineLoader(__FILE__, __COMPILER_HALT_OFFSET__),
     *         new Mustache_Loader_FilesystemLoader(__DIR__.'/templates')
     *     ));
     *
     * @param Mustache_Loader[] $loaders
     */
    
public function __construct(array $loaders = array())
    {
        
$this->loaders = array();
        foreach (
$loaders as $loader) {
            
$this->addLoader($loader);
        }
    }

    
/**
     * Add a Loader instance.
     *
     * @param Mustache_Loader $loader
     */
    
public function addLoader(Mustache_Loader $loader)
    {
        
$this->loaders[] = $loader;
    }

    
/**
     * Load a Template by name.
     *
     * @throws Mustache_Exception_UnknownTemplateException If a template file is not found
     *
     * @param string $name
     *
     * @return string Mustache Template source
     */
    
public function load($name)
    {
        foreach (
$this->loaders as $loader) {
            try {
                return 
$loader->load($name);
            } catch (
Mustache_Exception_UnknownTemplateException $e) {
                
// do nothing, check the next loader.
            
}
        }

        throw new 
Mustache_Exception_UnknownTemplateException($name);
    }
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A Mustache Template loader for inline templates.
 *
 * With the InlineLoader, templates can be defined at the end of any PHP source
 * file:
 *
 *     $loader  = new Mustache_Loader_InlineLoader(__FILE__, __COMPILER_HALT_OFFSET__);
 *     $hello   = $loader->load('hello');
 *     $goodbye = $loader->load('goodbye');
 *
 *     __halt_compiler();
 *
 *     @@ hello
 *     Hello, {{ planet }}!
 *
 *     @@ goodbye
 *     Goodbye, cruel {{ planet }}
 *
 * Templates are deliniated by lines containing only `@@ name`.
 *
 * The InlineLoader is well-suited to micro-frameworks such as Silex:
 *
 *     $app->register(new MustacheServiceProvider, array(
 *         'mustache.loader' => new Mustache_Loader_InlineLoader(__FILE__, __COMPILER_HALT_OFFSET__)
 *     ));
 *
 *     $app->get('/{name}', function ($name) use ($app) {
 *         return $app['mustache']->render('hello', compact('name'));
 *     })
 *     ->value('name', 'world');
 *
 *     // ...
 *
 *     __halt_compiler();
 *
 *     @@ hello
 *     Hello, {{ name }}!
 */
class Mustache_Loader_InlineLoader implements Mustache_Loader
{
    protected 
$fileName;
    protected 
$offset;
    protected 
$templates;

    
/**
     * The InlineLoader requires a filename and offset to process templates.
     *
     * The magic constants `__FILE__` and `__COMPILER_HALT_OFFSET__` are usually
     * perfectly suited to the job:
     *
     *     $loader = new Mustache_Loader_InlineLoader(__FILE__, __COMPILER_HALT_OFFSET__);
     *
     * Note that this only works if the loader is instantiated inside the same
     * file as the inline templates. If the templates are located in another
     * file, it would be necessary to manually specify the filename and offset.
     *
     * @param string $fileName The file to parse for inline templates
     * @param int    $offset   A string offset for the start of the templates.
     *                         This usually coincides with the `__halt_compiler`
     *                         call, and the `__COMPILER_HALT_OFFSET__`
     */
    
public function __construct($fileName$offset)
    {
        if (!
is_file($fileName)) {
            throw new 
Mustache_Exception_InvalidArgumentException('InlineLoader expects a valid filename.');
        }

        if (!
is_int($offset) || $offset 0) {
            throw new 
Mustache_Exception_InvalidArgumentException('InlineLoader expects a valid file offset.');
        }

        
$this->fileName $fileName;
        
$this->offset   $offset;
    }

    
/**
     * Load a Template by name.
     *
     * @throws Mustache_Exception_UnknownTemplateException If a template file is not found
     *
     * @param string $name
     *
     * @return string Mustache Template source
     */
    
public function load($name)
    {
        
$this->loadTemplates();

        if (!
array_key_exists($name$this->templates)) {
            throw new 
Mustache_Exception_UnknownTemplateException($name);
        }

        return 
$this->templates[$name];
    }

    
/**
     * Parse and load templates from the end of a source file.
     */
    
protected function loadTemplates()
    {
        if (
$this->templates === null) {
            
$this->templates = array();
            
$data file_get_contents($this->fileNamefalsenull$this->offset);
            foreach (
preg_split("/^@@(?= [\w\d\.]+$)/m"$data, -1) as $chunk) {
                if (
trim($chunk)) {
                    list(
$name$content)         = explode("\n"$chunk2);
                    
$this->templates[trim($name)] = trim($content);
                }
            }
        }
    }
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Mustache Template mutable Loader interface.
 */
interface Mustache_Loader_MutableLoader
{
    
/**
     * Set an associative array of Template sources for this loader.
     *
     * @param array $templates
     */
    
public function setTemplates(array $templates);

    
/**
     * Set a Template source by name.
     *
     * @param string $name
     * @param string $template Mustache Template source
     */
    
public function setTemplate($name$template);
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Mustache Template production filesystem Loader implementation.
 *
 * A production-ready FilesystemLoader, which doesn't require reading a file if it already exists in the template cache.
 *
 * {@inheritdoc}
 */
class Mustache_Loader_ProductionFilesystemLoader extends Mustache_Loader_FilesystemLoader
{
    private 
$statProps;

    
/**
     * Mustache production filesystem Loader constructor.
     *
     * Passing an $options array allows overriding certain Loader options during instantiation:
     *
     *     $options = array(
     *         // The filename extension used for Mustache templates. Defaults to '.mustache'
     *         'extension' => '.ms',
     *         'stat_props' => array('size', 'mtime'),
     *     );
     *
     * Specifying 'stat_props' overrides the stat properties used to invalidate the template cache. By default, this
     * uses 'mtime' and 'size', but this can be set to any of the properties supported by stat():
     *
     *     http://php.net/manual/en/function.stat.php
     *
     * You can also disable filesystem stat entirely:
     *
     *     $options = array('stat_props' => null);
     *
     * But with great power comes great responsibility. Namely, if you disable stat-based cache invalidation,
     * YOU MUST CLEAR THE TEMPLATE CACHE YOURSELF when your templates change. Make it part of your build or deploy
     * process so you don't forget!
     *
     * @throws Mustache_Exception_RuntimeException if $baseDir does not exist.
     *
     * @param string $baseDir Base directory containing Mustache template files.
     * @param array  $options Array of Loader options (default: array())
     */
    
public function __construct($baseDir, array $options = array())
    {
        
parent::__construct($baseDir$options);

        if (
array_key_exists('stat_props'$options)) {
            if (empty(
$options['stat_props'])) {
                
$this->statProps = array();
            } else {
                
$this->statProps $options['stat_props'];
            }
        } else {
            
$this->statProps = array('size''mtime');
        }
    }

    
/**
     * Helper function for loading a Mustache file by name.
     *
     * @throws Mustache_Exception_UnknownTemplateException If a template file is not found.
     *
     * @param string $name
     *
     * @return Mustache_Source Mustache Template source
     */
    
protected function loadFile($name)
    {
        
$fileName $this->getFileName($name);

        if (!
file_exists($fileName)) {
            throw new 
Mustache_Exception_UnknownTemplateException($name);
        }

        return new 
Mustache_Source_FilesystemSource($fileName$this->statProps);
    }
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Mustache Template array Loader implementation.
 *
 * An ArrayLoader instance loads Mustache Template source by name from an initial array:
 *
 *     $loader = new ArrayLoader(
 *         'foo' => '{{ bar }}',
 *         'baz' => 'Hey {{ qux }}!'
 *     );
 *
 *     $tpl = $loader->load('foo'); // '{{ bar }}'
 *
 * The ArrayLoader is used internally as a partials loader by Mustache_Engine instance when an array of partials
 * is set. It can also be used as a quick-and-dirty Template loader.
 */
class Mustache_Loader_ArrayLoader implements Mustache_LoaderMustache_Loader_MutableLoader
{
    private 
$templates;

    
/**
     * ArrayLoader constructor.
     *
     * @param array $templates Associative array of Template source (default: array())
     */
    
public function __construct(array $templates = array())
    {
        
$this->templates $templates;
    }

    
/**
     * Load a Template.
     *
     * @throws Mustache_Exception_UnknownTemplateException If a template file is not found
     *
     * @param string $name
     *
     * @return string Mustache Template source
     */
    
public function load($name)
    {
        if (!isset(
$this->templates[$name])) {
            throw new 
Mustache_Exception_UnknownTemplateException($name);
        }

        return 
$this->templates[$name];
    }

    
/**
     * Set an associative array of Template sources for this loader.
     *
     * @param array $templates
     */
    
public function setTemplates(array $templates)
    {
        
$this->templates $templates;
    }

    
/**
     * Set a Template source by name.
     *
     * @param string $name
     * @param string $template Mustache Template source
     */
    
public function setTemplate($name$template)
    {
        
$this->templates[$name] = $template;
    }
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Mustache Template string Loader implementation.
 *
 * A StringLoader instance is essentially a noop. It simply passes the 'name' argument straight through:
 *
 *     $loader = new StringLoader;
 *     $tpl = $loader->load('{{ foo }}'); // '{{ foo }}'
 *
 * This is the default Template Loader instance used by Mustache:
 *
 *     $m = new Mustache;
 *     $tpl = $m->loadTemplate('{{ foo }}');
 *     echo $tpl->render(array('foo' => 'bar')); // "bar"
 */
class Mustache_Loader_StringLoader implements Mustache_Loader
{
    
/**
     * Load a Template by source.
     *
     * @param string $name Mustache Template source
     *
     * @return string Mustache Template source
     */
    
public function load($name)
    {
        return 
$name;
    }
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Mustache Parser class.
 *
 * This class is responsible for turning a set of Mustache tokens into a parse tree.
 */
class Mustache_Parser
{
    private 
$lineNum;
    private 
$lineTokens;
    private 
$pragmas;
    private 
$defaultPragmas = array();

    private 
$pragmaFilters;
    private 
$pragmaBlocks;

    
/**
     * Process an array of Mustache tokens and convert them into a parse tree.
     *
     * @param array $tokens Set of Mustache tokens
     *
     * @return array Mustache token parse tree
     */
    
public function parse(array $tokens = array())
    {
        
$this->lineNum    = -1;
        
$this->lineTokens 0;
        
$this->pragmas    $this->defaultPragmas;

        
$this->pragmaFilters = isset($this->pragmas[Mustache_Engine::PRAGMA_FILTERS]);
        
$this->pragmaBlocks  = isset($this->pragmas[Mustache_Engine::PRAGMA_BLOCKS]);

        return 
$this->buildTree($tokens);
    }

    
/**
     * Enable pragmas across all templates, regardless of the presence of pragma
     * tags in the individual templates.
     *
     * @internal Users should set global pragmas in Mustache_Engine, not here :)
     *
     * @param string[] $pragmas
     */
    
public function setPragmas(array $pragmas)
    {
        
$this->pragmas = array();
        foreach (
$pragmas as $pragma) {
            
$this->enablePragma($pragma);
        }
        
$this->defaultPragmas $this->pragmas;
    }

    
/**
     * Helper method for recursively building a parse tree.
     *
     * @throws Mustache_Exception_SyntaxException when nesting errors or mismatched section tags are encountered
     *
     * @param array &$tokens Set of Mustache tokens
     * @param array $parent  Parent token (default: null)
     *
     * @return array Mustache Token parse tree
     */
    
private function buildTree(array &$tokens, array $parent null)
    {
        
$nodes = array();

        while (!empty(
$tokens)) {
            
$token array_shift($tokens);

            if (
$token[Mustache_Tokenizer::LINE] === $this->lineNum) {
                
$this->lineTokens++;
            } else {
                
$this->lineNum    $token[Mustache_Tokenizer::LINE];
                
$this->lineTokens 0;
            }

            if (
$this->pragmaFilters && isset($token[Mustache_Tokenizer::NAME])) {
                list(
$name$filters) = $this->getNameAndFilters($token[Mustache_Tokenizer::NAME]);
                if (!empty(
$filters)) {
                    
$token[Mustache_Tokenizer::NAME]    = $name;
                    
$token[Mustache_Tokenizer::FILTERS] = $filters;
                }
            }

            switch (
$token[Mustache_Tokenizer::TYPE]) {
                case 
Mustache_Tokenizer::T_DELIM_CHANGE:
                    
$this->checkIfTokenIsAllowedInParent($parent$token);
                    
$this->clearStandaloneLines($nodes$tokens);
                    break;

                case 
Mustache_Tokenizer::T_SECTION:
                case 
Mustache_Tokenizer::T_INVERTED:
                    
$this->checkIfTokenIsAllowedInParent($parent$token);
                    
$this->clearStandaloneLines($nodes$tokens);
                    
$nodes[] = $this->buildTree($tokens$token);
                    break;

                case 
Mustache_Tokenizer::T_END_SECTION:
                    if (!isset(
$parent)) {
                        
$msg sprintf(
                            
'Unexpected closing tag: /%s on line %d',
                            
$token[Mustache_Tokenizer::NAME],
                            
$token[Mustache_Tokenizer::LINE]
                        );
                        throw new 
Mustache_Exception_SyntaxException($msg$token);
                    }

                    if (
$token[Mustache_Tokenizer::NAME] !== $parent[Mustache_Tokenizer::NAME]) {
                        
$msg sprintf(
                            
'Nesting error: %s (on line %d) vs. %s (on line %d)',
                            
$parent[Mustache_Tokenizer::NAME],
                            
$parent[Mustache_Tokenizer::LINE],
                            
$token[Mustache_Tokenizer::NAME],
                            
$token[Mustache_Tokenizer::LINE]
                        );
                        throw new 
Mustache_Exception_SyntaxException($msg$token);
                    }

                    
$this->clearStandaloneLines($nodes$tokens);
                    
$parent[Mustache_Tokenizer::END]   = $token[Mustache_Tokenizer::INDEX];
                    
$parent[Mustache_Tokenizer::NODES] = $nodes;

                    return 
$parent;

                case 
Mustache_Tokenizer::T_PARTIAL:
                    
$this->checkIfTokenIsAllowedInParent($parent$token);
                    
//store the whitespace prefix for laters!
                    
if ($indent $this->clearStandaloneLines($nodes$tokens)) {
                        
$token[Mustache_Tokenizer::INDENT] = $indent[Mustache_Tokenizer::VALUE];
                    }
                    
$nodes[] = $token;
                    break;

                case 
Mustache_Tokenizer::T_PARENT:
                    
$this->checkIfTokenIsAllowedInParent($parent$token);
                    
$nodes[] = $this->buildTree($tokens$token);
                    break;

                case 
Mustache_Tokenizer::T_BLOCK_VAR:
                    if (
$this->pragmaBlocks) {
                        
// BLOCKS pragma is enabled, let's do this!
                        
if (isset($parent) && $parent[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_PARENT) {
                            
$token[Mustache_Tokenizer::TYPE] = Mustache_Tokenizer::T_BLOCK_ARG;
                        }
                        
$this->clearStandaloneLines($nodes$tokens);
                        
$nodes[] = $this->buildTree($tokens$token);
                    } else {
                        
// pretend this was just a normal "escaped" token...
                        
$token[Mustache_Tokenizer::TYPE] = Mustache_Tokenizer::T_ESCAPED;
                        
// TODO: figure out how to figure out if there was a space after this dollar:
                        
$token[Mustache_Tokenizer::NAME] = '$' $token[Mustache_Tokenizer::NAME];
                        
$nodes[] = $token;
                    }
                    break;

                case 
Mustache_Tokenizer::T_PRAGMA:
                    
$this->enablePragma($token[Mustache_Tokenizer::NAME]);
                    
// no break

                
case Mustache_Tokenizer::T_COMMENT:
                    
$this->clearStandaloneLines($nodes$tokens);
                    
$nodes[] = $token;
                    break;

                default:
                    
$nodes[] = $token;
                    break;
            }
        }

        if (isset(
$parent)) {
            
$msg sprintf(
                
'Missing closing tag: %s opened on line %d',
                
$parent[Mustache_Tokenizer::NAME],
                
$parent[Mustache_Tokenizer::LINE]
            );
            throw new 
Mustache_Exception_SyntaxException($msg$parent);
        }

        return 
$nodes;
    }

    
/**
     * Clear standalone line tokens.
     *
     * Returns a whitespace token for indenting partials, if applicable.
     *
     * @param array $nodes  Parsed nodes
     * @param array $tokens Tokens to be parsed
     *
     * @return array|null Resulting indent token, if any
     */
    
private function clearStandaloneLines(array &$nodes, array &$tokens)
    {
        if (
$this->lineTokens 1) {
            
// this is the third or later node on this line, so it can't be standalone
            
return;
        }

        
$prev null;
        if (
$this->lineTokens === 1) {
            
// this is the second node on this line, so it can't be standalone
            // unless the previous node is whitespace.
            
if ($prev end($nodes)) {
                if (!
$this->tokenIsWhitespace($prev)) {
                    return;
                }
            }
        }

        if (
$next reset($tokens)) {
            
// If we're on a new line, bail.
            
if ($next[Mustache_Tokenizer::LINE] !== $this->lineNum) {
                return;
            }

            
// If the next token isn't whitespace, bail.
            
if (!$this->tokenIsWhitespace($next)) {
                return;
            }

            if (
count($tokens) !== 1) {
                
// Unless it's the last token in the template, the next token
                // must end in newline for this to be standalone.
                
if (substr($next[Mustache_Tokenizer::VALUE], -1) !== "\n") {
                    return;
                }
            }

            
// Discard the whitespace suffix
            
array_shift($tokens);
        }

        if (
$prev) {
            
// Return the whitespace prefix, if any
            
return array_pop($nodes);
        }
    }

    
/**
     * Check whether token is a whitespace token.
     *
     * True if token type is T_TEXT and value is all whitespace characters.
     *
     * @param array $token
     *
     * @return bool True if token is a whitespace token
     */
    
private function tokenIsWhitespace(array $token)
    {
        if (
$token[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_TEXT) {
            return 
preg_match('/^\s*$/'$token[Mustache_Tokenizer::VALUE]);
        }

        return 
false;
    }

    
/**
     * Check whether a token is allowed inside a parent tag.
     *
     * @throws Mustache_Exception_SyntaxException if an invalid token is found inside a parent tag
     *
     * @param array|null $parent
     * @param array      $token
     */
    
private function checkIfTokenIsAllowedInParent($parent, array $token)
    {
        if (isset(
$parent) && $parent[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_PARENT) {
            throw new 
Mustache_Exception_SyntaxException('Illegal content in < parent tag'$token);
        }
    }

    
/**
     * Split a tag name into name and filters.
     *
     * @param string $name
     *
     * @return array [Tag name, Array of filters]
     */
    
private function getNameAndFilters($name)
    {
        
$filters array_map('trim'explode('|'$name));
        
$name    array_shift($filters);

        return array(
$name$filters);
    }

    
/**
     * Enable a pragma.
     *
     * @param string $name
     */
    
private function enablePragma($name)
    {
        
$this->pragmas[$name] = true;

        switch (
$name) {
            case 
Mustache_Engine::PRAGMA_BLOCKS:
                
$this->pragmaBlocks true;
                break;

            case 
Mustache_Engine::PRAGMA_FILTERS:
                
$this->pragmaFilters true;
                break;
        }
    }
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Invalid argument exception.
 */
class Mustache_Exception_InvalidArgumentException extends InvalidArgumentException implements Mustache_Exception
{
    
// This space intentionally left blank.
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Logic exception.
 */
class Mustache_Exception_LogicException extends LogicException implements Mustache_Exception
{
    
// This space intentionally left blank.
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Mustache syntax exception.
 */
class Mustache_Exception_SyntaxException extends LogicException implements Mustache_Exception
{
    protected 
$token;

    
/**
     * @param string    $msg
     * @param array     $token
     * @param Exception $previous
     */
    
public function __construct($msg, array $tokenException $previous null)
    {
        
$this->token $token;
        if (
version_compare(PHP_VERSION'5.3.0''>=')) {
            
parent::__construct($msg0$previous);
        } else {
            
parent::__construct($msg); // @codeCoverageIgnore
        
}
    }

    
/**
     * @return array
     */
    
public function getToken()
    {
        return 
$this->token;
    }
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Unknown template exception.
 */
class Mustache_Exception_UnknownTemplateException extends InvalidArgumentException implements Mustache_Exception
{
    protected 
$templateName;

    
/**
     * @param string    $templateName
     * @param Exception $previous
     */
    
public function __construct($templateNameException $previous null)
    {
        
$this->templateName $templateName;
        
$message sprintf('Unknown template: %s'$templateName);
        if (
version_compare(PHP_VERSION'5.3.0''>=')) {
            
parent::__construct($message0$previous);
        } else {
            
parent::__construct($message); // @codeCoverageIgnore
        
}
    }

    public function 
getTemplateName()
    {
        return 
$this->templateName;
    }
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Unknown filter exception.
 */
class Mustache_Exception_UnknownFilterException extends UnexpectedValueException implements Mustache_Exception
{
    protected 
$filterName;

    
/**
     * @param string    $filterName
     * @param Exception $previous
     */
    
public function __construct($filterNameException $previous null)
    {
        
$this->filterName $filterName;
        
$message sprintf('Unknown filter: %s'$filterName);
        if (
version_compare(PHP_VERSION'5.3.0''>=')) {
            
parent::__construct($message0$previous);
        } else {
            
parent::__construct($message); // @codeCoverageIgnore
        
}
    }

    public function 
getFilterName()
    {
        return 
$this->filterName;
    }
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Unknown helper exception.
 */
class Mustache_Exception_UnknownHelperException extends InvalidArgumentException implements Mustache_Exception
{
    protected 
$helperName;

    
/**
     * @param string    $helperName
     * @param Exception $previous
     */
    
public function __construct($helperNameException $previous null)
    {
        
$this->helperName $helperName;
        
$message sprintf('Unknown helper: %s'$helperName);
        if (
version_compare(PHP_VERSION'5.3.0''>=')) {
            
parent::__construct($message0$previous);
        } else {
            
parent::__construct($message); // @codeCoverageIgnore
        
}
    }

    public function 
getHelperName()
    {
        return 
$this->helperName;
    }
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Runtime exception.
 */
class Mustache_Exception_RuntimeException extends RuntimeException implements Mustache_Exception
{
    
// This space intentionally left blank.
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Abstract Mustache Cache class.
 *
 * Provides logging support to child implementations.
 *
 * @abstract
 */
abstract class Mustache_Cache_AbstractCache implements Mustache_Cache
{
    private 
$logger null;

    
/**
     * Get the current logger instance.
     *
     * @return Mustache_Logger|Psr\Log\LoggerInterface
     */
    
public function getLogger()
    {
        return 
$this->logger;
    }

    
/**
     * Set a logger instance.
     *
     * @param Mustache_Logger|Psr\Log\LoggerInterface $logger
     */
    
public function setLogger($logger null)
    {
        if (
$logger !== null && !($logger instanceof Mustache_Logger || is_a($logger'Psr\\Log\\LoggerInterface'))) {
            throw new 
Mustache_Exception_InvalidArgumentException('Expected an instance of Mustache_Logger or Psr\\Log\\LoggerInterface.');
        }

        
$this->logger $logger;
    }

    
/**
     * Add a log record if logging is enabled.
     *
     * @param string $level   The logging level
     * @param string $message The log message
     * @param array  $context The log context
     */
    
protected function log($level$message, array $context = array())
    {
        if (isset(
$this->logger)) {
            
$this->logger->log($level$message$context);
        }
    }
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Mustache Cache in-memory implementation.
 *
 * The in-memory cache is used for uncached lambda section templates. It's also useful during development, but is not
 * recommended for production use.
 */
class Mustache_Cache_NoopCache extends Mustache_Cache_AbstractCache
{
    
/**
     * Loads nothing. Move along.
     *
     * @param string $key
     *
     * @return bool
     */
    
public function load($key)
    {
        return 
false;
    }

    
/**
     * Loads the compiled Mustache Template class without caching.
     *
     * @param string $key
     * @param string $value
     */
    
public function cache($key$value)
    {
        
$this->log(
            
Mustache_Logger::WARNING,
            
'Template cache disabled, evaluating "{className}" class at runtime',
            array(
'className' => $key)
        );
        eval(
'?>' $value);
    }
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Mustache Cache filesystem implementation.
 *
 * A FilesystemCache instance caches Mustache Template classes from the filesystem by name:
 *
 *     $cache = new Mustache_Cache_FilesystemCache(dirname(__FILE__).'/cache');
 *     $cache->cache($className, $compiledSource);
 *
 * The FilesystemCache benefits from any opcode caching that may be setup in your environment. So do that, k?
 */
class Mustache_Cache_FilesystemCache extends Mustache_Cache_AbstractCache
{
    private 
$baseDir;
    private 
$fileMode;

    
/**
     * Filesystem cache constructor.
     *
     * @param string $baseDir  Directory for compiled templates
     * @param int    $fileMode Override default permissions for cache files. Defaults to using the system-defined umask
     */
    
public function __construct($baseDir$fileMode null)
    {
        
$this->baseDir $baseDir;
        
$this->fileMode $fileMode;
    }

    
/**
     * Load the class from cache using `require_once`.
     *
     * @param string $key
     *
     * @return bool
     */
    
public function load($key)
    {
        
$fileName $this->getCacheFilename($key);
        if (!
is_file($fileName)) {
            return 
false;
        }

        require_once 
$fileName;

        return 
true;
    }

    
/**
     * Cache and load the compiled class.
     *
     * @param string $key
     * @param string $value
     */
    
public function cache($key$value)
    {
        
$fileName $this->getCacheFilename($key);

        
$this->log(
            
Mustache_Logger::DEBUG,
            
'Writing to template cache: "{fileName}"',
            array(
'fileName' => $fileName)
        );

        
$this->writeFile($fileName$value);
        
$this->load($key);
    }

    
/**
     * Build the cache filename.
     * Subclasses should override for custom cache directory structures.
     *
     * @param string $name
     *
     * @return string
     */
    
protected function getCacheFilename($name)
    {
        return 
sprintf('%s/%s.php'$this->baseDir$name);
    }

    
/**
     * Create cache directory.
     *
     * @throws Mustache_Exception_RuntimeException If unable to create directory
     *
     * @param string $fileName
     *
     * @return string
     */
    
private function buildDirectoryForFilename($fileName)
    {
        
$dirName dirname($fileName);
        if (!
is_dir($dirName)) {
            
$this->log(
                
Mustache_Logger::INFO,
                
'Creating Mustache template cache directory: "{dirName}"',
                array(
'dirName' => $dirName)
            );

            @
mkdir($dirName0777true);
            
// @codeCoverageIgnoreStart
            
if (!is_dir($dirName)) {
                throw new 
Mustache_Exception_RuntimeException(sprintf('Failed to create cache directory "%s".'$dirName));
            }
            
// @codeCoverageIgnoreEnd
        
}

        return 
$dirName;
    }

    
/**
     * Write cache file.
     *
     * @throws Mustache_Exception_RuntimeException If unable to write file
     *
     * @param string $fileName
     * @param string $value
     */
    
private function writeFile($fileName$value)
    {
        
$dirName $this->buildDirectoryForFilename($fileName);

        
$this->log(
            
Mustache_Logger::DEBUG,
            
'Caching compiled template to "{fileName}"',
            array(
'fileName' => $fileName)
        );

        
$tempFile tempnam($dirNamebasename($fileName));
        if (
false !== @file_put_contents($tempFile$value)) {
            if (@
rename($tempFile$fileName)) {
                
$mode = isset($this->fileMode) ? $this->fileMode : (0666 & ~umask());
                @
chmod($fileName$mode);

                return;
            }

            
// @codeCoverageIgnoreStart
            
$this->log(
                
Mustache_Logger::ERROR,
                
'Unable to rename Mustache temp cache file: "{tempName}" -> "{fileName}"',
                array(
'tempName' => $tempFile'fileName' => $fileName)
            );
            
// @codeCoverageIgnoreEnd
        
}

        
// @codeCoverageIgnoreStart
        
throw new Mustache_Exception_RuntimeException(sprintf('Failed to write cache file "%s".'$fileName));
        
// @codeCoverageIgnoreEnd
    
}
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Mustache Cache interface.
 *
 * Interface for caching and loading Mustache_Template classes
 * generated by the Mustache_Compiler.
 */
interface Mustache_Cache
{
    
/**
     * Load a compiled Mustache_Template class from cache.
     *
     * @param string $key
     *
     * @return bool indicates successfully class load
     */
    
public function load($key);

    
/**
     * Cache and load a compiled Mustache_Template class.
     *
     * @param string $key
     * @param string $value
     */
    
public function cache($key$value);

    
/**
     * Set a logger instance.
     *
     * @param Mustache_Logger|Psr\Log\LoggerInterface $logger
     */
    
public function setLogger($logger null);
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Mustache template Source interface.
 */
interface Mustache_Source
{
    
/**
     * Get the Source key (used to generate the compiled class name).
     *
     * This must return a distinct key for each template source. For example, an
     * MD5 hash of the template contents would probably do the trick. The
     * ProductionFilesystemLoader uses mtime and file path. If your production
     * source directory is under version control, you could use the current Git
     * rev and the file path...
     *
     * @throws RuntimeException when a source file cannot be read
     *
     * @return string
     */
    
public function getKey();

    
/**
     * Get the template Source.
     *
     * @throws RuntimeException when a source file cannot be read
     *
     * @return string
     */
    
public function getSource();
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A Mustache Stream Logger.
 *
 * The Stream Logger wraps a file resource instance (such as a stream) or a
 * stream URL. All log messages over the threshold level will be appended to
 * this stream.
 *
 * Hint: Try `php://stderr` for your stream URL.
 */
class Mustache_Logger_StreamLogger extends Mustache_Logger_AbstractLogger
{
    protected static 
$levels = array(
        
self::DEBUG     => 100,
        
self::INFO      => 200,
        
self::NOTICE    => 250,
        
self::WARNING   => 300,
        
self::ERROR     => 400,
        
self::CRITICAL  => 500,
        
self::ALERT     => 550,
        
self::EMERGENCY => 600,
    );

    protected 
$level;
    protected 
$stream null;
    protected 
$url    null;

    
/**
     * @throws InvalidArgumentException if the logging level is unknown
     *
     * @param resource|string $stream Resource instance or URL
     * @param int             $level  The minimum logging level at which this handler will be triggered
     */
    
public function __construct($stream$level Mustache_Logger::ERROR)
    {
        
$this->setLevel($level);

        if (
is_resource($stream)) {
            
$this->stream $stream;
        } else {
            
$this->url $stream;
        }
    }

    
/**
     * Close stream resources.
     */
    
public function __destruct()
    {
        if (
is_resource($this->stream)) {
            
fclose($this->stream);
        }
    }

    
/**
     * Set the minimum logging level.
     *
     * @throws Mustache_Exception_InvalidArgumentException if the logging level is unknown
     *
     * @param int $level The minimum logging level which will be written
     */
    
public function setLevel($level)
    {
        if (!
array_key_exists($levelself::$levels)) {
            throw new 
Mustache_Exception_InvalidArgumentException(sprintf('Unexpected logging level: %s'$level));
        }

        
$this->level $level;
    }

    
/**
     * Get the current minimum logging level.
     *
     * @return int
     */
    
public function getLevel()
    {
        return 
$this->level;
    }

    
/**
     * Logs with an arbitrary level.
     *
     * @throws Mustache_Exception_InvalidArgumentException if the logging level is unknown
     *
     * @param mixed  $level
     * @param string $message
     * @param array  $context
     */
    
public function log($level$message, array $context = array())
    {
        if (!
array_key_exists($levelself::$levels)) {
            throw new 
Mustache_Exception_InvalidArgumentException(sprintf('Unexpected logging level: %s'$level));
        }

        if (
self::$levels[$level] >= self::$levels[$this->level]) {
            
$this->writeLog($level$message$context);
        }
    }

    
/**
     * Write a record to the log.
     *
     * @throws Mustache_Exception_LogicException   If neither a stream resource nor url is present
     * @throws Mustache_Exception_RuntimeException If the stream url cannot be opened
     *
     * @param int    $level   The logging level
     * @param string $message The log message
     * @param array  $context The log context
     */
    
protected function writeLog($level$message, array $context = array())
    {
        if (!
is_resource($this->stream)) {
            if (!isset(
$this->url)) {
                throw new 
Mustache_Exception_LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().');
            }

            
$this->stream fopen($this->url'a');
            if (!
is_resource($this->stream)) {
                
// @codeCoverageIgnoreStart
                
throw new Mustache_Exception_RuntimeException(sprintf('The stream or file "%s" could not be opened.'$this->url));
                
// @codeCoverageIgnoreEnd
            
}
        }

        
fwrite($this->streamself::formatLine($level$message$context));
    }

    
/**
     * Gets the name of the logging level.
     *
     * @throws InvalidArgumentException if the logging level is unknown
     *
     * @param int $level
     *
     * @return string
     */
    
protected static function getLevelName($level)
    {
        return 
strtoupper($level);
    }

    
/**
     * Format a log line for output.
     *
     * @param int    $level   The logging level
     * @param string $message The log message
     * @param array  $context The log context
     *
     * @return string
     */
    
protected static function formatLine($level$message, array $context = array())
    {
        return 
sprintf(
            
"%s: %s\n",
            
self::getLevelName($level),
            
self::interpolateMessage($message$context)
        );
    }

    
/**
     * Interpolate context values into the message placeholders.
     *
     * @param string $message
     * @param array  $context
     *
     * @return string
     */
    
protected static function interpolateMessage($message, array $context = array())
    {
        if (
strpos($message'{') === false) {
            return 
$message;
        }

        
// build a replacement array with braces around the context keys
        
$replace = array();
        foreach (
$context as $key => $val) {
            
$replace['{' $key '}'] = $val;
        }

        
// interpolate replacement values into the the message and return
        
return strtr($message$replace);
    }
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * This is a simple Logger implementation that other Loggers can inherit from.
 *
 * This is identical to the Psr\Log\AbstractLogger.
 *
 * 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 Mustache_Logger_AbstractLogger implements Mustache_Logger
{
    
/**
     * System is unusable.
     *
     * @param string $message
     * @param array  $context
     */
    
public function emergency($message, array $context = array())
    {
        
$this->log(Mustache_Logger::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.
     *
     * @param string $message
     * @param array  $context
     */
    
public function alert($message, array $context = array())
    {
        
$this->log(Mustache_Logger::ALERT$message$context);
    }

    
/**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     *
     * @param string $message
     * @param array  $context
     */
    
public function critical($message, array $context = array())
    {
        
$this->log(Mustache_Logger::CRITICAL$message$context);
    }

    
/**
     * Runtime errors that do not require immediate action but should typically
     * be logged and monitored.
     *
     * @param string $message
     * @param array  $context
     */
    
public function error($message, array $context = array())
    {
        
$this->log(Mustache_Logger::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.
     *
     * @param string $message
     * @param array  $context
     */
    
public function warning($message, array $context = array())
    {
        
$this->log(Mustache_Logger::WARNING$message$context);
    }

    
/**
     * Normal but significant events.
     *
     * @param string $message
     * @param array  $context
     */
    
public function notice($message, array $context = array())
    {
        
$this->log(Mustache_Logger::NOTICE$message$context);
    }

    
/**
     * Interesting events.
     *
     * Example: User logs in, SQL logs.
     *
     * @param string $message
     * @param array  $context
     */
    
public function info($message, array $context = array())
    {
        
$this->log(Mustache_Logger::INFO$message$context);
    }

    
/**
     * Detailed debug information.
     *
     * @param string $message
     * @param array  $context
     */
    
public function debug($message, array $context = array())
    {
        
$this->log(Mustache_Logger::DEBUG$message$context);
    }
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Mustache Lambda Helper.
 *
 * Passed as the second argument to section lambdas (higher order sections),
 * giving them access to a `render` method for rendering a string with the
 * current context.
 */
class Mustache_LambdaHelper
{
    private 
$mustache;
    private 
$context;
    private 
$delims;

    
/**
     * Mustache Lambda Helper constructor.
     *
     * @param Mustache_Engine  $mustache Mustache engine instance
     * @param Mustache_Context $context  Rendering context
     * @param string           $delims   Optional custom delimiters, in the format `{{= <% %> =}}`. (default: null)
     */
    
public function __construct(Mustache_Engine $mustacheMustache_Context $context$delims null)
    {
        
$this->mustache $mustache;
        
$this->context  $context;
        
$this->delims   $delims;
    }

    
/**
     * Render a string as a Mustache template with the current rendering context.
     *
     * @param string $string
     *
     * @return string Rendered template
     */
    
public function render($string)
    {
        return 
$this->mustache
            
->loadLambda((string) $string$this->delims)
            ->
renderInternal($this->context);
    }

    
/**
     * Render a string as a Mustache template with the current rendering context.
     *
     * @param string $string
     *
     * @return string Rendered template
     */
    
public function __invoke($string)
    {
        return 
$this->render($string);
    }

    
/**
     * Get a Lambda Helper with custom delimiters.
     *
     * @param string $delims Custom delimiters, in the format `{{= <% %> =}}`
     *
     * @return Mustache_LambdaHelper
     */
    
public function withDelimiters($delims)
    {
        return new 
self($this->mustache$this->context$delims);
    }
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A collection of helpers for a Mustache instance.
 */
class Mustache_HelperCollection
{
    private 
$helpers = array();

    
/**
     * Helper Collection constructor.
     *
     * Optionally accepts an array (or Traversable) of `$name => $helper` pairs.
     *
     * @throws Mustache_Exception_InvalidArgumentException if the $helpers argument isn't an array or Traversable
     *
     * @param array|Traversable $helpers (default: null)
     */
    
public function __construct($helpers null)
    {
        if (
$helpers === null) {
            return;
        }

        if (!
is_array($helpers) && !$helpers instanceof Traversable) {
            throw new 
Mustache_Exception_InvalidArgumentException('HelperCollection constructor expects an array of helpers');
        }

        foreach (
$helpers as $name => $helper) {
            
$this->add($name$helper);
        }
    }

    
/**
     * Magic mutator.
     *
     * @see Mustache_HelperCollection::add
     *
     * @param string $name
     * @param mixed  $helper
     */
    
public function __set($name$helper)
    {
        
$this->add($name$helper);
    }

    
/**
     * Add a helper to this collection.
     *
     * @param string $name
     * @param mixed  $helper
     */
    
public function add($name$helper)
    {
        
$this->helpers[$name] = $helper;
    }

    
/**
     * Magic accessor.
     *
     * @see Mustache_HelperCollection::get
     *
     * @param string $name
     *
     * @return mixed Helper
     */
    
public function __get($name)
    {
        return 
$this->get($name);
    }

    
/**
     * Get a helper by name.
     *
     * @throws Mustache_Exception_UnknownHelperException If helper does not exist
     *
     * @param string $name
     *
     * @return mixed Helper
     */
    
public function get($name)
    {
        if (!
$this->has($name)) {
            throw new 
Mustache_Exception_UnknownHelperException($name);
        }

        return 
$this->helpers[$name];
    }

    
/**
     * Magic isset().
     *
     * @see Mustache_HelperCollection::has
     *
     * @param string $name
     *
     * @return bool True if helper is present
     */
    
public function __isset($name)
    {
        return 
$this->has($name);
    }

    
/**
     * Check whether a given helper is present in the collection.
     *
     * @param string $name
     *
     * @return bool True if helper is present
     */
    
public function has($name)
    {
        return 
array_key_exists($name$this->helpers);
    }

    
/**
     * Magic unset().
     *
     * @see Mustache_HelperCollection::remove
     *
     * @param string $name
     */
    
public function __unset($name)
    {
        
$this->remove($name);
    }

    
/**
     * Check whether a given helper is present in the collection.
     *
     * @throws Mustache_Exception_UnknownHelperException if the requested helper is not present
     *
     * @param string $name
     */
    
public function remove($name)
    {
        if (!
$this->has($name)) {
            throw new 
Mustache_Exception_UnknownHelperException($name);
        }

        unset(
$this->helpers[$name]);
    }

    
/**
     * Clear the helper collection.
     *
     * Removes all helpers from this collection
     */
    
public function clear()
    {
        
$this->helpers = array();
    }

    
/**
     * Check whether the helper collection is empty.
     *
     * @return bool True if the collection is empty
     */
    
public function isEmpty()
    {
        return empty(
$this->helpers);
    }
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Abstract Mustache Template class.
 *
 * @abstract
 */
abstract class Mustache_Template
{
    
/**
     * @var Mustache_Engine
     */
    
protected $mustache;

    
/**
     * @var bool
     */
    
protected $strictCallables false;

    
/**
     * Mustache Template constructor.
     *
     * @param Mustache_Engine $mustache
     */
    
public function __construct(Mustache_Engine $mustache)
    {
        
$this->mustache $mustache;
    }

    
/**
     * Mustache Template instances can be treated as a function and rendered by simply calling them.
     *
     *     $m = new Mustache_Engine;
     *     $tpl = $m->loadTemplate('Hello, {{ name }}!');
     *     echo $tpl(array('name' => 'World')); // "Hello, World!"
     *
     * @see Mustache_Template::render
     *
     * @param mixed $context Array or object rendering context (default: array())
     *
     * @return string Rendered template
     */
    
public function __invoke($context = array())
    {
        return 
$this->render($context);
    }

    
/**
     * Render this template given the rendering context.
     *
     * @param mixed $context Array or object rendering context (default: array())
     *
     * @return string Rendered template
     */
    
public function render($context = array())
    {
        return 
$this->renderInternal(
            
$this->prepareContextStack($context)
        );
    }

    
/**
     * Internal rendering method implemented by Mustache Template concrete subclasses.
     *
     * This is where the magic happens :)
     *
     * NOTE: This method is not part of the Mustache.php public API.
     *
     * @param Mustache_Context $context
     * @param string           $indent  (default: '')
     *
     * @return string Rendered template
     */
    
abstract public function renderInternal(Mustache_Context $context$indent '');

    
/**
     * Tests whether a value should be iterated over (e.g. in a section context).
     *
     * In most languages there are two distinct array types: list and hash (or whatever you want to call them). Lists
     * should be iterated, hashes should be treated as objects. Mustache follows this paradigm for Ruby, Javascript,
     * Java, Python, etc.
     *
     * PHP, however, treats lists and hashes as one primitive type: array. So Mustache.php needs a way to distinguish
     * between between a list of things (numeric, normalized array) and a set of variables to be used as section context
     * (associative array). In other words, this will be iterated over:
     *
     *     $items = array(
     *         array('name' => 'foo'),
     *         array('name' => 'bar'),
     *         array('name' => 'baz'),
     *     );
     *
     * ... but this will be used as a section context block:
     *
     *     $items = array(
     *         1        => array('name' => 'foo'),
     *         'banana' => array('name' => 'bar'),
     *         42       => array('name' => 'baz'),
     *     );
     *
     * @param mixed $value
     *
     * @return bool True if the value is 'iterable'
     */
    
protected function isIterable($value)
    {
        switch (
gettype($value)) {
            case 
'object':
                return 
$value instanceof Traversable;

            case 
'array':
                
$i 0;
                foreach (
$value as $k => $v) {
                    if (
$k !== $i++) {
                        return 
false;
                    }
                }

                return 
true;

            default:
                return 
false;
        }
    }

    
/**
     * Helper method to prepare the Context stack.
     *
     * Adds the Mustache HelperCollection to the stack's top context frame if helpers are present.
     *
     * @param mixed $context Optional first context frame (default: null)
     *
     * @return Mustache_Context
     */
    
protected function prepareContextStack($context null)
    {
        
$stack = new Mustache_Context();

        
$helpers $this->mustache->getHelpers();
        if (!
$helpers->isEmpty()) {
            
$stack->push($helpers);
        }

        if (!empty(
$context)) {
            
$stack->push($context);
        }

        return 
$stack;
    }

    
/**
     * Resolve a context value.
     *
     * Invoke the value if it is callable, otherwise return the value.
     *
     * @param mixed            $value
     * @param Mustache_Context $context
     *
     * @return string
     */
    
protected function resolveValue($valueMustache_Context $context)
    {
        if ((
$this->strictCallables is_object($value) : !is_string($value)) && is_callable($value)) {
            return 
$this->mustache
                
->loadLambda((string) call_user_func($value))
                ->
renderInternal($context);
        }

        return 
$value;
    }
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Mustache class autoloader.
 */
class Mustache_Autoloader
{
    private 
$baseDir;

    
/**
     * An array where the key is the baseDir and the key is an instance of this
     * class.
     *
     * @var array
     */
    
private static $instances;

    
/**
     * Autoloader constructor.
     *
     * @param string $baseDir Mustache library base directory (default: dirname(__FILE__).'/..')
     */
    
public function __construct($baseDir null)
    {
        if (
$baseDir === null) {
            
$baseDir dirname(__FILE__) . '/..';
        }

        
// realpath doesn't always work, for example, with stream URIs
        
$realDir realpath($baseDir);
        if (
is_dir($realDir)) {
            
$this->baseDir $realDir;
        } else {
            
$this->baseDir $baseDir;
        }
    }

    
/**
     * Register a new instance as an SPL autoloader.
     *
     * @param string $baseDir Mustache library base directory (default: dirname(__FILE__).'/..')
     *
     * @return Mustache_Autoloader Registered Autoloader instance
     */
    
public static function register($baseDir null)
    {
        
$key $baseDir $baseDir 0;

        if (!isset(
self::$instances[$key])) {
            
self::$instances[$key] = new self($baseDir);
        }

        
$loader self::$instances[$key];
        
spl_autoload_register(array($loader'autoload'));

        return 
$loader;
    }

    
/**
     * Autoload Mustache classes.
     *
     * @param string $class
     */
    
public function autoload($class)
    {
        if (
$class[0] === '\\') {
            
$class substr($class1);
        }

        if (
strpos($class'Mustache') !== 0) {
            return;
        }

        
$file sprintf('%s/%s.php'$this->baseDirstr_replace('_''/'$class));
        if (
is_file($file)) {
            require 
$file;
        }
    }
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Mustache template Filesystem Source.
 *
 * This template Source uses stat() to generate the Source key, so that using
 * pre-compiled templates doesn't require hitting the disk to read the source.
 * It is more suitable for production use, and is used by default in the
 * ProductionFilesystemLoader.
 */
class Mustache_Source_FilesystemSource implements Mustache_Source
{
    private 
$fileName;
    private 
$statProps;
    private 
$stat;

    
/**
     * Filesystem Source constructor.
     *
     * @param string $fileName
     * @param array  $statProps
     */
    
public function __construct($fileName, array $statProps)
    {
        
$this->fileName $fileName;
        
$this->statProps $statProps;
    }

    
/**
     * Get the Source key (used to generate the compiled class name).
     *
     * @throws Mustache_Exception_RuntimeException when a source file cannot be read
     *
     * @return string
     */
    
public function getKey()
    {
        
$chunks = array(
            
'fileName' => $this->fileName,
        );

        if (!empty(
$this->statProps)) {
            if (!isset(
$this->stat)) {
                
$this->stat = @stat($this->fileName);
            }

            if (
$this->stat === false) {
                throw new 
Mustache_Exception_RuntimeException(sprintf('Failed to read source file "%s".'$this->fileName));
            }

            foreach (
$this->statProps as $prop) {
                
$chunks[$prop] = $this->stat[$prop];
            }
        }

        return 
json_encode($chunks);
    }

    
/**
     * Get the template Source.
     *
     * @return string
     */
    
public function getSource()
    {
        return 
file_get_contents($this->fileName);
    }
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * A Mustache Exception interface.
 */
interface Mustache_Exception
{
    
// This space intentionally left blank.
}
<?
php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Mustache Tokenizer class.
 *
 * This class is responsible for turning raw template source into a set of Mustache tokens.
 */
class Mustache_Tokenizer
{
    
// Finite state machine states
    
const IN_TEXT     0;
    const 
IN_TAG_TYPE 1;
    const 
IN_TAG      2;

    
// Token types
    
const T_SECTION      '#';
    const 
T_INVERTED     '^';
    const 
T_END_SECTION  '/';
    const 
T_COMMENT      '!';
    const 
T_PARTIAL      '>';
    const 
T_PARENT       '<';
    const 
T_DELIM_CHANGE '=';
    const 
T_ESCAPED      '_v';
    const 
T_UNESCAPED    '{';
    const 
T_UNESCAPED_2  '&';
    const 
T_TEXT         '_t';
    const 
T_PRAGMA       '%';
    const 
T_BLOCK_VAR    '$';
    const 
T_BLOCK_ARG    '$arg';

    
// Valid token types
    
private static $tagTypes = array(
        
self::T_SECTION      => true,
        
self::T_INVERTED     => true,
        
self::T_END_SECTION  => true,
        
self::T_COMMENT      => true,
        
self::T_PARTIAL      => true,
        
self::T_PARENT       => true,
        
self::T_DELIM_CHANGE => true,
        
self::T_ESCAPED      => true,
        
self::T_UNESCAPED    => true,
        
self::T_UNESCAPED_2  => true,
        
self::T_PRAGMA       => true,
        
self::T_BLOCK_VAR    => true,
    );

    
// Token properties
    
const TYPE    'type';
    const 
NAME    'name';
    const 
OTAG    'otag';
    const 
CTAG    'ctag';
    const 
LINE    'line';
    const 
INDEX   'index';
    const 
END     'end';
    const 
INDENT  'indent';
    const 
NODES   'nodes';
    const 
VALUE   'value';
    const 
FILTERS 'filters';

    private 
$state;
    private 
$tagType;
    private 
$buffer;
    private 
$tokens;
    private 
$seenTag;
    private 
$line;

    private 
$otag;
    private 
$otagChar;
    private 
$otagLen;

    private 
$ctag;
    private 
$ctagChar;
    private 
$ctagLen;

    
/**
     * Scan and tokenize template source.
     *
     * @throws Mustache_Exception_SyntaxException when mismatched section tags are encountered
     * @throws Mustache_Exception_InvalidArgumentException when $delimiters string is invalid
     *
     * @param string $text       Mustache template source to tokenize
     * @param string $delimiters Optionally, pass initial opening and closing delimiters (default: empty string)
     *
     * @return array Set of Mustache tokens
     */
    
public function scan($text$delimiters '')
    {
        
// Setting mbstring.func_overload makes things *really* slow.
        // Let's do everyone a favor and scan this string as ASCII instead.
        //
        // The INI directive was removed in PHP 8.0 so we don't need to check there (and can drop it
        // when we remove support for older versions of PHP).
        //
        // @codeCoverageIgnoreStart
        
$encoding null;
        if (
version_compare(PHP_VERSION'8.0.0''<')) {
            if (
function_exists('mb_internal_encoding') && ini_get('mbstring.func_overload') & 2) {
                
$encoding mb_internal_encoding();
                
mb_internal_encoding('ASCII');
            }
        }
        
// @codeCoverageIgnoreEnd

        
$this->reset();

        if (
is_string($delimiters) && $delimiters trim($delimiters)) {
            
$this->setDelimiters($delimiters);
        }

        
$len strlen($text);
        for (
$i 0$i $len$i++) {
            switch (
$this->state) {
                case 
self::IN_TEXT:
                    
$char $text[$i];
                    
// Test whether it's time to change tags.
                    
if ($char === $this->otagChar && substr($text$i$this->otagLen) === $this->otag) {
                        
$i--;
                        
$this->flushBuffer();
                        
$this->state self::IN_TAG_TYPE;
                    } else {
                        
$this->buffer .= $char;
                        if (
$char === "\n") {
                            
$this->flushBuffer();
                            
$this->line++;
                        }
                    }
                    break;

                case 
self::IN_TAG_TYPE:
                    
$i += $this->otagLen 1;
                    
$char $text[$i 1];
                    if (isset(
self::$tagTypes[$char])) {
                        
$tag $char;
                        
$this->tagType $tag;
                    } else {
                        
$tag null;
                        
$this->tagType self::T_ESCAPED;
                    }

                    if (
$this->tagType === self::T_DELIM_CHANGE) {
                        
$i $this->changeDelimiters($text$i);
                        
$this->state self::IN_TEXT;
                    } elseif (
$this->tagType === self::T_PRAGMA) {
                        
$i $this->addPragma($text$i);
                        
$this->state self::IN_TEXT;
                    } else {
                        if (
$tag !== null) {
                            
$i++;
                        }
                        
$this->state self::IN_TAG;
                    }
                    
$this->seenTag $i;
                    break;

                default:
                    
$char $text[$i];
                    
// Test whether it's time to change tags.
                    
if ($char === $this->ctagChar && substr($text$i$this->ctagLen) === $this->ctag) {
                        
$token = array(
                            
self::TYPE  => $this->tagType,
                            
self::NAME  => trim($this->buffer),
                            
self::OTAG  => $this->otag,
                            
self::CTAG  => $this->ctag,
                            
self::LINE  => $this->line,
                            
self::INDEX => ($this->tagType === self::T_END_SECTION) ? $this->seenTag $this->otagLen $i $this->ctagLen,
                        );

                        if (
$this->tagType === self::T_UNESCAPED) {
                            
// Clean up `{{{ tripleStache }}}` style tokens.
                            
if ($this->ctag === '}}') {
                                if ((
$i $len) && $text[$i 2] === '}') {
                                    
$i++;
                                } else {
                                    
$msg sprintf(
                                        
'Mismatched tag delimiters: %s on line %d',
                                        
$token[self::NAME],
                                        
$token[self::LINE]
                                    );

                                    throw new 
Mustache_Exception_SyntaxException($msg$token);
                                }
                            } else {
                                
$lastName $token[self::NAME];
                                if (
substr($lastName, -1) === '}') {
                                    
$token[self::NAME] = trim(substr($lastName0, -1));
                                } else {
                                    
$msg sprintf(
                                        
'Mismatched tag delimiters: %s on line %d',
                                        
$token[self::NAME],
                                        
$token[self::LINE]
                                    );

                                    throw new 
Mustache_Exception_SyntaxException($msg$token);
                                }
                            }
                        }

                        
$this->buffer '';
                        
$i += $this->ctagLen 1;
                        
$this->state self::IN_TEXT;
                        
$this->tokens[] = $token;
                    } else {
                        
$this->buffer .= $char;
                    }
                    break;
            }
        }

        
$this->flushBuffer();

        
// Restore the user's encoding...
        // @codeCoverageIgnoreStart
        
if ($encoding) {
            
mb_internal_encoding($encoding);
        }
        
// @codeCoverageIgnoreEnd

        
return $this->tokens;
    }

    
/**
     * Helper function to reset tokenizer internal state.
     */
    
private function reset()
    {
        
$this->state    self::IN_TEXT;
        
$this->tagType  null;
        
$this->buffer   '';
        
$this->tokens   = array();
        
$this->seenTag  false;
        
$this->line     0;

        
$this->otag     '{{';
        
$this->otagChar '{';
        
$this->otagLen  2;

        
$this->ctag     '}}';
        
$this->ctagChar '}';
        
$this->ctagLen  2;
    }

    
/**
     * Flush the current buffer to a token.
     */
    
private function flushBuffer()
    {
        if (
strlen($this->buffer) > 0) {
            
$this->tokens[] = array(
                
self::TYPE  => self::T_TEXT,
                
self::LINE  => $this->line,
                
self::VALUE => $this->buffer,
            );
            
$this->buffer   '';
        }
    }

    
/**
     * Change the current Mustache delimiters. Set new `otag` and `ctag` values.
     *
     * @throws Mustache_Exception_SyntaxException when delimiter string is invalid
     *
     * @param string $text  Mustache template source
     * @param int    $index Current tokenizer index
     *
     * @return int New index value
     */
    
private function changeDelimiters($text$index)
    {
        
$startIndex strpos($text'='$index) + 1;
        
$close      '=' $this->ctag;
        
$closeIndex strpos($text$close$index);

        
$token = array(
            
self::TYPE => self::T_DELIM_CHANGE,
            
self::LINE => $this->line,
        );

        try {
            
$this->setDelimiters(trim(substr($text$startIndex$closeIndex $startIndex)));
        } catch (
Mustache_Exception_InvalidArgumentException $e) {
            throw new 
Mustache_Exception_SyntaxException($e->getMessage(), $token);
        }

        
$this->tokens[] = $token;

        return 
$closeIndex strlen($close) - 1;
    }

    
/**
     * Set the current Mustache `otag` and `ctag` delimiters.
     *
     * @throws Mustache_Exception_InvalidArgumentException when delimiter string is invalid
     *
     * @param string $delimiters
     */
    
private function setDelimiters($delimiters)
    {
        if (!
preg_match('/^\s*(\S+)\s+(\S+)\s*$/'$delimiters$matches)) {
            throw new 
Mustache_Exception_InvalidArgumentException(sprintf('Invalid delimiters: %s'$delimiters));
        }

        list(
$_$otag$ctag) = $matches;

        
$this->otag     $otag;
        
$this->otagChar $otag[0];
        
$this->otagLen  strlen($otag);

        
$this->ctag     $ctag;
        
$this->ctagChar $ctag[0];
        
$this->ctagLen  strlen($ctag);
    }

    
/**
     * Add pragma token.
     *
     * Pragmas are hoisted to the front of the template, so all pragma tokens
     * will appear at the front of the token list.
     *
     * @param string $text
     * @param int    $index
     *
     * @return int New index value
     */
    
private function addPragma($text$index)
    {
        
$end    strpos($text$this->ctag$index);
        
$pragma trim(substr($text$index 2$end $index 2));

        
// Pragmas are hoisted to the front of the template.
        
array_unshift($this->tokens, array(
            
self::TYPE => self::T_PRAGMA,
            
self::NAME => $pragma,
            
self::LINE => 0,
        ));

        return 
$end $this->ctagLen 1;
    }
}
<?
php
/**
 * Cookie holder object
 *
 * @package Requests
 * @subpackage Cookies
 */

/**
 * Cookie holder object
 *
 * @package Requests
 * @subpackage Cookies
 */
class Requests_Cookie_Jar implements ArrayAccessIteratorAggregate {
    
/**
     * Actual item data
     *
     * @var array
     */
    
protected $cookies = array();

    
/**
     * Create a new jar
     *
     * @param array $cookies Existing cookie values
     */
    
public function __construct($cookies = array()) {
        
$this->cookies $cookies;
    }

    
/**
     * Normalise cookie data into a Requests_Cookie
     *
     * @param string|Requests_Cookie $cookie
     * @return Requests_Cookie
     */
    
public function normalize_cookie($cookie$key null) {
        if (
$cookie instanceof Requests_Cookie) {
            return 
$cookie;
        }

        return 
Requests_Cookie::parse($cookie$key);
    }

    
/**
     * Normalise cookie data into a Requests_Cookie
     *
     * @codeCoverageIgnore
     * @deprecated Use {@see Requests_Cookie_Jar::normalize_cookie}
     * @return Requests_Cookie
     */
    
public function normalizeCookie($cookie$key null) {
        return 
$this->normalize_cookie($cookie$key);
    }

    
/**
     * Check if the given item exists
     *
     * @param string $key Item key
     * @return boolean Does the item exist?
     */
    
public function offsetExists($key) {
        return isset(
$this->cookies[$key]);
    }

    
/**
     * Get the value for the item
     *
     * @param string $key Item key
     * @return string|null Item value (null if offsetExists is false)
     */
    
public function offsetGet($key) {
        if (!isset(
$this->cookies[$key])) {
            return 
null;
        }

        return 
$this->cookies[$key];
    }

    
/**
     * Set the given item
     *
     * @throws Requests_Exception On attempting to use dictionary as list (`invalidset`)
     *
     * @param string $key Item name
     * @param string $value Item value
     */
    
public function offsetSet($key$value) {
        if (
$key === null) {
            throw new 
Requests_Exception('Object is a dictionary, not a list''invalidset');
        }

        
$this->cookies[$key] = $value;
    }

    
/**
     * Unset the given header
     *
     * @param string $key
     */
    
public function offsetUnset($key) {
        unset(
$this->cookies[$key]);
    }

    
/**
     * Get an iterator for the data
     *
     * @return ArrayIterator
     */
    
public function getIterator() {
        return new 
ArrayIterator($this->cookies);
    }

    
/**
     * Register the cookie handler with the request's hooking system
     *
     * @param Requests_Hooker $hooks Hooking system
     */
    
public function register(Requests_Hooker $hooks) {
        
$hooks->register('requests.before_request', array($this'before_request'));
        
$hooks->register('requests.before_redirect_check', array($this'before_redirect_check'));
    }

    
/**
     * Add Cookie header to a request if we have any
     *
     * As per RFC 6265, cookies are separated by '; '
     *
     * @param string $url
     * @param array $headers
     * @param array $data
     * @param string $type
     * @param array $options
     */
    
public function before_request($url, &$headers, &$data, &$type, &$options) {
        if (!
$url instanceof Requests_IRI) {
            
$url = new Requests_IRI($url);
        }

        if (!empty(
$this->cookies)) {
            
$cookies = array();
            foreach (
$this->cookies as $key => $cookie) {
                
$cookie $this->normalize_cookie($cookie$key);

                
// Skip expired cookies
                
if ($cookie->is_expired()) {
                    continue;
                }

                if (
$cookie->domain_matches($url->host)) {
                    
$cookies[] = $cookie->format_for_header();
                }
            }

            
$headers['Cookie'] = implode('; '$cookies);
        }
    }

    
/**
     * Parse all cookies from a response and attach them to the response
     *
     * @var Requests_Response $response
     */
    
public function before_redirect_check(Requests_Response $return) {
        
$url $return->url;
        if (!
$url instanceof Requests_IRI) {
            
$url = new Requests_IRI($url);
        }

        
$cookies         Requests_Cookie::parse_from_headers($return->headers$url);
        
$this->cookies   array_merge($this->cookies$cookies);
        
$return->cookies $this;
    }
}
<?
php
/**
 * HTTP response class
 *
 * Contains a response from Requests::request()
 * @package Requests
 */

/**
 * HTTP response class
 *
 * Contains a response from Requests::request()
 * @package Requests
 */
class Requests_Response {
    
/**
     * Constructor
     */
    
public function __construct() {
        
$this->headers = new Requests_Response_Headers();
        
$this->cookies = new Requests_Cookie_Jar();
    }

    
/**
     * Response body
     *
     * @var string
     */
    
public $body '';

    
/**
     * Raw HTTP data from the transport
     *
     * @var string
     */
    
public $raw '';

    
/**
     * Headers, as an associative array
     *
     * @var Requests_Response_Headers Array-like object representing headers
     */
    
public $headers = array();

    
/**
     * Status code, false if non-blocking
     *
     * @var integer|boolean
     */
    
public $status_code false;

    
/**
     * Protocol version, false if non-blocking
     *
     * @var float|boolean
     */
    
public $protocol_version false;

    
/**
     * Whether the request succeeded or not
     *
     * @var boolean
     */
    
public $success false;

    
/**
     * Number of redirects the request used
     *
     * @var integer
     */
    
public $redirects 0;

    
/**
     * URL requested
     *
     * @var string
     */
    
public $url '';

    
/**
     * Previous requests (from redirects)
     *
     * @var array Array of Requests_Response objects
     */
    
public $history = array();

    
/**
     * Cookies from the request
     *
     * @var Requests_Cookie_Jar Array-like object representing a cookie jar
     */
    
public $cookies = array();

    
/**
     * Is the response a redirect?
     *
     * @return boolean True if redirect (3xx status), false if not.
     */
    
public function is_redirect() {
        
$code $this->status_code;
        return 
in_array($code, array(300301302303307), true) || $code 307 && $code 400;
    }

    
/**
     * Throws an exception if the request was not successful
     *
     * @throws Requests_Exception If `$allow_redirects` is false, and code is 3xx (`response.no_redirects`)
     * @throws Requests_Exception_HTTP On non-successful status code. Exception class corresponds to code (e.g. {@see Requests_Exception_HTTP_404})
     * @param boolean $allow_redirects Set to false to throw on a 3xx as well
     */
    
public function throw_for_status($allow_redirects true) {
        if (
$this->is_redirect()) {
            if (!
$allow_redirects) {
                throw new 
Requests_Exception('Redirection not allowed''response.no_redirects'$this);
            }
        }
        elseif (!
$this->success) {
            
$exception Requests_Exception_HTTP::get_class($this->status_code);
            throw new 
$exception(null$this);
        }
    }
}
<?
php
/**
 * Handles adding and dispatching events
 *
 * @package Requests
 * @subpackage Utilities
 */

/**
 * Handles adding and dispatching events
 *
 * @package Requests
 * @subpackage Utilities
 */
class Requests_Hooks implements Requests_Hooker {
    
/**
     * Registered callbacks for each hook
     *
     * @var array
     */
    
protected $hooks = array();

    
/**
     * Constructor
     */
    
public function __construct() {
        
// pass
    
}

    
/**
     * Register a callback for a hook
     *
     * @param string $hook Hook name
     * @param callback $callback Function/method to call on event
     * @param int $priority Priority number. <0 is executed earlier, >0 is executed later
     */
    
public function register($hook$callback$priority 0) {
        if (!isset(
$this->hooks[$hook])) {
            
$this->hooks[$hook] = array();
        }
        if (!isset(
$this->hooks[$hook][$priority])) {
            
$this->hooks[$hook][$priority] = array();
        }

        
$this->hooks[$hook][$priority][] = $callback;
    }

    
/**
     * Dispatch a message
     *
     * @param string $hook Hook name
     * @param array $parameters Parameters to pass to callbacks
     * @return boolean Successfulness
     */
    
public function dispatch($hook$parameters = array()) {
        if (empty(
$this->hooks[$hook])) {
            return 
false;
        }

        foreach (
$this->hooks[$hook] as $priority => $hooked) {
            foreach (
$hooked as $callback) {
                
call_user_func_array($callback$parameters);
            }
        }

        return 
true;
    }
}
<?
php
/**
 * SSL utilities for Requests
 *
 * @package Requests
 * @subpackage Utilities
 */

/**
 * SSL utilities for Requests
 *
 * Collection of utilities for working with and verifying SSL certificates.
 *
 * @package Requests
 * @subpackage Utilities
 */
class Requests_SSL {
    
/**
     * Verify the certificate against common name and subject alternative names
     *
     * Unfortunately, PHP doesn't check the certificate against the alternative
     * names, leading things like 'https://www.github.com/' to be invalid.
     *
     * @see https://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1
     *
     * @throws Requests_Exception On not obtaining a match for the host (`fsockopen.ssl.no_match`)
     * @param string $host Host name to verify against
     * @param array $cert Certificate data from openssl_x509_parse()
     * @return bool
     */
    
public static function verify_certificate($host$cert) {
        
$has_dns_alt false;

        
// Check the subjectAltName
        
if (!empty($cert['extensions']) && !empty($cert['extensions']['subjectAltName'])) {
            
$altnames explode(','$cert['extensions']['subjectAltName']);
            foreach (
$altnames as $altname) {
                
$altname trim($altname);
                if (
strpos($altname'DNS:') !== 0) {
                    continue;
                }

                
$has_dns_alt true;

                
// Strip the 'DNS:' prefix and trim whitespace
                
$altname trim(substr($altname4));

                
// Check for a match
                
if (self::match_domain($host$altname) === true) {
                    return 
true;
                }
            }
        }

        
// Fall back to checking the common name if we didn't get any dNSName
        // alt names, as per RFC2818
        
if (!$has_dns_alt && !empty($cert['subject']['CN'])) {
            
// Check for a match
            
if (self::match_domain($host$cert['subject']['CN']) === true) {
                return 
true;
            }
        }

        return 
false;
    }

    
/**
     * Verify that a reference name is valid
     *
     * Verifies a dNSName for HTTPS usage, (almost) as per Firefox's rules:
     * - Wildcards can only occur in a name with more than 3 components
     * - Wildcards can only occur as the last character in the first
     *   component
     * - Wildcards may be preceded by additional characters
     *
     * We modify these rules to be a bit stricter and only allow the wildcard
     * character to be the full first component; that is, with the exclusion of
     * the third rule.
     *
     * @param string $reference Reference dNSName
     * @return boolean Is the name valid?
     */
    
public static function verify_reference_name($reference) {
        
$parts explode('.'$reference);

        
// Check the first part of the name
        
$first array_shift($parts);

        if (
strpos($first'*') !== false) {
            
// Check that the wildcard is the full part
            
if ($first !== '*') {
                return 
false;
            }

            
// Check that we have at least 3 components (including first)
            
if (count($parts) < 2) {
                return 
false;
            }
        }

        
// Check the remaining parts
        
foreach ($parts as $part) {
            if (
strpos($part'*') !== false) {
                return 
false;
            }
        }

        
// Nothing found, verified!
        
return true;
    }

    
/**
     * Match a hostname against a dNSName reference
     *
     * @param string $host Requested host
     * @param string $reference dNSName to match against
     * @return boolean Does the domain match?
     */
    
public static function match_domain($host$reference) {
        
// Check if the reference is blocklisted first
        
if (self::verify_reference_name($reference) !== true) {
            return 
false;
        }

        
// Check for a direct match
        
if ($host === $reference) {
            return 
true;
        }

        
// Calculate the valid wildcard match if the host is not an IP address
        // Also validates that the host has 3 parts or more, as per Firefox's
        // ruleset.
        
if (ip2long($host) === false) {
            
$parts    explode('.'$host);
            
$parts[0] = '*';
            
$wildcard implode('.'$parts);
            if (
$wildcard === $reference) {
                return 
true;
            }
        }

        return 
false;
    }
}
<?
php
/**
 * Event dispatcher
 *
 * @package Requests
 * @subpackage Utilities
 */

/**
 * Event dispatcher
 *
 * @package Requests
 * @subpackage Utilities
 */
interface Requests_Hooker {
    
/**
     * Register a callback for a hook
     *
     * @param string $hook Hook name
     * @param callback $callback Function/method to call on event
     * @param int $priority Priority number. <0 is executed earlier, >0 is executed later
     */
    
public function register($hook$callback$priority 0);

    
/**
     * Dispatch a message
     *
     * @param string $hook Hook name
     * @param array $parameters Parameters to pass to callbacks
     * @return boolean Successfulness
     */
    
public function dispatch($hook$parameters = array());
}
<?
php
/**
 * cURL HTTP transport
 *
 * @package Requests
 * @subpackage Transport
 */

/**
 * cURL HTTP transport
 *
 * @package Requests
 * @subpackage Transport
 */
class Requests_Transport_cURL implements Requests_Transport {
    const 
CURL_7_10_5 0x070A05;
    const 
CURL_7_16_2 0x071002;

    
/**
     * Raw HTTP data
     *
     * @var string
     */
    
public $headers '';

    
/**
     * Raw body data
     *
     * @var string
     */
    
public $response_data '';

    
/**
     * Information on the current request
     *
     * @var array cURL information array, see {@see https://secure.php.net/curl_getinfo}
     */
    
public $info;

    
/**
     * cURL version number
     *
     * @var int
     */
    
public $version;

    
/**
     * cURL handle
     *
     * @var resource
     */
    
protected $handle;

    
/**
     * Hook dispatcher instance
     *
     * @var Requests_Hooks
     */
    
protected $hooks;

    
/**
     * Have we finished the headers yet?
     *
     * @var boolean
     */
    
protected $done_headers false;

    
/**
     * If streaming to a file, keep the file pointer
     *
     * @var resource
     */
    
protected $stream_handle;

    
/**
     * How many bytes are in the response body?
     *
     * @var int
     */
    
protected $response_bytes;

    
/**
     * What's the maximum number of bytes we should keep?
     *
     * @var int|bool Byte count, or false if no limit.
     */
    
protected $response_byte_limit;

    
/**
     * Constructor
     */
    
public function __construct() {
        
$curl          curl_version();
        
$this->version $curl['version_number'];
        
$this->handle  curl_init();

        
curl_setopt($this->handleCURLOPT_HEADERfalse);
        
curl_setopt($this->handleCURLOPT_RETURNTRANSFER1);
        if (
$this->version >= self::CURL_7_10_5) {
            
curl_setopt($this->handleCURLOPT_ENCODING'');
        }
        if (
defined('CURLOPT_PROTOCOLS')) {
            
// phpcs:ignore PHPCompatibility.Constants.NewConstants.curlopt_protocolsFound
            
curl_setopt($this->handleCURLOPT_PROTOCOLSCURLPROTO_HTTP CURLPROTO_HTTPS);
        }
        if (
defined('CURLOPT_REDIR_PROTOCOLS')) {
            
// phpcs:ignore PHPCompatibility.Constants.NewConstants.curlopt_redir_protocolsFound
            
curl_setopt($this->handleCURLOPT_REDIR_PROTOCOLSCURLPROTO_HTTP CURLPROTO_HTTPS);
        }
    }

    
/**
     * Destructor
     */
    
public function __destruct() {
        if (
is_resource($this->handle)) {
            
curl_close($this->handle);
        }
    }

    
/**
     * Perform a request
     *
     * @throws Requests_Exception On a cURL error (`curlerror`)
     *
     * @param string $url URL to request
     * @param array $headers Associative array of request headers
     * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
     * @param array $options Request options, see {@see Requests::response()} for documentation
     * @return string Raw HTTP result
     */
    
public function request($url$headers = array(), $data = array(), $options = array()) {
        
$this->hooks $options['hooks'];

        
$this->setup_handle($url$headers$data$options);

        
$options['hooks']->dispatch('curl.before_send', array(&$this->handle));

        if (
$options['filename'] !== false) {
            
$this->stream_handle fopen($options['filename'], 'wb');
        }

        
$this->response_data       '';
        
$this->response_bytes      0;
        
$this->response_byte_limit false;
        if (
$options['max_bytes'] !== false) {
            
$this->response_byte_limit $options['max_bytes'];
        }

        if (isset(
$options['verify'])) {
            if (
$options['verify'] === false) {
                
curl_setopt($this->handleCURLOPT_SSL_VERIFYHOST0);
                
curl_setopt($this->handleCURLOPT_SSL_VERIFYPEER0);
            }
            elseif (
is_string($options['verify'])) {
                
curl_setopt($this->handleCURLOPT_CAINFO$options['verify']);
            }
        }

        if (isset(
$options['verifyname']) && $options['verifyname'] === false) {
            
curl_setopt($this->handleCURLOPT_SSL_VERIFYHOST0);
        }

        
curl_exec($this->handle);
        
$response $this->response_data;

        
$options['hooks']->dispatch('curl.after_send', array());

        if (
curl_errno($this->handle) === 23 || curl_errno($this->handle) === 61) {
            
// Reset encoding and try again
            
curl_setopt($this->handleCURLOPT_ENCODING'none');

            
$this->response_data  '';
            
$this->response_bytes 0;
            
curl_exec($this->handle);
            
$response $this->response_data;
        }

        
$this->process_response($response$options);

        
// Need to remove the $this reference from the curl handle.
        // Otherwise Requests_Transport_cURL wont be garbage collected and the curl_close() will never be called.
        
curl_setopt($this->handleCURLOPT_HEADERFUNCTIONnull);
        
curl_setopt($this->handleCURLOPT_WRITEFUNCTIONnull);

        return 
$this->headers;
    }

    
/**
     * Send multiple requests simultaneously
     *
     * @param array $requests Request data
     * @param array $options Global options
     * @return array Array of Requests_Response objects (may contain Requests_Exception or string responses as well)
     */
    
public function request_multiple($requests$options) {
        
// If you're not requesting, we can't get any responses ¯\_(ツ)_/¯
        
if (empty($requests)) {
            return array();
        }

        
$multihandle curl_multi_init();
        
$subrequests = array();
        
$subhandles  = array();

        
$class get_class($this);
        foreach (
$requests as $id => $request) {
            
$subrequests[$id] = new $class();
            
$subhandles[$id]  = $subrequests[$id]->get_subrequest_handle($request['url'], $request['headers'], $request['data'], $request['options']);
            
$request['options']['hooks']->dispatch('curl.before_multi_add', array(&$subhandles[$id]));
            
curl_multi_add_handle($multihandle$subhandles[$id]);
        }

        
$completed       0;
        
$responses       = array();
        
$subrequestcount count($subrequests);

        
$request['options']['hooks']->dispatch('curl.before_multi_exec', array(&$multihandle));

        do {
            
$active 0;

            do {
                
$status curl_multi_exec($multihandle$active);
            }
            while (
$status === CURLM_CALL_MULTI_PERFORM);

            
$to_process = array();

            
// Read the information as needed
            
while ($done curl_multi_info_read($multihandle)) {
                
$key array_search($done['handle'], $subhandlestrue);
                if (!isset(
$to_process[$key])) {
                    
$to_process[$key] = $done;
                }
            }

            
// Parse the finished requests before we start getting the new ones
            
foreach ($to_process as $key => $done) {
                
$options $requests[$key]['options'];
                if (
$done['result'] !== CURLE_OK) {
                    
//get error string for handle.
                    
$reason          curl_error($done['handle']);
                    
$exception       = new Requests_Exception_Transport_cURL(
                        
$reason,
                        
Requests_Exception_Transport_cURL::EASY,
                        
$done['handle'],
                        
$done['result']
                    );
                    
$responses[$key] = $exception;
                    
$options['hooks']->dispatch('transport.internal.parse_error', array(&$responses[$key], $requests[$key]));
                }
                else {
                    
$responses[$key] = $subrequests[$key]->process_response($subrequests[$key]->response_data$options);

                    
$options['hooks']->dispatch('transport.internal.parse_response', array(&$responses[$key], $requests[$key]));
                }

                
curl_multi_remove_handle($multihandle$done['handle']);
                
curl_close($done['handle']);

                if (!
is_string($responses[$key])) {
                    
$options['hooks']->dispatch('multiple.request.complete', array(&$responses[$key], $key));
                }
                
$completed++;
            }
        }
        while (
$active || $completed $subrequestcount);

        
$request['options']['hooks']->dispatch('curl.after_multi_exec', array(&$multihandle));

        
curl_multi_close($multihandle);

        return 
$responses;
    }

    
/**
     * Get the cURL handle for use in a multi-request
     *
     * @param string $url URL to request
     * @param array $headers Associative array of request headers
     * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
     * @param array $options Request options, see {@see Requests::response()} for documentation
     * @return resource Subrequest's cURL handle
     */
    
public function &get_subrequest_handle($url$headers$data$options) {
        
$this->setup_handle($url$headers$data$options);

        if (
$options['filename'] !== false) {
            
$this->stream_handle fopen($options['filename'], 'wb');
        }

        
$this->response_data       '';
        
$this->response_bytes      0;
        
$this->response_byte_limit false;
        if (
$options['max_bytes'] !== false) {
            
$this->response_byte_limit $options['max_bytes'];
        }
        
$this->hooks $options['hooks'];

        return 
$this->handle;
    }

    
/**
     * Setup the cURL handle for the given data
     *
     * @param string $url URL to request
     * @param array $headers Associative array of request headers
     * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
     * @param array $options Request options, see {@see Requests::response()} for documentation
     */
    
protected function setup_handle($url$headers$data$options) {
        
$options['hooks']->dispatch('curl.before_request', array(&$this->handle));

        
// Force closing the connection for old versions of cURL (<7.22).
        
if (!isset($headers['Connection'])) {
            
$headers['Connection'] = 'close';
        }

        
/**
         * Add "Expect" header.
         *
         * By default, cURL adds a "Expect: 100-Continue" to most requests. This header can
         * add as much as a second to the time it takes for cURL to perform a request. To
         * prevent this, we need to set an empty "Expect" header. To match the behaviour of
         * Guzzle, we'll add the empty header to requests that are smaller than 1 MB and use
         * HTTP/1.1.
         *
         * https://curl.se/mail/lib-2017-07/0013.html
         */
        
if (!isset($headers['Expect']) && $options['protocol_version'] === 1.1) {
            
$headers['Expect'] = $this->get_expect_header($data);
        }

        
$headers Requests::flatten($headers);

        if (!empty(
$data)) {
            
$data_format $options['data_format'];

            if (
$data_format === 'query') {
                
$url  self::format_get($url$data);
                
$data '';
            }
            elseif (!
is_string($data)) {
                
$data http_build_query($datanull'&');
            }
        }

        switch (
$options['type']) {
            case 
Requests::POST:
                
curl_setopt($this->handleCURLOPT_POSTtrue);
                
curl_setopt($this->handleCURLOPT_POSTFIELDS$data);
                break;
            case 
Requests::HEAD:
                
curl_setopt($this->handleCURLOPT_CUSTOMREQUEST$options['type']);
                
curl_setopt($this->handleCURLOPT_NOBODYtrue);
                break;
            case 
Requests::TRACE:
                
curl_setopt($this->handleCURLOPT_CUSTOMREQUEST$options['type']);
                break;
            case 
Requests::PATCH:
            case 
Requests::PUT:
            case 
Requests::DELETE:
            case 
Requests::OPTIONS:
            default:
                
curl_setopt($this->handleCURLOPT_CUSTOMREQUEST$options['type']);
                if (!empty(
$data)) {
                    
curl_setopt($this->handleCURLOPT_POSTFIELDS$data);
                }
        }

        
// cURL requires a minimum timeout of 1 second when using the system
        // DNS resolver, as it uses `alarm()`, which is second resolution only.
        // There's no way to detect which DNS resolver is being used from our
        // end, so we need to round up regardless of the supplied timeout.
        //
        // https://github.com/curl/curl/blob/4f45240bc84a9aa648c8f7243be7b79e9f9323a5/lib/hostip.c#L606-L609
        
$timeout max($options['timeout'], 1);

        if (
is_int($timeout) || $this->version self::CURL_7_16_2) {
            
curl_setopt($this->handleCURLOPT_TIMEOUTceil($timeout));
        }
        else {
            
// phpcs:ignore PHPCompatibility.Constants.NewConstants.curlopt_timeout_msFound
            
curl_setopt($this->handleCURLOPT_TIMEOUT_MSround($timeout 1000));
        }

        if (
is_int($options['connect_timeout']) || $this->version self::CURL_7_16_2) {
            
curl_setopt($this->handleCURLOPT_CONNECTTIMEOUTceil($options['connect_timeout']));
        }
        else {
            
// phpcs:ignore PHPCompatibility.Constants.NewConstants.curlopt_connecttimeout_msFound
            
curl_setopt($this->handleCURLOPT_CONNECTTIMEOUT_MSround($options['connect_timeout'] * 1000));
        }
        
curl_setopt($this->handleCURLOPT_URL$url);
        
curl_setopt($this->handleCURLOPT_REFERER$url);
        
curl_setopt($this->handleCURLOPT_USERAGENT$options['useragent']);
        if (!empty(
$headers)) {
            
curl_setopt($this->handleCURLOPT_HTTPHEADER$headers);
        }
        if (
$options['protocol_version'] === 1.1) {
            
curl_setopt($this->handleCURLOPT_HTTP_VERSIONCURL_HTTP_VERSION_1_1);
        }
        else {
            
curl_setopt($this->handleCURLOPT_HTTP_VERSIONCURL_HTTP_VERSION_1_0);
        }

        if (
$options['blocking'] === true) {
            
curl_setopt($this->handleCURLOPT_HEADERFUNCTION, array($this'stream_headers'));
            
curl_setopt($this->handleCURLOPT_WRITEFUNCTION, array($this'stream_body'));
            
curl_setopt($this->handleCURLOPT_BUFFERSIZERequests::BUFFER_SIZE);
        }
    }

    
/**
     * Process a response
     *
     * @param string $response Response data from the body
     * @param array $options Request options
     * @return string|false HTTP response data including headers. False if non-blocking.
     * @throws Requests_Exception
     */
    
public function process_response($response$options) {
        if (
$options['blocking'] === false) {
            
$fake_headers '';
            
$options['hooks']->dispatch('curl.after_request', array(&$fake_headers));
            return 
false;
        }
        if (
$options['filename'] !== false && $this->stream_handle) {
            
fclose($this->stream_handle);
            
$this->headers trim($this->headers);
        }
        else {
            
$this->headers .= $response;
        }

        if (
curl_errno($this->handle)) {
            
$error sprintf(
                
'cURL error %s: %s',
                
curl_errno($this->handle),
                
curl_error($this->handle)
            );
            throw new 
Requests_Exception($error'curlerror'$this->handle);
        }
        
$this->info curl_getinfo($this->handle);

        
$options['hooks']->dispatch('curl.after_request', array(&$this->headers, &$this->info));
        return 
$this->headers;
    }

    
/**
     * Collect the headers as they are received
     *
     * @param resource $handle cURL resource
     * @param string $headers Header string
     * @return integer Length of provided header
     */
    
public function stream_headers($handle$headers) {
        
// Why do we do this? cURL will send both the final response and any
        // interim responses, such as a 100 Continue. We don't need that.
        // (We may want to keep this somewhere just in case)
        
if ($this->done_headers) {
            
$this->headers      '';
            
$this->done_headers false;
        }
        
$this->headers .= $headers;

        if (
$headers === "\r\n") {
            
$this->done_headers true;
        }
        return 
strlen($headers);
    }

    
/**
     * Collect data as it's received
     *
     * @since 1.6.1
     *
     * @param resource $handle cURL resource
     * @param string $data Body data
     * @return integer Length of provided data
     */
    
public function stream_body($handle$data) {
        
$this->hooks->dispatch('request.progress', array($data$this->response_bytes$this->response_byte_limit));
        
$data_length strlen($data);

        
// Are we limiting the response size?
        
if ($this->response_byte_limit) {
            if (
$this->response_bytes === $this->response_byte_limit) {
                
// Already at maximum, move on
                
return $data_length;
            }

            if ((
$this->response_bytes $data_length) > $this->response_byte_limit) {
                
// Limit the length
                
$limited_length = ($this->response_byte_limit $this->response_bytes);
                
$data           substr($data0$limited_length);
            }
        }

        if (
$this->stream_handle) {
            
fwrite($this->stream_handle$data);
        }
        else {
            
$this->response_data .= $data;
        }

        
$this->response_bytes += strlen($data);
        return 
$data_length;
    }

    
/**
     * Format a URL given GET data
     *
     * @param string $url
     * @param array|object $data Data to build query using, see {@see https://secure.php.net/http_build_query}
     * @return string URL with data
     */
    
protected static function format_get($url$data) {
        if (!empty(
$data)) {
            
$query     '';
            
$url_parts parse_url($url);
            if (empty(
$url_parts['query'])) {
                
$url_parts['query'] = '';
            }
            else {
                
$query $url_parts['query'];
            }

            
$query .= '&' http_build_query($datanull'&');
            
$query  trim($query'&');

            if (empty(
$url_parts['query'])) {
                
$url .= '?' $query;
            }
            else {
                
$url str_replace($url_parts['query'], $query$url);
            }
        }
        return 
$url;
    }

    
/**
     * Whether this transport is valid
     *
     * @codeCoverageIgnore
     * @return boolean True if the transport is valid, false otherwise.
     */
    
public static function test($capabilities = array()) {
        if (!
function_exists('curl_init') || !function_exists('curl_exec')) {
            return 
false;
        }

        
// If needed, check that our installed curl version supports SSL
        
if (isset($capabilities['ssl']) && $capabilities['ssl']) {
            
$curl_version curl_version();
            if (!(
CURL_VERSION_SSL $curl_version['features'])) {
                return 
false;
            }
        }

        return 
true;
    }

    
/**
     * Get the correct "Expect" header for the given request data.
     *
     * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD.
     * @return string The "Expect" header.
     */
    
protected function get_expect_header($data) {
        if (!
is_array($data)) {
            return 
strlen((string) $data) >= 1048576 '100-Continue' '';
        }

        
$bytesize 0;
        
$iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($data));

        foreach (
$iterator as $datum) {
            
$bytesize += strlen((string) $datum);

            if (
$bytesize >= 1048576) {
                return 
'100-Continue';
            }
        }

        return 
'';
    }
}
<?
php
/**
 * fsockopen HTTP transport
 *
 * @package Requests
 * @subpackage Transport
 */

/**
 * fsockopen HTTP transport
 *
 * @package Requests
 * @subpackage Transport
 */
class Requests_Transport_fsockopen implements Requests_Transport {
    
/**
     * Second to microsecond conversion
     *
     * @var integer
     */
    
const SECOND_IN_MICROSECONDS 1000000;

    
/**
     * Raw HTTP data
     *
     * @var string
     */
    
public $headers '';

    
/**
     * Stream metadata
     *
     * @var array Associative array of properties, see {@see https://secure.php.net/stream_get_meta_data}
     */
    
public $info;

    
/**
     * What's the maximum number of bytes we should keep?
     *
     * @var int|bool Byte count, or false if no limit.
     */
    
protected $max_bytes false;

    protected 
$connect_error '';

    
/**
     * Perform a request
     *
     * @throws Requests_Exception On failure to connect to socket (`fsockopenerror`)
     * @throws Requests_Exception On socket timeout (`timeout`)
     *
     * @param string $url URL to request
     * @param array $headers Associative array of request headers
     * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
     * @param array $options Request options, see {@see Requests::response()} for documentation
     * @return string Raw HTTP result
     */
    
public function request($url$headers = array(), $data = array(), $options = array()) {
        
$options['hooks']->dispatch('fsockopen.before_request');

        
$url_parts parse_url($url);
        if (empty(
$url_parts)) {
            throw new 
Requests_Exception('Invalid URL.''invalidurl'$url);
        }
        
$host                     $url_parts['host'];
        
$context                  stream_context_create();
        
$verifyname               false;
        
$case_insensitive_headers = new Requests_Utility_CaseInsensitiveDictionary($headers);

        
// HTTPS support
        
if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) === 'https') {
            
$remote_socket 'ssl://' $host;
            if (!isset(
$url_parts['port'])) {
                
$url_parts['port'] = 443;
            }

            
$context_options = array(
                
'verify_peer'       => true,
                
'capture_peer_cert' => true,
            );
            
$verifyname      true;

            
// SNI, if enabled (OpenSSL >=0.9.8j)
            // phpcs:ignore PHPCompatibility.Constants.NewConstants.openssl_tlsext_server_nameFound
            
if (defined('OPENSSL_TLSEXT_SERVER_NAME') && OPENSSL_TLSEXT_SERVER_NAME) {
                
$context_options['SNI_enabled'] = true;
                if (isset(
$options['verifyname']) && $options['verifyname'] === false) {
                    
$context_options['SNI_enabled'] = false;
                }
            }

            if (isset(
$options['verify'])) {
                if (
$options['verify'] === false) {
                    
$context_options['verify_peer']      = false;
                    
$context_options['verify_peer_name'] = false;
                    
$verifyname                          false;
                }
                elseif (
is_string($options['verify'])) {
                    
$context_options['cafile'] = $options['verify'];
                }
            }

            if (isset(
$options['verifyname']) && $options['verifyname'] === false) {
                
$context_options['verify_peer_name'] = false;
                
$verifyname                          false;
            }

            
stream_context_set_option($context, array('ssl' => $context_options));
        }
        else {
            
$remote_socket 'tcp://' $host;
        }

        
$this->max_bytes $options['max_bytes'];

        if (!isset(
$url_parts['port'])) {
            
$url_parts['port'] = 80;
        }
        
$remote_socket .= ':' $url_parts['port'];

        
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_set_error_handler
        
set_error_handler(array($this'connect_error_handler'), E_WARNING E_NOTICE);

        
$options['hooks']->dispatch('fsockopen.remote_socket', array(&$remote_socket));

        
$socket stream_socket_client($remote_socket$errno$errstrceil($options['connect_timeout']), STREAM_CLIENT_CONNECT$context);

        
restore_error_handler();

        if (
$verifyname && !$this->verify_certificate_from_context($host$context)) {
            throw new 
Requests_Exception('SSL certificate did not match the requested domain name''ssl.no_match');
        }

        if (!
$socket) {
            if (
$errno === 0) {
                
// Connection issue
                
throw new Requests_Exception(rtrim($this->connect_error), 'fsockopen.connect_error');
            }

            throw new 
Requests_Exception($errstr'fsockopenerror'null$errno);
        }

        
$data_format $options['data_format'];

        if (
$data_format === 'query') {
            
$path self::format_get($url_parts$data);
            
$data '';
        }
        else {
            
$path self::format_get($url_parts, array());
        }

        
$options['hooks']->dispatch('fsockopen.remote_host_path', array(&$path$url));

        
$request_body '';
        
$out          sprintf("%s %s HTTP/%.1F\r\n"$options['type'], $path$options['protocol_version']);

        if (
$options['type'] !== Requests::TRACE) {
            if (
is_array($data)) {
                
$request_body http_build_query($data'''&');
            }
            else {
                
$request_body $data;
            }

            
// Always include Content-length on POST requests to prevent
            // 411 errors from some servers when the body is empty.
            
if (!empty($data) || $options['type'] === Requests::POST) {
                if (!isset(
$case_insensitive_headers['Content-Length'])) {
                    
$headers['Content-Length'] = strlen($request_body);
                }

                if (!isset(
$case_insensitive_headers['Content-Type'])) {
                    
$headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
                }
            }
        }

        if (!isset(
$case_insensitive_headers['Host'])) {
            
$out .= sprintf('Host: %s'$url_parts['host']);

            if ((
strtolower($url_parts['scheme']) === 'http' && $url_parts['port'] !== 80) || (strtolower($url_parts['scheme']) === 'https' && $url_parts['port'] !== 443)) {
                
$out .= ':' $url_parts['port'];
            }
            
$out .= "\r\n";
        }

        if (!isset(
$case_insensitive_headers['User-Agent'])) {
            
$out .= sprintf("User-Agent: %s\r\n"$options['useragent']);
        }

        
$accept_encoding $this->accept_encoding();
        if (!isset(
$case_insensitive_headers['Accept-Encoding']) && !empty($accept_encoding)) {
            
$out .= sprintf("Accept-Encoding: %s\r\n"$accept_encoding);
        }

        
$headers Requests::flatten($headers);

        if (!empty(
$headers)) {
            
$out .= implode("\r\n"$headers) . "\r\n";
        }

        
$options['hooks']->dispatch('fsockopen.after_headers', array(&$out));

        if (
substr($out, -2) !== "\r\n") {
            
$out .= "\r\n";
        }

        if (!isset(
$case_insensitive_headers['Connection'])) {
            
$out .= "Connection: Close\r\n";
        }

        
$out .= "\r\n" $request_body;

        
$options['hooks']->dispatch('fsockopen.before_send', array(&$out));

        
fwrite($socket$out);
        
$options['hooks']->dispatch('fsockopen.after_send', array($out));

        if (!
$options['blocking']) {
            
fclose($socket);
            
$fake_headers '';
            
$options['hooks']->dispatch('fsockopen.after_request', array(&$fake_headers));
            return 
'';
        }

        
$timeout_sec = (int) floor($options['timeout']);
        if (
$timeout_sec === $options['timeout']) {
            
$timeout_msec 0;
        }
        else {
            
$timeout_msec self::SECOND_IN_MICROSECONDS $options['timeout'] % self::SECOND_IN_MICROSECONDS;
        }
        
stream_set_timeout($socket$timeout_sec$timeout_msec);

        
$response   '';
        
$body       '';
        
$headers    '';
        
$this->info stream_get_meta_data($socket);
        
$size       0;
        
$doingbody  false;
        
$download   false;
        if (
$options['filename']) {
            
$download fopen($options['filename'], 'wb');
        }

        while (!
feof($socket)) {
            
$this->info stream_get_meta_data($socket);
            if (
$this->info['timed_out']) {
                throw new 
Requests_Exception('fsocket timed out''timeout');
            }

            
$block fread($socketRequests::BUFFER_SIZE);
            if (!
$doingbody) {
                
$response .= $block;
                if (
strpos($response"\r\n\r\n")) {
                    list(
$headers$block) = explode("\r\n\r\n"$response2);
                    
$doingbody             true;
                }
            }

            
// Are we in body mode now?
            
if ($doingbody) {
                
$options['hooks']->dispatch('request.progress', array($block$size$this->max_bytes));
                
$data_length strlen($block);
                if (
$this->max_bytes) {
                    
// Have we already hit a limit?
                    
if ($size === $this->max_bytes) {
                        continue;
                    }
                    if ((
$size $data_length) > $this->max_bytes) {
                        
// Limit the length
                        
$limited_length = ($this->max_bytes $size);
                        
$block          substr($block0$limited_length);
                    }
                }

                
$size += strlen($block);
                if (
$download) {
                    
fwrite($download$block);
                }
                else {
                    
$body .= $block;
                }
            }
        }
        
$this->headers $headers;

        if (
$download) {
            
fclose($download);
        }
        else {
            
$this->headers .= "\r\n\r\n" $body;
        }
        
fclose($socket);

        
$options['hooks']->dispatch('fsockopen.after_request', array(&$this->headers, &$this->info));
        return 
$this->headers;
    }

    
/**
     * Send multiple requests simultaneously
     *
     * @param array $requests Request data (array of 'url', 'headers', 'data', 'options') as per {@see Requests_Transport::request}
     * @param array $options Global options, see {@see Requests::response()} for documentation
     * @return array Array of Requests_Response objects (may contain Requests_Exception or string responses as well)
     */
    
public function request_multiple($requests$options) {
        
$responses = array();
        
$class     get_class($this);
        foreach (
$requests as $id => $request) {
            try {
                
$handler        = new $class();
                
$responses[$id] = $handler->request($request['url'], $request['headers'], $request['data'], $request['options']);

                
$request['options']['hooks']->dispatch('transport.internal.parse_response', array(&$responses[$id], $request));
            }
            catch (
Requests_Exception $e) {
                
$responses[$id] = $e;
            }

            if (!
is_string($responses[$id])) {
                
$request['options']['hooks']->dispatch('multiple.request.complete', array(&$responses[$id], $id));
            }
        }

        return 
$responses;
    }

    
/**
     * Retrieve the encodings we can accept
     *
     * @return string Accept-Encoding header value
     */
    
protected static function accept_encoding() {
        
$type = array();
        if (
function_exists('gzinflate')) {
            
$type[] = 'deflate;q=1.0';
        }

        if (
function_exists('gzuncompress')) {
            
$type[] = 'compress;q=0.5';
        }

        
$type[] = 'gzip;q=0.5';

        return 
implode(', '$type);
    }

    
/**
     * Format a URL given GET data
     *
     * @param array $url_parts
     * @param array|object $data Data to build query using, see {@see https://secure.php.net/http_build_query}
     * @return string URL with data
     */
    
protected static function format_get($url_parts$data) {
        if (!empty(
$data)) {
            if (empty(
$url_parts['query'])) {
                
$url_parts['query'] = '';
            }

            
$url_parts['query'] .= '&' http_build_query($data'''&');
            
$url_parts['query']  = trim($url_parts['query'], '&');
        }
        if (isset(
$url_parts['path'])) {
            if (isset(
$url_parts['query'])) {
                
$get $url_parts['path'] . '?' $url_parts['query'];
            }
            else {
                
$get $url_parts['path'];
            }
        }
        else {
            
$get '/';
        }
        return 
$get;
    }

    
/**
     * Error handler for stream_socket_client()
     *
     * @param int $errno Error number (e.g. E_WARNING)
     * @param string $errstr Error message
     */
    
public function connect_error_handler($errno$errstr) {
        
// Double-check we can handle it
        
if (($errno E_WARNING) === && ($errno E_NOTICE) === 0) {
            
// Return false to indicate the default error handler should engage
            
return false;
        }

        
$this->connect_error .= $errstr "\n";
        return 
true;
    }

    
/**
     * Verify the certificate against common name and subject alternative names
     *
     * Unfortunately, PHP doesn't check the certificate against the alternative
     * names, leading things like 'https://www.github.com/' to be invalid.
     * Instead
     *
     * @see https://tools.ietf.org/html/rfc2818#section-3.1 RFC2818, Section 3.1
     *
     * @throws Requests_Exception On failure to connect via TLS (`fsockopen.ssl.connect_error`)
     * @throws Requests_Exception On not obtaining a match for the host (`fsockopen.ssl.no_match`)
     * @param string $host Host name to verify against
     * @param resource $context Stream context
     * @return bool
     */
    
public function verify_certificate_from_context($host$context) {
        
$meta stream_context_get_options($context);

        
// If we don't have SSL options, then we couldn't make the connection at
        // all
        
if (empty($meta) || empty($meta['ssl']) || empty($meta['ssl']['peer_certificate'])) {
            throw new 
Requests_Exception(rtrim($this->connect_error), 'ssl.connect_error');
        }

        
$cert openssl_x509_parse($meta['ssl']['peer_certificate']);

        return 
Requests_SSL::verify_certificate($host$cert);
    }

    
/**
     * Whether this transport is valid
     *
     * @codeCoverageIgnore
     * @return boolean True if the transport is valid, false otherwise.
     */
    
public static function test($capabilities = array()) {
        if (!
function_exists('fsockopen')) {
            return 
false;
        }

        
// If needed, check that streams support SSL
        
if (isset($capabilities['ssl']) && $capabilities['ssl']) {
            if (!
extension_loaded('openssl') || !function_exists('openssl_x509_parse')) {
                return 
false;
            }

            
// Currently broken, thanks to https://github.com/facebook/hhvm/issues/2156
            
if (defined('HHVM_VERSION')) {
                return 
false;
            }
        }

        return 
true;
    }
}
<?
php

class Requests_Exception_Transport_cURL extends Requests_Exception_Transport {

    const 
EASY  'cURLEasy';
    const 
MULTI 'cURLMulti';
    const 
SHARE 'cURLShare';

    
/**
     * cURL error code
     *
     * @var integer
     */
    
protected $code = -1;

    
/**
     * Which type of cURL error
     *
     * EASY|MULTI|SHARE
     *
     * @var string
     */
    
protected $type 'Unknown';

    
/**
     * Clear text error message
     *
     * @var string
     */
    
protected $reason 'Unknown';

    public function 
__construct($message$type$data null$code 0) {
        if (
$type !== null) {
            
$this->type $type;
        }

        if (
$code !== null) {
            
$this->code $code;
        }

        if (
$message !== null) {
            
$this->reason $message;
        }

        
$message sprintf('%d %s'$this->code$this->reason);
        
parent::__construct($message$this->type$data$this->code);
    }

    
/**
     * Get the error message
     */
    
public function getReason() {
        return 
$this->reason;
    }

}
<?
php
/**
 * Exception based on HTTP response
 *
 * @package Requests
 */

/**
 * Exception based on HTTP response
 *
 * @package Requests
 */
class Requests_Exception_HTTP extends Requests_Exception {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 0;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Unknown';

    
/**
     * Create a new exception
     *
     * There is no mechanism to pass in the status code, as this is set by the
     * subclass used. Reason phrases can vary, however.
     *
     * @param string|null $reason Reason phrase
     * @param mixed $data Associated data
     */
    
public function __construct($reason null$data null) {
        if (
$reason !== null) {
            
$this->reason $reason;
        }

        
$message sprintf('%d %s'$this->code$this->reason);
        
parent::__construct($message'httpresponse'$data$this->code);
    }

    
/**
     * Get the status message
     */
    
public function getReason() {
        return 
$this->reason;
    }

    
/**
     * Get the correct exception class for a given error code
     *
     * @param int|bool $code HTTP status code, or false if unavailable
     * @return string Exception class name to use
     */
    
public static function get_class($code) {
        if (!
$code) {
            return 
'Requests_Exception_HTTP_Unknown';
        }

        
$class sprintf('Requests_Exception_HTTP_%d'$code);
        if (
class_exists($class)) {
            return 
$class;
        }

        return 
'Requests_Exception_HTTP_Unknown';
    }
}
<?
php
/**
 * Exception for unknown status responses
 *
 * @package Requests
 */

/**
 * Exception for unknown status responses
 *
 * @package Requests
 */
class Requests_Exception_HTTP_Unknown extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer|bool Code if available, false if an error occurred
     */
    
protected $code 0;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Unknown';

    
/**
     * Create a new exception
     *
     * If `$data` is an instance of {@see Requests_Response}, uses the status
     * code from it. Otherwise, sets as 0
     *
     * @param string|null $reason Reason phrase
     * @param mixed $data Associated data
     */
    
public function __construct($reason null$data null) {
        if (
$data instanceof Requests_Response) {
            
$this->code $data->status_code;
        }

        
parent::__construct($reason$data);
    }
}
<?
php
/**
 * Exception for 410 Gone responses
 *
 * @package Requests
 */

/**
 * Exception for 410 Gone responses
 *
 * @package Requests
 */
class Requests_Exception_HTTP_410 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 410;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Gone';
}
<?
php
/**
 * Exception for 428 Precondition Required responses
 *
 * @see https://tools.ietf.org/html/rfc6585
 * @package Requests
 */

/**
 * Exception for 428 Precondition Required responses
 *
 * @see https://tools.ietf.org/html/rfc6585
 * @package Requests
 */
class Requests_Exception_HTTP_428 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 428;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Precondition Required';
}
<?
php
/**
 * Exception for 409 Conflict responses
 *
 * @package Requests
 */

/**
 * Exception for 409 Conflict responses
 *
 * @package Requests
 */
class Requests_Exception_HTTP_409 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 409;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Conflict';
}
<?
php
/**
 * Exception for 504 Gateway Timeout responses
 *
 * @package Requests
 */

/**
 * Exception for 504 Gateway Timeout responses
 *
 * @package Requests
 */
class Requests_Exception_HTTP_504 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 504;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Gateway Timeout';
}
<?
php
/**
 * Exception for 416 Requested Range Not Satisfiable responses
 *
 * @package Requests
 */

/**
 * Exception for 416 Requested Range Not Satisfiable responses
 *
 * @package Requests
 */
class Requests_Exception_HTTP_416 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 416;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Requested Range Not Satisfiable';
}
<?
php
/**
 * Exception for 405 Method Not Allowed responses
 *
 * @package Requests
 */

/**
 * Exception for 405 Method Not Allowed responses
 *
 * @package Requests
 */
class Requests_Exception_HTTP_405 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 405;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Method Not Allowed';
}
<?
php
/**
 * Exception for 501 Not Implemented responses
 *
 * @package Requests
 */

/**
 * Exception for 501 Not Implemented responses
 *
 * @package Requests
 */
class Requests_Exception_HTTP_501 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 501;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Not Implemented';
}
<?
php
/**
 * Exception for 505 HTTP Version Not Supported responses
 *
 * @package Requests
 */

/**
 * Exception for 505 HTTP Version Not Supported responses
 *
 * @package Requests
 */
class Requests_Exception_HTTP_505 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 505;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'HTTP Version Not Supported';
}
<?
php
/**
 * Exception for 418 I'm A Teapot responses
 *
 * @see https://tools.ietf.org/html/rfc2324
 * @package Requests
 */

/**
 * Exception for 418 I'm A Teapot responses
 *
 * @see https://tools.ietf.org/html/rfc2324
 * @package Requests
 */
class Requests_Exception_HTTP_418 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 418;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason "I'm A Teapot";
}
<?
php
/**
 * Exception for 407 Proxy Authentication Required responses
 *
 * @package Requests
 */

/**
 * Exception for 407 Proxy Authentication Required responses
 *
 * @package Requests
 */
class Requests_Exception_HTTP_407 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 407;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Proxy Authentication Required';
}
<?
php
/**
 * Exception for 402 Payment Required responses
 *
 * @package Requests
 */

/**
 * Exception for 402 Payment Required responses
 *
 * @package Requests
 */
class Requests_Exception_HTTP_402 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 402;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Payment Required';
}
<?
php
/**
 * Exception for 415 Unsupported Media Type responses
 *
 * @package Requests
 */

/**
 * Exception for 415 Unsupported Media Type responses
 *
 * @package Requests
 */
class Requests_Exception_HTTP_415 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 415;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Unsupported Media Type';
}
<?
php
/**
 * Exception for 404 Not Found responses
 *
 * @package Requests
 */

/**
 * Exception for 404 Not Found responses
 *
 * @package Requests
 */
class Requests_Exception_HTTP_404 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 404;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Not Found';
}
<?
php
/**
 * Exception for 408 Request Timeout responses
 *
 * @package Requests
 */

/**
 * Exception for 408 Request Timeout responses
 *
 * @package Requests
 */
class Requests_Exception_HTTP_408 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 408;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Request Timeout';
}
<?
php
/**
 * Exception for 431 Request Header Fields Too Large responses
 *
 * @see https://tools.ietf.org/html/rfc6585
 * @package Requests
 */

/**
 * Exception for 431 Request Header Fields Too Large responses
 *
 * @see https://tools.ietf.org/html/rfc6585
 * @package Requests
 */
class Requests_Exception_HTTP_431 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 431;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Request Header Fields Too Large';
}
<?
php
/**
 * Exception for 403 Forbidden responses
 *
 * @package Requests
 */

/**
 * Exception for 403 Forbidden responses
 *
 * @package Requests
 */
class Requests_Exception_HTTP_403 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 403;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Forbidden';
}
<?
php
/**
 * Exception for 400 Bad Request responses
 *
 * @package Requests
 */

/**
 * Exception for 400 Bad Request responses
 *
 * @package Requests
 */
class Requests_Exception_HTTP_400 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 400;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Bad Request';
}
<?
php
/**
 * Exception for 306 Switch Proxy responses
 *
 * @package Requests
 */

/**
 * Exception for 306 Switch Proxy responses
 *
 * @package Requests
 */
class Requests_Exception_HTTP_306 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 306;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Switch Proxy';
}
<?
php
/**
 * Exception for 511 Network Authentication Required responses
 *
 * @see https://tools.ietf.org/html/rfc6585
 * @package Requests
 */

/**
 * Exception for 511 Network Authentication Required responses
 *
 * @see https://tools.ietf.org/html/rfc6585
 * @package Requests
 */
class Requests_Exception_HTTP_511 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 511;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Network Authentication Required';
}
<?
php
/**
 * Exception for 503 Service Unavailable responses
 *
 * @package Requests
 */

/**
 * Exception for 503 Service Unavailable responses
 *
 * @package Requests
 */
class Requests_Exception_HTTP_503 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 503;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Service Unavailable';
}
<?
php
/**
 * Exception for 401 Unauthorized responses
 *
 * @package Requests
 */

/**
 * Exception for 401 Unauthorized responses
 *
 * @package Requests
 */
class Requests_Exception_HTTP_401 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 401;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Unauthorized';
}
<?
php
/**
 * Exception for 502 Bad Gateway responses
 *
 * @package Requests
 */

/**
 * Exception for 502 Bad Gateway responses
 *
 * @package Requests
 */
class Requests_Exception_HTTP_502 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 502;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Bad Gateway';
}
<?
php
/**
 * Exception for 413 Request Entity Too Large responses
 *
 * @package Requests
 */

/**
 * Exception for 413 Request Entity Too Large responses
 *
 * @package Requests
 */
class Requests_Exception_HTTP_413 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 413;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Request Entity Too Large';
}
<?
php
/**
 * Exception for 414 Request-URI Too Large responses
 *
 * @package Requests
 */

/**
 * Exception for 414 Request-URI Too Large responses
 *
 * @package Requests
 */
class Requests_Exception_HTTP_414 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 414;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Request-URI Too Large';
}
<?
php
/**
 * Exception for 304 Not Modified responses
 *
 * @package Requests
 */

/**
 * Exception for 304 Not Modified responses
 *
 * @package Requests
 */
class Requests_Exception_HTTP_304 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 304;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Not Modified';
}
<?
php
/**
 * Exception for 412 Precondition Failed responses
 *
 * @package Requests
 */

/**
 * Exception for 412 Precondition Failed responses
 *
 * @package Requests
 */
class Requests_Exception_HTTP_412 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 412;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Precondition Failed';
}
<?
php
/**
 * Exception for 305 Use Proxy responses
 *
 * @package Requests
 */

/**
 * Exception for 305 Use Proxy responses
 *
 * @package Requests
 */
class Requests_Exception_HTTP_305 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 305;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Use Proxy';
}
<?
php
/**
 * Exception for 500 Internal Server Error responses
 *
 * @package Requests
 */

/**
 * Exception for 500 Internal Server Error responses
 *
 * @package Requests
 */
class Requests_Exception_HTTP_500 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 500;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Internal Server Error';
}
<?
php
/**
 * Exception for 411 Length Required responses
 *
 * @package Requests
 */

/**
 * Exception for 411 Length Required responses
 *
 * @package Requests
 */
class Requests_Exception_HTTP_411 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 411;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Length Required';
}
<?
php
/**
 * Exception for 429 Too Many Requests responses
 *
 * @see https://tools.ietf.org/html/draft-nottingham-http-new-status-04
 * @package Requests
 */

/**
 * Exception for 429 Too Many Requests responses
 *
 * @see https://tools.ietf.org/html/draft-nottingham-http-new-status-04
 * @package Requests
 */
class Requests_Exception_HTTP_429 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 429;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Too Many Requests';
}
<?
php
/**
 * Exception for 406 Not Acceptable responses
 *
 * @package Requests
 */

/**
 * Exception for 406 Not Acceptable responses
 *
 * @package Requests
 */
class Requests_Exception_HTTP_406 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 406;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Not Acceptable';
}
<?
php
/**
 * Exception for 417 Expectation Failed responses
 *
 * @package Requests
 */

/**
 * Exception for 417 Expectation Failed responses
 *
 * @package Requests
 */
class Requests_Exception_HTTP_417 extends Requests_Exception_HTTP {
    
/**
     * HTTP status code
     *
     * @var integer
     */
    
protected $code 417;

    
/**
     * Reason phrase
     *
     * @var string
     */
    
protected $reason 'Expectation Failed';
}
<?
php

class Requests_Exception_Transport extends Requests_Exception {

}
<?
php
/**
 * Basic Authentication provider
 *
 * @package Requests
 * @subpackage Authentication
 */

/**
 * Basic Authentication provider
 *
 * Provides a handler for Basic HTTP authentication via the Authorization
 * header.
 *
 * @package Requests
 * @subpackage Authentication
 */
class Requests_Auth_Basic implements Requests_Auth {
    
/**
     * Username
     *
     * @var string
     */
    
public $user;

    
/**
     * Password
     *
     * @var string
     */
    
public $pass;

    
/**
     * Constructor
     *
     * @throws Requests_Exception On incorrect number of arguments (`authbasicbadargs`)
     * @param array|null $args Array of user and password. Must have exactly two elements
     */
    
public function __construct($args null) {
        if (
is_array($args)) {
            if (
count($args) !== 2) {
                throw new 
Requests_Exception('Invalid number of arguments''authbasicbadargs');
            }

            list(
$this->user$this->pass) = $args;
        }
    }

    
/**
     * Register the necessary callbacks
     *
     * @see curl_before_send
     * @see fsockopen_header
     * @param Requests_Hooks $hooks Hook system
     */
    
public function register(Requests_Hooks $hooks) {
        
$hooks->register('curl.before_send', array($this'curl_before_send'));
        
$hooks->register('fsockopen.after_headers', array($this'fsockopen_header'));
    }

    
/**
     * Set cURL parameters before the data is sent
     *
     * @param resource $handle cURL resource
     */
    
public function curl_before_send(&$handle) {
        
curl_setopt($handleCURLOPT_HTTPAUTHCURLAUTH_BASIC);
        
curl_setopt($handleCURLOPT_USERPWD$this->getAuthString());
    }

    
/**
     * Add extra headers to the request before sending
     *
     * @param string $out HTTP header string
     */
    
public function fsockopen_header(&$out) {
        
$out .= sprintf("Authorization: Basic %s\r\n"base64_encode($this->getAuthString()));
    }

    
/**
     * Get the authentication string (user:pass)
     *
     * @return string
     */
    
public function getAuthString() {
        return 
$this->user ':' $this->pass;
    }
}
<?
php
/**
 * Authentication provider interface
 *
 * @package Requests
 * @subpackage Authentication
 */

/**
 * Authentication provider interface
 *
 * Implement this interface to act as an authentication provider.
 *
 * Parameters should be passed via the constructor where possible, as this
 * makes it much easier for users to use your provider.
 *
 * @see Requests_Hooks
 * @package Requests
 * @subpackage Authentication
 */
interface Requests_Auth {
    
/**
     * Register hooks as needed
     *
     * This method is called in {@see Requests::request} when the user has set
     * an instance as the 'auth' option. Use this callback to register all the
     * hooks you'll need.
     *
     * @see Requests_Hooks::register
     * @param Requests_Hooks $hooks Hook system
     */
    
public function register(Requests_Hooks $hooks);
}
<?
php
/**
 * HTTP Proxy connection interface
 *
 * @package Requests
 * @subpackage Proxy
 * @since 1.6
 */

/**
 * HTTP Proxy connection interface
 *
 * Provides a handler for connection via an HTTP proxy
 *
 * @package Requests
 * @subpackage Proxy
 * @since 1.6
 */
class Requests_Proxy_HTTP implements Requests_Proxy {
    
/**
     * Proxy host and port
     *
     * Notation: "host:port" (eg 127.0.0.1:8080 or someproxy.com:3128)
     *
     * @var string
     */
    
public $proxy;

    
/**
     * Username
     *
     * @var string
     */
    
public $user;

    
/**
     * Password
     *
     * @var string
     */
    
public $pass;

    
/**
     * Do we need to authenticate? (ie username & password have been provided)
     *
     * @var boolean
     */
    
public $use_authentication;

    
/**
     * Constructor
     *
     * @since 1.6
     * @throws Requests_Exception On incorrect number of arguments (`authbasicbadargs`)
     * @param array|null $args Array of user and password. Must have exactly two elements
     */
    
public function __construct($args null) {
        if (
is_string($args)) {
            
$this->proxy $args;
        }
        elseif (
is_array($args)) {
            if (
count($args) === 1) {
                list(
$this->proxy) = $args;
            }
            elseif (
count($args) === 3) {
                list(
$this->proxy$this->user$this->pass) = $args;
                
$this->use_authentication                    true;
            }
            else {
                throw new 
Requests_Exception('Invalid number of arguments''proxyhttpbadargs');
            }
        }
    }

    
/**
     * Register the necessary callbacks
     *
     * @since 1.6
     * @see curl_before_send
     * @see fsockopen_remote_socket
     * @see fsockopen_remote_host_path
     * @see fsockopen_header
     * @param Requests_Hooks $hooks Hook system
     */
    
public function register(Requests_Hooks $hooks) {
        
$hooks->register('curl.before_send', array($this'curl_before_send'));

        
$hooks->register('fsockopen.remote_socket', array($this'fsockopen_remote_socket'));
        
$hooks->register('fsockopen.remote_host_path', array($this'fsockopen_remote_host_path'));
        if (
$this->use_authentication) {
            
$hooks->register('fsockopen.after_headers', array($this'fsockopen_header'));
        }
    }

    
/**
     * Set cURL parameters before the data is sent
     *
     * @since 1.6
     * @param resource $handle cURL resource
     */
    
public function curl_before_send(&$handle) {
        
curl_setopt($handleCURLOPT_PROXYTYPECURLPROXY_HTTP);
        
curl_setopt($handleCURLOPT_PROXY$this->proxy);

        if (
$this->use_authentication) {
            
curl_setopt($handleCURLOPT_PROXYAUTHCURLAUTH_ANY);
            
curl_setopt($handleCURLOPT_PROXYUSERPWD$this->get_auth_string());
        }
    }

    
/**
     * Alter remote socket information before opening socket connection
     *
     * @since 1.6
     * @param string $remote_socket Socket connection string
     */
    
public function fsockopen_remote_socket(&$remote_socket) {
        
$remote_socket $this->proxy;
    }

    
/**
     * Alter remote path before getting stream data
     *
     * @since 1.6
     * @param string $path Path to send in HTTP request string ("GET ...")
     * @param string $url Full URL we're requesting
     */
    
public function fsockopen_remote_host_path(&$path$url) {
        
$path $url;
    }

    
/**
     * Add extra headers to the request before sending
     *
     * @since 1.6
     * @param string $out HTTP header string
     */
    
public function fsockopen_header(&$out) {
        
$out .= sprintf("Proxy-Authorization: Basic %s\r\n"base64_encode($this->get_auth_string()));
    }

    
/**
     * Get the authentication string (user:pass)
     *
     * @since 1.6
     * @return string
     */
    
public function get_auth_string() {
        return 
$this->user ':' $this->pass;
    }
}
<?
php
/**
 * Class to validate and to work with IPv6 addresses
 *
 * @package Requests
 * @subpackage Utilities
 */

/**
 * Class to validate and to work with IPv6 addresses
 *
 * This was originally based on the PEAR class of the same name, but has been
 * entirely rewritten.
 *
 * @package Requests
 * @subpackage Utilities
 */
class Requests_IPv6 {
    
/**
     * Uncompresses an IPv6 address
     *
     * RFC 4291 allows you to compress consecutive zero pieces in an address to
     * '::'. This method expects a valid IPv6 address and expands the '::' to
     * the required number of zero pieces.
     *
     * Example:  FF01::101   ->  FF01:0:0:0:0:0:0:101
     *           ::1         ->  0:0:0:0:0:0:0:1
     *
     * @author Alexander Merz <[email protected]>
     * @author elfrink at introweb dot nl
     * @author Josh Peck <jmp at joshpeck dot org>
     * @copyright 2003-2005 The PHP Group
     * @license http://www.opensource.org/licenses/bsd-license.php
     * @param string $ip An IPv6 address
     * @return string The uncompressed IPv6 address
     */
    
public static function uncompress($ip) {
        if (
substr_count($ip'::') !== 1) {
            return 
$ip;
        }

        list(
$ip1$ip2) = explode('::'$ip);
        
$c1              = ($ip1 === '') ? -substr_count($ip1':');
        
$c2              = ($ip2 === '') ? -substr_count($ip2':');

        if (
strpos($ip2'.') !== false) {
            
$c2++;
        }
        
// ::
        
if ($c1 === -&& $c2 === -1) {
            
$ip '0:0:0:0:0:0:0:0';
        }
        
// ::xxx
        
elseif ($c1 === -1) {
            
$fill str_repeat('0:'$c2);
            
$ip   str_replace('::'$fill$ip);
        }
        
// xxx::
        
elseif ($c2 === -1) {
            
$fill str_repeat(':0'$c1);
            
$ip   str_replace('::'$fill$ip);
        }
        
// xxx::xxx
        
else {
            
$fill ':' str_repeat('0:'$c2 $c1);
            
$ip   str_replace('::'$fill$ip);
        }
        return 
$ip;
    }

    
/**
     * Compresses an IPv6 address
     *
     * RFC 4291 allows you to compress consecutive zero pieces in an address to
     * '::'. This method expects a valid IPv6 address and compresses consecutive
     * zero pieces to '::'.
     *
     * Example:  FF01:0:0:0:0:0:0:101   ->  FF01::101
     *           0:0:0:0:0:0:0:1        ->  ::1
     *
     * @see uncompress()
     * @param string $ip An IPv6 address
     * @return string The compressed IPv6 address
     */
    
public static function compress($ip) {
        
// Prepare the IP to be compressed
        
$ip       self::uncompress($ip);
        
$ip_parts self::split_v6_v4($ip);

        
// Replace all leading zeros
        
$ip_parts[0] = preg_replace('/(^|:)0+([0-9])/''\1\2'$ip_parts[0]);

        
// Find bunches of zeros
        
if (preg_match_all('/(?:^|:)(?:0(?::|$))+/'$ip_parts[0], $matchesPREG_OFFSET_CAPTURE)) {
            
$max 0;
            
$pos null;
            foreach (
$matches[0] as $match) {
                if (
strlen($match[0]) > $max) {
                    
$max strlen($match[0]);
                    
$pos $match[1];
                }
            }

            
$ip_parts[0] = substr_replace($ip_parts[0], '::'$pos$max);
        }

        if (
$ip_parts[1] !== '') {
            return 
implode(':'$ip_parts);
        }
        else {
            return 
$ip_parts[0];
        }
    }

    
/**
     * Splits an IPv6 address into the IPv6 and IPv4 representation parts
     *
     * RFC 4291 allows you to represent the last two parts of an IPv6 address
     * using the standard IPv4 representation
     *
     * Example:  0:0:0:0:0:0:13.1.68.3
     *           0:0:0:0:0:FFFF:129.144.52.38
     *
     * @param string $ip An IPv6 address
     * @return string[] [0] contains the IPv6 represented part, and [1] the IPv4 represented part
     */
    
protected static function split_v6_v4($ip) {
        if (
strpos($ip'.') !== false) {
            
$pos       strrpos($ip':');
            
$ipv6_part substr($ip0$pos);
            
$ipv4_part substr($ip$pos 1);
            return array(
$ipv6_part$ipv4_part);
        }
        else {
            return array(
$ip'');
        }
    }

    
/**
     * Checks an IPv6 address
     *
     * Checks if the given IP is a valid IPv6 address
     *
     * @param string $ip An IPv6 address
     * @return bool true if $ip is a valid IPv6 address
     */
    
public static function check_ipv6($ip) {
        
$ip                self::uncompress($ip);
        list(
$ipv6$ipv4) = self::split_v6_v4($ip);
        
$ipv6              explode(':'$ipv6);
        
$ipv4              explode('.'$ipv4);
        if (
count($ipv6) === && count($ipv4) === || count($ipv6) === && count($ipv4) === 4) {
            foreach (
$ipv6 as $ipv6_part) {
                
// The section can't be empty
                
if ($ipv6_part === '') {
                    return 
false;
                }

                
// Nor can it be over four characters
                
if (strlen($ipv6_part) > 4) {
                    return 
false;
                }

                
// Remove leading zeros (this is safe because of the above)
                
$ipv6_part ltrim($ipv6_part'0');
                if (
$ipv6_part === '') {
                    
$ipv6_part '0';
                }

                
// Check the value is valid
                
$value hexdec($ipv6_part);
                if (
dechex($value) !== strtolower($ipv6_part) || $value || $value 0xFFFF) {
                    return 
false;
                }
            }
            if (
count($ipv4) === 4) {
                foreach (
$ipv4 as $ipv4_part) {
                    
$value = (int) $ipv4_part;
                    if ((string) 
$value !== $ipv4_part || $value || $value 0xFF) {
                        return 
false;
                    }
                }
            }
            return 
true;
        }
        else {
            return 
false;
        }
    }
}
<?
php
/**
 * Proxy connection interface
 *
 * @package Requests
 * @subpackage Proxy
 * @since 1.6
 */

/**
 * Proxy connection interface
 *
 * Implement this interface to handle proxy settings and authentication
 *
 * Parameters should be passed via the constructor where possible, as this
 * makes it much easier for users to use your provider.
 *
 * @see Requests_Hooks
 * @package Requests
 * @subpackage Proxy
 * @since 1.6
 */
interface Requests_Proxy {
    
/**
     * Register hooks as needed
     *
     * This method is called in {@see Requests::request} when the user has set
     * an instance as the 'auth' option. Use this callback to register all the
     * hooks you'll need.
     *
     * @see Requests_Hooks::register
     * @param Requests_Hooks $hooks Hook system
     */
    
public function register(Requests_Hooks $hooks);
}
<?
php

/**
 * IDNA URL encoder
 *
 * Note: Not fully compliant, as nameprep does nothing yet.
 *
 * @package Requests
 * @subpackage Utilities
 * @see https://tools.ietf.org/html/rfc3490 IDNA specification
 * @see https://tools.ietf.org/html/rfc3492 Punycode/Bootstrap specification
 */
class Requests_IDNAEncoder {
    
/**
     * ACE prefix used for IDNA
     *
     * @see https://tools.ietf.org/html/rfc3490#section-5
     * @var string
     */
    
const ACE_PREFIX 'xn--';

    
/**#@+
     * Bootstrap constant for Punycode
     *
     * @see https://tools.ietf.org/html/rfc3492#section-5
     * @var int
     */
    
const BOOTSTRAP_BASE         36;
    const 
BOOTSTRAP_TMIN         1;
    const 
BOOTSTRAP_TMAX         26;
    const 
BOOTSTRAP_SKEW         38;
    const 
BOOTSTRAP_DAMP         700;
    const 
BOOTSTRAP_INITIAL_BIAS 72;
    const 
BOOTSTRAP_INITIAL_N    128;
    
/**#@-*/

    /**
     * Encode a hostname using Punycode
     *
     * @param string $string Hostname
     * @return string Punycode-encoded hostname
     */
    
public static function encode($string) {
        
$parts explode('.'$string);
        foreach (
$parts as &$part) {
            
$part self::to_ascii($part);
        }
        return 
implode('.'$parts);
    }

    
/**
     * Convert a UTF-8 string to an ASCII string using Punycode
     *
     * @throws Requests_Exception Provided string longer than 64 ASCII characters (`idna.provided_too_long`)
     * @throws Requests_Exception Prepared string longer than 64 ASCII characters (`idna.prepared_too_long`)
     * @throws Requests_Exception Provided string already begins with xn-- (`idna.provided_is_prefixed`)
     * @throws Requests_Exception Encoded string longer than 64 ASCII characters (`idna.encoded_too_long`)
     *
     * @param string $string ASCII or UTF-8 string (max length 64 characters)
     * @return string ASCII string
     */
    
public static function to_ascii($string) {
        
// Step 1: Check if the string is already ASCII
        
if (self::is_ascii($string)) {
            
// Skip to step 7
            
if (strlen($string) < 64) {
                return 
$string;
            }

            throw new 
Requests_Exception('Provided string is too long''idna.provided_too_long'$string);
        }

        
// Step 2: nameprep
        
$string self::nameprep($string);

        
// Step 3: UseSTD3ASCIIRules is false, continue
        // Step 4: Check if it's ASCII now
        
if (self::is_ascii($string)) {
            
// Skip to step 7
            
if (strlen($string) < 64) {
                return 
$string;
            }

            throw new 
Requests_Exception('Prepared string is too long''idna.prepared_too_long'$string);
        }

        
// Step 5: Check ACE prefix
        
if (strpos($stringself::ACE_PREFIX) === 0) {
            throw new 
Requests_Exception('Provided string begins with ACE prefix''idna.provided_is_prefixed'$string);
        }

        
// Step 6: Encode with Punycode
        
$string self::punycode_encode($string);

        
// Step 7: Prepend ACE prefix
        
$string self::ACE_PREFIX $string;

        
// Step 8: Check size
        
if (strlen($string) < 64) {
            return 
$string;
        }

        throw new 
Requests_Exception('Encoded string is too long''idna.encoded_too_long'$string);
    }

    
/**
     * Check whether a given string contains only ASCII characters
     *
     * @internal (Testing found regex was the fastest implementation)
     *
     * @param string $string
     * @return bool Is the string ASCII-only?
     */
    
protected static function is_ascii($string) {
        return (
preg_match('/(?:[^\x00-\x7F])/'$string) !== 1);
    }

    
/**
     * Prepare a string for use as an IDNA name
     *
     * @todo Implement this based on RFC 3491 and the newer 5891
     * @param string $string
     * @return string Prepared string
     */
    
protected static function nameprep($string) {
        return 
$string;
    }

    
/**
     * Convert a UTF-8 string to a UCS-4 codepoint array
     *
     * Based on Requests_IRI::replace_invalid_with_pct_encoding()
     *
     * @throws Requests_Exception Invalid UTF-8 codepoint (`idna.invalidcodepoint`)
     * @param string $input
     * @return array Unicode code points
     */
    
protected static function utf8_to_codepoints($input) {
        
$codepoints = array();

        
// Get number of bytes
        
$strlen strlen($input);

        
// phpcs:ignore Generic.CodeAnalysis.JumbledIncrementer -- This is a deliberate choice.
        
for ($position 0$position $strlen$position++) {
            
$value ord($input[$position]);

            
// One byte sequence:
            
if ((~$value 0x80) === 0x80) {
                
$character $value;
                
$length    1;
                
$remaining 0;
            }
            
// Two byte sequence:
            
elseif (($value 0xE0) === 0xC0) {
                
$character = ($value 0x1F) << 6;
                
$length    2;
                
$remaining 1;
            }
            
// Three byte sequence:
            
elseif (($value 0xF0) === 0xE0) {
                
$character = ($value 0x0F) << 12;
                
$length    3;
                
$remaining 2;
            }
            
// Four byte sequence:
            
elseif (($value 0xF8) === 0xF0) {
                
$character = ($value 0x07) << 18;
                
$length    4;
                
$remaining 3;
            }
            
// Invalid byte:
            
else {
                throw new 
Requests_Exception('Invalid Unicode codepoint''idna.invalidcodepoint'$value);
            }

            if (
$remaining 0) {
                if (
$position $length $strlen) {
                    throw new 
Requests_Exception('Invalid Unicode codepoint''idna.invalidcodepoint'$character);
                }
                for (
$position++; $remaining 0$position++) {
                    
$value ord($input[$position]);

                    
// If it is invalid, count the sequence as invalid and reprocess the current byte:
                    
if (($value 0xC0) !== 0x80) {
                        throw new 
Requests_Exception('Invalid Unicode codepoint''idna.invalidcodepoint'$character);
                    }

                    --
$remaining;
                    
$character |= ($value 0x3F) << ($remaining 6);
                }
                
$position--;
            }

            if (
// Non-shortest form sequences are invalid
                
$length && $character <= 0x7F
                
|| $length && $character <= 0x7FF
                
|| $length && $character <= 0xFFFF
                
// Outside of range of ucschar codepoints
                // Noncharacters
                
|| ($character 0xFFFE) === 0xFFFE
                
|| $character >= 0xFDD0 && $character <= 0xFDEF
                
|| (
                    
// Everything else not in ucschar
                    
$character 0xD7FF && $character 0xF900
                    
|| $character 0x20
                    
|| $character 0x7E && $character 0xA0
                    
|| $character 0xEFFFD
                
)
            ) {
                throw new 
Requests_Exception('Invalid Unicode codepoint''idna.invalidcodepoint'$character);
            }

            
$codepoints[] = $character;
        }

        return 
$codepoints;
    }

    
/**
     * RFC3492-compliant encoder
     *
     * @internal Pseudo-code from Section 6.3 is commented with "#" next to relevant code
     * @throws Requests_Exception On character outside of the domain (never happens with Punycode) (`idna.character_outside_domain`)
     *
     * @param string $input UTF-8 encoded string to encode
     * @return string Punycode-encoded string
     */
    
public static function punycode_encode($input) {
        
$output '';
        
// let n = initial_n
        
$n self::BOOTSTRAP_INITIAL_N;
        
// let delta = 0
        
$delta 0;
        
// let bias = initial_bias
        
$bias self::BOOTSTRAP_INITIAL_BIAS;
        
// let h = b = the number of basic code points in the input
        
$h 0;
        
$b 0// see loop
        // copy them to the output in order
        
$codepoints self::utf8_to_codepoints($input);
        
$extended   = array();

        foreach (
$codepoints as $char) {
            if (
$char 128) {
                
// Character is valid ASCII
                // TODO: this should also check if it's valid for a URL
                
$output .= chr($char);
                
$h++;
            }
            
// Check if the character is non-ASCII, but below initial n
            // This never occurs for Punycode, so ignore in coverage
            // @codeCoverageIgnoreStart
            
elseif ($char $n) {
                throw new 
Requests_Exception('Invalid character''idna.character_outside_domain'$char);
            }
            
// @codeCoverageIgnoreEnd
            
else {
                
$extended[$char] = true;
            }
        }
        
$extended array_keys($extended);
        
sort($extended);
        
$b $h;
        
// [copy them] followed by a delimiter if b > 0
        
if (strlen($output) > 0) {
            
$output .= '-';
        }
        
// {if the input contains a non-basic code point < n then fail}
        // while h < length(input) do begin
        
$codepointcount count($codepoints);
        while (
$h $codepointcount) {
            
// let m = the minimum code point >= n in the input
            
$m array_shift($extended);
            
//printf('next code point to insert is %s' . PHP_EOL, dechex($m));
            // let delta = delta + (m - n) * (h + 1), fail on overflow
            
$delta += ($m $n) * ($h 1);
            
// let n = m
            
$n $m;
            
// for each code point c in the input (in order) do begin
            
for ($num 0$num $codepointcount$num++) {
                
$c $codepoints[$num];
                
// if c < n then increment delta, fail on overflow
                
if ($c $n) {
                    
$delta++;
                }
                
// if c == n then begin
                
elseif ($c === $n) {
                    
// let q = delta
                    
$q $delta;
                    
// for k = base to infinity in steps of base do begin
                    
for ($k self::BOOTSTRAP_BASE; ; $k += self::BOOTSTRAP_BASE) {
                        
// let t = tmin if k <= bias {+ tmin}, or
                        //     tmax if k >= bias + tmax, or k - bias otherwise
                        
if ($k <= ($bias self::BOOTSTRAP_TMIN)) {
                            
$t self::BOOTSTRAP_TMIN;
                        }
                        elseif (
$k >= ($bias self::BOOTSTRAP_TMAX)) {
                            
$t self::BOOTSTRAP_TMAX;
                        }
                        else {
                            
$t $k $bias;
                        }
                        
// if q < t then break
                        
if ($q $t) {
                            break;
                        }
                        
// output the code point for digit t + ((q - t) mod (base - t))
                        
$digit   $t + (($q $t) % (self::BOOTSTRAP_BASE $t));
                        
$output .= self::digit_to_char($digit);
                        
// let q = (q - t) div (base - t)
                        
$q floor(($q $t) / (self::BOOTSTRAP_BASE $t));
                    } 
// end
                    // output the code point for digit q
                    
$output .= self::digit_to_char($q);
                    
// let bias = adapt(delta, h + 1, test h equals b?)
                    
$bias self::adapt($delta$h 1$h === $b);
                    
// let delta = 0
                    
$delta 0;
                    
// increment h
                    
$h++;
                } 
// end
            
// end
            // increment delta and n
            
$delta++;
            
$n++;
        } 
// end

        
return $output;
    }

    
/**
     * Convert a digit to its respective character
     *
     * @see https://tools.ietf.org/html/rfc3492#section-5
     * @throws Requests_Exception On invalid digit (`idna.invalid_digit`)
     *
     * @param int $digit Digit in the range 0-35
     * @return string Single character corresponding to digit
     */
    
protected static function digit_to_char($digit) {
        
// @codeCoverageIgnoreStart
        // As far as I know, this never happens, but still good to be sure.
        
if ($digit || $digit 35) {
            throw new 
Requests_Exception(sprintf('Invalid digit %d'$digit), 'idna.invalid_digit'$digit);
        }
        
// @codeCoverageIgnoreEnd
        
$digits 'abcdefghijklmnopqrstuvwxyz0123456789';
        return 
substr($digits$digit1);
    }

    
/**
     * Adapt the bias
     *
     * @see https://tools.ietf.org/html/rfc3492#section-6.1
     * @param int $delta
     * @param int $numpoints
     * @param bool $firsttime
     * @return int New bias
     *
     * function adapt(delta,numpoints,firsttime):
     */
    
protected static function adapt($delta$numpoints$firsttime) {
        
// if firsttime then let delta = delta div damp
        
if ($firsttime) {
            
$delta floor($delta self::BOOTSTRAP_DAMP);
        }
        
// else let delta = delta div 2
        
else {
            
$delta floor($delta 2);
        }
        
// let delta = delta + (delta div numpoints)
        
$delta += floor($delta $numpoints);
        
// let k = 0
        
$k 0;
        
// while delta > ((base - tmin) * tmax) div 2 do begin
        
$max floor(((self::BOOTSTRAP_BASE self::BOOTSTRAP_TMIN) * self::BOOTSTRAP_TMAX) / 2);
        while (
$delta $max) {
            
// let delta = delta div (base - tmin)
            
$delta floor($delta / (self::BOOTSTRAP_BASE self::BOOTSTRAP_TMIN));
            
// let k = k + base
            
$k += self::BOOTSTRAP_BASE;
        } 
// end
        // return k + (((base - tmin + 1) * delta) div (delta + skew))
        
return $k floor(((self::BOOTSTRAP_BASE self::BOOTSTRAP_TMIN 1) * $delta) / ($delta self::BOOTSTRAP_SKEW));
    }
}
<?
php
/**
 * Exception for HTTP requests
 *
 * @package Requests
 */

/**
 * Exception for HTTP requests
 *
 * @package Requests
 */
class Requests_Exception extends Exception {
    
/**
     * Type of exception
     *
     * @var string
     */
    
protected $type;

    
/**
     * Data associated with the exception
     *
     * @var mixed
     */
    
protected $data;

    
/**
     * Create a new exception
     *
     * @param string $message Exception message
     * @param string $type Exception type
     * @param mixed $data Associated data
     * @param integer $code Exception numerical code, if applicable
     */
    
public function __construct($message$type$data null$code 0) {
        
parent::__construct($message$code);

        
$this->type $type;
        
$this->data $data;
    }

    
/**
     * Like {@see getCode()}, but a string code.
     *
     * @codeCoverageIgnore
     * @return string
     */
    
public function getType() {
        return 
$this->type;
    }

    
/**
     * Gives any relevant data
     *
     * @codeCoverageIgnore
     * @return mixed
     */
    
public function getData() {
        return 
$this->data;
    }
}
<?
php
/**
 * Cookie storage object
 *
 * @package Requests
 * @subpackage Cookies
 */

/**
 * Cookie storage object
 *
 * @package Requests
 * @subpackage Cookies
 */
class Requests_Cookie {
    
/**
     * Cookie name.
     *
     * @var string
     */
    
public $name;

    
/**
     * Cookie value.
     *
     * @var string
     */
    
public $value;

    
/**
     * Cookie attributes
     *
     * Valid keys are (currently) path, domain, expires, max-age, secure and
     * httponly.
     *
     * @var Requests_Utility_CaseInsensitiveDictionary|array Array-like object
     */
    
public $attributes = array();

    
/**
     * Cookie flags
     *
     * Valid keys are (currently) creation, last-access, persistent and
     * host-only.
     *
     * @var array
     */
    
public $flags = array();

    
/**
     * Reference time for relative calculations
     *
     * This is used in place of `time()` when calculating Max-Age expiration and
     * checking time validity.
     *
     * @var int
     */
    
public $reference_time 0;

    
/**
     * Create a new cookie object
     *
     * @param string $name
     * @param string $value
     * @param array|Requests_Utility_CaseInsensitiveDictionary $attributes Associative array of attribute data
     */
    
public function __construct($name$value$attributes = array(), $flags = array(), $reference_time null) {
        
$this->name       $name;
        
$this->value      $value;
        
$this->attributes $attributes;
        
$default_flags    = array(
            
'creation'    => time(),
            
'last-access' => time(),
            
'persistent'  => false,
            
'host-only'   => true,
        );
        
$this->flags      array_merge($default_flags$flags);

        
$this->reference_time time();
        if (
$reference_time !== null) {
            
$this->reference_time $reference_time;
        }

        
$this->normalize();
    }

    
/**
     * Check if a cookie is expired.
     *
     * Checks the age against $this->reference_time to determine if the cookie
     * is expired.
     *
     * @return boolean True if expired, false if time is valid.
     */
    
public function is_expired() {
        
// RFC6265, s. 4.1.2.2:
        // If a cookie has both the Max-Age and the Expires attribute, the Max-
        // Age attribute has precedence and controls the expiration date of the
        // cookie.
        
if (isset($this->attributes['max-age'])) {
            
$max_age $this->attributes['max-age'];
            return 
$max_age $this->reference_time;
        }

        if (isset(
$this->attributes['expires'])) {
            
$expires $this->attributes['expires'];
            return 
$expires $this->reference_time;
        }

        return 
false;
    }

    
/**
     * Check if a cookie is valid for a given URI
     *
     * @param Requests_IRI $uri URI to check
     * @return boolean Whether the cookie is valid for the given URI
     */
    
public function uri_matches(Requests_IRI $uri) {
        if (!
$this->domain_matches($uri->host)) {
            return 
false;
        }

        if (!
$this->path_matches($uri->path)) {
            return 
false;
        }

        return empty(
$this->attributes['secure']) || $uri->scheme === 'https';
    }

    
/**
     * Check if a cookie is valid for a given domain
     *
     * @param string $string Domain to check
     * @return boolean Whether the cookie is valid for the given domain
     */
    
public function domain_matches($string) {
        if (!isset(
$this->attributes['domain'])) {
            
// Cookies created manually; cookies created by Requests will set
            // the domain to the requested domain
            
return true;
        }

        
$domain_string $this->attributes['domain'];
        if (
$domain_string === $string) {
            
// The domain string and the string are identical.
            
return true;
        }

        
// If the cookie is marked as host-only and we don't have an exact
        // match, reject the cookie
        
if ($this->flags['host-only'] === true) {
            return 
false;
        }

        if (
strlen($string) <= strlen($domain_string)) {
            
// For obvious reasons, the string cannot be a suffix if the domain
            // is shorter than the domain string
            
return false;
        }

        if (
substr($string, -strlen($domain_string)) !== $domain_string) {
            
// The domain string should be a suffix of the string.
            
return false;
        }

        
$prefix substr($string0strlen($string) - strlen($domain_string));
        if (
substr($prefix, -1) !== '.') {
            
// The last character of the string that is not included in the
            // domain string should be a %x2E (".") character.
            
return false;
        }

        
// The string should be a host name (i.e., not an IP address).
        
return !preg_match('#^(.+\.)\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$#'$string);
    }

    
/**
     * Check if a cookie is valid for a given path
     *
     * From the path-match check in RFC 6265 section 5.1.4
     *
     * @param string $request_path Path to check
     * @return boolean Whether the cookie is valid for the given path
     */
    
public function path_matches($request_path) {
        if (empty(
$request_path)) {
            
// Normalize empty path to root
            
$request_path '/';
        }

        if (!isset(
$this->attributes['path'])) {
            
// Cookies created manually; cookies created by Requests will set
            // the path to the requested path
            
return true;
        }

        
$cookie_path $this->attributes['path'];

        if (
$cookie_path === $request_path) {
            
// The cookie-path and the request-path are identical.
            
return true;
        }

        if (
strlen($request_path) > strlen($cookie_path) && substr($request_path0strlen($cookie_path)) === $cookie_path) {
            if (
substr($cookie_path, -1) === '/') {
                
// The cookie-path is a prefix of the request-path, and the last
                // character of the cookie-path is %x2F ("/").
                
return true;
            }

            if (
substr($request_pathstrlen($cookie_path), 1) === '/') {
                
// The cookie-path is a prefix of the request-path, and the
                // first character of the request-path that is not included in
                // the cookie-path is a %x2F ("/") character.
                
return true;
            }
        }

        return 
false;
    }

    
/**
     * Normalize cookie and attributes
     *
     * @return boolean Whether the cookie was successfully normalized
     */
    
public function normalize() {
        foreach (
$this->attributes as $key => $value) {
            
$orig_value $value;
            
$value      $this->normalize_attribute($key$value);
            if (
$value === null) {
                unset(
$this->attributes[$key]);
                continue;
            }

            if (
$value !== $orig_value) {
                
$this->attributes[$key] = $value;
            }
        }

        return 
true;
    }

    
/**
     * Parse an individual cookie attribute
     *
     * Handles parsing individual attributes from the cookie values.
     *
     * @param string $name Attribute name
     * @param string|boolean $value Attribute value (string value, or true if empty/flag)
     * @return mixed Value if available, or null if the attribute value is invalid (and should be skipped)
     */
    
protected function normalize_attribute($name$value) {
        switch (
strtolower($name)) {
            case 
'expires':
                
// Expiration parsing, as per RFC 6265 section 5.2.1
                
if (is_int($value)) {
                    return 
$value;
                }

                
$expiry_time strtotime($value);
                if (
$expiry_time === false) {
                    return 
null;
                }

                return 
$expiry_time;

            case 
'max-age':
                
// Expiration parsing, as per RFC 6265 section 5.2.2
                
if (is_int($value)) {
                    return 
$value;
                }

                
// Check that we have a valid age
                
if (!preg_match('/^-?\d+$/'$value)) {
                    return 
null;
                }

                
$delta_seconds = (int) $value;
                if (
$delta_seconds <= 0) {
                    
$expiry_time 0;
                }
                else {
                    
$expiry_time $this->reference_time $delta_seconds;
                }

                return 
$expiry_time;

            case 
'domain':
                
// Domains are not required as per RFC 6265 section 5.2.3
                
if (empty($value)) {
                    return 
null;
                }

                
// Domain normalization, as per RFC 6265 section 5.2.3
                
if ($value[0] === '.') {
                    
$value substr($value1);
                }

                return 
$value;

            default:
                return 
$value;
        }
    }

    
/**
     * Format a cookie for a Cookie header
     *
     * This is used when sending cookies to a server.
     *
     * @return string Cookie formatted for Cookie header
     */
    
public function format_for_header() {
        return 
sprintf('%s=%s'$this->name$this->value);
    }

    
/**
     * Format a cookie for a Cookie header
     *
     * @codeCoverageIgnore
     * @deprecated Use {@see Requests_Cookie::format_for_header}
     * @return string
     */
    
public function formatForHeader() {
        return 
$this->format_for_header();
    }

    
/**
     * Format a cookie for a Set-Cookie header
     *
     * This is used when sending cookies to clients. This isn't really
     * applicable to client-side usage, but might be handy for debugging.
     *
     * @return string Cookie formatted for Set-Cookie header
     */
    
public function format_for_set_cookie() {
        
$header_value $this->format_for_header();
        if (!empty(
$this->attributes)) {
            
$parts = array();
            foreach (
$this->attributes as $key => $value) {
                
// Ignore non-associative attributes
                
if (is_numeric($key)) {
                    
$parts[] = $value;
                }
                else {
                    
$parts[] = sprintf('%s=%s'$key$value);
                }
            }

            
$header_value .= '; ' implode('; '$parts);
        }
        return 
$header_value;
    }

    
/**
     * Format a cookie for a Set-Cookie header
     *
     * @codeCoverageIgnore
     * @deprecated Use {@see Requests_Cookie::format_for_set_cookie}
     * @return string
     */
    
public function formatForSetCookie() {
        return 
$this->format_for_set_cookie();
    }

    
/**
     * Get the cookie value
     *
     * Attributes and other data can be accessed via methods.
     */
    
public function __toString() {
        return 
$this->value;
    }

    
/**
     * Parse a cookie string into a cookie object
     *
     * Based on Mozilla's parsing code in Firefox and related projects, which
     * is an intentional deviation from RFC 2109 and RFC 2616. RFC 6265
     * specifies some of this handling, but not in a thorough manner.
     *
     * @param string Cookie header value (from a Set-Cookie header)
     * @return Requests_Cookie Parsed cookie object
     */
    
public static function parse($string$name ''$reference_time null) {
        
$parts   explode(';'$string);
        
$kvparts array_shift($parts);

        if (!empty(
$name)) {
            
$value $string;
        }
        elseif (
strpos($kvparts'=') === false) {
            
// Some sites might only have a value without the equals separator.
            // Deviate from RFC 6265 and pretend it was actually a blank name
            // (`=foo`)
            //
            // https://bugzilla.mozilla.org/show_bug.cgi?id=169091
            
$name  '';
            
$value $kvparts;
        }
        else {
            list(
$name$value) = explode('='$kvparts2);
        }
        
$name  trim($name);
        
$value trim($value);

        
// Attribute key are handled case-insensitively
        
$attributes = new Requests_Utility_CaseInsensitiveDictionary();

        if (!empty(
$parts)) {
            foreach (
$parts as $part) {
                if (
strpos($part'=') === false) {
                    
$part_key   $part;
                    
$part_value true;
                }
                else {
                    list(
$part_key$part_value) = explode('='$part2);
                    
$part_value                  trim($part_value);
                }

                
$part_key              trim($part_key);
                
$attributes[$part_key] = $part_value;
            }
        }

        return new 
Requests_Cookie($name$value$attributes, array(), $reference_time);
    }

    
/**
     * Parse all Set-Cookie headers from request headers
     *
     * @param Requests_Response_Headers $headers Headers to parse from
     * @param Requests_IRI|null $origin URI for comparing cookie origins
     * @param int|null $time Reference time for expiration calculation
     * @return array
     */
    
public static function parse_from_headers(Requests_Response_Headers $headersRequests_IRI $origin null$time null) {
        
$cookie_headers $headers->getValues('Set-Cookie');
        if (empty(
$cookie_headers)) {
            return array();
        }

        
$cookies = array();
        foreach (
$cookie_headers as $header) {
            
$parsed self::parse($header''$time);

            
// Default domain/path attributes
            
if (empty($parsed->attributes['domain']) && !empty($origin)) {
                
$parsed->attributes['domain'] = $origin->host;
                
$parsed->flags['host-only']   = true;
            }
            else {
                
$parsed->flags['host-only'] = false;
            }

            
$path_is_valid = (!empty($parsed->attributes['path']) && $parsed->attributes['path'][0] === '/');
            if (!
$path_is_valid && !empty($origin)) {
                
$path $origin->path;

                
// Default path normalization as per RFC 6265 section 5.1.4
                
if (substr($path01) !== '/') {
                    
// If the uri-path is empty or if the first character of
                    // the uri-path is not a %x2F ("/") character, output
                    // %x2F ("/") and skip the remaining steps.
                    
$path '/';
                }
                elseif (
substr_count($path'/') === 1) {
                    
// If the uri-path contains no more than one %x2F ("/")
                    // character, output %x2F ("/") and skip the remaining
                    // step.
                    
$path '/';
                }
                else {
                    
// Output the characters of the uri-path from the first
                    // character up to, but not including, the right-most
                    // %x2F ("/").
                    
$path substr($path0strrpos($path'/'));
                }
                
$parsed->attributes['path'] = $path;
            }

            
// Reject invalid cookie domains
            
if (!empty($origin) && !$parsed->domain_matches($origin->host)) {
                continue;
            }

            
$cookies[$parsed->name] = $parsed;
        }

        return 
$cookies;
    }

    
/**
     * Parse all Set-Cookie headers from request headers
     *
     * @codeCoverageIgnore
     * @deprecated Use {@see Requests_Cookie::parse_from_headers}
     * @return array
     */
    
public static function parseFromHeaders(Requests_Response_Headers $headers) {
        return 
self::parse_from_headers($headers);
    }
}
<?
php
/**
 * Session handler for persistent requests and default parameters
 *
 * @package Requests
 * @subpackage Session Handler
 */

/**
 * Session handler for persistent requests and default parameters
 *
 * Allows various options to be set as default values, and merges both the
 * options and URL properties together. A base URL can be set for all requests,
 * with all subrequests resolved from this. Base options can be set (including
 * a shared cookie jar), then overridden for individual requests.
 *
 * @package Requests
 * @subpackage Session Handler
 */
class Requests_Session {
    
/**
     * Base URL for requests
     *
     * URLs will be made absolute using this as the base
     *
     * @var string|null
     */
    
public $url null;

    
/**
     * Base headers for requests
     *
     * @var array
     */
    
public $headers = array();

    
/**
     * Base data for requests
     *
     * If both the base data and the per-request data are arrays, the data will
     * be merged before sending the request.
     *
     * @var array
     */
    
public $data = array();

    
/**
     * Base options for requests
     *
     * The base options are merged with the per-request data for each request.
     * The only default option is a shared cookie jar between requests.
     *
     * Values here can also be set directly via properties on the Session
     * object, e.g. `$session->useragent = 'X';`
     *
     * @var array
     */
    
public $options = array();

    
/**
     * Create a new session
     *
     * @param string|null $url Base URL for requests
     * @param array $headers Default headers for requests
     * @param array $data Default data for requests
     * @param array $options Default options for requests
     */
    
public function __construct($url null$headers = array(), $data = array(), $options = array()) {
        
$this->url     $url;
        
$this->headers $headers;
        
$this->data    $data;
        
$this->options $options;

        if (empty(
$this->options['cookies'])) {
            
$this->options['cookies'] = new Requests_Cookie_Jar();
        }
    }

    
/**
     * Get a property's value
     *
     * @param string $key Property key
     * @return mixed|null Property value, null if none found
     */
    
public function __get($key) {
        if (isset(
$this->options[$key])) {
            return 
$this->options[$key];
        }

        return 
null;
    }

    
/**
     * Set a property's value
     *
     * @param string $key Property key
     * @param mixed $value Property value
     */
    
public function __set($key$value) {
        
$this->options[$key] = $value;
    }

    
/**
     * Remove a property's value
     *
     * @param string $key Property key
     */
    
public function __isset($key) {
        return isset(
$this->options[$key]);
    }

    
/**
     * Remove a property's value
     *
     * @param string $key Property key
     */
    
public function __unset($key) {
        if (isset(
$this->options[$key])) {
            unset(
$this->options[$key]);
        }
    }

    
/**#@+
     * @see request()
     * @param string $url
     * @param array $headers
     * @param array $options
     * @return Requests_Response
     */
    /**
     * Send a GET request
     */
    
public function get($url$headers = array(), $options = array()) {
        return 
$this->request($url$headersnullRequests::GET$options);
    }

    
/**
     * Send a HEAD request
     */
    
public function head($url$headers = array(), $options = array()) {
        return 
$this->request($url$headersnullRequests::HEAD$options);
    }

    
/**
     * Send a DELETE request
     */
    
public function delete($url$headers = array(), $options = array()) {
        return 
$this->request($url$headersnullRequests::DELETE$options);
    }
    
/**#@-*/

    /**#@+
     * @see request()
     * @param string $url
     * @param array $headers
     * @param array $data
     * @param array $options
     * @return Requests_Response
     */
    /**
     * Send a POST request
     */
    
public function post($url$headers = array(), $data = array(), $options = array()) {
        return 
$this->request($url$headers$dataRequests::POST$options);
    }

    
/**
     * Send a PUT request
     */
    
public function put($url$headers = array(), $data = array(), $options = array()) {
        return 
$this->request($url$headers$dataRequests::PUT$options);
    }

    
/**
     * Send a PATCH request
     *
     * Note: Unlike {@see post} and {@see put}, `$headers` is required, as the
     * specification recommends that should send an ETag
     *
     * @link https://tools.ietf.org/html/rfc5789
     */
    
public function patch($url$headers$data = array(), $options = array()) {
        return 
$this->request($url$headers$dataRequests::PATCH$options);
    }
    
/**#@-*/

    /**
     * Main interface for HTTP requests
     *
     * This method initiates a request and sends it via a transport before
     * parsing.
     *
     * @see Requests::request()
     *
     * @throws Requests_Exception On invalid URLs (`nonhttp`)
     *
     * @param string $url URL to request
     * @param array $headers Extra headers to send with the request
     * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests
     * @param string $type HTTP request type (use Requests constants)
     * @param array $options Options for the request (see {@see Requests::request})
     * @return Requests_Response
     */
    
public function request($url$headers = array(), $data = array(), $type Requests::GET$options = array()) {
        
$request $this->merge_request(compact('url''headers''data''options'));

        return 
Requests::request($request['url'], $request['headers'], $request['data'], $type$request['options']);
    }

    
/**
     * Send multiple HTTP requests simultaneously
     *
     * @see Requests::request_multiple()
     *
     * @param array $requests Requests data (see {@see Requests::request_multiple})
     * @param array $options Global and default options (see {@see Requests::request})
     * @return array Responses (either Requests_Response or a Requests_Exception object)
     */
    
public function request_multiple($requests$options = array()) {
        foreach (
$requests as $key => $request) {
            
$requests[$key] = $this->merge_request($requestfalse);
        }

        
$options array_merge($this->options$options);

        
// Disallow forcing the type, as that's a per request setting
        
unset($options['type']);

        return 
Requests::request_multiple($requests$options);
    }

    
/**
     * Merge a request's data with the default data
     *
     * @param array $request Request data (same form as {@see request_multiple})
     * @param boolean $merge_options Should we merge options as well?
     * @return array Request data
     */
    
protected function merge_request($request$merge_options true) {
        if (
$this->url !== null) {
            
$request['url'] = Requests_IRI::absolutize($this->url$request['url']);
            
$request['url'] = $request['url']->uri;
        }

        if (empty(
$request['headers'])) {
            
$request['headers'] = array();
        }
        
$request['headers'] = array_merge($this->headers$request['headers']);

        if (empty(
$request['data'])) {
            if (
is_array($this->data)) {
                
$request['data'] = $this->data;
            }
        }
        elseif (
is_array($request['data']) && is_array($this->data)) {
            
$request['data'] = array_merge($this->data$request['data']);
        }

        if (
$merge_options !== false) {
            
$request['options'] = array_merge($this->options$request['options']);

            
// Disallow forcing the type, as that's a per request setting
            
unset($request['options']['type']);
        }

        return 
$request;
    }
}
<?
php
/**
 * Case-insensitive dictionary, suitable for HTTP headers
 *
 * @package Requests
 * @subpackage Utilities
 */

/**
 * Case-insensitive dictionary, suitable for HTTP headers
 *
 * @package Requests
 * @subpackage Utilities
 */
class Requests_Utility_CaseInsensitiveDictionary implements ArrayAccessIteratorAggregate {
    
/**
     * Actual item data
     *
     * @var array
     */
    
protected $data = array();

    
/**
     * Creates a case insensitive dictionary.
     *
     * @param array $data Dictionary/map to convert to case-insensitive
     */
    
public function __construct(array $data = array()) {
        foreach (
$data as $key => $value) {
            
$this->offsetSet($key$value);
        }
    }

    
/**
     * Check if the given item exists
     *
     * @param string $key Item key
     * @return boolean Does the item exist?
     */
    
public function offsetExists($key) {
        
$key strtolower($key);
        return isset(
$this->data[$key]);
    }

    
/**
     * Get the value for the item
     *
     * @param string $key Item key
     * @return string|null Item value (null if offsetExists is false)
     */
    
public function offsetGet($key) {
        
$key strtolower($key);
        if (!isset(
$this->data[$key])) {
            return 
null;
        }

        return 
$this->data[$key];
    }

    
/**
     * Set the given item
     *
     * @throws Requests_Exception On attempting to use dictionary as list (`invalidset`)
     *
     * @param string $key Item name
     * @param string $value Item value
     */
    
public function offsetSet($key$value) {
        if (
$key === null) {
            throw new 
Requests_Exception('Object is a dictionary, not a list''invalidset');
        }

        
$key              strtolower($key);
        
$this->data[$key] = $value;
    }

    
/**
     * Unset the given header
     *
     * @param string $key
     */
    
public function offsetUnset($key) {
        unset(
$this->data[strtolower($key)]);
    }

    
/**
     * Get an iterator for the data
     *
     * @return ArrayIterator
     */
    
public function getIterator() {
        return new 
ArrayIterator($this->data);
    }

    
/**
     * Get the headers as an array
     *
     * @return array Header data
     */
    
public function getAll() {
        return 
$this->data;
    }
}
<?
php
/**
 * Iterator for arrays requiring filtered values
 *
 * @package Requests
 * @subpackage Utilities
 */

/**
 * Iterator for arrays requiring filtered values
 *
 * @package Requests
 * @subpackage Utilities
 */
class Requests_Utility_FilteredIterator extends ArrayIterator {
    
/**
     * Callback to run as a filter
     *
     * @var callable
     */
    
protected $callback;

    
/**
     * Create a new iterator
     *
     * @param array $data
     * @param callable $callback Callback to be called on each value
     */
    
public function __construct($data$callback) {
        
parent::__construct($data);

        
$this->callback $callback;
    }

    
/**
     * Get the current item's value after filtering
     *
     * @return string
     */
    
public function current() {
        
$value parent::current();

        if (
is_callable($this->callback)) {
            
$value call_user_func($this->callback$value);
        }

        return 
$value;
    }

    
/**
     * @inheritdoc
     */
    
public function unserialize($serialized) {}

    
/**
     * @inheritdoc
     *
     * @phpcs:disable PHPCompatibility.FunctionNameRestrictions.NewMagicMethods.__unserializeFound
     */
    
public function __unserialize($serialized) {}

    public function 
__wakeup() {
        unset(
$this->callback);
    }
}
<?
php
/**
 * IRI parser/serialiser/normaliser
 *
 * @package Requests
 * @subpackage Utilities
 */

/**
 * IRI parser/serialiser/normaliser
 *
 * Copyright (c) 2007-2010, Geoffrey Sneddon and Steve Minutillo.
 * 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 SimplePie Team 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 HOLDERS AND 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.
 *
 * @package Requests
 * @subpackage Utilities
 * @author Geoffrey Sneddon
 * @author Steve Minutillo
 * @copyright 2007-2009 Geoffrey Sneddon and Steve Minutillo
 * @license http://www.opensource.org/licenses/bsd-license.php
 * @link http://hg.gsnedders.com/iri/
 *
 * @property string $iri IRI we're working with
 * @property-read string $uri IRI in URI form, {@see to_uri}
 * @property string $scheme Scheme part of the IRI
 * @property string $authority Authority part, formatted for a URI (userinfo + host + port)
 * @property string $iauthority Authority part of the IRI (userinfo + host + port)
 * @property string $userinfo Userinfo part, formatted for a URI (after '://' and before '@')
 * @property string $iuserinfo Userinfo part of the IRI (after '://' and before '@')
 * @property string $host Host part, formatted for a URI
 * @property string $ihost Host part of the IRI
 * @property string $port Port part of the IRI (after ':')
 * @property string $path Path part, formatted for a URI (after first '/')
 * @property string $ipath Path part of the IRI (after first '/')
 * @property string $query Query part, formatted for a URI (after '?')
 * @property string $iquery Query part of the IRI (after '?')
 * @property string $fragment Fragment, formatted for a URI (after '#')
 * @property string $ifragment Fragment part of the IRI (after '#')
 */
class Requests_IRI {
    
/**
     * Scheme
     *
     * @var string|null
     */
    
protected $scheme null;

    
/**
     * User Information
     *
     * @var string|null
     */
    
protected $iuserinfo null;

    
/**
     * ihost
     *
     * @var string|null
     */
    
protected $ihost null;

    
/**
     * Port
     *
     * @var string|null
     */
    
protected $port null;

    
/**
     * ipath
     *
     * @var string
     */
    
protected $ipath '';

    
/**
     * iquery
     *
     * @var string|null
     */
    
protected $iquery null;

    
/**
     * ifragment|null
     *
     * @var string
     */
    
protected $ifragment null;

    
/**
     * Normalization database
     *
     * Each key is the scheme, each value is an array with each key as the IRI
     * part and value as the default value for that part.
     *
     * @var array
     */
    
protected $normalization = array(
        
'acap' => array(
            
'port' => 674
        
),
        
'dict' => array(
            
'port' => 2628
        
),
        
'file' => array(
            
'ihost' => 'localhost'
        
),
        
'http' => array(
            
'port' => 80,
        ),
        
'https' => array(
            
'port' => 443,
        ),
    );

    
/**
     * Return the entire IRI when you try and read the object as a string
     *
     * @return string
     */
    
public function __toString() {
        return 
$this->get_iri();
    }

    
/**
     * Overload __set() to provide access via properties
     *
     * @param string $name Property name
     * @param mixed $value Property value
     */
    
public function __set($name$value) {
        if (
method_exists($this'set_' $name)) {
            
call_user_func(array($this'set_' $name), $value);
        }
        elseif (
               
$name === 'iauthority'
            
|| $name === 'iuserinfo'
            
|| $name === 'ihost'
            
|| $name === 'ipath'
            
|| $name === 'iquery'
            
|| $name === 'ifragment'
        
) {
            
call_user_func(array($this'set_' substr($name1)), $value);
        }
    }

    
/**
     * Overload __get() to provide access via properties
     *
     * @param string $name Property name
     * @return mixed
     */
    
public function __get($name) {
        
// isset() returns false for null, we don't want to do that
        // Also why we use array_key_exists below instead of isset()
        
$props get_object_vars($this);

        if (
            
$name === 'iri' ||
            
$name === 'uri' ||
            
$name === 'iauthority' ||
            
$name === 'authority'
        
) {
            
$method 'get_' $name;
            
$return $this->$method();
        }
        elseif (
array_key_exists($name$props)) {
            
$return $this->$name;
        }
        
// host -> ihost
        
elseif (($prop 'i' $name) && array_key_exists($prop$props)) {
            
$name $prop;
            
$return $this->$prop;
        }
        
// ischeme -> scheme
        
elseif (($prop substr($name1)) && array_key_exists($prop$props)) {
            
$name $prop;
            
$return $this->$prop;
        }
        else {
            
trigger_error('Undefined property: ' get_class($this) . '::' $nameE_USER_NOTICE);
            
$return null;
        }

        if (
$return === null && isset($this->normalization[$this->scheme][$name])) {
            return 
$this->normalization[$this->scheme][$name];
        }
        else {
            return 
$return;
        }
    }

    
/**
     * Overload __isset() to provide access via properties
     *
     * @param string $name Property name
     * @return bool
     */
    
public function __isset($name) {
        return (
method_exists($this'get_' $name) || isset($this->$name));
    }

    
/**
     * Overload __unset() to provide access via properties
     *
     * @param string $name Property name
     */
    
public function __unset($name) {
        if (
method_exists($this'set_' $name)) {
            
call_user_func(array($this'set_' $name), '');
        }
    }

    
/**
     * Create a new IRI object, from a specified string
     *
     * @param string|null $iri
     */
    
public function __construct($iri null) {
        
$this->set_iri($iri);
    }

    
/**
     * Create a new IRI object by resolving a relative IRI
     *
     * Returns false if $base is not absolute, otherwise an IRI.
     *
     * @param Requests_IRI|string $base (Absolute) Base IRI
     * @param Requests_IRI|string $relative Relative IRI
     * @return Requests_IRI|false
     */
    
public static function absolutize($base$relative) {
        if (!(
$relative instanceof Requests_IRI)) {
            
$relative = new Requests_IRI($relative);
        }
        if (!
$relative->is_valid()) {
            return 
false;
        }
        elseif (
$relative->scheme !== null) {
            return clone 
$relative;
        }

        if (!(
$base instanceof Requests_IRI)) {
            
$base = new Requests_IRI($base);
        }
        if (
$base->scheme === null || !$base->is_valid()) {
            return 
false;
        }

        if (
$relative->get_iri() !== '') {
            if (
$relative->iuserinfo !== null || $relative->ihost !== null || $relative->port !== null) {
                
$target = clone $relative;
                
$target->scheme $base->scheme;
            }
            else {
                
$target = new Requests_IRI;
                
$target->scheme $base->scheme;
                
$target->iuserinfo $base->iuserinfo;
                
$target->ihost $base->ihost;
                
$target->port $base->port;
                if (
$relative->ipath !== '') {
                    if (
$relative->ipath[0] === '/') {
                        
$target->ipath $relative->ipath;
                    }
                    elseif ((
$base->iuserinfo !== null || $base->ihost !== null || $base->port !== null) && $base->ipath === '') {
                        
$target->ipath '/' $relative->ipath;
                    }
                    elseif ((
$last_segment strrpos($base->ipath'/')) !== false) {
                        
$target->ipath substr($base->ipath0$last_segment 1) . $relative->ipath;
                    }
                    else {
                        
$target->ipath $relative->ipath;
                    }
                    
$target->ipath $target->remove_dot_segments($target->ipath);
                    
$target->iquery $relative->iquery;
                }
                else {
                    
$target->ipath $base->ipath;
                    if (
$relative->iquery !== null) {
                        
$target->iquery $relative->iquery;
                    }
                    elseif (
$base->iquery !== null) {
                        
$target->iquery $base->iquery;
                    }
                }
                
$target->ifragment $relative->ifragment;
            }
        }
        else {
            
$target = clone $base;
            
$target->ifragment null;
        }
        
$target->scheme_normalization();
        return 
$target;
    }

    
/**
     * Parse an IRI into scheme/authority/path/query/fragment segments
     *
     * @param string $iri
     * @return array
     */
    
protected function parse_iri($iri) {
        
$iri trim($iri"\x20\x09\x0A\x0C\x0D");
        
$has_match preg_match('/^((?P<scheme>[^:\/?#]+):)?(\/\/(?P<authority>[^\/?#]*))?(?P<path>[^?#]*)(\?(?P<query>[^#]*))?(#(?P<fragment>.*))?$/'$iri$match);
        if (!
$has_match) {
            throw new 
Requests_Exception('Cannot parse supplied IRI''iri.cannot_parse'$iri);
        }

        if (
$match[1] === '') {
            
$match['scheme'] = null;
        }
        if (!isset(
$match[3]) || $match[3] === '') {
            
$match['authority'] = null;
        }
        if (!isset(
$match[5])) {
            
$match['path'] = '';
        }
        if (!isset(
$match[6]) || $match[6] === '') {
            
$match['query'] = null;
        }
        if (!isset(
$match[8]) || $match[8] === '') {
            
$match['fragment'] = null;
        }
        return 
$match;
    }

    
/**
     * Remove dot segments from a path
     *
     * @param string $input
     * @return string
     */
    
protected function remove_dot_segments($input) {
        
$output '';
        while (
strpos($input'./') !== false || strpos($input'/.') !== false || $input === '.' || $input === '..') {
            
// A: If the input buffer begins with a prefix of "../" or "./",
            // then remove that prefix from the input buffer; otherwise,
            
if (strpos($input'../') === 0) {
                
$input substr($input3);
            }
            elseif (
strpos($input'./') === 0) {
                
$input substr($input2);
            }
            
// B: if the input buffer begins with a prefix of "/./" or "/.",
            // where "." is a complete path segment, then replace that prefix
            // with "/" in the input buffer; otherwise,
            
elseif (strpos($input'/./') === 0) {
                
$input substr($input2);
            }
            elseif (
$input === '/.') {
                
$input '/';
            }
            
// C: if the input buffer begins with a prefix of "/../" or "/..",
            // where ".." is a complete path segment, then replace that prefix
            // with "/" in the input buffer and remove the last segment and its
            // preceding "/" (if any) from the output buffer; otherwise,
            
elseif (strpos($input'/../') === 0) {
                
$input substr($input3);
                
$output substr_replace($output''strrpos($output'/'));
            }
            elseif (
$input === '/..') {
                
$input '/';
                
$output substr_replace($output''strrpos($output'/'));
            }
            
// D: if the input buffer consists only of "." or "..", then remove
            // that from the input buffer; otherwise,
            
elseif ($input === '.' || $input === '..') {
                
$input '';
            }
            
// E: move the first path segment in the input buffer to the end of
            // the output buffer, including the initial "/" character (if any)
            // and any subsequent characters up to, but not including, the next
            // "/" character or the end of the input buffer
            
elseif (($pos strpos($input'/'1)) !== false) {
                
$output .= substr($input0$pos);
                
$input substr_replace($input''0$pos);
            }
            else {
                
$output .= $input;
                
$input '';
            }
        }
        return 
$output $input;
    }

    
/**
     * Replace invalid character with percent encoding
     *
     * @param string $string Input string
     * @param string $extra_chars Valid characters not in iunreserved or
     *                            iprivate (this is ASCII-only)
     * @param bool $iprivate Allow iprivate
     * @return string
     */
    
protected function replace_invalid_with_pct_encoding($string$extra_chars$iprivate false) {
        
// Normalize as many pct-encoded sections as possible
        
$string preg_replace_callback('/(?:%[A-Fa-f0-9]{2})+/', array($this'remove_iunreserved_percent_encoded'), $string);

        
// Replace invalid percent characters
        
$string preg_replace('/%(?![A-Fa-f0-9]{2})/''%25'$string);

        
// Add unreserved and % to $extra_chars (the latter is safe because all
        // pct-encoded sections are now valid).
        
$extra_chars .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~%';

        
// Now replace any bytes that aren't allowed with their pct-encoded versions
        
$position 0;
        
$strlen strlen($string);
        while ((
$position += strspn($string$extra_chars$position)) < $strlen) {
            
$value ord($string[$position]);

            
// Start position
            
$start $position;

            
// By default we are valid
            
$valid true;

            
// No one byte sequences are valid due to the while.
            // Two byte sequence:
            
if (($value 0xE0) === 0xC0) {
                
$character = ($value 0x1F) << 6;
                
$length 2;
                
$remaining 1;
            }
            
// Three byte sequence:
            
elseif (($value 0xF0) === 0xE0) {
                
$character = ($value 0x0F) << 12;
                
$length 3;
                
$remaining 2;
            }
            
// Four byte sequence:
            
elseif (($value 0xF8) === 0xF0) {
                
$character = ($value 0x07) << 18;
                
$length 4;
                
$remaining 3;
            }
            
// Invalid byte:
            
else {
                
$valid false;
                
$length 1;
                
$remaining 0;
            }

            if (
$remaining) {
                if (
$position $length <= $strlen) {
                    for (
$position++; $remaining$position++) {
                        
$value ord($string[$position]);

                        
// Check that the byte is valid, then add it to the character:
                        
if (($value 0xC0) === 0x80) {
                            
$character |= ($value 0x3F) << (--$remaining 6);
                        }
                        
// If it is invalid, count the sequence as invalid and reprocess the current byte:
                        
else {
                            
$valid false;
                            
$position--;
                            break;
                        }
                    }
                }
                else {
                    
$position $strlen 1;
                    
$valid false;
                }
            }

            
// Percent encode anything invalid or not in ucschar
            
if (
                
// Invalid sequences
                
!$valid
                
// Non-shortest form sequences are invalid
                
|| $length && $character <= 0x7F
                
|| $length && $character <= 0x7FF
                
|| $length && $character <= 0xFFFF
                
// Outside of range of ucschar codepoints
                // Noncharacters
                
|| ($character 0xFFFE) === 0xFFFE
                
|| $character >= 0xFDD0 && $character <= 0xFDEF
                
|| (
                    
// Everything else not in ucschar
                       
$character 0xD7FF && $character 0xF900
                    
|| $character 0xA0
                    
|| $character 0xEFFFD
                
)
                && (
                    
// Everything not in iprivate, if it applies
                       
!$iprivate
                    
|| $character 0xE000
                    
|| $character 0x10FFFD
                
)
            ) {
                
// If we were a character, pretend we weren't, but rather an error.
                
if ($valid) {
                    
$position--;
                }

                for (
$j $start$j <= $position$j++) {
                    
$string substr_replace($stringsprintf('%%%02X'ord($string[$j])), $j1);
                    
$j += 2;
                    
$position += 2;
                    
$strlen += 2;
                }
            }
        }

        return 
$string;
    }

    
/**
     * Callback function for preg_replace_callback.
     *
     * Removes sequences of percent encoded bytes that represent UTF-8
     * encoded characters in iunreserved
     *
     * @param array $match PCRE match
     * @return string Replacement
     */
    
protected function remove_iunreserved_percent_encoded($match) {
        
// As we just have valid percent encoded sequences we can just explode
        // and ignore the first member of the returned array (an empty string).
        
$bytes explode('%'$match[0]);

        
// Initialize the new string (this is what will be returned) and that
        // there are no bytes remaining in the current sequence (unsurprising
        // at the first byte!).
        
$string '';
        
$remaining 0;

        
// Loop over each and every byte, and set $value to its value
        
for ($i 1$len count($bytes); $i $len$i++) {
            
$value hexdec($bytes[$i]);

            
// If we're the first byte of sequence:
            
if (!$remaining) {
                
// Start position
                
$start $i;

                
// By default we are valid
                
$valid true;

                
// One byte sequence:
                
if ($value <= 0x7F) {
                    
$character $value;
                    
$length 1;
                }
                
// Two byte sequence:
                
elseif (($value 0xE0) === 0xC0) {
                    
$character = ($value 0x1F) << 6;
                    
$length 2;
                    
$remaining 1;
                }
                
// Three byte sequence:
                
elseif (($value 0xF0) === 0xE0) {
                    
$character = ($value 0x0F) << 12;
                    
$length 3;
                    
$remaining 2;
                }
                
// Four byte sequence:
                
elseif (($value 0xF8) === 0xF0) {
                    
$character = ($value 0x07) << 18;
                    
$length 4;
                    
$remaining 3;
                }
                
// Invalid byte:
                
else {
                    
$valid false;
                    
$remaining 0;
                }
            }
            
// Continuation byte:
            
else {
                
// Check that the byte is valid, then add it to the character:
                
if (($value 0xC0) === 0x80) {
                    
$remaining--;
                    
$character |= ($value 0x3F) << ($remaining 6);
                }
                
// If it is invalid, count the sequence as invalid and reprocess the current byte as the start of a sequence:
                
else {
                    
$valid false;
                    
$remaining 0;
                    
$i--;
                }
            }

            
// If we've reached the end of the current byte sequence, append it to Unicode::$data
            
if (!$remaining) {
                
// Percent encode anything invalid or not in iunreserved
                
if (
                    
// Invalid sequences
                    
!$valid
                    
// Non-shortest form sequences are invalid
                    
|| $length && $character <= 0x7F
                    
|| $length && $character <= 0x7FF
                    
|| $length && $character <= 0xFFFF
                    
// Outside of range of iunreserved codepoints
                    
|| $character 0x2D
                    
|| $character 0xEFFFD
                    
// Noncharacters
                    
|| ($character 0xFFFE) === 0xFFFE
                    
|| $character >= 0xFDD0 && $character <= 0xFDEF
                    
// Everything else not in iunreserved (this is all BMP)
                    
|| $character === 0x2F
                    
|| $character 0x39 && $character 0x41
                    
|| $character 0x5A && $character 0x61
                    
|| $character 0x7A && $character 0x7E
                    
|| $character 0x7E && $character 0xA0
                    
|| $character 0xD7FF && $character 0xF900
                
) {
                    for (
$j $start$j <= $i$j++) {
                        
$string .= '%' strtoupper($bytes[$j]);
                    }
                }
                else {
                    for (
$j $start$j <= $i$j++) {
                        
$string .= chr(hexdec($bytes[$j]));
                    }
                }
            }
        }

        
// If we have any bytes left over they are invalid (i.e., we are
        // mid-way through a multi-byte sequence)
        
if ($remaining) {
            for (
$j $start$j $len$j++) {
                
$string .= '%' strtoupper($bytes[$j]);
            }
        }

        return 
$string;
    }

    protected function 
scheme_normalization() {
        if (isset(
$this->normalization[$this->scheme]['iuserinfo']) && $this->iuserinfo === $this->normalization[$this->scheme]['iuserinfo']) {
            
$this->iuserinfo null;
        }
        if (isset(
$this->normalization[$this->scheme]['ihost']) && $this->ihost === $this->normalization[$this->scheme]['ihost']) {
            
$this->ihost null;
        }
        if (isset(
$this->normalization[$this->scheme]['port']) && $this->port === $this->normalization[$this->scheme]['port']) {
            
$this->port null;
        }
        if (isset(
$this->normalization[$this->scheme]['ipath']) && $this->ipath === $this->normalization[$this->scheme]['ipath']) {
            
$this->ipath '';
        }
        if (isset(
$this->ihost) && empty($this->ipath)) {
            
$this->ipath '/';
        }
        if (isset(
$this->normalization[$this->scheme]['iquery']) && $this->iquery === $this->normalization[$this->scheme]['iquery']) {
            
$this->iquery null;
        }
        if (isset(
$this->normalization[$this->scheme]['ifragment']) && $this->ifragment === $this->normalization[$this->scheme]['ifragment']) {
            
$this->ifragment null;
        }
    }

    
/**
     * Check if the object represents a valid IRI. This needs to be done on each
     * call as some things change depending on another part of the IRI.
     *
     * @return bool
     */
    
public function is_valid() {
        
$isauthority $this->iuserinfo !== null || $this->ihost !== null || $this->port !== null;
        if (
$this->ipath !== '' &&
            (
                
$isauthority && $this->ipath[0] !== '/' ||
                (
                    
$this->scheme === null &&
                    !
$isauthority &&
                    
strpos($this->ipath':') !== false &&
                    (
strpos($this->ipath'/') === false true strpos($this->ipath':') < strpos($this->ipath'/'))
                )
            )
        ) {
            return 
false;
        }

        return 
true;
    }

    
/**
     * Set the entire IRI. Returns true on success, false on failure (if there
     * are any invalid characters).
     *
     * @param string $iri
     * @return bool
     */
    
protected function set_iri($iri) {
        static 
$cache;
        if (!
$cache) {
            
$cache = array();
        }

        if (
$iri === null) {
            return 
true;
        }
        if (isset(
$cache[$iri])) {
            list(
$this->scheme,
                 
$this->iuserinfo,
                 
$this->ihost,
                 
$this->port,
                 
$this->ipath,
                 
$this->iquery,
                 
$this->ifragment,
                 
$return) = $cache[$iri];
            return 
$return;
        }

        
$parsed $this->parse_iri((string) $iri);

        
$return $this->set_scheme($parsed['scheme'])
            && 
$this->set_authority($parsed['authority'])
            && 
$this->set_path($parsed['path'])
            && 
$this->set_query($parsed['query'])
            && 
$this->set_fragment($parsed['fragment']);

        
$cache[$iri] = array($this->scheme,
                             
$this->iuserinfo,
                             
$this->ihost,
                             
$this->port,
                             
$this->ipath,
                             
$this->iquery,
                             
$this->ifragment,
                             
$return);
        return 
$return;
    }

    
/**
     * Set the scheme. Returns true on success, false on failure (if there are
     * any invalid characters).
     *
     * @param string $scheme
     * @return bool
     */
    
protected function set_scheme($scheme) {
        if (
$scheme === null) {
            
$this->scheme null;
        }
        elseif (!
preg_match('/^[A-Za-z][0-9A-Za-z+\-.]*$/'$scheme)) {
            
$this->scheme null;
            return 
false;
        }
        else {
            
$this->scheme strtolower($scheme);
        }
        return 
true;
    }

    
/**
     * Set the authority. Returns true on success, false on failure (if there are
     * any invalid characters).
     *
     * @param string $authority
     * @return bool
     */
    
protected function set_authority($authority) {
        static 
$cache;
        if (!
$cache) {
            
$cache = array();
        }

        if (
$authority === null) {
            
$this->iuserinfo null;
            
$this->ihost null;
            
$this->port null;
            return 
true;
        }
        if (isset(
$cache[$authority])) {
            list(
$this->iuserinfo,
                 
$this->ihost,
                 
$this->port,
                 
$return) = $cache[$authority];

            return 
$return;
        }

        
$remaining $authority;
        if ((
$iuserinfo_end strrpos($remaining'@')) !== false) {
            
$iuserinfo substr($remaining0$iuserinfo_end);
            
$remaining substr($remaining$iuserinfo_end 1);
        }
        else {
            
$iuserinfo null;
        }
        if ((
$port_start strpos($remaining':'strpos($remaining']'))) !== false) {
            
$port substr($remaining$port_start 1);
            if (
$port === false || $port === '') {
                
$port null;
            }
            
$remaining substr($remaining0$port_start);
        }
        else {
            
$port null;
        }

        
$return $this->set_userinfo($iuserinfo) &&
                  
$this->set_host($remaining) &&
                  
$this->set_port($port);

        
$cache[$authority] = array($this->iuserinfo,
                                   
$this->ihost,
                                   
$this->port,
                                   
$return);

        return 
$return;
    }

    
/**
     * Set the iuserinfo.
     *
     * @param string $iuserinfo
     * @return bool
     */
    
protected function set_userinfo($iuserinfo) {
        if (
$iuserinfo === null) {
            
$this->iuserinfo null;
        }
        else {
            
$this->iuserinfo $this->replace_invalid_with_pct_encoding($iuserinfo'!$&\'()*+,;=:');
            
$this->scheme_normalization();
        }

        return 
true;
    }

    
/**
     * Set the ihost. Returns true on success, false on failure (if there are
     * any invalid characters).
     *
     * @param string $ihost
     * @return bool
     */
    
protected function set_host($ihost) {
        if (
$ihost === null) {
            
$this->ihost null;
            return 
true;
        }
        if (
substr($ihost01) === '[' && substr($ihost, -1) === ']') {
            if (
Requests_IPv6::check_ipv6(substr($ihost1, -1))) {
                
$this->ihost '[' Requests_IPv6::compress(substr($ihost1, -1)) . ']';
            }
            else {
                
$this->ihost null;
                return 
false;
            }
        }
        else {
            
$ihost $this->replace_invalid_with_pct_encoding($ihost'!$&\'()*+,;=');

            
// Lowercase, but ignore pct-encoded sections (as they should
            // remain uppercase). This must be done after the previous step
            // as that can add unescaped characters.
            
$position 0;
            
$strlen strlen($ihost);
            while ((
$position += strcspn($ihost'ABCDEFGHIJKLMNOPQRSTUVWXYZ%'$position)) < $strlen) {
                if (
$ihost[$position] === '%') {
                    
$position += 3;
                }
                else {
                    
$ihost[$position] = strtolower($ihost[$position]);
                    
$position++;
                }
            }

            
$this->ihost $ihost;
        }

        
$this->scheme_normalization();

        return 
true;
    }

    
/**
     * Set the port. Returns true on success, false on failure (if there are
     * any invalid characters).
     *
     * @param string $port
     * @return bool
     */
    
protected function set_port($port) {
        if (
$port === null) {
            
$this->port null;
            return 
true;
        }

        if (
strspn($port'0123456789') === strlen($port)) {
            
$this->port = (int) $port;
            
$this->scheme_normalization();
            return 
true;
        }

        
$this->port null;
        return 
false;
    }

    
/**
     * Set the ipath.
     *
     * @param string $ipath
     * @return bool
     */
    
protected function set_path($ipath) {
        static 
$cache;
        if (!
$cache) {
            
$cache = array();
        }

        
$ipath = (string) $ipath;

        if (isset(
$cache[$ipath])) {
            
$this->ipath $cache[$ipath][(int) ($this->scheme !== null)];
        }
        else {
            
$valid $this->replace_invalid_with_pct_encoding($ipath'!$&\'()*+,;=@:/');
            
$removed $this->remove_dot_segments($valid);

            
$cache[$ipath] = array($valid$removed);
            
$this->ipath = ($this->scheme !== null) ? $removed $valid;
        }
        
$this->scheme_normalization();
        return 
true;
    }

    
/**
     * Set the iquery.
     *
     * @param string $iquery
     * @return bool
     */
    
protected function set_query($iquery) {
        if (
$iquery === null) {
            
$this->iquery null;
        }
        else {
            
$this->iquery $this->replace_invalid_with_pct_encoding($iquery'!$&\'()*+,;=:@/?'true);
            
$this->scheme_normalization();
        }
        return 
true;
    }

    
/**
     * Set the ifragment.
     *
     * @param string $ifragment
     * @return bool
     */
    
protected function set_fragment($ifragment) {
        if (
$ifragment === null) {
            
$this->ifragment null;
        }
        else {
            
$this->ifragment $this->replace_invalid_with_pct_encoding($ifragment'!$&\'()*+,;=:@/?');
            
$this->scheme_normalization();
        }
        return 
true;
    }

    
/**
     * Convert an IRI to a URI (or parts thereof)
     *
     * @param string|bool IRI to convert (or false from {@see get_iri})
     * @return string|false URI if IRI is valid, false otherwise.
     */
    
protected function to_uri($string) {
        if (!
is_string($string)) {
            return 
false;
        }

        static 
$non_ascii;
        if (!
$non_ascii) {
            
$non_ascii implode(''range("\x80""\xFF"));
        }

        
$position 0;
        
$strlen strlen($string);
        while ((
$position += strcspn($string$non_ascii$position)) < $strlen) {
            
$string substr_replace($stringsprintf('%%%02X'ord($string[$position])), $position1);
            
$position += 3;
            
$strlen += 2;
        }

        return 
$string;
    }

    
/**
     * Get the complete IRI
     *
     * @return string|false
     */
    
protected function get_iri() {
        if (!
$this->is_valid()) {
            return 
false;
        }

        
$iri '';
        if (
$this->scheme !== null) {
            
$iri .= $this->scheme ':';
        }
        if ((
$iauthority $this->get_iauthority()) !== null) {
            
$iri .= '//' $iauthority;
        }
        
$iri .= $this->ipath;
        if (
$this->iquery !== null) {
            
$iri .= '?' $this->iquery;
        }
        if (
$this->ifragment !== null) {
            
$iri .= '#' $this->ifragment;
        }

        return 
$iri;
    }

    
/**
     * Get the complete URI
     *
     * @return string
     */
    
protected function get_uri() {
        return 
$this->to_uri($this->get_iri());
    }

    
/**
     * Get the complete iauthority
     *
     * @return string|null
     */
    
protected function get_iauthority() {
        if (
$this->iuserinfo === null && $this->ihost === null && $this->port === null) {
            return 
null;
        }

        
$iauthority '';
        if (
$this->iuserinfo !== null) {
            
$iauthority .= $this->iuserinfo '@';
        }
        if (
$this->ihost !== null) {
            
$iauthority .= $this->ihost;
        }
        if (
$this->port !== null) {
            
$iauthority .= ':' $this->port;
        }
        return 
$iauthority;
    }

    
/**
     * Get the complete authority
     *
     * @return string
     */
    
protected function get_authority() {
        
$iauthority $this->get_iauthority();
        if (
is_string($iauthority)) {
            return 
$this->to_uri($iauthority);
        }
        else {
            return 
$iauthority;
        }
    }
}
<?
php
/**
 * Case-insensitive dictionary, suitable for HTTP headers
 *
 * @package Requests
 */

/**
 * Case-insensitive dictionary, suitable for HTTP headers
 *
 * @package Requests
 */
class Requests_Response_Headers extends Requests_Utility_CaseInsensitiveDictionary {
    
/**
     * Get the given header
     *
     * Unlike {@see self::getValues()}, this returns a string. If there are
     * multiple values, it concatenates them with a comma as per RFC2616.
     *
     * Avoid using this where commas may be used unquoted in values, such as
     * Set-Cookie headers.
     *
     * @param string $key
     * @return string|null Header value
     */
    
public function offsetGet($key) {
        
$key strtolower($key);
        if (!isset(
$this->data[$key])) {
            return 
null;
        }

        return 
$this->flatten($this->data[$key]);
    }

    
/**
     * Set the given item
     *
     * @throws Requests_Exception On attempting to use dictionary as list (`invalidset`)
     *
     * @param string $key Item name
     * @param string $value Item value
     */
    
public function offsetSet($key$value) {
        if (
$key === null) {
            throw new 
Requests_Exception('Object is a dictionary, not a list''invalidset');
        }

        
$key strtolower($key);

        if (!isset(
$this->data[$key])) {
            
$this->data[$key] = array();
        }

        
$this->data[$key][] = $value;
    }

    
/**
     * Get all values for a given header
     *
     * @param string $key
     * @return array|null Header values
     */
    
public function getValues($key) {
        
$key strtolower($key);
        if (!isset(
$this->data[$key])) {
            return 
null;
        }

        return 
$this->data[$key];
    }

    
/**
     * Flattens a value into a string
     *
     * Converts an array into a string by imploding values with a comma, as per
     * RFC2616's rules for folding headers.
     *
     * @param string|array $value Value to flatten
     * @return string Flattened value
     */
    
public function flatten($value) {
        if (
is_array($value)) {
            
$value implode(','$value);
        }

        return 
$value;
    }

    
/**
     * Get an iterator for the data
     *
     * Converts the internal
     * @return ArrayIterator
     */
    
public function getIterator() {
        return new 
Requests_Utility_FilteredIterator($this->data, array($this'flatten'));
    }
}
<?
php
/**
 * Base HTTP transport
 *
 * @package Requests
 * @subpackage Transport
 */

/**
 * Base HTTP transport
 *
 * @package Requests
 * @subpackage Transport
 */
interface Requests_Transport {
    
/**
     * Perform a request
     *
     * @param string $url URL to request
     * @param array $headers Associative array of request headers
     * @param string|array $data Data to send either as the POST body, or as parameters in the URL for a GET/HEAD
     * @param array $options Request options, see {@see Requests::response()} for documentation
     * @return string Raw HTTP result
     */
    
public function request($url$headers = array(), $data = array(), $options = array());

    
/**
     * Send multiple requests simultaneously
     *
     * @param array $requests Request data (array of 'url', 'headers', 'data', 'options') as per {@see Requests_Transport::request}
     * @param array $options Global options, see {@see Requests::response()} for documentation
     * @return array Array of Requests_Response objects (may contain Requests_Exception or string responses as well)
     */
    
public function request_multiple($requests$options);

    
/**
     * Self-test whether the transport can be used
     * @return bool
     */
    
public static function test();
}
<?
php
/**
 * Requests for PHP
 *
 * Inspired by Requests for Python.
 *
 * Based on concepts from SimplePie_File, RequestCore and WP_Http.
 *
 * @package Requests
 */

/**
 * Requests for PHP
 *
 * Inspired by Requests for Python.
 *
 * Based on concepts from SimplePie_File, RequestCore and WP_Http.
 *
 * @package Requests
 */
class Requests {
    
/**
     * POST method
     *
     * @var string
     */
    
const POST 'POST';

    
/**
     * PUT method
     *
     * @var string
     */
    
const PUT 'PUT';

    
/**
     * GET method
     *
     * @var string
     */
    
const GET 'GET';

    
/**
     * HEAD method
     *
     * @var string
     */
    
const HEAD 'HEAD';

    
/**
     * DELETE method
     *
     * @var string
     */
    
const DELETE 'DELETE';

    
/**
     * OPTIONS method
     *
     * @var string
     */
    
const OPTIONS 'OPTIONS';

    
/**
     * TRACE method
     *
     * @var string
     */
    
const TRACE 'TRACE';

    
/**
     * PATCH method
     *
     * @link https://tools.ietf.org/html/rfc5789
     * @var string
     */
    
const PATCH 'PATCH';

    
/**
     * Default size of buffer size to read streams
     *
     * @var integer
     */
    
const BUFFER_SIZE 1160;

    
/**
     * Current version of Requests
     *
     * @var string
     */
    
const VERSION '1.8.1';

    
/**
     * Registered transport classes
     *
     * @var array
     */
    
protected static $transports = array();

    
/**
     * Selected transport name
     *
     * Use {@see get_transport()} instead
     *
     * @var array
     */
    
public static $transport = array();

    
/**
     * Default certificate path.
     *
     * @see Requests::get_certificate_path()
     * @see Requests::set_certificate_path()
     *
     * @var string
     */
    
protected static $certificate_path;

    
/**
     * This is a static class, do not instantiate it
     *
     * @codeCoverageIgnore
     */
    
private function __construct() {}

    
/**
     * Autoloader for Requests
     *
     * Register this with {@see register_autoloader()} if you'd like to avoid
     * having to create your own.
     *
     * (You can also use `spl_autoload_register` directly if you'd prefer.)
     *
     * @codeCoverageIgnore
     *
     * @param string $class Class name to load
     */
    
public static function autoloader($class) {
        
// Check that the class starts with "Requests"
        
if (strpos($class'Requests') !== 0) {
            return;
        }

        
$file str_replace('_''/'$class);
        if (
file_exists(dirname(__FILE__) . '/' $file '.php')) {
            require_once 
dirname(__FILE__) . '/' $file '.php';
        }
    }

    
/**
     * Register the built-in autoloader
     *
     * @codeCoverageIgnore
     */
    
public static function register_autoloader() {
        
spl_autoload_register(array('Requests''autoloader'));
    }

    
/**
     * Register a transport
     *
     * @param string $transport Transport class to add, must support the Requests_Transport interface
     */
    
public static function add_transport($transport) {
        if (empty(
self::$transports)) {
            
self::$transports = array(
                
'Requests_Transport_cURL',
                
'Requests_Transport_fsockopen',
            );
        }

        
self::$transports array_merge(self::$transports, array($transport));
    }

    
/**
     * Get a working transport
     *
     * @throws Requests_Exception If no valid transport is found (`notransport`)
     * @return Requests_Transport
     */
    
protected static function get_transport($capabilities = array()) {
        
// Caching code, don't bother testing coverage
        // @codeCoverageIgnoreStart
        // array of capabilities as a string to be used as an array key
        
ksort($capabilities);
        
$cap_string serialize($capabilities);

        
// Don't search for a transport if it's already been done for these $capabilities
        
if (isset(self::$transport[$cap_string]) && self::$transport[$cap_string] !== null) {
            
$class self::$transport[$cap_string];
            return new 
$class();
        }
        
// @codeCoverageIgnoreEnd

        
if (empty(self::$transports)) {
            
self::$transports = array(
                
'Requests_Transport_cURL',
                
'Requests_Transport_fsockopen',
            );
        }

        
// Find us a working transport
        
foreach (self::$transports as $class) {
            if (!
class_exists($class)) {
                continue;
            }

            
$result call_user_func(array($class'test'), $capabilities);
            if (
$result) {
                
self::$transport[$cap_string] = $class;
                break;
            }
        }
        if (
self::$transport[$cap_string] === null) {
            throw new 
Requests_Exception('No working transports found''notransport'self::$transports);
        }

        
$class self::$transport[$cap_string];
        return new 
$class();
    }

    
/**#@+
     * @see request()
     * @param string $url
     * @param array $headers
     * @param array $options
     * @return Requests_Response
     */
    /**
     * Send a GET request
     */
    
public static function get($url$headers = array(), $options = array()) {
        return 
self::request($url$headersnullself::GET$options);
    }

    
/**
     * Send a HEAD request
     */
    
public static function head($url$headers = array(), $options = array()) {
        return 
self::request($url$headersnullself::HEAD$options);
    }

    
/**
     * Send a DELETE request
     */
    
public static function delete($url$headers = array(), $options = array()) {
        return 
self::request($url$headersnullself::DELETE$options);
    }

    
/**
     * Send a TRACE request
     */
    
public static function trace($url$headers = array(), $options = array()) {
        return 
self::request($url$headersnullself::TRACE$options);
    }
    
/**#@-*/

    /**#@+
     * @see request()
     * @param string $url
     * @param array $headers
     * @param array $data
     * @param array $options
     * @return Requests_Response
     */
    /**
     * Send a POST request
     */
    
public static function post($url$headers = array(), $data = array(), $options = array()) {
        return 
self::request($url$headers$dataself::POST$options);
    }
    
/**
     * Send a PUT request
     */
    
public static function put($url$headers = array(), $data = array(), $options = array()) {
        return 
self::request($url$headers$dataself::PUT$options);
    }

    
/**
     * Send an OPTIONS request
     */
    
public static function options($url$headers = array(), $data = array(), $options = array()) {
        return 
self::request($url$headers$dataself::OPTIONS$options);
    }

    
/**
     * Send a PATCH request
     *
     * Note: Unlike {@see post} and {@see put}, `$headers` is required, as the
     * specification recommends that should send an ETag
     *
     * @link https://tools.ietf.org/html/rfc5789
     */
    
public static function patch($url$headers$data = array(), $options = array()) {
        return 
self::request($url$headers$dataself::PATCH$options);
    }
    
/**#@-*/

    /**
     * Main interface for HTTP requests
     *
     * This method initiates a request and sends it via a transport before
     * parsing.
     *
     * The `$options` parameter takes an associative array with the following
     * options:
     *
     * - `timeout`: How long should we wait for a response?
     *    Note: for cURL, a minimum of 1 second applies, as DNS resolution
     *    operates at second-resolution only.
     *    (float, seconds with a millisecond precision, default: 10, example: 0.01)
     * - `connect_timeout`: How long should we wait while trying to connect?
     *    (float, seconds with a millisecond precision, default: 10, example: 0.01)
     * - `useragent`: Useragent to send to the server
     *    (string, default: php-requests/$version)
     * - `follow_redirects`: Should we follow 3xx redirects?
     *    (boolean, default: true)
     * - `redirects`: How many times should we redirect before erroring?
     *    (integer, default: 10)
     * - `blocking`: Should we block processing on this request?
     *    (boolean, default: true)
     * - `filename`: File to stream the body to instead.
     *    (string|boolean, default: false)
     * - `auth`: Authentication handler or array of user/password details to use
     *    for Basic authentication
     *    (Requests_Auth|array|boolean, default: false)
     * - `proxy`: Proxy details to use for proxy by-passing and authentication
     *    (Requests_Proxy|array|string|boolean, default: false)
     * - `max_bytes`: Limit for the response body size.
     *    (integer|boolean, default: false)
     * - `idn`: Enable IDN parsing
     *    (boolean, default: true)
     * - `transport`: Custom transport. Either a class name, or a
     *    transport object. Defaults to the first working transport from
     *    {@see getTransport()}
     *    (string|Requests_Transport, default: {@see getTransport()})
     * - `hooks`: Hooks handler.
     *    (Requests_Hooker, default: new Requests_Hooks())
     * - `verify`: Should we verify SSL certificates? Allows passing in a custom
     *    certificate file as a string. (Using true uses the system-wide root
     *    certificate store instead, but this may have different behaviour
     *    across transports.)
     *    (string|boolean, default: library/Requests/Transport/cacert.pem)
     * - `verifyname`: Should we verify the common name in the SSL certificate?
     *    (boolean, default: true)
     * - `data_format`: How should we send the `$data` parameter?
     *    (string, one of 'query' or 'body', default: 'query' for
     *    HEAD/GET/DELETE, 'body' for POST/PUT/OPTIONS/PATCH)
     *
     * @throws Requests_Exception On invalid URLs (`nonhttp`)
     *
     * @param string $url URL to request
     * @param array $headers Extra headers to send with the request
     * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests
     * @param string $type HTTP request type (use Requests constants)
     * @param array $options Options for the request (see description for more information)
     * @return Requests_Response
     */
    
public static function request($url$headers = array(), $data = array(), $type self::GET$options = array()) {
        if (empty(
$options['type'])) {
            
$options['type'] = $type;
        }
        
$options array_merge(self::get_default_options(), $options);

        
self::set_defaults($url$headers$data$type$options);

        
$options['hooks']->dispatch('requests.before_request', array(&$url, &$headers, &$data, &$type, &$options));

        if (!empty(
$options['transport'])) {
            
$transport $options['transport'];

            if (
is_string($options['transport'])) {
                
$transport = new $transport();
            }
        }
        else {
            
$need_ssl     = (stripos($url'https://') === 0);
            
$capabilities = array('ssl' => $need_ssl);
            
$transport    self::get_transport($capabilities);
        }
        
$response $transport->request($url$headers$data$options);

        
$options['hooks']->dispatch('requests.before_parse', array(&$response$url$headers$data$type$options));

        return 
self::parse_response($response$url$headers$data$options);
    }

    
/**
     * Send multiple HTTP requests simultaneously
     *
     * The `$requests` parameter takes an associative or indexed array of
     * request fields. The key of each request can be used to match up the
     * request with the returned data, or with the request passed into your
     * `multiple.request.complete` callback.
     *
     * The request fields value is an associative array with the following keys:
     *
     * - `url`: Request URL Same as the `$url` parameter to
     *    {@see Requests::request}
     *    (string, required)
     * - `headers`: Associative array of header fields. Same as the `$headers`
     *    parameter to {@see Requests::request}
     *    (array, default: `array()`)
     * - `data`: Associative array of data fields or a string. Same as the
     *    `$data` parameter to {@see Requests::request}
     *    (array|string, default: `array()`)
     * - `type`: HTTP request type (use Requests constants). Same as the `$type`
     *    parameter to {@see Requests::request}
     *    (string, default: `Requests::GET`)
     * - `cookies`: Associative array of cookie name to value, or cookie jar.
     *    (array|Requests_Cookie_Jar)
     *
     * If the `$options` parameter is specified, individual requests will
     * inherit options from it. This can be used to use a single hooking system,
     * or set all the types to `Requests::POST`, for example.
     *
     * In addition, the `$options` parameter takes the following global options:
     *
     * - `complete`: A callback for when a request is complete. Takes two
     *    parameters, a Requests_Response/Requests_Exception reference, and the
     *    ID from the request array (Note: this can also be overridden on a
     *    per-request basis, although that's a little silly)
     *    (callback)
     *
     * @param array $requests Requests data (see description for more information)
     * @param array $options Global and default options (see {@see Requests::request})
     * @return array Responses (either Requests_Response or a Requests_Exception object)
     */
    
public static function request_multiple($requests$options = array()) {
        
$options array_merge(self::get_default_options(true), $options);

        if (!empty(
$options['hooks'])) {
            
$options['hooks']->register('transport.internal.parse_response', array('Requests''parse_multiple'));
            if (!empty(
$options['complete'])) {
                
$options['hooks']->register('multiple.request.complete'$options['complete']);
            }
        }

        foreach (
$requests as $id => &$request) {
            if (!isset(
$request['headers'])) {
                
$request['headers'] = array();
            }
            if (!isset(
$request['data'])) {
                
$request['data'] = array();
            }
            if (!isset(
$request['type'])) {
                
$request['type'] = self::GET;
            }
            if (!isset(
$request['options'])) {
                
$request['options']         = $options;
                
$request['options']['type'] = $request['type'];
            }
            else {
                if (empty(
$request['options']['type'])) {
                    
$request['options']['type'] = $request['type'];
                }
                
$request['options'] = array_merge($options$request['options']);
            }

            
self::set_defaults($request['url'], $request['headers'], $request['data'], $request['type'], $request['options']);

            
// Ensure we only hook in once
            
if ($request['options']['hooks'] !== $options['hooks']) {
                
$request['options']['hooks']->register('transport.internal.parse_response', array('Requests''parse_multiple'));
                if (!empty(
$request['options']['complete'])) {
                    
$request['options']['hooks']->register('multiple.request.complete'$request['options']['complete']);
                }
            }
        }
        unset(
$request);

        if (!empty(
$options['transport'])) {
            
$transport $options['transport'];

            if (
is_string($options['transport'])) {
                
$transport = new $transport();
            }
        }
        else {
            
$transport self::get_transport();
        }
        
$responses $transport->request_multiple($requests$options);

        foreach (
$responses as $id => &$response) {
            
// If our hook got messed with somehow, ensure we end up with the
            // correct response
            
if (is_string($response)) {
                
$request $requests[$id];
                
self::parse_multiple($response$request);
                
$request['options']['hooks']->dispatch('multiple.request.complete', array(&$response$id));
            }
        }

        return 
$responses;
    }

    
/**
     * Get the default options
     *
     * @see Requests::request() for values returned by this method
     * @param boolean $multirequest Is this a multirequest?
     * @return array Default option values
     */
    
protected static function get_default_options($multirequest false) {
        
$defaults = array(
            
'timeout'          => 10,
            
'connect_timeout'  => 10,
            
'useragent'        => 'php-requests/' self::VERSION,
            
'protocol_version' => 1.1,
            
'redirected'       => 0,
            
'redirects'        => 10,
            
'follow_redirects' => true,
            
'blocking'         => true,
            
'type'             => self::GET,
            
'filename'         => false,
            
'auth'             => false,
            
'proxy'            => false,
            
'cookies'          => false,
            
'max_bytes'        => false,
            
'idn'              => true,
            
'hooks'            => null,
            
'transport'        => null,
            
'verify'           => self::get_certificate_path(),
            
'verifyname'       => true,
        );
        if (
$multirequest !== false) {
            
$defaults['complete'] = null;
        }
        return 
$defaults;
    }

    
/**
     * Get default certificate path.
     *
     * @return string Default certificate path.
     */
    
public static function get_certificate_path() {
        if (!empty(
self::$certificate_path)) {
            return 
self::$certificate_path;
        }

        return 
dirname(__FILE__) . '/Requests/Transport/cacert.pem';
    }

    
/**
     * Set default certificate path.
     *
     * @param string $path Certificate path, pointing to a PEM file.
     */
    
public static function set_certificate_path($path) {
        
self::$certificate_path $path;
    }

    
/**
     * Set the default values
     *
     * @param string $url URL to request
     * @param array $headers Extra headers to send with the request
     * @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests
     * @param string $type HTTP request type
     * @param array $options Options for the request
     * @return array $options
     */
    
protected static function set_defaults(&$url, &$headers, &$data, &$type, &$options) {
        if (!
preg_match('/^http(s)?:\/\//i'$url$matches)) {
            throw new 
Requests_Exception('Only HTTP(S) requests are handled.''nonhttp'$url);
        }

        if (empty(
$options['hooks'])) {
            
$options['hooks'] = new Requests_Hooks();
        }

        if (
is_array($options['auth'])) {
            
$options['auth'] = new Requests_Auth_Basic($options['auth']);
        }
        if (
$options['auth'] !== false) {
            
$options['auth']->register($options['hooks']);
        }

        if (
is_string($options['proxy']) || is_array($options['proxy'])) {
            
$options['proxy'] = new Requests_Proxy_HTTP($options['proxy']);
        }
        if (
$options['proxy'] !== false) {
            
$options['proxy']->register($options['hooks']);
        }

        if (
is_array($options['cookies'])) {
            
$options['cookies'] = new Requests_Cookie_Jar($options['cookies']);
        }
        elseif (empty(
$options['cookies'])) {
            
$options['cookies'] = new Requests_Cookie_Jar();
        }
        if (
$options['cookies'] !== false) {
            
$options['cookies']->register($options['hooks']);
        }

        if (
$options['idn'] !== false) {
            
$iri       = new Requests_IRI($url);
            
$iri->host Requests_IDNAEncoder::encode($iri->ihost);
            
$url       $iri->uri;
        }

        
// Massage the type to ensure we support it.
        
$type strtoupper($type);

        if (!isset(
$options['data_format'])) {
            if (
in_array($type, array(self::HEADself::GETself::DELETE), true)) {
                
$options['data_format'] = 'query';
            }
            else {
                
$options['data_format'] = 'body';
            }
        }
    }

    
/**
     * HTTP response parser
     *
     * @throws Requests_Exception On missing head/body separator (`requests.no_crlf_separator`)
     * @throws Requests_Exception On missing head/body separator (`noversion`)
     * @throws Requests_Exception On missing head/body separator (`toomanyredirects`)
     *
     * @param string $headers Full response text including headers and body
     * @param string $url Original request URL
     * @param array $req_headers Original $headers array passed to {@link request()}, in case we need to follow redirects
     * @param array $req_data Original $data array passed to {@link request()}, in case we need to follow redirects
     * @param array $options Original $options array passed to {@link request()}, in case we need to follow redirects
     * @return Requests_Response
     */
    
protected static function parse_response($headers$url$req_headers$req_data$options) {
        
$return = new Requests_Response();
        if (!
$options['blocking']) {
            return 
$return;
        }

        
$return->raw  $headers;
        
$return->url  = (string) $url;
        
$return->body '';

        if (!
$options['filename']) {
            
$pos strpos($headers"\r\n\r\n");
            if (
$pos === false) {
                
// Crap!
                
throw new Requests_Exception('Missing header/body separator''requests.no_crlf_separator');
            }

            
$headers substr($return->raw0$pos);
            
// Headers will always be separated from the body by two new lines - `\n\r\n\r`.
            
$body substr($return->raw$pos 4);
            if (!empty(
$body)) {
                
$return->body $body;
            }
        }
        
// Pretend CRLF = LF for compatibility (RFC 2616, section 19.3)
        
$headers str_replace("\r\n""\n"$headers);
        
// Unfold headers (replace [CRLF] 1*( SP | HT ) with SP) as per RFC 2616 (section 2.2)
        
$headers preg_replace('/\n[ \t]/'' '$headers);
        
$headers explode("\n"$headers);
        
preg_match('#^HTTP/(1\.\d)[ \t]+(\d+)#i'array_shift($headers), $matches);
        if (empty(
$matches)) {
            throw new 
Requests_Exception('Response could not be parsed''noversion'$headers);
        }
        
$return->protocol_version = (float) $matches[1];
        
$return->status_code      = (int) $matches[2];
        if (
$return->status_code >= 200 && $return->status_code 300) {
            
$return->success true;
        }

        foreach (
$headers as $header) {
            list(
$key$value) = explode(':'$header2);
            
$value             trim($value);
            
preg_replace('#(\s+)#i'' '$value);
            
$return->headers[$key] = $value;
        }
        if (isset(
$return->headers['transfer-encoding'])) {
            
$return->body self::decode_chunked($return->body);
            unset(
$return->headers['transfer-encoding']);
        }
        if (isset(
$return->headers['content-encoding'])) {
            
$return->body self::decompress($return->body);
        }

        
//fsockopen and cURL compatibility
        
if (isset($return->headers['connection'])) {
            unset(
$return->headers['connection']);
        }

        
$options['hooks']->dispatch('requests.before_redirect_check', array(&$return$req_headers$req_data$options));

        if (
$return->is_redirect() && $options['follow_redirects'] === true) {
            if (isset(
$return->headers['location']) && $options['redirected'] < $options['redirects']) {
                if (
$return->status_code === 303) {
                    
$options['type'] = self::GET;
                }
                
$options['redirected']++;
                
$location $return->headers['location'];
                if (
strpos($location'http://') !== && strpos($location'https://') !== 0) {
                    
// relative redirect, for compatibility make it absolute
                    
$location Requests_IRI::absolutize($url$location);
                    
$location $location->uri;
                }

                
$hook_args = array(
                    &
$location,
                    &
$req_headers,
                    &
$req_data,
                    &
$options,
                    
$return,
                );
                
$options['hooks']->dispatch('requests.before_redirect'$hook_args);
                
$redirected            self::request($location$req_headers$req_data$options['type'], $options);
                
$redirected->history[] = $return;
                return 
$redirected;
            }
            elseif (
$options['redirected'] >= $options['redirects']) {
                throw new 
Requests_Exception('Too many redirects''toomanyredirects'$return);
            }
        }

        
$return->redirects $options['redirected'];

        
$options['hooks']->dispatch('requests.after_request', array(&$return$req_headers$req_data$options));
        return 
$return;
    }

    
/**
     * Callback for `transport.internal.parse_response`
     *
     * Internal use only. Converts a raw HTTP response to a Requests_Response
     * while still executing a multiple request.
     *
     * @param string $response Full response text including headers and body (will be overwritten with Response instance)
     * @param array $request Request data as passed into {@see Requests::request_multiple()}
     * @return null `$response` is either set to a Requests_Response instance, or a Requests_Exception object
     */
    
public static function parse_multiple(&$response$request) {
        try {
            
$url      $request['url'];
            
$headers  $request['headers'];
            
$data     $request['data'];
            
$options  $request['options'];
            
$response self::parse_response($response$url$headers$data$options);
        }
        catch (
Requests_Exception $e) {
            
$response $e;
        }
    }

    
/**
     * Decoded a chunked body as per RFC 2616
     *
     * @see https://tools.ietf.org/html/rfc2616#section-3.6.1
     * @param string $data Chunked body
     * @return string Decoded body
     */
    
protected static function decode_chunked($data) {
        if (!
preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i'trim($data))) {
            return 
$data;
        }

        
$decoded '';
        
$encoded $data;

        while (
true) {
            
$is_chunked = (bool) preg_match('/^([0-9a-f]+)(?:;(?:[\w-]*)(?:=(?:(?:[\w-]*)*|"(?:[^\r\n])*"))?)*\r\n/i'$encoded$matches);
            if (!
$is_chunked) {
                
// Looks like it's not chunked after all
                
return $data;
            }

            
$length hexdec(trim($matches[1]));
            if (
$length === 0) {
                
// Ignore trailer headers
                
return $decoded;
            }

            
$chunk_length strlen($matches[0]);
            
$decoded     .= substr($encoded$chunk_length$length);
            
$encoded      substr($encoded$chunk_length $length 2);

            if (
trim($encoded) === '0' || empty($encoded)) {
                return 
$decoded;
            }
        }

        
// We'll never actually get down here
        // @codeCoverageIgnoreStart
    
}
    
// @codeCoverageIgnoreEnd

    /**
     * Convert a key => value array to a 'key: value' array for headers
     *
     * @param array $array Dictionary of header values
     * @return array List of headers
     */
    
public static function flatten($array) {
        
$return = array();
        foreach (
$array as $key => $value) {
            
$return[] = sprintf('%s: %s'$key$value);
        }
        return 
$return;
    }

    
/**
     * Convert a key => value array to a 'key: value' array for headers
     *
     * @codeCoverageIgnore
     * @deprecated Misspelling of {@see Requests::flatten}
     * @param array $array Dictionary of header values
     * @return array List of headers
     */
    
public static function flattern($array) {
        return 
self::flatten($array);
    }

    
/**
     * Decompress an encoded body
     *
     * Implements gzip, compress and deflate. Guesses which it is by attempting
     * to decode.
     *
     * @param string $data Compressed data in one of the above formats
     * @return string Decompressed string
     */
    
public static function decompress($data) {
        if (
substr($data02) !== "\x1f\x8b" && substr($data02) !== "\x78\x9c") {
            
// Not actually compressed. Probably cURL ruining this for us.
            
return $data;
        }

        if (
function_exists('gzdecode')) {
            
// phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.gzdecodeFound -- Wrapped in function_exists() for PHP 5.2.
            
$decoded = @gzdecode($data);
            if (
$decoded !== false) {
                return 
$decoded;
            }
        }

        if (
function_exists('gzinflate')) {
            
$decoded = @gzinflate($data);
            if (
$decoded !== false) {
                return 
$decoded;
            }
        }

        
$decoded self::compatible_gzinflate($data);
        if (
$decoded !== false) {
            return 
$decoded;
        }

        if (
function_exists('gzuncompress')) {
            
$decoded = @gzuncompress($data);
            if (
$decoded !== false) {
                return 
$decoded;
            }
        }

        return 
$data;
    }

    
/**
     * Decompression of deflated string while staying compatible with the majority of servers.
     *
     * Certain Servers will return deflated data with headers which PHP's gzinflate()
     * function cannot handle out of the box. The following function has been created from
     * various snippets on the gzinflate() PHP documentation.
     *
     * Warning: Magic numbers within. Due to the potential different formats that the compressed
     * data may be returned in, some "magic offsets" are needed to ensure proper decompression
     * takes place. For a simple progmatic way to determine the magic offset in use, see:
     * https://core.trac.wordpress.org/ticket/18273
     *
     * @since 2.8.1
     * @link https://core.trac.wordpress.org/ticket/18273
     * @link https://secure.php.net/manual/en/function.gzinflate.php#70875
     * @link https://secure.php.net/manual/en/function.gzinflate.php#77336
     *
     * @param string $gz_data String to decompress.
     * @return string|bool False on failure.
     */
    
public static function compatible_gzinflate($gz_data) {
        
// Compressed data might contain a full zlib header, if so strip it for
        // gzinflate()
        
if (substr($gz_data03) === "\x1f\x8b\x08") {
            
$i   10;
            
$flg ord(substr($gz_data31));
            if (
$flg 0) {
                if (
$flg 4) {
                    list(
$xlen) = unpack('v'substr($gz_data$i2));
                    
$i         += $xlen;
                }
                if (
$flg 8) {
                    
$i strpos($gz_data"\0"$i) + 1;
                }
                if (
$flg 16) {
                    
$i strpos($gz_data"\0"$i) + 1;
                }
                if (
$flg 2) {
                    
$i += 2;
                }
            }
            
$decompressed self::compatible_gzinflate(substr($gz_data$i));
            if (
$decompressed !== false) {
                return 
$decompressed;
            }
        }

        
// If the data is Huffman Encoded, we must first strip the leading 2
        // byte Huffman marker for gzinflate()
        // The response is Huffman coded by many compressors such as
        // java.util.zip.Deflater, Ruby’s Zlib::Deflate, and .NET's
        // System.IO.Compression.DeflateStream.
        //
        // See https://decompres.blogspot.com/ for a quick explanation of this
        // data type
        
$huffman_encoded false;

        
// low nibble of first byte should be 0x08
        
list(, $first_nibble) = unpack('h'$gz_data);

        
// First 2 bytes should be divisible by 0x1F
        
list(, $first_two_bytes) = unpack('n'$gz_data);

        if (
$first_nibble === 0x08 && ($first_two_bytes 0x1F) === 0) {
            
$huffman_encoded true;
        }

        if (
$huffman_encoded) {
            
$decompressed = @gzinflate(substr($gz_data2));
            if (
$decompressed !== false) {
                return 
$decompressed;
            }
        }

        if (
substr($gz_data04) === "\x50\x4b\x03\x04") {
            
// ZIP file format header
            // Offset 6: 2 bytes, General-purpose field
            // Offset 26: 2 bytes, filename length
            // Offset 28: 2 bytes, optional field length
            // Offset 30: Filename field, followed by optional field, followed
            // immediately by data
            
list(, $general_purpose_flag) = unpack('v'substr($gz_data62));

            
// If the file has been compressed on the fly, 0x08 bit is set of
            // the general purpose field. We can use this to differentiate
            // between a compressed document, and a ZIP file
            
$zip_compressed_on_the_fly = ((0x08 $general_purpose_flag) === 0x08);

            if (!
$zip_compressed_on_the_fly) {
                
// Don't attempt to decode a compressed zip file
                
return $gz_data;
            }

            
// Determine the first byte of data, based on the above ZIP header
            // offsets:
            
$first_file_start array_sum(unpack('v2'substr($gz_data264)));
            
$decompressed     = @gzinflate(substr($gz_data30 $first_file_start));
            if (
$decompressed !== false) {
                return 
$decompressed;
            }
            return 
false;
        }

        
// Finally fall back to straight gzinflate
        
$decompressed = @gzinflate($gz_data);
        if (
$decompressed !== false) {
            return 
$decompressed;
        }

        
// Fallback for all above failing, not expected, but included for
        // debugging and preventing regressions and to track stats
        
$decompressed = @gzinflate(substr($gz_data2));
        if (
$decompressed !== false) {
            return 
$decompressed;
        }

        return 
false;
    }

    public static function 
match_domain($host$reference) {
        
// Check for a direct match
        
if ($host === $reference) {
            return 
true;
        }

        
// Calculate the valid wildcard match if the host is not an IP address
        // Also validates that the host has 3 parts or more, as per Firefox's
        // ruleset.
        
$parts explode('.'$host);
        if (
ip2long($host) === false && count($parts) >= 3) {
            
$parts[0] = '*';
            
$wildcard implode('.'$parts);
            if (
$wildcard === $reference) {
                return 
true;
            }
        }

        return 
false;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 * @see    https://www.php-fig.org/psr/psr-0/
 * @see    https://www.php-fig.org/psr/psr-4/
 */
class ClassLoader
{
    
/** @var ?string */
    
private $vendorDir;

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

    
// PSR-0
    /**
     * @var array[]
     * @psalm-var array<string, array<string, string[]>>
     */
    
private $prefixesPsr0 = array();
    
/**
     * @var array[]
     * @psalm-var array<string, string>
     */
    
private $fallbackDirsPsr0 = array();

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

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

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

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

    
/** @var ?string */
    
private $apcuPrefix;

    
/**
     * @var self[]
     */
    
private static $registeredLoaders = array();

    
/**
     * @param ?string $vendorDir
     */
    
public function __construct($vendorDir null)
    {
        
$this->vendorDir $vendorDir;
    }

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

        return array();
    }

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

    
/**
     * @return array[]
     * @psalm-return array<string, string>
     */
    
public function getFallbackDirs()
    {
        return 
$this->fallbackDirsPsr0;
    }

    
/**
     * @return array[]
     * @psalm-return array<string, string>
     */
    
public function getFallbackDirsPsr4()
    {
        return 
$this->fallbackDirsPsr4;
    }

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

    
/**
     * @param string[] $classMap Class to filename map
     * @psalm-param array<string, string> $classMap
     *
     * @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 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)
    {
        if (!
$prefix) {
            if (
$prepend) {
                
$this->fallbackDirsPsr0 array_merge(
                    (array) 
$paths,
                    
$this->fallbackDirsPsr0
                
);
            } else {
                
$this->fallbackDirsPsr0 array_merge(
                    
$this->fallbackDirsPsr0,
                    (array) 
$paths
                
);
            }

            return;
        }

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

            return;
        }
        if (
$prepend) {
            
$this->prefixesPsr0[$first][$prefix] = array_merge(
                (array) 
$paths,
                
$this->prefixesPsr0[$first][$prefix]
            );
        } else {
            
$this->prefixesPsr0[$first][$prefix] = array_merge(
                
$this->prefixesPsr0[$first][$prefix],
                (array) 
$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 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)
    {
        if (!
$prefix) {
            
// Register directories for the root namespace.
            
if ($prepend) {
                
$this->fallbackDirsPsr4 array_merge(
                    (array) 
$paths,
                    
$this->fallbackDirsPsr4
                
);
            } else {
                
$this->fallbackDirsPsr4 array_merge(
                    
$this->fallbackDirsPsr4,
                    (array) 
$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] = (array) $paths;
        } elseif (
$prepend) {
            
// Prepend directories for an already registered namespace.
            
$this->prefixDirsPsr4[$prefix] = array_merge(
                (array) 
$paths,
                
$this->prefixDirsPsr4[$prefix]
            );
        } else {
            
// Append directories for an already registered namespace.
            
$this->prefixDirsPsr4[$prefix] = array_merge(
                
$this->prefixDirsPsr4[$prefix],
                (array) 
$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 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 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($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 indexed by their corresponding vendor directories.
     *
     * @return 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($subPath0$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($logicalPathPsr40$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 (
=== 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;
    }
}

/**
 * Scope isolated include.
 *
 * Prevents access to $this/self from included files.
 *
 * @param  string $file
 * @return void
 * @private
 */
function includeFile($file)
{
    include 
$file;
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 * @see    https://www.php-fig.org/psr/psr-0/
 * @see    https://www.php-fig.org/psr/psr-4/
 */
class ClassLoader
{
    
/** @var ?string */
    
private $vendorDir;

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

    
// PSR-0
    /**
     * @var array[]
     * @psalm-var array<string, array<string, string[]>>
     */
    
private $prefixesPsr0 = array();
    
/**
     * @var array[]
     * @psalm-var array<string, string>
     */
    
private $fallbackDirsPsr0 = array();

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

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

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

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

    
/** @var ?string */
    
private $apcuPrefix;

    
/**
     * @var self[]
     */
    
private static $registeredLoaders = array();

    
/**
     * @param ?string $vendorDir
     */
    
public function __construct($vendorDir null)
    {
        
$this->vendorDir $vendorDir;
    }

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

        return array();
    }

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

    
/**
     * @return array[]
     * @psalm-return array<string, string>
     */
    
public function getFallbackDirs()
    {
        return 
$this->fallbackDirsPsr0;
    }

    
/**
     * @return array[]
     * @psalm-return array<string, string>
     */
    
public function getFallbackDirsPsr4()
    {
        return 
$this->fallbackDirsPsr4;
    }

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

    
/**
     * @param string[] $classMap Class to filename map
     * @psalm-param array<string, string> $classMap
     *
     * @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 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)
    {
        if (!
$prefix) {
            if (
$prepend) {
                
$this->fallbackDirsPsr0 array_merge(
                    (array) 
$paths,
                    
$this->fallbackDirsPsr0
                
);
            } else {
                
$this->fallbackDirsPsr0 array_merge(
                    
$this->fallbackDirsPsr0,
                    (array) 
$paths
                
);
            }

            return;
        }

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

            return;
        }
        if (
$prepend) {
            
$this->prefixesPsr0[$first][$prefix] = array_merge(
                (array) 
$paths,
                
$this->prefixesPsr0[$first][$prefix]
            );
        } else {
            
$this->prefixesPsr0[$first][$prefix] = array_merge(
                
$this->prefixesPsr0[$first][$prefix],
                (array) 
$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 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)
    {
        if (!
$prefix) {
            
// Register directories for the root namespace.
            
if ($prepend) {
                
$this->fallbackDirsPsr4 array_merge(
                    (array) 
$paths,
                    
$this->fallbackDirsPsr4
                
);
            } else {
                
$this->fallbackDirsPsr4 array_merge(
                    
$this->fallbackDirsPsr4,
                    (array) 
$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] = (array) $paths;
        } elseif (
$prepend) {
            
// Prepend directories for an already registered namespace.
            
$this->prefixDirsPsr4[$prefix] = array_merge(
                (array) 
$paths,
                
$this->prefixDirsPsr4[$prefix]
            );
        } else {
            
// Append directories for an already registered namespace.
            
$this->prefixDirsPsr4[$prefix] = array_merge(
                
$this->prefixDirsPsr4[$prefix],
                (array) 
$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 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 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($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 indexed by their corresponding vendor directories.
     *
     * @return 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($subPath0$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($logicalPathPsr40$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 (
=== 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;
    }
}

/**
 * Scope isolated include.
 *
 * Prevents access to $this/self from included files.
 *
 * @param  string $file
 * @return void
 * @private
 */
function includeFile($file)
{
    include 
$file;
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Autoload;

use 
Composer\Pcre\Preg;

/**
 * @author Jordi Boggiano <[email protected]>
 * @internal
 */
class PhpFileCleaner
{
    
/** @var array<array{name: string, length: int, pattern: string}> */
    
private static $typeConfig;

    
/** @var 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
     * @return void
     */
    
public static function setTypeConfig($types)
    {
        foreach (
$types as $type) {
            
self::$typeConfig[$type[0]] = array(
                
'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';
    }

    
/**
     * @param string $contents
     * @param int $maxMatches
     */
    
public function __construct($contents$maxMatches)
    {
        
$this->contents $contents;
        
$this->len \strlen($this->contents);
        
$this->maxMatches $maxMatches;
    }

    
/**
     * @return string
     */
    
public function clean()
    {
        
$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 === && isset(self::$typeConfig[$char])) {
                    
$type self::$typeConfig[$char];
                    if (
                        
\substr($this->contents$this->index$type['length']) === $type['name']
                        && 
Preg::isMatch($type['pattern'], $this->contents$match0$this->index 1)
                    ) {
                        
$clean .= $match[0];

                        return 
$clean;
                    }
                }

                
$this->index += 1;
                if (
$this->match(self::$restPattern$match)) {
                    
$clean .= $char $match[0];
                    
$this->index += \strlen($match[0]);
                } else {
                    
$clean .= $char;
                }
            }
        }

        return 
$clean;
    }

    
/**
     * @return void
     */
    
private function skipToPhp()
    {
        while (
$this->index $this->len) {
            if (
$this->contents[$this->index] === '<' && $this->peek('?')) {
                
$this->index += 2;
                break;
            }

            
$this->index += 1;
        }
    }

    
/**
     * @param string $delimiter
     * @return void
     */
    
private function skipString($delimiter)
    {
        
$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;
        }
    }

    
/**
     * @return void
     */
    
private function skipComment()
    {
        
$this->index += 2;
        while (
$this->index $this->len) {
            if (
$this->contents[$this->index] === '*' && $this->peek('/')) {
                
$this->index += 2;
                break;
            }

            
$this->index += 1;
        }
    }

    
/**
     * @return void
     */
    
private function skipToNewline()
    {
        while (
$this->index $this->len) {
            if (
$this->contents[$this->index] === "\r" || $this->contents[$this->index] === "\n") {
                return;
            }
            
$this->index += 1;
        }
    }

    
/**
     * @param string $delimiter
     * @return void
     */
    
private function skipHeredoc($delimiter)
    {
        
$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;
            }
        }
    }

    
/**
     * @param string $char
     * @return bool
     */
    
private function peek($char)
    {
        return 
$this->index $this->len && $this->contents[$this->index 1] === $char;
    }

    
/**
     * @param string $regex
     * @param ?array<int, string> $match
     * @return bool
     */
    
private function match($regex, array &$match null)
    {
        return 
Preg::isMatch($regex$this->contents$match0$this->index);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Autoload;

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\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;

/**
 * @author Igor Wiedler <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 */
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 $runScripts false;

    
/**
     * @var PlatformRequirementFilterInterface
     */
    
private $platformRequirementFilter;

    public function 
__construct(EventDispatcher $eventDispatcherIOInterface $io null)
    {
        
$this->eventDispatcher $eventDispatcher;
        
$this->io $io;

        
$this->platformRequirementFilter PlatformRequirementFilterFactory::ignoreNothing();
    }

    
/**
     * @param bool $devMode
     * @return void
     */
    
public function setDevMode($devMode true)
    {
        
$this->devMode = (bool) $devMode;
    }

    
/**
     * Whether generated autoloader considers the class map authoritative.
     *
     * @param bool $classMapAuthoritative
     * @return void
     */
    
public function setClassMapAuthoritative($classMapAuthoritative)
    {
        
$this->classMapAuthoritative = (bool) $classMapAuthoritative;
    }

    
/**
     * Whether generated autoloader considers APCu caching.
     *
     * @param bool        $apcu
     * @param string|null $apcuPrefix
     * @return void
     */
    
public function setApcu($apcu$apcuPrefix null)
    {
        
$this->apcu = (bool) $apcu;
        
$this->apcuPrefix $apcuPrefix !== null ? (string) $apcuPrefix $apcuPrefix;
    }

    
/**
     * Whether to run scripts or not
     *
     * @param bool $runScripts
     * @return void
     */
    
public function setRunScripts($runScripts true)
    {
        
$this->runScripts = (bool) $runScripts;
    }

    
/**
     * 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;
    }

    
/**
     * @param string $targetDir
     * @param bool $scanPsrPackages
     * @param string $suffix
     * @return int
     * @throws \Seld\JsonLint\ParsingException
     */
    
public function dump(Config $configInstalledRepositoryInterface $localRepoRootPackageInterface $rootPackageInstallationManager $installationManager$targetDir$scanPsrPackages false$suffix '')
    {
        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, array(), array(
                
'optimize' => (bool) $scanPsrPackages,
            ));
        }

        
$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(getcwd())));
        
$vendorPath $filesystem->normalizePath(realpath(realpath($config->get('vendor-dir'))));
        
$useGlobalIncludePath = (bool) $config->get('use-include-path');
        
$prependAutoloader $config->get('prepend-autoloader') === false 'false' 'true';
        
$targetDir $vendorPath.'/'.$targetDir;
        
$filesystem->ensureDirectoryExists($targetDir);

        
$vendorPathCode $filesystem->findShortestPathCode(realpath($targetDir), $vendorPathtrue);
        
$vendorPathCode52 str_replace('__DIR__''dirname(__FILE__)'$vendorPathCode);
        
$vendorPathToTargetDirCode $filesystem->findShortestPathCode($vendorPathrealpath($targetDir), true);

        
$appBaseDirCode $filesystem->findShortestPathCode($vendorPath$basePathtrue);
        
$appBaseDirCode str_replace('__DIR__''$vendorDir'$appBaseDirCode);

        
$namespacesFile = <<<EOF
<?php

// autoload_namespaces.php @generated by Composer

\$vendorDir = 
$vendorPathCode52;
\$baseDir = 
$appBaseDirCode;

return array(

EOF;

        
$psr4File = <<<EOF
<?php

// autoload_psr4.php @generated by Composer

\$vendorDir = 
$vendorPathCode52;
\$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 = array();
            foreach (
$paths as $path) {
                
$exportedPaths[] = $this->getPathCode($filesystem$basePath$vendorPath$path);
            }
            
$exportedPrefix var_export($namespacetrue);
            
$namespacesFile .= "    $exportedPrefix => ";
            
$namespacesFile .= "array(".implode(', '$exportedPaths)."),\n";
        }
        
$namespacesFile .= ");\n";

        
// Process the 'psr-4' base directories.
        
foreach ($autoloads['psr-4'] as $namespace => $paths) {
            
$exportedPaths = array();
            foreach (
$paths as $path) {
                
$exportedPaths[] = $this->getPathCode($filesystem$basePath$vendorPath$path);
            }
            
$exportedPrefix var_export($namespacetrue);
            
$psr4File .= "    $exportedPrefix => ";
            
$psr4File .= "array(".implode(', '$exportedPaths)."),\n";
        }
        
$psr4File .= ");\n";

        
$classmapFile = <<<EOF
<?php

// autoload_classmap.php @generated by Composer

\$vendorDir = 
$vendorPathCode52;
\$baseDir = 
$appBaseDirCode;

return array(

EOF;

        
// 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(function ($prefix) {
                return 
var_export($prefixtrue);
            }, 
array_keys($mainAutoload['psr-0'])));
            
$baseDirFromTargetDirCode $filesystem->findShortestPathCode($targetDir$basePathtrue);

            
$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 null;
        if (!empty(
$autoloads['exclude-from-classmap'])) {
            
$excluded $autoloads['exclude-from-classmap'];
        }

        
$classMap = array();
        
$ambiguousClasses = array();
        
$scannedFiles = array();
        foreach (
$autoloads['classmap'] as $dir) {
            
$classMap $this->addClassMapCode($filesystem$basePath$vendorPath$dir$excludednullnull$classMap$ambiguousClasses$scannedFiles);
        }

        if (
$scanPsrPackages) {
            
$namespacesToScan = array();

            
// Scan the PSR-0/4 directories for class files, and add them to the class map
            
foreach (array('psr-4''psr-0') as $psrType) {
                foreach (
$autoloads[$psrType] as $namespace => $paths) {
                    
$namespacesToScan[$namespace][] = array('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;
                        }

                        
$classMap $this->addClassMapCode($filesystem$basePath$vendorPath$dir$excluded$namespace$group['type'], $classMap$ambiguousClasses$scannedFiles);
                    }
                }
            }
        }

        foreach (
$ambiguousClasses as $className => $ambiguousPaths) {
            
$cleanPath str_replace(array('$vendorDir . \'''$baseDir . \''"',\n"), array($vendorPath$basePath''), $classMap[$className]);

            
$this->io->writeError(
                
'<warning>Warning: Ambiguous class resolution, "'.$className.'"'.
                
' was found '. (count($ambiguousPaths) + 1) .'x: in "'.$cleanPath.'" and "'implode('", "'$ambiguousPaths) .'", the first will be used.</warning>'
            
);
        }

        
$classMap['Composer\\InstalledVersions'] = "\$vendorDir . '/composer/InstalledVersions.php',\n";
        
ksort($classMap);
        foreach (
$classMap as $class => $code) {
            
$classmapFile .= '    '.var_export($classtrue).' => '.$code;
        }
        
$classmapFile .= ");\n";

        if (!
$suffix) {
            if (!
$config->get('autoloader-suffix') && Filesystem::isReadable($vendorPath.'/autoload.php')) {
                
$content file_get_contents($vendorPath.'/autoload.php');
                if (
Preg::isMatch('{ComposerAutoloaderInit([^:\s]+)::}'$content$match)) {
                    
$suffix $match[1];
                }
            }

            if (!
$suffix) {
                
$suffix $config->get('autoloader-suffix') ?: md5(uniqid(''true));
            }
        }

        
$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$vendorPathCode52$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$vendorPathCode52$appBaseDirCode)) {
            
$filesystem->filePutContentsIfModified($includeFilesFilePath$includeFilesFileContents);
        } elseif (
file_exists($includeFilesFilePath)) {
            
unlink($includeFilesFilePath);
        }
        
$filesystem->filePutContentsIfModified($targetDir.'/autoload_static.php'$this->getStaticFile($suffix$targetDir$vendorPath$basePath$staticPhpVersion));
        
$checkPlatform $config->get('platform-check') && !($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$staticPhpVersion$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, array(), array(
                
'optimize' => (bool) $scanPsrPackages,
            ));
        }

        return 
count($classMap);
    }

    
/**
     * @param string $basePath
     * @param string $vendorPath
     * @param string $dir
     * @param ?array<int, string> $excluded
     * @param ?string $namespaceFilter
     * @param ?string $autoloadType
     * @param array<class-string, string> $classMap
     * @param array<class-string, array<int, string>> $ambiguousClasses
     * @param array<string, true> $scannedFiles
     * @return array<class-string, string>
     */
    
private function addClassMapCode(Filesystem $filesystem$basePath$vendorPath$dir$excluded$namespaceFilter$autoloadType, array $classMap, array &$ambiguousClasses, array &$scannedFiles)
    {
        foreach (
$this->generateClassMap($dir$excluded$namespaceFilter$autoloadTypetrue$scannedFiles) as $class => $path) {
            
$pathCode $this->getPathCode($filesystem$basePath$vendorPath$path).",\n";
            if (!isset(
$classMap[$class])) {
                
$classMap[$class] = $pathCode;
            } elseif (
$this->io && $classMap[$class] !== $pathCode && !Preg::isMatch('{/(test|fixture|example|stub)s?/}i'strtr($classMap[$class].' '.$path'\\''/'))) {
                
$ambiguousClasses[$class][] = $path;
            }
        }

        return 
$classMap;
    }

    
/**
     * @param string $dir
     * @param ?array<int, string> $excluded
     * @param ?string $namespaceFilter
     * @param ?string $autoloadType
     * @param bool $showAmbiguousWarning
     * @param array<string, true> $scannedFiles
     * @return array<class-string, string>
     */
    
private function generateClassMap($dir$excluded$namespaceFilter$autoloadType$showAmbiguousWarning, array &$scannedFiles)
    {
        if (
$excluded) {
            
// 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), '\\''/'));
                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 (!== strpos($pattern$dirMatch) && !== strpos($dirMatch$pattern)) {
                        unset(
$excluded[$index]);
                    }
                }
            }

            
$excluded $excluded '{(' implode('|'$excluded) . ')}' null;
        }

        return 
ClassMapGenerator::createMap($dir$excluded$showAmbiguousWarning $this->io null$namespaceFilter$autoloadType$scannedFiles);
    }

    
/**
     * @param InstallationManager $installationManager
     * @param PackageInterface[] $packages
     * @return array<int, array{0: PackageInterface, 1: string}>
     */
    
public function buildPackageMap(InstallationManager $installationManagerPackageInterface $rootPackage, array $packages)
    {
        
// build package => install path map
        
$packageMap = array(array($rootPackage''));

        foreach (
$packages as $package) {
            if (
$package instanceof AliasPackage) {
                continue;
            }
            
$this->validatePackage($package);

            
$packageMap[] = array(
                
$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 array<int, array{0: PackageInterface, 1: string}> $packageMap array of array(package, installDir-relative-to-composer.json)
     * @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 $packageMapPackageInterface $rootPackage$filteredDevPackages false)
    {
        
$rootPackageMap array_shift($packageMap);
        if (
is_array($filteredDevPackages)) {
            
$packageMap array_filter($packageMap, function ($item) use ($filteredDevPackages) {
                return !
in_array($item[0]->getName(), $filteredDevPackagestrue);
            });
        } elseif (
$filteredDevPackages) {
            
$packageMap $this->filterPackageMap($packageMap$rootPackage);
        }
        
$sortedPackageMap $this->sortPackageMap($packageMap);
        
$sortedPackageMap[] = $rootPackageMap;
        
array_unshift($packageMap$rootPackageMap);

        
$psr0 $this->parseAutoloadsType($packageMap'psr-0'$rootPackage);
        
$psr4 $this->parseAutoloadsType($packageMap'psr-4'$rootPackage);
        
$classmap $this->parseAutoloadsType(array_reverse($sortedPackageMap), 'classmap'$rootPackage);
        
$files $this->parseAutoloadsType($sortedPackageMap'files'$rootPackage);
        
$exclude $this->parseAutoloadsType($sortedPackageMap'exclude-from-classmap'$rootPackage);

        
krsort($psr0);
        
krsort($psr4);

        return array(
            
'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
     * @param ?string $vendorDir
     * @return ClassLoader
     */
    
public function createLoader(array $autoloads$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 null;
            if (!empty(
$autoloads['exclude-from-classmap'])) {
                
$excluded $autoloads['exclude-from-classmap'];
            }

            
$scannedFiles = array();
            foreach (
$autoloads['classmap'] as $dir) {
                try {
                    
$loader->addClassMap($this->generateClassMap($dir$excludednullnullfalse$scannedFiles));
                } catch (
\RuntimeException $e) {
                    
$this->io->writeError('<warning>'.$e->getMessage().'</warning>');
                }
            }
        }

        return 
$loader;
    }

    
/**
     * @param array<int, array{0: PackageInterface, 1: string}> $packageMap
     * @param string $basePath
     * @param string $vendorPath
     * @param string $vendorPathCode
     * @param string $appBaseDirCode
     * @return ?string
     */
    
protected function getIncludePathsFile(array $packageMapFilesystem $filesystem$basePath$vendorPath$vendorPathCode$appBaseDirCode)
    {
        
$includePaths = array();

        foreach (
$packageMap as $item) {
            list(
$package$installPath) = $item;

            if (
null !== $package->getTargetDir() && strlen($package->getTargetDir()) > 0) {
                
$installPath substr($installPath0, -strlen('/'.$package->getTargetDir()));
            }

            foreach (
$package->getIncludePaths() as $includePath) {
                
$includePath trim($includePath'/');
                
$includePaths[] = empty($installPath) ? $includePath $installPath.'/'.$includePath;
            }
        }

        if (!
$includePaths) {
            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
     * @param string $basePath
     * @param string $vendorPath
     * @param string $vendorPathCode
     * @param string $appBaseDirCode
     * @return ?string
     */
    
protected function getIncludeFilesFile(array $filesFilesystem $filesystem$basePath$vendorPath$vendorPathCode$appBaseDirCode)
    {
        
$filesCode '';
        foreach (
$files as $fileIdentifier => $functionFile) {
            
$filesCode .= '    ' var_export($fileIdentifiertrue) . ' => '
                
$this->getPathCode($filesystem$basePath$vendorPath$functionFile) . ",\n";
        }

        if (!
$filesCode) {
            return 
null;
        }

        return <<<EOF
<?php

// autoload_files.php @generated by Composer

\$vendorDir = 
$vendorPathCode;
\$baseDir = 
$appBaseDirCode;

return array(
$filesCode);

EOF;
    }

    
/**
     * @param string $basePath
     * @param string $vendorPath
     * @param string $path
     * @return string
     */
    
protected function getPathCode(Filesystem $filesystem$basePath$vendorPath$path)
    {
        if (!
$filesystem->isAbsolutePath($path)) {
            
$path $basePath '/' $path;
        }
        
$path $filesystem->normalizePath($path);

        
$baseDir '';
        if (
strpos($path.'/'$vendorPath.'/') === 0) {
            
$path substr($pathstrlen($vendorPath));
            
$baseDir '$vendorDir';

            if (
$path !== false) {
                
$baseDir .= " . ";
            }
        } else {
            
$path $filesystem->normalizePath($filesystem->findShortestPath($basePath$pathtrue));
            if (!
$filesystem->isAbsolutePath($path)) {
                
$baseDir '$baseDir . ';
                
$path '/' $path;
            }
        }

        if (
strpos($path'.phar') !== false) {
            
$baseDir "'phar://' . " $baseDir;
        }

        return 
$baseDir var_export($pathtrue);
    }

    
/**
     * @param array<int, array{0: PackageInterface, 1: string}> $packageMap
     * @param bool $checkPlatform
     * @param string[] $devPackageNames
     * @return ?string
     */
    
protected function getPlatformCheck(array $packageMap$checkPlatform, array $devPackageNames)
    {
        
$lowestPhpVersion Bound::zero();
        
$requiredExtensions = array();
        
$extensionProviders = array();

        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(), $devPackageNamestrue)) {
                continue;
            }

            foreach (
$package->getRequires() as $link) {
                if (
$this->platformRequirementFilter->isIgnored($link->getTarget())) {
                    continue;
                }

                if (
'php' === $link->getTarget()) {
                    
$constraint $link->getConstraint();
                    if (
$constraint->getLowerBound()->compareTo($lowestPhpVersion'>')) {
                        
$lowestPhpVersion $constraint->getLowerBound();
                    }
                }

                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 = function (Bound $bound) {
            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 = function (Bound $bound) {
            if (
$bound->isZero()) {
                return 
0;
            }

            if (
$bound->isPositiveInfinity()) {
                return 
99999;
            }

            
$version str_replace('-''.'$bound->getVersion());
            
$chunks explode('.'$version);
            
$chunks array_slice($chunks03);

            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;
        }

        
$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;
        }
    }
    trigger_error(
        'Composer detected issues in your platform: ' . implode(' ', \$issues),
        E_USER_ERROR
    );
}

PLATFORM_CHECK;
    }

    
/**
     * @param  string $vendorPathToTargetDirCode
     * @param  string $suffix
     * @return string
     */
    
protected function getAutoloadFile($vendorPathToTargetDirCode$suffix)
    {
        
$lastChar $vendorPathToTargetDirCode[strlen($vendorPathToTargetDirCode) - 1];
        if (
"'" === $lastChar || '"' === $lastChar) {
            
$vendorPathToTargetDirCode substr($vendorPathToTargetDirCode0, -1).'/autoload_real.php'.$lastChar;
        } else {
            
$vendorPathToTargetDirCode .= " . '/autoload_real.php'";
        }

        return <<<AUTOLOAD
<?php

// autoload.php @generated by Composer

require_once 
$vendorPathToTargetDirCode;

return ComposerAutoloaderInit
$suffix::getLoader();

AUTOLOAD;
    }

    
/**
     * @param bool $useClassMap
     * @param bool $useIncludePath
     * @param ?string $targetDirLoader
     * @param bool $useIncludeFiles
     * @param string $vendorPathCode unused in this method
     * @param string $appBaseDirCode unused in this method
     * @param string $suffix
     * @param bool $useGlobalIncludePath
     * @param string $prependAutoloader 'true'|'false'
     * @param string $staticPhpVersion
     * @param bool $checkPlatform
     * @return string
     */
    
protected function getAutoloadRealFile($useClassMap$useIncludePath$targetDirLoader$useIncludeFiles$vendorPathCode$appBaseDirCode$suffix$useGlobalIncludePath$prependAutoloader$staticPhpVersion$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(\\dirname(__FILE__)));
        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;
        }

        
$file .= <<<STATIC_INIT
        \$useStaticLoader = PHP_VERSION_ID >= $staticPhpVersion && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
        if (\$useStaticLoader) {
            require __DIR__ . '/autoload_static.php';

            call_user_func(\Composer\Autoload\ComposerStaticInit
$suffix::getInitializer(\$loader));
        } else {

STATIC_INIT;

        if (!
$this->classMapAuthoritative) {
            
$file .= <<<'PSR04'
            $map = require __DIR__ . '/autoload_namespaces.php';
            foreach ($map as $namespace => $path) {
                $loader->set($namespace, $path);
            }

            $map = require __DIR__ . '/autoload_psr4.php';
            foreach ($map as $namespace => $path) {
                $loader->setPsr4($namespace, $path);
            }


PSR04;
        }

        if (
$useClassMap) {
            
$file .= <<<'CLASSMAP'
            $classMap = require __DIR__ . '/autoload_classmap.php';
            if ($classMap) {
                $loader->addClassMap($classMap);
            }

CLASSMAP;
        }

        
$file .= "        }\n\n";

        if (
$this->classMapAuthoritative) {
            
$file .= <<<'CLASSMAPAUTHORITATIVE'
        $loader->setClassMapAuthoritative(true);

CLASSMAPAUTHORITATIVE;
        }

        if (
$this->apcu) {
            
$apcuPrefix var_export(($this->apcuPrefix !== null $this->apcuPrefix substr(base64_encode(md5(uniqid(''true), true)), 0, -3)), 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
        if (\$useStaticLoader) {
            \$includeFiles = Composer\Autoload\ComposerStaticInit
$suffix::\$files;
        } else {
            \$includeFiles = require __DIR__ . '/autoload_files.php';
        }
        foreach (\$includeFiles as \$fileIdentifier => \$file) {
            composerRequire
$suffix(\$fileIdentifier, \$file);
        }


INCLUDE_FILES;
        }

        
$file .= <<<METHOD_FOOTER
        return \$loader;
    }

METHOD_FOOTER;

        
$file .= $targetDirLoader;

        if (
$useIncludeFiles) {
            return 
$file . <<<FOOTER
}

/**
 * @param string \$fileIdentifier
 * @param string \$file
 * @return void
 */
function composerRequire
$suffix(\$fileIdentifier, \$file)
{
    if (empty(\$GLOBALS['__composer_autoload_files'][\$fileIdentifier])) {
        \$GLOBALS['__composer_autoload_files'][\$fileIdentifier] = true;

        require \$file;
    }
}

FOOTER;
        }

        return 
$file . <<<FOOTER
}

FOOTER;
    }

    
/**
     * @param string $suffix
     * @param string $targetDir
     * @param string $vendorPath input for findShortestPathCode
     * @param string $basePath input for findShortestPathCode
     * @param string $staticPhpVersion
     * @return string
     */
    
protected function getStaticFile($suffix$targetDir$vendorPath$basePath, &$staticPhpVersion)
    {
        
$staticPhpVersion 50600;

        
$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);
        }

        
$classMap = require $targetDir '/autoload_classmap.php';
        if (
$classMap) {
            
$loader->addClassMap($classMap);
        }

        
$filesystem = new Filesystem();

        
$vendorPathCode ' => ' $filesystem->findShortestPathCode(realpath($targetDir), $vendorPathtruetrue) . " . '/";
        
$vendorPharPathCode ' => \'phar://\' . ' $filesystem->findShortestPathCode(realpath($targetDir), $vendorPathtruetrue) . " . '/";
        
$appBaseDirCode ' => ' $filesystem->findShortestPathCode(realpath($targetDir), $basePathtruetrue) . " . '/";
        
$appBaseDirPharCode ' => \'phar://\' . ' $filesystem->findShortestPathCode(realpath($targetDir), $basePathtruetrue) . " . '/";

        
$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 = array('files' => require $targetDir '/autoload_files.php');
        } else {
            
$maps = array();
        }

        foreach ((array) 
$loader as $prop => $value) {
            if (
$value && === strpos($prop$prefix)) {
                
$maps[substr($prop$prefixLen)] = $value;
            }
        }

        foreach (
$maps as $prop => $value) {
            if (
count($value) > 32767) {
                
// Static arrays are limited to 32767 values on PHP 5.6
                // See https://bugs.php.net/68057
                
$staticPhpVersion 70000;
            }
            
$value strtr(
                
var_export($valuetrue),
                array(
                    
$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}> $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$typeRootPackageInterface $rootPackage)
    {
        
$autoloads = array();

        foreach (
$packageMap as $item) {
            list(
$package$installPath) = $item;

            
$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($installPath0, -strlen('/'.$package->getTargetDir()));
            }

            foreach (
$autoload[$type] as $namespace => $paths) {
                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(array('/''\\'), '<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, array('\\*\\*' => '.+?''\\*' => '[^/]+?'));

                        
// add support for up-level relative paths
                        
$updir null;
                        
$path Preg::replaceCallback(
                            
'{^((?:(?:\\\\\\.){1,2}+/)+)}',
                            function (
$matches) use (&$updir) {
                                if (isset(
$matches[1])) {
                                    
// undo preg_quote for the matched string
                                    
$updir str_replace('\\.''.'$matches[1]);
                                }

                                return 
'';
                            },
                            
$path
                        
);
                        if (empty(
$installPath)) {
                            
$installPath strtr(getcwd(), '\\''/');
                        }

                        
$resolvedPath realpath($installPath '/' $updir);
                        
$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;
    }

    
/**
     * @param string $path
     * @return string
     */
    
protected function getFileIdentifier(PackageInterface $package$path)
    {
        return 
md5($package->getName() . ':' $path);
    }

    
/**
     * Filters out dev-dependencies
     *
     * @param array<int, array{0: PackageInterface, 1: string}> $packageMap
     * @param  RootPackageInterface $rootPackage
     * @return array<int, array{0: PackageInterface, 1: string}>
     *
     * @phpstan-param array<int, array{0: PackageInterface, 1: string}> $packageMap
     */
    
protected function filterPackageMap(array $packageMapRootPackageInterface $rootPackage)
    {
        
$packages = array();
        
$include = array();
        
$replacedBy = array();

        foreach (
$packageMap as $item) {
            
$package $item[0];
            
$name $package->getName();
            
$packages[$name] = $package;
            foreach (
$package->getReplaces() as $replace) {
                
$replacedBy[$replace->getTarget()] = $name;
            }
        }

        
$add = function (PackageInterface $package) use (&$add$packages, &$include$replacedBy) {
            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,
            function (
$item) use ($include) {
                
$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 retain the original order
     *
     * @param array<int, array{0: PackageInterface, 1: string}> $packageMap
     * @return array<int, array{0: PackageInterface, 1: string}>
     */
    
protected function sortPackageMap(array $packageMap)
    {
        
$packages = array();
        
$paths = array();

        foreach (
$packageMap as $item) {
            list(
$package$path) = $item;
            
$name $package->getName();
            
$packages[$name] = $package;
            
$paths[$name] = $path;
        }

        
$sortedPackages PackageSorter::sortPackages($packages);

        
$sortedPackageMap = array();

        foreach (
$sortedPackages as $package) {
            
$name $package->getName();
            
$sortedPackageMap[] = array($packages[$name], $paths[$name]);
        }

        return 
$sortedPackageMap;
    }
}

/**
 * @param string $fileIdentifier
 * @param string $file
 * @return void
 */
function composerRequire($fileIdentifier$file)
{
    if (empty(
$GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
        
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;

        require 
$file;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */

namespace Composer\Autoload;

use 
Composer\Pcre\Preg;
use 
Symfony\Component\Finder\Finder;
use 
Composer\IO\IOInterface;
use 
Composer\Util\Filesystem;

/**
 * ClassMapGenerator
 *
 * @author Gyula Sallai <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 */
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
     * @return void
     */
    
public static function dump($dirs$file)
    {
        
$maps = array();

        foreach (
$dirs as $dir) {
            
$maps array_merge($maps, static::createMap($dir));
        }

        
file_put_contents($filesprintf('<?php return %s;'var_export($mapstrue)));
    }

    
/**
     * Iterate over all files in the given directory searching for classes
     *
     * @param \Traversable<\SplFileInfo>|string|array<string> $path The path to search in or an iterator
     * @param string              $excluded     Regex that matches file paths to be excluded from the classmap
     * @param ?IOInterface        $io           IO object
     * @param ?string             $namespace    Optional namespace prefix to filter by
     * @param ?string             $autoloadType psr-0|psr-4 Optional autoload standard to use mapping rules
     * @param array<string, true> $scannedFiles
     * @return array<class-string, string> A class map array
     * @throws \RuntimeException When the path is neither an existing file nor directory
     */
    
public static function createMap($path$excluded nullIOInterface $io null$namespace null$autoloadType null, &$scannedFiles = array())
    {
        
$basePath $path;
        if (
is_string($path)) {
            if (
is_file($path)) {
                
$path = array(new \SplFileInfo($path));
            } elseif (
is_dir($path) || strpos($path'*') !== false) {
                
$path Finder::create()->files()->followLinks()->name('/\.(php|inc|hh)$/')->in($path);
            } else {
                throw new 
\RuntimeException(
                    
'Could not scan for classes inside "'.$path.
                    
'" which does not appear to be a file nor a folder'
                
);
            }
        } elseif (
null !== $autoloadType) {
            throw new 
\RuntimeException('Path must be a string when specifying an autoload type');
        }

        
$map = array();
        
$filesystem = new Filesystem();
        
$cwd realpath(getcwd());

        foreach (
$path as $file) {
            
$filePath $file->getPathname();
            if (!
in_array(pathinfo($filePathPATHINFO_EXTENSION), array('php''inc''hh'))) {
                continue;
            }

            if (!
$filesystem->isAbsolutePath($filePath)) {
                
$filePath $cwd '/' $filePath;
                
$filePath $filesystem->normalizePath($filePath);
            } else {
                
$filePath Preg::replace('{[\\\\/]{2,}}''/'$filePath);
            }

            
$realPath realpath($filePath);

            
// 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 (isset($scannedFiles[$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 ($excluded && Preg::isMatch($excludedstrtr($realPath'\\''/'))) {
                continue;
            }
            
// check non-realpath of file for directories symlink in project dir
            
if ($excluded && Preg::isMatch($excludedstrtr($filePath'\\''/'))) {
                continue;
            }

            
$classes self::findClasses($filePath);
            if (
null !== $autoloadType) {
                
$classes self::filterByNamespace($classes$filePath$namespace$autoloadType$basePath$io);

                
// 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 ($classes) {
                    
$scannedFiles[$realPath] = true;
                }
            } else {
                
// classmap autoload rules always collect all classes so for these we definitely do not want to scan again
                
$scannedFiles[$realPath] = true;
            }

            foreach (
$classes as $class) {
                
// skip classes not within the given namespace prefix
                
if (null === $autoloadType && null !== $namespace && '' !== $namespace && !== strpos($class$namespace)) {
                    continue;
                }

                if (!isset(
$map[$class])) {
                    
$map[$class] = $filePath;
                } elseif (
$io && $map[$class] !== $filePath && !Preg::isMatch('{/(test|fixture|example|stub)s?/}i'strtr($map[$class].' '.$filePath'\\''/'))) {
                    
$io->writeError(
                        
'<warning>Warning: Ambiguous class resolution, "'.$class.'"'.
                        
' was found in both "'.$map[$class].'" and "'.$filePath.'", the first will be used.</warning>'
                    
);
                }
            }
        }

        return 
$map;
    }

    
/**
     * 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  string                   $namespaceType psr-0|psr-4
     * @param  string                   $basePath      root directory of given autoload mapping
     * @param  ?IOInterface             $io            IO object
     * @return array<int, class-string> valid classes
     */
    
private static function filterByNamespace($classes$filePath$baseNamespace$namespaceType$basePath$io)
    {
        
$validClasses = array();
        
$rejectedClasses = array();

        
$realSubPath substr($filePathstrlen($basePath) + 1);
        
$realSubPath substr($realSubPath0strrpos($realSubPath'.'));

        foreach (
$classes as $class) {
            
// silently skip if ns doesn't have common root
            
if ('' !== $baseNamespace && !== strpos($class$baseNamespace)) {
                continue;
            }
            
// transform class name to file path and validate
            
if ('psr-0' === $namespaceType) {
                
$namespaceLength strrpos($class'\\');
                if (
false !== $namespaceLength) {
                    
$namespace substr($class0$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($classstrlen($baseNamespace)) : $class;
                
$subPath str_replace('\\'DIRECTORY_SEPARATOR$subNamespace);
            } else {
                throw new 
\RuntimeException("namespaceType must be psr-0 or psr-4, $namespaceType given");
            }
            if (
$subPath === $realSubPath) {
                
$validClasses[] = $class;
            } else {
                
$rejectedClasses[] = $class;
            }
        }
        
// warn only if no valid classes, else silently skip invalid
        
if (empty($validClasses)) {
            foreach (
$rejectedClasses as $class) {
                if (
$io) {
                    
$io->writeError("<warning>Class $class located in ".Preg::replace('{^'.preg_quote(getcwd()).'}''.'$filePath1)." does not comply with $namespaceType autoloading standard. Skipping.</warning>");
                }
            }

            return array();
        }

        return 
$validClasses;
    }

    
/**
     * Extract the classes in the given file
     *
     * @param  string            $path The file to check
     * @throws \RuntimeException
     * @return array<int, class-string> The found classes
     */
    
private static function findClasses($path)
    {
        
$extraTypes self::getExtraTypes();

        
// 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 (!
Filesystem::isReadable($path)) {
                
$message 'File at "%s" is not readable, check its permissions';
            } elseif (
'' === trim(file_get_contents($path))) {
                
// The input file was really empty and thus contains no classes
                
return array();
            } 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::matchAll('{\b(?:class|interface'.$extraTypes.')\s}i'$contents$matches);
        if (!
$matches) {
            return array();
        }

        
$p = new PhpFileCleaner($contentscount($matches[0]));
        
$contents $p->clean();
        unset(
$p);

        
Preg::matchAll('{
            (?:
                 \b(?<![\$:>])(?P<type>class|interface'
.$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 = array();
        
$namespace '';

        for (
$i 0$len count($matches['type']); $i $len$i++) {
            if (!empty(
$matches['ns'][$i])) {
                
$namespace str_replace(array(' '"\t""\r""\n"), ''$matches['nsname'][$i]) . '\\';
            } else {
                
$name $matches['name'][$i];
                
// skip anon classes extending/implementing
                
if ($name === 'extends' || $name === 'implements') {
                    continue;
                }
                if (
$name[0] === ':') {
                    
// This is an XHP class, https://github.com/facebook/xhp
                    
$name 'xhp'.substr(str_replace(array('-'':'), array('_''__'), $name), 1);
                } elseif (
$matches['type'][$i] === 'enum') {
                    
// In Hack, something like:
                    //   enum Foo: int { HERP = '123'; }
                    // The regex above captures the colon, which isn't part of
                    // the class name.
                    
$name rtrim($name':');
                }
                
$classes[] = ltrim($namespace $name'\\');
            }
        }

        return 
$classes;
    }

    
/**
     * @return string
     */
    
private static function getExtraTypes()
    {
        static 
$extraTypes null;
        if (
null === $extraTypes) {
            
$extraTypes PHP_VERSION_ID 50400 '' '|trait';
            if (
PHP_VERSION_ID >= 80100 || (defined('HHVM_VERSION') && version_compare(HHVM_VERSION'3.3''>='))) {
                
$extraTypes .= '|enum';
            }
            
PhpFileCleaner::setTypeConfig(array_merge(array('class''interface'), array_filter(explode('|'$extraTypes))));
        }

        return 
$extraTypes;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @author Beau Simensen <[email protected]>
 */
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)
     *
     * @return void
     */
    
public function addRepository($name$config$append true);

    
/**
     * Remove a repository
     *
     * @param string $name
     *
     * @return void
     */
    
public function removeRepository($name);

    
/**
     * Add a config setting
     *
     * @param string $name  Name
     * @param mixed  $value Value
     *
     * @return void
     */
    
public function addConfigSetting($name$value);

    
/**
     * Remove a config setting
     *
     * @param string $name
     *
     * @return void
     */
    
public function removeConfigSetting($name);

    
/**
     * Add a property
     *
     * @param string $name  Name
     * @param string $value Value
     *
     * @return void
     */
    
public function addProperty($name$value);

    
/**
     * Remove a property
     *
     * @param string $name
     *
     * @return void
     */
    
public function removeProperty($name);

    
/**
     * Add a package link
     *
     * @param string $type  Type (require, require-dev, provide, suggest, replace, conflict)
     * @param string $name  Name
     * @param string $value Value
     *
     * @return void
     */
    
public function addLink($type$name$value);

    
/**
     * Remove a package link
     *
     * @param string $type Type (require, require-dev, provide, suggest, replace, conflict)
     * @param string $name Name
     *
     * @return void
     */
    
public function removeLink($type$name);

    
/**
     * Gives a user-friendly name to this source (file path or so)
     *
     * @return string
     */
    
public function getName();
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @author Beau Simensen <[email protected]>
 */
class JsonConfigSource implements ConfigSourceInterface
{
    
/**
     * @var JsonFile
     */
    
private $file;

    
/**
     * @var bool
     */
    
private $authConfig;

    
/**
     * Constructor
     *
     * @param JsonFile $file
     * @param bool     $authConfig
     */
    
public function __construct(JsonFile $file$authConfig false)
    {
        
$this->file $file;
        
$this->authConfig $authConfig;
    }

    
/**
     * @inheritDoc
     */
    
public function getName()
    {
        return 
$this->file->getPath();
    }

    
/**
     * @inheritDoc
     */
    
public function addRepository($name$config$append true)
    {
        
$this->manipulateJson('addRepository'$name$config$append, function (&$config$repo$repoConfig) use ($append) {
            
// 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 === array('packagist' => false) || $val === array('packagist.org' => false))) {
                        unset(
$config['repositories'][$index]);
                        
$config['repositories']['packagist.org'] = false;
                        break;
                    }
                }
            }

            if (
$append) {
                
$config['repositories'][$repo] = $repoConfig;
            } else {
                
$config['repositories'] = array($repo => $repoConfig) + $config['repositories'];
            }
        });
    }

    
/**
     * @inheritDoc
     */
    
public function removeRepository($name)
    {
        
$this->manipulateJson('removeRepository'$name, function (&$config$repo) {
            unset(
$config['repositories'][$repo]);
        });
    }

    
/**
     * @inheritDoc
     */
    
public function addConfigSetting($name$value)
    {
        
$authConfig $this->authConfig;
        
$this->manipulateJson('addConfigSetting'$name$value, function (&$config$key$val) use ($authConfig) {
            if (
Preg::isMatch('{^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|bearer|http-basic|platform)\.}'$key)) {
                list(
$key$host) = explode('.'$key2);
                if (
$authConfig) {
                    
$config[$key][$host] = $val;
                } else {
                    
$config['config'][$key][$host] = $val;
                }
            } else {
                
$config['config'][$key] = $val;
            }
        });
    }

    
/**
     * @inheritDoc
     */
    
public function removeConfigSetting($name)
    {
        
$authConfig $this->authConfig;
        
$this->manipulateJson('removeConfigSetting'$name, function (&$config$key) use ($authConfig) {
            if (
Preg::isMatch('{^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|bearer|http-basic|platform)\.}'$key)) {
                list(
$key$host) = explode('.'$key2);
                if (
$authConfig) {
                    unset(
$config[$key][$host]);
                } else {
                    unset(
$config['config'][$key][$host]);
                }
            } else {
                unset(
$config['config'][$key]);
            }
        });
    }

    
/**
     * @inheritDoc
     */
    
public function addProperty($name$value)
    {
        
$this->manipulateJson('addProperty'$name$value, function (&$config$key$val) {
            if (
strpos($key'extra.') === || 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] = array();
                    }
                    
$arr = &$arr[$bit];
                }
                
$arr[$last] = $val;
            } else {
                
$config[$key] = $val;
            }
        });
    }

    
/**
     * @inheritDoc
     */
    
public function removeProperty($name)
    {
        
$this->manipulateJson('removeProperty'$name, function (&$config$key) {
            if (
strpos($key'extra.') === || strpos($key'scripts.') === 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]);
            }
        });
    }

    
/**
     * @inheritDoc
     */
    
public function addLink($type$name$value)
    {
        
$this->manipulateJson('addLink'$type$name$value, function (&$config$type$name$value) {
            
$config[$type][$name] = $value;
        });
    }

    
/**
     * @inheritDoc
     */
    
public function removeLink($type$name)
    {
        
$this->manipulateJson('removeSubNode'$type$name, function (&$config$type$name) {
            unset(
$config[$type][$name]);
        });
        
$this->manipulateJson('removeMainKeyIfEmpty'$type, function (&$config$type) {
            if (
=== count($config[$type])) {
                unset(
$config[$type]);
            }
        });
    }

    
/**
     * @param string $method
     * @param mixed ...$args
     * @param callable $fallback
     *
     * @return void
     */
    
protected function manipulateJson($method$args$fallback)
    {
        
$args func_get_args();
        
// remove method & fallback
        
array_shift($args);
        
$fallback array_pop($args);

        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';
            list(
$mainNode$name) = explode('.'$args[0], 2);
            
$args = array($mainNode$name$args[1]);
        } elseif (
$this->authConfig && $method === 'removeConfigSetting') {
            
$method 'removeSubNode';
            list(
$mainNode$name) = explode('.'$args[0], 2);
            
$args = array($mainNode$name);
        }

        
// try to update cleanly
        
if (call_user_func_array(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);
            
call_user_func_array($fallback$args);
            
// avoid ending up with arrays for keys that should be objects
            
foreach (array('require''require-dev''conflict''provide''replace''suggest''config''autoload''autoload-dev''scripts''scripts-descriptions''support') as $prop) {
                if (isset(
$config[$prop]) && $config[$prop] === array()) {
                    
$config[$prop] = new \stdClass;
                }
            }
            foreach (array(
'psr-0''psr-4') as $prop) {
                if (isset(
$config['autoload'][$prop]) && $config['autoload'][$prop] === array()) {
                    
$config['autoload'][$prop] = new \stdClass;
                }
                if (isset(
$config['autoload-dev'][$prop]) && $config['autoload-dev'][$prop] === array()) {
                    
$config['autoload-dev'][$prop] = new \stdClass;
                }
            }
            foreach (array(
'platform''http-basic''bearer''gitlab-token''gitlab-oauth''github-oauth''preferred-install') as $prop) {
                if (isset(
$config['config'][$prop]) && $config['config'][$prop] === array()) {
                    
$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
     * @return int
     */
    
private function arrayUnshiftRef(&$array, &$value)
    {
        
$return array_unshift($array'');
        
$array[0] = &$value;

        return 
$return;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\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 <[email protected]>
 * @author Johann Reinke <[email protected]>
 */
class PathDownloader extends FileDownloader implements VcsCapableDownloaderInterface
{
    const 
STRATEGY_SYMLINK 10;
    const 
STRATEGY_MIRROR 20;

    
/**
     * @inheritDoc
     */
    
public function download(PackageInterface $package$pathPackageInterface $prevPackage null$output true)
    {
        
$path Filesystem::trimTrailingSlash($path);
        
$url $package->getDistUrl();
        
$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();
        }

        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();
    }

    
/**
     * @inheritDoc
     */
    
public function install(PackageInterface $package$path$output true)
    {
        
$path Filesystem::trimTrailingSlash($path);
        
$url $package->getDistUrl();
        
$realUrl realpath($url);

        if (
realpath($path) === $realUrl) {
            if (
$output) {
                
$this->io->writeError("  - " InstallOperation::format($package) . $this->getInstallOperationAppendix($package$path));
            }

            return 
\React\Promise\resolve();
        }

        
// Get the transport options with default values
        
$transportOptions $package->getTransportOptions() + array('relative' => true);

        list(
$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 {
                    
$absolutePath $path;
                    if (!
$this->filesystem->isAbsolutePath($absolutePath)) {
                        
$absolutePath getcwd() . DIRECTORY_SEPARATOR $path;
                    }
                    
$shortestPath $this->filesystem->findShortestPath($absolutePath$realUrl);
                    
$path rtrim($path"/");
                    if (
$output) {
                        
$this->io->writeError(sprintf('Symlinking from %s'$url), false);
                    }
                    if (
$transportOptions['relative']) {
                        
$symfonyFilesystem->symlink($shortestPath$path);
                    } else {
                        
$symfonyFilesystem->symlink($realUrl$path);
                    }
                }
            } catch (
IOException $e) {
                if (
in_array(self::STRATEGY_MIRROR$allowedStrategies)) {
                    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, array());
            
$symfonyFilesystem->mirror($realUrl$path$iterator);
        }

        if (
$output) {
            
$this->io->writeError('');
        }

        return 
\React\Promise\resolve();
    }

    
/**
     * @inheritDoc
     */
    
public function remove(PackageInterface $package$path$output true)
    {
        
$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();
        }

        
// 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 getcwd() . '/' $path;
        
$absDistUrl $fs->isAbsolutePath($package->getDistUrl()) ? $package->getDistUrl() : getcwd() . '/' $package->getDistUrl();
        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();
        }

        return 
parent::remove($package$path$output);
    }

    
/**
     * @inheritDoc
     */
    
public function getVcsReference(PackageInterface $package$path)
    {
        
$path Filesystem::trimTrailingSlash($path);
        
$parser = new VersionParser;
        
$guesser = new VersionGuesser($this->config$this->process$parser);
        
$dumper = new ArrayDumper;

        
$packageConfig $dumper->dump($package);
        if (
$packageVersion $guesser->guessVersion($packageConfig$path)) {
            return 
$packageVersion['commit'];
        }

        return 
null;
    }

    
/**
     * @inheritDoc
     */
    
protected function getInstallOperationAppendix(PackageInterface $package$path)
    {
        
$realUrl realpath($package->getDistUrl());

        if (
realpath($path) === $realUrl) {
            return 
': Source already present';
        }

        list(
$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)
    {
        
// When symlink transport option is null, both symlink and mirror are allowed
        
$currentStrategy self::STRATEGY_SYMLINK;
        
$allowedStrategies = array(self::STRATEGY_SYMLINKself::STRATEGY_MIRROR);

        
$mirrorPathRepos Platform::getEnv('COMPOSER_MIRROR_PATH_REPOS');
        if (
$mirrorPathRepos) {
            
$currentStrategy self::STRATEGY_MIRROR;
        }

        
$symlinkOption = isset($transportOptions['symlink']) ? $transportOptions['symlink'] : null;

        if (
true === $symlinkOption) {
            
$currentStrategy self::STRATEGY_SYMLINK;
            
$allowedStrategies = array(self::STRATEGY_SYMLINK);
        } elseif (
false === $symlinkOption) {
            
$currentStrategy self::STRATEGY_MIRROR;
            
$allowedStrategies = array(self::STRATEGY_MIRROR);
        }

        
// Check we can use junctions safely if we are on Windows
        
if (Platform::isWindows() && self::STRATEGY_SYMLINK === $currentStrategy && !$this->safeJunctions()) {
            
$currentStrategy self::STRATEGY_MIRROR;
            
$allowedStrategies = array(self::STRATEGY_MIRROR);
        }

        return array(
$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).
     *
     * @return bool
     */
    
private function safeJunctions()
    {
        
// We need to call mklink, and rmdir on Windows 7 (version 6.1)
        
return function_exists('proc_open') &&
            (
PHP_WINDOWS_VERSION_MAJOR ||
            (
PHP_WINDOWS_VERSION_MAJOR === && PHP_WINDOWS_VERSION_MINOR >= 1));
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\ProcessExecutor;

/**
 * @author BohwaZ <http://bohwaz.net/>
 */
class FossilDownloader extends VcsDownloader
{
    
/**
     * @inheritDoc
     */
    
protected function doDownload(PackageInterface $package$path$urlPackageInterface $prevPackage null)
    {
        return 
\React\Promise\resolve();
    }

    
/**
     * @inheritDoc
     */
    
protected function doInstall(PackageInterface $package$path$url)
    {
        
// Ensure we are allowed to use this URL by config
        
$this->config->prohibitUrlByConfig($url$this->io);

        
$url ProcessExecutor::escape($url);
        
$ref ProcessExecutor::escape($package->getSourceReference());
        
$repoFile $path '.fossil';
        
$this->io->writeError("Cloning ".$package->getSourceReference());
        
$command sprintf('fossil clone -- %s %s'$urlProcessExecutor::escape($repoFile));
        if (
!== $this->process->execute($command$ignoredOutput)) {
            throw new 
\RuntimeException('Failed to execute ' $command "\n\n" $this->process->getErrorOutput());
        }
        
$command sprintf('fossil open --nested -- %s'ProcessExecutor::escape($repoFile));
        if (
!== $this->process->execute($command$ignoredOutputrealpath($path))) {
            throw new 
\RuntimeException('Failed to execute ' $command "\n\n" $this->process->getErrorOutput());
        }
        
$command sprintf('fossil update -- %s'$ref);
        if (
!== $this->process->execute($command$ignoredOutputrealpath($path))) {
            throw new 
\RuntimeException('Failed to execute ' $command "\n\n" $this->process->getErrorOutput());
        }

        return 
\React\Promise\resolve();
    }

    
/**
     * @inheritDoc
     */
    
protected function doUpdate(PackageInterface $initialPackageInterface $target$path$url)
    {
        
// Ensure we are allowed to use this URL by config
        
$this->config->prohibitUrlByConfig($url$this->io);

        
$ref ProcessExecutor::escape($target->getSourceReference());
        
$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');
        }

        
$command sprintf('fossil pull && fossil up %s'$ref);
        if (
!== $this->process->execute($command$ignoredOutputrealpath($path))) {
            throw new 
\RuntimeException('Failed to execute ' $command "\n\n" $this->process->getErrorOutput());
        }

        return 
\React\Promise\resolve();
    }

    
/**
     * @inheritDoc
     */
    
public function getLocalChanges(PackageInterface $package$path)
    {
        if (!
$this->hasMetadataRepository($path)) {
            return 
null;
        }

        
$this->process->execute('fossil changes'$outputrealpath($path));

        return 
trim($output) ?: null;
    }

    
/**
     * @inheritDoc
     */
    
protected function getCommitLogs($fromReference$toReference$path)
    {
        
$command sprintf('fossil timeline -t ci -W 0 -n 0 before %s'ProcessExecutor::escape($toReference));

        if (
!== $this->process->execute($command$outputrealpath($path))) {
            throw new 
\RuntimeException('Failed to execute ' $command "\n\n" $this->process->getErrorOutput());
        }

        
$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;
    }

    
/**
     * @inheritDoc
     */
    
protected function hasMetadataRepository($path)
    {
        return 
is_file($path '/.fslckout') || is_file($path '/_FOSSIL_');
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\ProcessExecutor;
use 
Composer\Util\Hg as HgUtils;

/**
 * @author Per Bernhardt <[email protected]>
 */
class HgDownloader extends VcsDownloader
{
    
/**
     * @inheritDoc
     */
    
protected function doDownload(PackageInterface $package$path$urlPackageInterface $prevPackage null)
    {
        if (
null === HgUtils::getVersion($this->process)) {
            throw new 
\RuntimeException('hg was not found in your PATH, skipping source download');
        }

        return 
\React\Promise\resolve();
    }

    
/**
     * @inheritDoc
     */
    
protected function doInstall(PackageInterface $package$path$url)
    {
        
$hgUtils = new HgUtils($this->io$this->config$this->process);

        
$cloneCommand = function ($url) use ($path) {
            return 
sprintf('hg clone -- %s %s'ProcessExecutor::escape($url), ProcessExecutor::escape($path));
        };

        
$hgUtils->runCommand($cloneCommand$url$path);

        
$ref ProcessExecutor::escape($package->getSourceReference());
        
$command sprintf('hg up -- %s'$ref);
        if (
!== $this->process->execute($command$ignoredOutputrealpath($path))) {
            throw new 
\RuntimeException('Failed to execute ' $command "\n\n" $this->process->getErrorOutput());
        }

        return 
\React\Promise\resolve();
    }

    
/**
     * @inheritDoc
     */
    
protected function doUpdate(PackageInterface $initialPackageInterface $target$path$url)
    {
        
$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 = function ($url) use ($ref) {
            return 
sprintf('hg pull -- %s && hg up -- %s'ProcessExecutor::escape($url), ProcessExecutor::escape($ref));
        };

        
$hgUtils->runCommand($command$url$path);

        return 
\React\Promise\resolve();
    }

    
/**
     * @inheritDoc
     */
    
public function getLocalChanges(PackageInterface $package$path)
    {
        if (!
is_dir($path.'/.hg')) {
            return 
null;
        }

        
$this->process->execute('hg st'$outputrealpath($path));

        return 
trim($output) ?: null;
    }

    
/**
     * @inheritDoc
     */
    
protected function getCommitLogs($fromReference$toReference$path)
    {
        
$command sprintf('hg log -r %s:%s --style compact'ProcessExecutor::escape($fromReference), ProcessExecutor::escape($toReference));

        if (
!== $this->process->execute($command$outputrealpath($path))) {
            throw new 
\RuntimeException('Failed to execute ' $command "\n\n" $this->process->getErrorOutput());
        }

        return 
$output;
    }

    
/**
     * @inheritDoc
     */
    
protected function hasMetadataRepository($path)
    {
        return 
is_dir($path '/.hg');
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 
Composer\Util\ProcessExecutor;

/**
 * GZip archive downloader.
 *
 * @author Pavel Puchkin <[email protected]>
 */
class GzipDownloader extends ArchiveDownloader
{
    protected function 
extract(PackageInterface $package$file$path)
    {
        
$filename pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_FILENAME);
        
$targetFilepath $path DIRECTORY_SEPARATOR $filename;

        
// Try to use gunzip on *nix
        
if (!Platform::isWindows()) {
            
$command 'gzip -cd -- ' ProcessExecutor::escape($file) . ' > ' ProcessExecutor::escape($targetFilepath);

            if (
=== $this->process->execute($command$ignoredOutput)) {
                return 
\React\Promise\resolve();
            }

            if (
extension_loaded('zlib')) {
                
// Fallback to using the PHP extension.
                
$this->extractUsingExt($file$targetFilepath);

                return 
\React\Promise\resolve();
            }

            
$processError 'Failed to execute ' $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();
    }

    
/**
     * @param string $file
     * @param string $targetFilepath
     *
     * @return void
     */
    
private function extractUsingExt($file$targetFilepath)
    {
        
$archiveFile gzopen($file'rb');
        
$targetFile fopen($targetFilepath'wb');
        while (
$string gzread($archiveFile4096)) {
            
fwrite($targetFile$stringPlatform::strlen($string));
        }
        
gzclose($archiveFile);
        
fclose($targetFile);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class FilesystemException extends \Exception
{
    
/**
     * @param string          $message
     * @param int             $code
     * @param \Exception|null $previous
     */
    
public function __construct($message ''$code 0\Exception $previous null)
    {
        
parent::__construct("Filesystem exception: \n".$message$code$previous);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 
Symfony\Component\Finder\Finder;
use 
React\Promise\PromiseInterface;
use 
Composer\DependencyResolver\Operation\InstallOperation;

/**
 * Base downloader for archives
 *
 * @author Kirill chEbba Chebunin <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 * @author François Pluchino <[email protected]>
 */
abstract class ArchiveDownloader extends FileDownloader
{
    
/**
     * @var array<string, true>
     * @protected
     */
    
public $cleanupExecuted = array();

    
/**
     * @return PromiseInterface|null
     */
    
public function prepare($typePackageInterface $package$pathPackageInterface $prevPackage null)
    {
        unset(
$this->cleanupExecuted[$package->getName()]);

        return 
parent::prepare($type$package$path$prevPackage);
    }

    
/**
     * @return PromiseInterface|null
     */
    
public function cleanup($typePackageInterface $package$pathPackageInterface $prevPackage null)
    {
        
$this->cleanupExecuted[$package->getName()] = true;

        return 
parent::cleanup($type$package$path$prevPackage);
    }

    
/**
     * @inheritDoc
     *
     * @param bool $output
     *
     * @return PromiseInterface
     *
     * @throws \RuntimeException
     * @throws \UnexpectedValueException
     */
    
public function install(PackageInterface $package$path$output true)
    {
        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/'.substr(md5(uniqid(''true)), 08);
        } 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) !== getcwd()) {
            
$this->addCleanupPath($package$path);
        }

        
$this->filesystem->ensureDirectoryExists($temporaryDir);
        
$fileName $this->getFileName($package$path);

        
$filesystem $this->filesystem;
        
$self $this;

        
$cleanup = function () use ($path$filesystem$temporaryDir$package$self) {
            
// remove cache if the file was corrupted
            
$self->clearLastCacheWrite($package);

            
// clean up
            
$filesystem->removeDirectory($temporaryDir);
            if (
is_dir($path) && realpath($path) !== getcwd()) {
                
$filesystem->removeDirectory($path);
            }
            
$self->removeCleanupPath($package$temporaryDir);
            
$self->removeCleanupPath($packagerealpath($path));
        };

        
$promise null;
        try {
            
$promise $this->extract($package$fileName$temporaryDir);
        } catch (
\Exception $e) {
            
$cleanup();
            throw 
$e;
        }

        if (!
$promise instanceof PromiseInterface) {
            
$promise \React\Promise\resolve();
        }

        return 
$promise->then(function () use ($self$package$filesystem$fileName$temporaryDir$path) {
            
$filesystem->unlink($fileName);

            
/**
             * Returns the folder content, excluding .DS_Store
             *
             * @param  string         $dir Directory
             * @return \SplFileInfo[]
             */
            
$getFolderContent = function ($dir) {
                
$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 = 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 === count($contentDir) && is_dir(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 ($self$package$path$temporaryDir) {
                
$self->removeCleanupPath($package$temporaryDir);
                
$self->removeCleanupPath($package$path);
            });
        }, function (
$e) use ($cleanup) {
            
$cleanup();

            throw 
$e;
        });
    }

    
/**
     * @inheritDoc
     */
    
protected function getInstallOperationAppendix(PackageInterface $package$path)
    {
        return 
': Extracting archive';
    }

    
/**
     * Extract file to directory
     *
     * @param string $file Extracted file
     * @param string $path Directory
     *
     * @throws \UnexpectedValueException If can not extract downloaded file to path
     * @return PromiseInterface|null
     */
    
abstract protected function extract(PackageInterface $package$file$path);
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\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 <[email protected]>
 */
class RarDownloader extends ArchiveDownloader
{
    protected function 
extract(PackageInterface $package$file$path)
    {
        
$processError null;

        
// Try to use unrar on *nix
        
if (!Platform::isWindows()) {
            
$command 'unrar x -- ' ProcessExecutor::escape($file) . ' ' ProcessExecutor::escape($path) . ' >/dev/null && chmod -R u+w ' ProcessExecutor::escape($path);

            if (
=== $this->process->execute($command$ignoredOutput)) {
                return 
\React\Promise\resolve();
            }

            
$processError 'Failed to execute ' $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();
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\ProcessExecutor;

/**
 * Xz archive downloader.
 *
 * @author Pavel Puchkin <[email protected]>
 * @author Pierre Rudloff <[email protected]>
 */
class XzDownloader extends ArchiveDownloader
{
    protected function 
extract(PackageInterface $package$file$path)
    {
        
$command 'tar -xJf ' ProcessExecutor::escape($file) . ' -C ' ProcessExecutor::escape($path);

        if (
=== $this->process->execute($command$ignoredOutput)) {
            return 
\React\Promise\resolve();
        }

        
$processError 'Failed to execute ' $command "\n\n" $this->process->getErrorOutput();

        throw new 
\RuntimeException($processError);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\IniHelper;
use 
Composer\Util\Platform;
use 
Composer\Util\ProcessExecutor;
use 
Symfony\Component\Process\ExecutableFinder;
use 
React\Promise\PromiseInterface;
use 
ZipArchive;

/**
 * @author Jordi Boggiano <[email protected]>
 */
class ZipDownloader extends ArchiveDownloader
{
    
/** @var array<int, array{0: string, 1: string}> */
    
private static $unzipCommands;
    
/** @var bool */
    
private static $hasZipArchive;
    
/** @var bool */
    
private static $isWindows;

    
/** @var ZipArchive|null */
    
private $zipArchiveObject// @phpstan-ignore-line helper property that is set via reflection for testing purposes

    /**
     * @inheritDoc
     */
    
public function download(PackageInterface $package$pathPackageInterface $prevPackage null$output true)
    {
        if (
null === self::$unzipCommands) {
            
self::$unzipCommands = array();
            
$finder = new ExecutableFinder;
            if (
Platform::isWindows() && ($cmd $finder->find('7z'null, array('C:\Program Files\7-Zip')))) {
                
self::$unzipCommands[] = array('7z'ProcessExecutor::escape($cmd).' x -bb0 -y %s -o%s');
            }
            if (
$cmd $finder->find('unzip')) {
                
self::$unzipCommands[] = array('unzip'ProcessExecutor::escape($cmd).' -qq %s -d %s');
            }
            if (!
Platform::isWindows() && ($cmd $finder->find('7z'))) { // 7z linux/macOS support is only used if unzip is not present
                
self::$unzipCommands[] = array('7z'ProcessExecutor::escape($cmd).' x -bb0 -y %s -o%s');
            }
            if (!
Platform::isWindows() && ($cmd $finder->find('7zz'))) { // 7zz linux/macOS support is only used if unzip is not present
                
self::$unzipCommands[] = array('7zz'ProcessExecutor::escape($cmd).' x -bb0 -y %s -o%s');
            }
        }

        
$procOpenMissing false;
        if (!
function_exists('proc_open')) {
            
self::$unzipCommands = array();
            
$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' 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' 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
     * @return PromiseInterface
     */
    
private function extractWithSystemUnzip(PackageInterface $package$file$path)
    {
        
// Force Exception throwing if the other alternative extraction method is not available
        
$isLastChance = !self::$hasZipArchive;

        if (!
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);
        
$command sprintf($commandSpec[1], ProcessExecutor::escape($file), ProcessExecutor::escape($path));
        
// normalize separators to backslashes to avoid problems with 7-zip on windows
        // see https://github.com/composer/composer/issues/10058
        
if (Platform::isWindows()) {
            
$command sprintf($commandSpec[1], ProcessExecutor::escape(strtr($file'/''\\')), ProcessExecutor::escape(strtr($path'/''\\')));
        }

        
$executable $commandSpec[0];

        
$self $this;
        
$io $this->io;
        
$tryFallback = function ($processError) use ($isLastChance$io$self$file$path$package$executable) {
            if (
$isLastChance) {
                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');
            }

            return 
$self->extractWithZipArchive($package$file$path);
        };

        try {
            
$promise $this->process->executeAsync($command);

            return 
$promise->then(function ($process) use ($tryFallback$command$package$file$self) {
                if (!
$process->isSuccessful()) {
                    if (isset(
$self->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().') '.$command."\n\n".$output));
                }
            });
        } catch (
\Exception $e) {
            return 
$tryFallback($e);
        } 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
     * @return PromiseInterface
     *
     * TODO v3 should make this private once we can drop PHP 5.3 support
     * @protected
     */
    
public function extractWithZipArchive(PackageInterface $package$file$path)
    {
        
$processError null;
        
$zipArchive $this->zipArchiveObject ?: new ZipArchive();

        try {
            if (
true === ($retval $zipArchive->open($file))) {
                
$extractResult $zipArchive->extractTo($path);

                if (
true === $extractResult) {
                    
$zipArchive->close();

                    return 
\React\Promise\resolve();
                }

                
$processError = new \RuntimeException(rtrim("There was an error extracting the ZIP file, 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 may contain identical file names with different capitalization (which fails on case insensitive filesystems): '.$e->getMessage(), 0$e);
        } catch (
\Exception $e) {
            
$processError $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
     * @return PromiseInterface|null
     *
     * TODO v3 should make this private once we can drop PHP 5.3 support
     * @protected
     */
    
public function extract(PackageInterface $package$file$path)
    {
        return 
$this->extractWithSystemUnzip($package$file$path);
    }

    
/**
     * Give a meaningful error message to the user.
     *
     * @param  int    $retval
     * @param  string $file
     * @return string
     */
    
protected function getErrorMessage($retval$file)
    {
        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);
            default:
                return 
sprintf("'%s' is not a valid zip archive, got error code: %s"$file$retval);
        }
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 */
interface DownloaderInterface
{
    
/**
     * Returns installation source (either source or dist).
     *
     * @return string "source" or "dist"
     */
    
public function getInstallationSource();

    
/**
     * This should do any network-related tasks to prepare for an upcoming install/update
     *
     * @param  string $path download path
     * @return PromiseInterface|null
     */
    
public function download(PackageInterface $package$pathPackageInterface $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  string                $path        download path
     * @param  PackageInterface      $prevPackage previous package instance in case of an update
     * @return PromiseInterface|null
     */
    
public function prepare($typePackageInterface $package$pathPackageInterface $prevPackage null);

    
/**
     * Installs specific package into specific folder.
     *
     * @param  PackageInterface      $package package instance
     * @param  string                $path    download path
     * @return PromiseInterface|null
     */
    
public function install(PackageInterface $package$path);

    
/**
     * 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
     * @return PromiseInterface|null
     */
    
public function update(PackageInterface $initialPackageInterface $target$path);

    
/**
     * Removes specific package from specific folder.
     *
     * @param  PackageInterface      $package package instance
     * @param  string                $path    download path
     * @return PromiseInterface|null
     */
    
public function remove(PackageInterface $package$path);

    
/**
     * 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
     * @return PromiseInterface|null
     */
    
public function cleanup($typePackageInterface $package$pathPackageInterface $prevPackage null);
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\Repository\VcsRepository;
use 
Composer\Util\Perforce;

/**
 * @author Matt Whittom <[email protected]>
 */
class PerforceDownloader extends VcsDownloader
{
    
/** @var Perforce|null */
    
protected $perforce;

    
/**
     * @inheritDoc
     */
    
protected function doDownload(PackageInterface $package$path$urlPackageInterface $prevPackage null)
    {
        return 
\React\Promise\resolve();
    }

    
/**
     * @inheritDoc
     */
    
public function doInstall(PackageInterface $package$path$url)
    {
        
$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();
    }

    
/**
     * @param string $ref
     *
     * @return string|null
     */
    
private function getLabelFromSourceReference($ref)
    {
        
$pos strpos($ref'@');
        if (
false !== $pos) {
            return 
substr($ref$pos 1);
        }

        return 
null;
    }

    
/**
     * @param string $path
     * @param string $url
     *
     * @return void
     */
    
public function initPerforce(PackageInterface $package$path$url)
    {
        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)
    {
        return 
$repository->getRepoConfig();
    }

    
/**
     * @inheritDoc
     */
    
protected function doUpdate(PackageInterface $initialPackageInterface $target$path$url)
    {
        return 
$this->doInstall($target$path$url);
    }

    
/**
     * @inheritDoc
     */
    
public function getLocalChanges(PackageInterface $package$path)
    {
        
$this->io->writeError('Perforce driver does not check for local changes before overriding');

        return 
null;
    }

    
/**
     * @inheritDoc
     */
    
protected function getCommitLogs($fromReference$toReference$path)
    {
        return 
$this->perforce->getCommitLogs($fromReference$toReference);
    }

    
/**
     * @return void
     */
    
public function setPerforce(Perforce $perforce)
    {
        
$this->perforce $perforce;
    }

    
/**
     * @inheritDoc
     */
    
protected function hasMetadataRepository($path)
    {
        return 
true;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\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 <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 * @author François Pluchino <[email protected]>
 * @author Nils Adermann <[email protected]>
 */
class FileDownloader implements DownloaderInterfaceChangeReportInterface
{
    
/** @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 = array();

    
/**
     * @private this is only public for php 5.3 support in closures
     *
     * @var array<string, string> Map of package name to cache key
     */
    
public $lastCacheWrites = array();
    
/** @var array<string, string[]> Map of package name to list of paths */
    
private $additionalCleanupPaths = array();

    
/**
     * 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 $ioConfig $configHttpDownloader $httpDownloaderEventDispatcher $eventDispatcher nullCache $cache nullFilesystem $filesystem nullProcessExecutor $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 && $this->cache->gcIsNecessary()) {
            
$this->io->writeError('Running cache garbage collection'trueIOInterface::VERY_VERBOSE);
            
$this->cache->gc((int) $config->get('cache-files-ttl'), (int) $config->get('cache-files-maxsize'));
        }
    }

    
/**
     * @inheritDoc
     */
    
public function getInstallationSource()
    {
        return 
'dist';
    }

    
/**
     * @inheritDoc
     *
     * @param bool $output
     */
    
public function download(PackageInterface $package$pathPackageInterface $prevPackage null$output true)
    {
        if (!
$package->getDistUrl()) {
            throw new 
\InvalidArgumentException('The given package is missing url information');
        }

        
$cacheKeyGenerator = function (PackageInterface $package$key) {
            
$cacheKey sha1($key);

            return 
$package->getName().'/'.$cacheKey.'.'.$package->getDistType();
        };

        
$retries 3;
        
$distUrls $package->getDistUrls();
        
/** @var array<array{base: string, processed: string, cacheKey: string}> $urls */
        
$urls = array();
        foreach (
$distUrls as $index => $url) {
            
$processedUrl $this->processUrl($package$url);
            
$urls[$index] = array(
                
'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),
            );
        }

        
$fileName $this->getFileName($package$path);
        
$this->filesystem->ensureDirectoryExists($path);
        
$this->filesystem->ensureDirectoryExists(dirname($fileName));

        
$io $this->io;
        
$cache $this->cache;
        
$httpDownloader $this->httpDownloader;
        
$eventDispatcher $this->eventDispatcher;
        
$filesystem $this->filesystem;
        
$self $this;

        
$accept null;
        
$reject null;
        
$download = function () use ($io$output$httpDownloader$cache$cacheKeyGenerator$eventDispatcher$package$fileName, &$urls, &$accept, &$reject$self) {
            
/** @var array{base: string, processed: string, cacheKey: string} $url */
            
$url reset($urls);
            
$index key($urls);

            if (
$eventDispatcher) {
                
$preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD$httpDownloader$url['processed'], 'package'$package);
                
$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 ($cache && (!$checksum || $checksum === $cache->sha1($cacheKey)) && $cache->copyTo($cacheKey$fileName)) {
                if (
$output) {
                    
$io->writeError("  - Loading <info>" $package->getName() . "</info> (<comment>" $package->getFullPrettyVersion() . "</comment>) from cache"trueIOInterface::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 (!$cache->isReadOnly()) {
                    
$self->lastCacheWrites[$package->getName()] = $cacheKey;
                }
                
$result \React\Promise\resolve($fileName);
            } else {
                if (
$output) {
                    
$io->writeError("  - Downloading <info>" $package->getName() . "</info> (<comment>" $package->getFullPrettyVersion() . "</comment>)");
                }

                
$result $httpDownloader->addCopy($url['processed'], $fileName$package->getTransportOptions())
                    ->
then($accept$reject);
            }

            return 
$result->then(function ($result) use ($fileName$checksum$url$package$eventDispatcher) {
                
// 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 && hash_file('sha1'$fileName) !== $checksum) {
                    throw new 
\UnexpectedValueException('The checksum verification of the file failed (downloaded from '.$url['base'].')');
                }

                if (
$eventDispatcher) {
                    
$postFileDownloadEvent = new PostFileDownloadEvent(PluginEvents::POST_FILE_DOWNLOAD$fileName$checksum$url['processed'], 'package'$package);
                    
$eventDispatcher->dispatch($postFileDownloadEvent->getName(), $postFileDownloadEvent);
                }

                return 
$fileName;
            });
        };

        
$accept = function ($response) use ($cache$package$fileName$self, &$urls) {
            
$url reset($urls);
            
$cacheKey $url['cacheKey'];
            
FileDownloader::$downloadMetadata[$package->getName()] = @filesize($fileName) ?: $response->getHeader('Content-Length') ?: '?';

            if (
$cache && !$cache->isReadOnly()) {
                
$self->lastCacheWrites[$package->getName()] = $cacheKey;
                
$cache->copyFrom($cacheKey$fileName);
            }

            
$response->collect();

            return 
$fileName;
        };

        
$reject = function ($e) use ($io, &$urls$download$fileName$package, &$retries$filesystem$self) {
            
// clean up
            
if (file_exists($fileName)) {
                
$filesystem->unlink($fileName);
            }
            
$self->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 ((!== $e->getCode() && !in_array($e->getCode(), array(500502503504))) || !$retries) {
                    
$retries 0;
                }
            }

            
// special error code returned when network is being artificially disabled
            
if ($e instanceof TransportException && $e->getStatusCode() === 499) {
                
$retries 0;
                
$urls = array();
            }

            if (
$retries) {
                
usleep(500000);
                
$retries--;

                return 
$download();
            }

            
array_shift($urls);
            if (
$urls) {
                if (
$io->isDebug()) {
                    
$io->writeError('    Failed downloading '.$package->getName().': ['.get_class($e).'] '.$e->getCode().': '.$e->getMessage());
                    
$io->writeError('    Trying the next URL for '.$package->getName());
                } else {
                    
$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($typePackageInterface $package$pathPackageInterface $prevPackage null)
    {
        return 
\React\Promise\resolve();
    }

    
/**
     * @inheritDoc
     */
    
public function cleanup($typePackageInterface $package$pathPackageInterface $prevPackage null)
    {
        
$fileName $this->getFileName($package$path);
        if (
file_exists($fileName)) {
            
$this->filesystem->unlink($fileName);
        }

        
$dirsToCleanUp = array(
            
$this->config->get('vendor-dir').'/composer/',
            
$this->config->get('vendor-dir'),
            
$path,
        );

        if (isset(
$this->additionalCleanupPaths[$package->getName()])) {
            foreach (
$this->additionalCleanupPaths[$package->getName()] as $path) {
                
$this->filesystem->remove($path);
            }
        }

        foreach (
$dirsToCleanUp as $dir) {
            if (
is_dir($dir) && $this->filesystem->isDirEmpty($dir) && realpath($dir) !== getcwd()) {
                
$this->filesystem->removeDirectoryPhp($dir);
            }
        }

        return 
\React\Promise\resolve();
    }

    
/**
     * @inheritDoc
     *
     * @param bool $output
     */
    
public function install(PackageInterface $package$path$output true)
    {
        if (
$output) {
            
$this->io->writeError("  - " InstallOperation::format($package));
        }

        
$this->filesystem->emptyDirectory($path);
        
$this->filesystem->ensureDirectoryExists($path);
        
$this->filesystem->rename($this->getFileName($package$path), $path '/' pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME));

        if (
$package->getBinaries()) {
            
// 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 '/' $bin0777 & ~umask());
                }
            }
        }

        return 
\React\Promise\resolve();
    }

    
/**
     * TODO mark private in v3
     * @protected This is public due to PHP 5.3
     *
     * @return void
     */
    
public function clearLastCacheWrite(PackageInterface $package)
    {
        if (
$this->cache && isset($this->lastCacheWrites[$package->getName()])) {
            
$this->cache->remove($this->lastCacheWrites[$package->getName()]);
            unset(
$this->lastCacheWrites[$package->getName()]);
        }
    }

    
/**
     * TODO mark private in v3
     * @protected This is public due to PHP 5.3
     *
     * @param string $path
     *
     * @return void
     */
    
public function addCleanupPath(PackageInterface $package$path)
    {
        
$this->additionalCleanupPaths[$package->getName()][] = $path;
    }

    
/**
     * TODO mark private in v3
     * @protected This is public due to PHP 5.3
     *
     * @param string $path
     *
     * @return void
     */
    
public function removeCleanupPath(PackageInterface $package$path)
    {
        if (isset(
$this->additionalCleanupPaths[$package->getName()])) {
            
$idx array_search($path$this->additionalCleanupPaths[$package->getName()]);
            if (
false !== $idx) {
                unset(
$this->additionalCleanupPaths[$package->getName()][$idx]);
            }
        }
    }

    
/**
     * @inheritDoc
     */
    
public function update(PackageInterface $initialPackageInterface $target$path)
    {
        
$this->io->writeError("  - " UpdateOperation::format($initial$target) . $this->getInstallOperationAppendix($target$path));

        
$promise $this->remove($initial$pathfalse);
        if (!
$promise instanceof PromiseInterface) {
            
$promise \React\Promise\resolve();
        }
        
$self $this;
        
$io $this->io;

        return 
$promise->then(function () use ($self$target$path) {
            
$promise $self->install($target$pathfalse);

            return 
$promise;
        });
    }

    
/**
     * @inheritDoc
     *
     * @param bool $output
     */
    
public function remove(PackageInterface $package$path$output true)
    {
        if (
$output) {
            
$this->io->writeError("  - " UninstallOperation::format($package));
        }
        
$promise $this->filesystem->removeDirectoryAsync($path);

        return 
$promise->then(function ($result) use ($path) {
            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$path)
    {
        return 
rtrim($this->config->get('vendor-dir').'/composer/tmp-'.md5($package.spl_object_hash($package)).'.'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_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
     * @return string
     */
    
protected function getInstallOperationAppendix(PackageInterface $package$path)
    {
        return 
'';
    }

    
/**
     * Process the download url
     *
     * @param  PackageInterface  $package package the url is coming from
     * @param  string            $url     download url
     * @throws \RuntimeException If any problem with the url
     * @return string            url
     */
    
protected function processUrl(PackageInterface $package$url)
    {
        if (!
extension_loaded('openssl') && === strpos($url'https:')) {
            throw new 
\RuntimeException('You must enable the openssl extension to download files via https');
        }

        if (
$package->getDistReference()) {
            
$url UrlUtil::updateDistReference($this->config$url$package->getDistReference());
        }

        return 
$url;
    }

    
/**
     * @inheritDoc
     * @throws \RuntimeException
     */
    
public function getLocalChanges(PackageInterface $package$targetDir)
    {
        
$prevIO $this->io;

        
$this->io = new NullIO;
        
$this->io->loadConfiguration($this->config);
        
$e null;
        
$output '';

        
$targetDir Filesystem::trimTrailingSlash($targetDir);
        try {
            if (
is_dir($targetDir.'_compare')) {
                
$this->filesystem->removeDirectory($targetDir.'_compare');
            }

            
$this->download($package$targetDir.'_compare'nullfalse);
            
$this->httpDownloader->wait();
            
$this->install($package$targetDir.'_compare'false);
            
$this->process->wait();

            
$comparer = new Comparer();
            
$comparer->setSource($targetDir.'_compare');
            
$comparer->setUpdate($targetDir);
            
$comparer->doCompare();
            
$output $comparer->getChanged(truetrue);
            
$this->filesystem->removeDirectory($targetDir.'_compare');
        } catch (
\Exception $e) {
        }

        
$this->io $prevIO;

        if (
$e) {
            throw 
$e;
        }

        return 
trim($output);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @author Till Klampaeckel <[email protected]>
 */
class SvnDownloader extends VcsDownloader
{
    
/** @var bool */
    
protected $cacheCredentials true;

    
/**
     * @inheritDoc
     */
    
protected function doDownload(PackageInterface $package$path$urlPackageInterface $prevPackage null)
    {
        
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();
    }

    
/**
     * @inheritDoc
     */
    
protected function doInstall(PackageInterface $package$path$url)
    {
        
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();
    }

    
/**
     * @inheritDoc
     */
    
protected function doUpdate(PackageInterface $initialPackageInterface $target$path$url)
    {
        
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"svn switch" $flagssprintf("%s/%s"$url$ref), $path);

        return 
\React\Promise\resolve();
    }

    
/**
     * @inheritDoc
     */
    
public function getLocalChanges(PackageInterface $package$path)
    {
        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  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
     * @return string
     */
    
protected function execute(PackageInterface $package$baseUrl$command$url$cwd null$path null)
    {
        
$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$path$update)
    {
        if (!
$changes $this->getLocalChanges($package$path)) {
            return 
\React\Promise\resolve();
        }

        if (!
$this->io->isInteractive()) {
            if (
true === $this->config->get('discard-changes')) {
                return 
$this->discardChanges($path);
            }

            return 
parent::cleanChanges($package$path$update);
        }

        
$changes array_map(function ($elem) {
            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 === '' 's'));
        
$this->io->writeError(array_slice($changes010));
        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 === '' '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(array(
                        
'    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();
    }

    
/**
     * @inheritDoc
     */
    
protected function getCommitLogs($fromReference$toReference$path)
    {
        if (
Preg::isMatch('{@(\d+)$}'$fromReference) && Preg::isMatch('{@(\d+)$}'$toReference)) {
            
// retrieve the svn base url from the checkout folder
            
$command sprintf('svn info --non-interactive --xml -- %s'ProcessExecutor::escape($path));
            if (
!== $this->process->execute($command$output$path)) {
                throw new 
\RuntimeException(
                    
'Failed to execute ' $command "\n\n" $this->process->getErrorOutput()
                );
            }

            
$urlPattern '#<url>(.*)</url>#';
            if (
Preg::isMatch($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 sprintf('svn log -r%s:%s --incremental'ProcessExecutor::escape($fromRevision), ProcessExecutor::escape($toRevision));

            
$util = new SvnUtil($baseUrl$this->io$this->config$this->process);
            
$util->setCacheCredentials($this->cacheCredentials);
            try {
                return 
$util->executeLocal($command$pathnull$this->io->isVerbose());
            } catch (
\RuntimeException $e) {
                throw new 
\RuntimeException(
                    
'Failed to execute ' $command "\n\n".$e->getMessage()
                );
            }
        }

        return 
"Could not retrieve changes between $fromReference and $toReference due to missing revision information";
    }

    
/**
     * @param string $path
     *
     * @return PromiseInterface
     */
    
protected function discardChanges($path)
    {
        if (
!== $this->process->execute('svn revert -R .'$output$path)) {
            throw new 
\RuntimeException("Could not reset changes\n\n:".$this->process->getErrorOutput());
        }

        return 
\React\Promise\resolve();
    }

    
/**
     * @inheritDoc
     */
    
protected function hasMetadataRepository($path)
    {
        return 
is_dir($path.'/.svn');
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
interface DvcsDownloaderInterface
{
    
/**
     * Checks for unpushed changes to a current branch
     *
     * @param  PackageInterface $package package directory
     * @param  string           $path    package directory
     * @return string|null      changes or null
     */
    
public function getUnpushedChanges(PackageInterface $package$path);
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class DownloadManager
{
    
/** @var IOInterface */
    
private $io;
    
/** @var bool */
    
private $preferDist false;
    
/** @var bool */
    
private $preferSource;
    
/** @var array<string, string> */
    
private $packagePreferences = array();
    
/** @var Filesystem */
    
private $filesystem;
    
/** @var array<string, DownloaderInterface> */
    
private $downloaders = array();

    
/**
     * 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$preferSource falseFilesystem $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($preferSource)
    {
        
$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($preferDist)
    {
        
$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)
    {
        
$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($typeDownloaderInterface $downloader)
    {
        
$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
     * @return DownloaderInterface
     */
    
public function getDownloader($type)
    {
        
$type strtolower($type);
        if (!isset(
$this->downloaders[$type])) {
            throw new 
\InvalidArgumentException(sprintf('Unknown downloader type: %s. Available types: %s.'$typeimplode(', '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
     * @return DownloaderInterface|null
     */
    
public function getDownloaderForPackage(PackageInterface $package)
    {
        
$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;
    }

    
/**
     * @return string
     */
    
public function getDownloaderType(DownloaderInterface $downloader)
    {
        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
     *
     * @throws \InvalidArgumentException if package have no urls to download from
     * @throws \RuntimeException
     * @return PromiseInterface
     */
    
public function download(PackageInterface $package$targetDirPackageInterface $prevPackage null)
    {
        
$targetDir $this->normalizeTargetDir($targetDir);
        
$this->filesystem->ensureDirectoryExists(dirname($targetDir));

        
$sources $this->getAvailableSources($package$prevPackage);

        
$io $this->io;
        
$self $this;

        
$download = function ($retry false) use (&$sources$io$package$self$targetDir, &$download$prevPackage) {
            
$source array_shift($sources);
            if (
$retry) {
                
$io->writeError('    <warning>Now trying to download from ' $source '</warning>');
            }
            
$package->setInstallationSource($source);

            
$downloader $self->getDownloaderForPackage($package);
            if (!
$downloader) {
                return 
\React\Promise\resolve();
            }

            
$handleError = 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);
            }
            if (!
$result instanceof PromiseInterface) {
                return 
\React\Promise\resolve($result);
            }

            
$res $result->then(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
     *
     * @return PromiseInterface|null
     */
    
public function prepare($typePackageInterface $package$targetDirPackageInterface $prevPackage null)
    {
        
$targetDir $this->normalizeTargetDir($targetDir);
        
$downloader $this->getDownloaderForPackage($package);
        if (
$downloader) {
            return 
$downloader->prepare($type$package$targetDir$prevPackage);
        }

        return 
\React\Promise\resolve();
    }

    
/**
     * Installs package into target dir.
     *
     * @param PackageInterface $package   package instance
     * @param string           $targetDir target dir
     *
     * @throws \InvalidArgumentException if package have no urls to download from
     * @throws \RuntimeException
     * @return PromiseInterface|null
     */
    
public function install(PackageInterface $package$targetDir)
    {
        
$targetDir $this->normalizeTargetDir($targetDir);
        
$downloader $this->getDownloaderForPackage($package);
        if (
$downloader) {
            return 
$downloader->install($package$targetDir);
        }

        return 
\React\Promise\resolve();
    }

    
/**
     * Updates package from initial to target version.
     *
     * @param PackageInterface $initial   initial package version
     * @param PackageInterface $target    target package version
     * @param string           $targetDir target dir
     *
     * @throws \InvalidArgumentException if initial package is not installed
     * @return PromiseInterface|null
     */
    
public function update(PackageInterface $initialPackageInterface $target$targetDir)
    {
        
$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();
        }

        
// 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);
        if (
$promise) {
            
$self $this;

            return 
$promise->then(function ($res) use ($self$target$targetDir) {
                return 
$self->install($target$targetDir);
            });
        }

        return 
$this->install($target$targetDir);
    }

    
/**
     * Removes package from target dir.
     *
     * @param PackageInterface $package   package instance
     * @param string           $targetDir target dir
     *
     * @return PromiseInterface|null
     */
    
public function remove(PackageInterface $package$targetDir)
    {
        
$targetDir $this->normalizeTargetDir($targetDir);
        
$downloader $this->getDownloaderForPackage($package);
        if (
$downloader) {
            return 
$downloader->remove($package$targetDir);
        }

        return 
\React\Promise\resolve();
    }

    
/**
     * 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
     *
     * @return PromiseInterface|null
     */
    
public function cleanup($typePackageInterface $package$targetDirPackageInterface $prevPackage null)
    {
        
$targetDir $this->normalizeTargetDir($targetDir);
        
$downloader $this->getDownloaderForPackage($package);
        if (
$downloader) {
            return 
$downloader->cleanup($type$package$targetDir$prevPackage);
        }

        return 
\React\Promise\resolve();
    }

    
/**
     * Determines the install preference of a package
     *
     * @param PackageInterface $package package instance
     *
     * @return string
     */
    
protected function resolvePackageInstallPreference(PackageInterface $package)
    {
        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 $packagePackageInterface $prevPackage null)
    {
        
$sourceType $package->getSourceType();
        
$distType $package->getDistType();

        
// add source before dist by default
        
$sources = array();
        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(), $sourcestrue)
            
// 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, function ($a$b) use ($prevSource) {
                return 
$a === $prevSource ? -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
     *
     * @param string $dir
     *
     * @return string
     */
    
private function normalizeTargetDir($dir)
    {
        if (
$dir === '\\' || $dir === '/') {
            return 
$dir;
        }

        return 
rtrim($dir'\\/');
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
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$path);
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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;

/**
 * Downloader for phar files
 *
 * @author Kirill chEbba Chebunin <[email protected]>
 */
class PharDownloader extends ArchiveDownloader
{
    
/**
     * @inheritDoc
     */
    
protected function extract(PackageInterface $package$file$path)
    {
        
// Can throw an UnexpectedValueException
        
$archive = new \Phar($file);
        
$archive->extractTo($pathnulltrue);
        
/* 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();
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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;

/**
 * Downloader for tar files: tar, tar.gz or tar.bz2
 *
 * @author Kirill chEbba Chebunin <[email protected]>
 */
class TarDownloader extends ArchiveDownloader
{
    
/**
     * @inheritDoc
     */
    
protected function extract(PackageInterface $package$file$path)
    {
        
// Can throw an UnexpectedValueException
        
$archive = new \PharData($file);
        
$archive->extractTo($pathnulltrue);

        return 
\React\Promise\resolve();
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
{
    
/**
     * @var bool[]
     * @phpstan-var array<string, bool>
     */
    
private $hasStashedChanges = array();
    
/**
     * @var bool[]
     * @phpstan-var array<string, bool>
     */
    
private $hasDiscardedChanges = array();
    
/**
     * @var GitUtil
     */
    
private $gitUtil;
    
/**
     * @var array
     * @phpstan-var array<int, array<string, bool>>
     */
    
private $cachedPackages = array();

    public function 
__construct(IOInterface $ioConfig $configProcessExecutor $process nullFilesystem $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$path$urlPackageInterface $prevPackage null)
    {
        
GitUtil::cleanEnv();

        
$cachePath $this->config->get('cache-vcs-dir').'/'.Preg::replace('{[^a-z0-9.]}i''-'$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'ProcessExecutor::escape($cachePath)), trueIOInterface::DEBUG);
            
$ref $package->getSourceReference();
            if (
$this->gitUtil->fetchRefOrSyncMirror($url$cachePath$ref) && 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();
    }

    
/**
     * @inheritDoc
     */
    
protected function doInstall(PackageInterface $package$path$url)
    {
        
GitUtil::cleanEnv();
        
$path $this->normalizePath($path);
        
$cachePath $this->config->get('cache-vcs-dir').'/'.Preg::replace('{[^a-z0-9.]}i''-'$url).'/';
        
$ref $package->getSourceReference();
        
$flag Platform::isWindows() ? '/D ' '';

        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 '';
            }

            
$command =
                
'git clone --no-checkout %cachePath% %path% ' $cloneFlags
                
'&& cd '.$flag.'%path% '
                
'&& git remote set-url origin -- %sanitizedUrl% && git remote add composer -- %sanitizedUrl%';
        } else {
            
$msg "Cloning ".$this->getShortHash($ref);
            
$command 'git clone --no-checkout -- %url% %path% && cd '.$flag.'%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);

        
$commandCallable = function ($url) use ($path$command$cachePath) {
            return 
str_replace(
                array(
'%url%''%path%''%cachePath%''%sanitizedUrl%'),
                array(
                    
ProcessExecutor::escape($url),
                    
ProcessExecutor::escape($path),
                    
ProcessExecutor::escape($cachePath),
                    
ProcessExecutor::escape(Preg::replace('{://([^@]+?):(.+?)@}''://'$url)),
                ),
                
$command
            
);
        };

        
$this->gitUtil->runCommand($commandCallable$url$pathtrue);
        
$sourceUrl $package->getSourceUrl();
        if (
$url !== $sourceUrl && $sourceUrl !== null) {
            
$this->updateOriginUrl($path$sourceUrl);
        } else {
            
$this->setPushUrl($path$url);
        }

        if (
$newRef $this->updateToCommit($path$ref$package->getPrettyVersion(), $package->getReleaseDate())) {
            if (
$package->getDistReference() === $package->getSourceReference()) {
                
$package->setDistReference($newRef);
            }
            
$package->setSourceReference($newRef);
        }

        return 
\React\Promise\resolve();
    }

    
/**
     * @inheritDoc
     */
    
protected function doUpdate(PackageInterface $initialPackageInterface $target$path$url)
    {
        
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).'/';
        
$ref $target->getSourceReference();

        if (!empty(
$this->cachedPackages[$target->getId()][$ref])) {
            
$msg "Checking out ".$this->getShortHash($ref).' from cache';
            
$command '(git rev-parse --quiet --verify %ref% || (git remote set-url composer -- %cachePath% && git fetch composer && git fetch --tags composer)) && git remote set-url composer -- %sanitizedUrl%';
        } else {
            
$msg "Checking out ".$this->getShortHash($ref);
            
$command '(git remote set-url composer -- %url% && git rev-parse --quiet --verify %ref% || (git fetch composer && git fetch --tags composer)) && git remote set-url composer -- %sanitizedUrl%';
            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);

        
$commandCallable = function ($url) use ($ref$command$cachePath) {
            return 
str_replace(
                array(
'%url%''%ref%''%cachePath%''%sanitizedUrl%'),
                array(
                    
ProcessExecutor::escape($url),
                    
ProcessExecutor::escape($ref.'^{commit}'),
                    
ProcessExecutor::escape($cachePath),
                    
ProcessExecutor::escape(Preg::replace('{://([^@]+?):(.+?)@}''://'$url)),
                ),
                
$command
            
);
        };

        
$this->gitUtil->runCommand($commandCallable$url$path);
        if (
$newRef $this->updateToCommit($path$ref$target->getPrettyVersion(), $target->getReleaseDate())) {
            if (
$target->getDistReference() === $target->getSourceReference()) {
                
$target->setDistReference($newRef);
            }
            
$target->setSourceReference($newRef);
        }

        
$updateOriginUrl false;
        if (
            
=== $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();
    }

    
/**
     * @inheritDoc
     */
    
public function getLocalChanges(PackageInterface $package$path)
    {
        
GitUtil::cleanEnv();
        if (!
$this->hasMetadataRepository($path)) {
            return 
null;
        }

        
$command 'git status --porcelain --untracked-files=no';
        if (
!== $this->process->execute($command$output$path)) {
            throw new 
\RuntimeException('Failed to execute ' $command "\n\n" $this->process->getErrorOutput());
        }

        return 
trim($output) ?: null;
    }

    
/**
     * @return null|string
     */
    
public function getUnpushedChanges(PackageInterface $package$path)
    {
        
GitUtil::cleanEnv();
        
$path $this->normalizePath($path);
        if (!
$this->hasMetadataRepository($path)) {
            return 
null;
        }

        
$command 'git show-ref --head -d';
        if (
!== $this->process->execute($command$output$path)) {
            throw new 
\RuntimeException('Failed to execute ' $command "\n\n" $this->process->getErrorOutput());
        }

        
$refs trim($output);
        if (!
Preg::isMatch('{^([a-f0-9]+) HEAD$}mi'$refs$match)) {
            
// could not match the HEAD for some reason
            
return null;
        }

        
$headRef $match[1];
        if (!
Preg::isMatchAll('{^'.$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 = array();

            
// try to find matching branch names in remote repos
            
foreach ($candidateBranches as $candidate) {
                if (
Preg::isMatchAll('{^[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 (!$remoteBranches) {
                
$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 sprintf('git diff --name-status %s...%s --'$remoteBranch$branch);
                    if (
!== $this->process->execute($command$output$path)) {
                        throw new 
\RuntimeException('Failed to execute ' $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 (
!== $this->process->execute($command$output$path)) {
                    throw new 
\RuntimeException('Failed to execute ' $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$path$update)
    {
        
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 (!
$changes $this->getLocalChanges($package$path)) {
            return 
\React\Promise\resolve();
        }

        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(function ($elem) {
            return 
'    '.$elem;
        }, 
Preg::split('{\s*\r?\n\s*}'$changes));
        
$this->io->writeError('    <error>'.$package->getPrettyName().' has modified files:</error>');
        
$this->io->writeError(array_slice($changes010));
        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(array(
                        
'    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();
    }

    
/**
     * @inheritDoc
     */
    
protected function reapplyChanges($path)
    {
        
$path $this->normalizePath($path);
        if (!empty(
$this->hasStashedChanges[$path])) {
            unset(
$this->hasStashedChanges[$path]);
            
$this->io->writeError('    <info>Re-applying stashed changes</info>');
            if (
!== $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
     *
     * @param  string            $path
     * @param  string            $reference
     * @param  string            $branch
     * @param  \DateTime         $date
     * @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($path$reference$branch$date)
    {
        
$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.
        
$template 'git checkout '.$force.'%s -- && git reset --hard %1$s --';
        
$branch Preg::replace('{(?:^dev-|(?:\.x)?-dev$)}i'''$branch);

        
$branches null;
        if (
=== $this->process->execute('git branch -r'$output$path)) {
            
$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)
            && 
$branches
            
&& Preg::isMatch('{^\s+composer/'.preg_quote($reference).'$}m'$branches)
        ) {
            
$command sprintf('git checkout '.$force.'-B %s %s -- && git reset --hard %2$s --'ProcessExecutor::escape($branch), ProcessExecutor::escape('composer/'.$reference));
            if (
=== $this->process->execute($command$output$path)) {
                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 (!Preg::isMatch('{^\s+composer/'.preg_quote($branch).'$}m'$branches) && Preg::isMatch('{^\s+composer/v'.preg_quote($branch).'$}m'$branches)) {
                
$branch 'v' $branch;
            }

            
$command sprintf('git checkout %s --'ProcessExecutor::escape($branch));
            
$fallbackCommand sprintf('git checkout '.$force.'-B %s %s --'ProcessExecutor::escape($branch), ProcessExecutor::escape('composer/'.$branch));
            
$resetCommand sprintf('git reset --hard %s --'ProcessExecutor::escape($reference));

            if (
=== $this->process->execute("($command || $fallbackCommand) && $resetCommand"$output$path)) {
                return 
null;
            }
        }

        
$command sprintf($templateProcessExecutor::escape($gitRef));
        if (
=== $this->process->execute($command$output$path)) {
            return 
null;
        }

        
// 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>');
        }

        throw new 
\RuntimeException(Url::sanitize('Failed to execute ' $command "\n\n" $this->process->getErrorOutput()));
    }

    
/**
     * @param string $path
     * @param string $url
     *
     * @return void
     */
    
protected function updateOriginUrl($path$url)
    {
        
$this->process->execute(sprintf('git remote set-url origin -- %s'ProcessExecutor::escape($url)), $output$path);
        
$this->setPushUrl($path$url);
    }

    
/**
     * @param string $path
     * @param string $url
     *
     * @return void
     */
    
protected function setPushUrl($path$url)
    {
        
// 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'$protocolstrue)) {
                
$pushUrl 'https://' $match[1] . '/'.$match[2].'/'.$match[3].'.git';
            }
            
$cmd sprintf('git remote set-url --push origin -- %s'ProcessExecutor::escape($pushUrl));
            
$this->process->execute($cmd$ignoredOutput$path);
        }
    }

    
/**
     * @inheritDoc
     */
    
protected function getCommitLogs($fromReference$toReference$path)
    {
        
$path $this->normalizePath($path);
        
$command sprintf('git log %s..%s --pretty=format:"%%h - %%an: %%s"'.GitUtil::getNoShowSignatureFlag($this->process), ProcessExecutor::escape($fromReference), ProcessExecutor::escape($toReference));

        if (
!== $this->process->execute($command$output$path)) {
            throw new 
\RuntimeException('Failed to execute ' $command "\n\n" $this->process->getErrorOutput());
        }

        return 
$output;
    }

    
/**
     * @param string $path
     *
     * @return PromiseInterface
     *
     * @throws \RuntimeException
     */
    
protected function discardChanges($path)
    {
        
$path $this->normalizePath($path);
        if (
!== $this->process->execute('git clean -df && git reset --hard'$output$path)) {
            throw new 
\RuntimeException("Could not reset changes\n\n:".$output);
        }

        
$this->hasDiscardedChanges[$path] = true;

        return 
\React\Promise\resolve();
    }

    
/**
     * @param string $path
     *
     * @return PromiseInterface
     *
     * @throws \RuntimeException
     */
    
protected function stashChanges($path)
    {
        
$path $this->normalizePath($path);
        if (
!== $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();
    }

    
/**
     * @param string $path
     *
     * @return void
     *
     * @throws \RuntimeException
     */
    
protected function viewDiff($path)
    {
        
$path $this->normalizePath($path);
        if (
!== $this->process->execute('git diff HEAD'$output$path)) {
            throw new 
\RuntimeException("Could not view diff\n\n:".$output);
        }

        
$this->io->writeError($output);
    }

    
/**
     * @param string $path
     *
     * @return string
     */
    
protected function normalizePath($path)
    {
        if (
Platform::isWindows() && strlen($path) > 0) {
            
$basePath $path;
            
$removed = array();

            while (!
is_dir($basePath) && $basePath !== '\\') {
                
array_unshift($removedbasename($basePath));
                
$basePath dirname($basePath);
            }

            if (
$basePath === '\\') {
                return 
$path;
            }

            
$path rtrim(realpath($basePath) . '/' implode('/'$removed), '/');
        }

        return 
$path;
    }

    
/**
     * @inheritDoc
     */
    
protected function hasMetadataRepository($path)
    {
        
$path $this->normalizePath($path);

        return 
is_dir($path.'/.git');
    }

    
/**
     * @param  string $reference
     * @return string
     */
    
protected function getShortHash($reference)
    {
        if (!
$this->io->isVerbose() && Preg::isMatch('{^[0-9a-f]{40}$}'$reference)) {
            return 
substr($reference010);
        }

        return 
$reference;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class TransportException extends \RuntimeException
{
    
/** @var ?array<string> */
    
protected $headers;
    
/** @var ?string */
    
protected $response;
    
/** @var ?int */
    
protected $statusCode;
    
/** @var array<mixed> */
    
protected $responseInfo = array();

    
/**
     * @param array<string> $headers
     *
     * @return void
     */
    
public function setHeaders($headers)
    {
        
$this->headers $headers;
    }

    
/**
     * @return ?array<string>
     */
    
public function getHeaders()
    {
        return 
$this->headers;
    }

    
/**
     * @param ?string $response
     *
     * @return void
     */
    
public function setResponse($response)
    {
        
$this->response $response;
    }

    
/**
     * @return ?string
     */
    
public function getResponse()
    {
        return 
$this->response;
    }

    
/**
     * @param ?int $statusCode
     *
     * @return void
     */
    
public function setStatusCode($statusCode)
    {
        
$this->statusCode $statusCode;
    }

    
/**
     * @return ?int
     */
    
public function getStatusCode()
    {
        return 
$this->statusCode;
    }

    
/**
     * @return array<mixed>
     */
    
public function getResponseInfo()
    {
        return 
$this->responseInfo;
    }

    
/**
     * @param array<mixed> $responseInfo
     *
     * @return void
     */
    
public function setResponseInfo(array $responseInfo)
    {
        
$this->responseInfo $responseInfo;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
abstract class VcsDownloader implements DownloaderInterfaceChangeReportInterfaceVcsCapableDownloaderInterface
{
    
/** @var IOInterface */
    
protected $io;
    
/** @var Config */
    
protected $config;
    
/** @var ProcessExecutor */
    
protected $process;
    
/** @var Filesystem */
    
protected $filesystem;
    
/** @var array<string, true> */
    
protected $hasCleanedChanges = array();

    public function 
__construct(IOInterface $ioConfig $configProcessExecutor $process nullFilesystem $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()
    {
        return 
'source';
    }

    
/**
     * @inheritDoc
     */
    
public function download(PackageInterface $package$pathPackageInterface $prevPackage null)
    {
        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();
    }

    
/**
     * @inheritDoc
     */
    
public function prepare($typePackageInterface $package$pathPackageInterface $prevPackage null)
    {
        if (
$type === 'update') {
            
$this->cleanChanges($prevPackage$pathtrue);
            
$this->hasCleanedChanges[$prevPackage->getUniqueName()] = true;
        } elseif (
$type === 'install') {
            
$this->filesystem->emptyDirectory($path);
        } elseif (
$type === 'uninstall') {
            
$this->cleanChanges($package$pathfalse);
        }

        return 
\React\Promise\resolve();
    }

    
/**
     * @inheritDoc
     */
    
public function cleanup($typePackageInterface $package$pathPackageInterface $prevPackage null)
    {
        if (
$type === 'update' && isset($this->hasCleanedChanges[$prevPackage->getUniqueName()])) {
            
$this->reapplyChanges($path);
            unset(
$this->hasCleanedChanges[$prevPackage->getUniqueName()]);
        }

        return 
\React\Promise\resolve();
    }

    
/**
     * @inheritDoc
     */
    
public function install(PackageInterface $package$path)
    {
        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();
    }

    
/**
     * @inheritDoc
     */
    
public function update(PackageInterface $initialPackageInterface $target$path)
    {
        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(function ($line) {
                    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();
    }

    
/**
     * @inheritDoc
     */
    
public function remove(PackageInterface $package$path)
    {
        
$this->io->writeError("  - " UninstallOperation::format($package));

        
$promise $this->filesystem->removeDirectoryAsync($path);

        return 
$promise->then(function ($result) use ($path) {
            if (!
$result) {
                throw new 
\RuntimeException('Could not completely delete '.$path.', aborting.');
            }
        });
    }

    
/**
     * @inheritDoc
     */
    
public function getVcsReference(PackageInterface $package$path)
    {
        
$parser = new VersionParser;
        
$guesser = new VersionGuesser($this->config$this->process$parser);
        
$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  PackageInterface  $package
     * @param  string            $path
     * @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
     *
     * @return PromiseInterface
     *
     * @throws \RuntimeException in case the operation must be aborted
     */
    
protected function cleanChanges(PackageInterface $package$path$update)
    {
        
// 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();
    }

    
/**
     * Reapply previously stashes changes if applicable, only called after an update (regardless if successful or not)
     *
     * @param string $path
     *
     * @return void
     *
     * @throws \RuntimeException in case the operation must be aborted or the patch does not apply cleanly
     */
    
protected function reapplyChanges($path)
    {
    }

    
/**
     * 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)
     *
     * @return PromiseInterface|null
     */
    
abstract protected function doDownload(PackageInterface $package$path$urlPackageInterface $prevPackage null);

    
/**
     * Downloads specific package into specific folder.
     *
     * @param PackageInterface $package package instance
     * @param string           $path    download path
     * @param string           $url     package url
     *
     * @return PromiseInterface|null
     */
    
abstract protected function doInstall(PackageInterface $package$path$url);

    
/**
     * 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
     *
     * @return PromiseInterface|null
     */
    
abstract protected function doUpdate(PackageInterface $initialPackageInterface $target$path$url);

    
/**
     * 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
     * @return string
     */
    
abstract protected function getCommitLogs($fromReference$toReference$path);

    
/**
     * Checks if VCS metadata repository has been initialized
     * repository example: .git|.svn|.hg
     *
     * @param  string $path
     * @return bool
     */
    
abstract protected function hasMetadataRepository($path);

    
/**
     * @param string[] $urls
     *
     * @return string[]
     */
    
private function prepareUrls(array $urls)
    {
        foreach (
$urls as $index => $url) {
            if (
Filesystem::isLocalPath($url)) {
                
// realpath() below will not understand
                // url that starts with "file://"
                
$fileProtocol 'file://';
                
$isFileProtocol false;
                if (
=== strpos($url$fileProtocol)) {
                    
$url substr($urlstrlen($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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
interface VcsCapableDownloaderInterface
{
    
/**
     * Gets the VCS Reference for the package at path
     *
     * @param  PackageInterface $package package directory
     * @param  string           $path    package directory
     * @return string|null      reference or null
     */
    
public function getVcsReference(PackageInterface $package$path);
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class StrictConfirmationQuestion extends Question
{
    
/** @var string */
    
private $trueAnswerRegex;
    
/** @var 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 string $trueAnswerRegex  A regex to match the "yes" answer
     * @param string $falseAnswerRegex A regex to match the "no" answer
     */
    
public function __construct($question$default true$trueAnswerRegex '/^y(?:es)?$/i'$falseAnswerRegex '/^no?$/i')
    {
        
parent::__construct($question, (bool) $default);

        
$this->trueAnswerRegex $trueAnswerRegex;
        
$this->falseAnswerRegex $falseAnswerRegex;
        
$this->setNormalizer($this->getDefaultNormalizer());
        
$this->setValidator($this->getDefaultValidator());
    }

    
/**
     * Returns the default answer normalizer.
     *
     * @return callable
     */
    
private function getDefaultNormalizer()
    {
        
$default $this->getDefault();
        
$trueRegex $this->trueAnswerRegex;
        
$falseRegex $this->falseAnswerRegex;

        return 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.
     *
     * @return callable
     */
    
private function getDefaultValidator()
    {
        return function (
$answer) {
            if (!
is_bool($answer)) {
                throw new 
InvalidArgumentException('Please answer yes, y, no, or n.');
            }

            return 
$answer;
        };
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
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 InputInterface  $input
     * @param OutputInterface $output
     * @param mixed[]         $args        Arguments passed by the user
     * @param mixed[]         $flags       Optional flags to pass data not as argument
     */
    
public function __construct($name$commandName$input$output, array $args = array(), array $flags = array())
    {
        
parent::__construct($name$args$flags);
        
$this->commandName $commandName;
        
$this->input $input;
        
$this->output $output;
    }

    
/**
     * Returns the command input interface
     *
     * @return InputInterface
     */
    
public function getInput()
    {
        return 
$this->input;
    }

    
/**
     * Retrieves the command output interface
     *
     * @return OutputInterface
     */
    
public function getOutput()
    {
        return 
$this->output;
    }

    
/**
     * Retrieves the name of the command being run
     *
     * @return string
     */
    
public function getCommandName()
    {
        return 
$this->commandName;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
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($name$fileName$checksum$url$type$context null)
    {
        
/** @phpstan-ignore-next-line */
        
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.
     *
     * @return string|null
     */
    
public function getFileName()
    {
        return 
$this->fileName;
    }

    
/**
     * Gets the checksum.
     *
     * @return string|null
     */
    
public function getChecksum()
    {
        return 
$this->checksum;
    }

    
/**
     * Gets the processed URL.
     *
     * @return string
     */
    
public function getUrl()
    {
        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()
    {
        
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).
     *
     * @return string
     */
    
public function getType()
    {
        return 
$this->type;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
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
     */
    
const INIT 'init';

    
/**
     * The COMMAND event occurs as a command begins
     *
     * The event listener method receives a
     * Composer\Plugin\CommandEvent instance.
     *
     * @var string
     */
    
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
     */
    
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
     */
    
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
     */
    
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
     */
    
const PRE_POOL_CREATE 'pre-pool-create';
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class PreFileDownloadEvent extends Event
{
    
/**
     * @var HttpDownloader
     */
    
private $httpDownloader;

    
/**
     * @var string
     */
    
private $processedUrl;

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

    
/**
     * @var string
     */
    
private $type;

    
/**
     * @var mixed
     */
    
private $context;

    
/**
     * @var mixed[]
     */
    
private $transportOptions = array();

    
/**
     * Constructor.
     *
     * @param string         $name           The event name
     * @param HttpDownloader $httpDownloader
     * @param string         $processedUrl
     * @param string         $type
     * @param mixed          $context
     */
    
public function __construct($nameHttpDownloader $httpDownloader$processedUrl$type$context null)
    {
        
parent::__construct($name);
        
$this->httpDownloader $httpDownloader;
        
$this->processedUrl $processedUrl;
        
$this->type $type;
        
$this->context $context;
    }

    
/**
     * @return HttpDownloader
     */
    
public function getHttpDownloader()
    {
        return 
$this->httpDownloader;
    }

    
/**
     * Retrieves the processed URL that will be downloaded.
     *
     * @return string
     */
    
public function getProcessedUrl()
    {
        return 
$this->processedUrl;
    }

    
/**
     * Sets the processed URL that will be downloaded.
     *
     * @param string $processedUrl New processed URL
     *
     * @return void
     */
    
public function setProcessedUrl($processedUrl)
    {
        
$this->processedUrl $processedUrl;
    }

    
/**
     * Retrieves a custom package cache key for this download.
     *
     * @return string|null
     */
    
public function getCustomCacheKey()
    {
        return 
$this->customCacheKey;
    }

    
/**
     * Sets a custom package cache key for this download.
     *
     * @param string|null $customCacheKey New cache key
     *
     * @return void
     */
    
public function setCustomCacheKey($customCacheKey)
    {
        
$this->customCacheKey $customCacheKey;
    }

    
/**
     * Returns the type of this download (package, metadata).
     *
     * @return string
     */
    
public function getType()
    {
        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()
    {
        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
     *
     * @return void
     */
    
public function setTransportOptions(array $options)
    {
        
$this->transportOptions $options;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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é <[email protected]>
 */
interface CommandProvider extends Capability
{
    
/**
     * Retrieves an array of commands
     *
     * @return \Composer\Command\BaseCommand[]
     */
    
public function getCommands();
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
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($name, array $repositoriesRequest $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()
    {
        return 
$this->repositories;
    }

    
/**
     * @return Request
     */
    
public function getRequest()
    {
        return 
$this->request;
    }

    
/**
     * @return int[] array of stability => BasePackage::STABILITY_* value
     * @phpstan-return array<string, BasePackage::STABILITY_*>
     */
    
public function getAcceptableStabilities()
    {
        return 
$this->acceptableStabilities;
    }

    
/**
     * @return int[] array of package name => BasePackage::STABILITY_* value
     * @phpstan-return array<string, BasePackage::STABILITY_*>
     */
    
public function getStabilityFlags()
    {
        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()
    {
        return 
$this->rootAliases;
    }

    
/**
     * @return string[]
     * @phpstan-return array<string, string>
     */
    
public function getRootReferences()
    {
        return 
$this->rootReferences;
    }

    
/**
     * @return BasePackage[]
     */
    
public function getPackages()
    {
        return 
$this->packages;
    }

    
/**
     * @return BasePackage[]
     */
    
public function getUnacceptableFixedPackages()
    {
        return 
$this->unacceptableFixedPackages;
    }

    
/**
     * @param BasePackage[] $packages
     *
     * @return void
     */
    
public function setPackages(array $packages)
    {
        
$this->packages $packages;
    }

    
/**
     * @param BasePackage[] $packages
     *
     * @return void
     */
    
public function setUnacceptableFixedPackages(array $packages)
    {
        
$this->unacceptableFixedPackages $packages;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
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
     */
    
const PLUGIN_API_VERSION '2.2.0';

    
/**
     * Apply plugin modifications to Composer
     *
     * @param Composer    $composer
     * @param IOInterface $io
     *
     * @return void
     */
    
public function activate(Composer $composerIOInterface $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.
     *
     * @param Composer    $composer
     * @param IOInterface $io
     *
     * @return void
     */
    
public function deactivate(Composer $composerIOInterface $io);

    
/**
     * Prepare the plugin to be uninstalled
     *
     * This will be called after deactivate.
     *
     * @param Composer    $composer
     * @param IOInterface $io
     *
     * @return void
     */
    
public function uninstall(Composer $composerIOInterface $io);
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\Package;
use 
Composer\Package\Version\VersionParser;
use 
Composer\Pcre\Preg;
use 
Composer\Repository\RepositoryInterface;
use 
Composer\Repository\InstalledRepository;
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 <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 */
class PluginManager
{
    
/** @var Composer */
    
protected $composer;
    
/** @var IOInterface */
    
protected $io;
    
/** @var ?Composer */
    
protected $globalComposer;
    
/** @var VersionParser */
    
protected $versionParser;
    
/** @var bool */
    
protected $disablePlugins false;

    
/** @var array<PluginInterface> */
    
protected $plugins = array();
    
/** @var array<string, PluginInterface|InstallerInterface> */
    
protected $registeredPlugins = array();

    
/**
     * @var array<string, bool>|null
     */
    
private $allowPluginRules;

    
/**
     * @var array<string, bool>|null
     */
    
private $allowGlobalPluginRules;

    
/** @var int */
    
private static $classCounter 0;

    
/**
     * Initializes plugin manager
     *
     * @param IOInterface $io
     * @param Composer    $composer
     * @param Composer    $globalComposer
     * @param bool        $disablePlugins
     */
    
public function __construct(IOInterface $ioComposer $composerComposer $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'));
        
$this->allowGlobalPluginRules $this->parseAllowedPlugins($globalComposer !== null $globalComposer->getConfig()->get('allow-plugins') : false);
    }

    
/**
     * Loads all plugins from currently installed plugin packages
     *
     * @return void
     */
    
public function loadInstalledPlugins()
    {
        if (
$this->disablePlugins) {
            return;
        }

        
$repo $this->composer->getRepositoryManager()->getLocalRepository();
        
$globalRepo $this->globalComposer !== null $this->globalComposer->getRepositoryManager()->getLocalRepository() : null;
        
$this->loadRepository($repofalse);
        if (
$globalRepo) {
            
$this->loadRepository($globalRepotrue);
        }
    }

    
/**
     * Deactivate all plugins from currently installed plugin packages
     *
     * @return void
     */
    
public function deactivateInstalledPlugins()
    {
        if (
$this->disablePlugins) {
            return;
        }

        
$repo $this->composer->getRepositoryManager()->getLocalRepository();
        
$globalRepo $this->globalComposer $this->globalComposer->getRepositoryManager()->getLocalRepository() : null;
        
$this->deactivateRepository($repofalse);
        if (
$globalRepo) {
            
$this->deactivateRepository($globalRepotrue);
        }
    }

    
/**
     * Gets all currently active plugin instances
     *
     * @return array<PluginInterface> plugins
     */
    
public function getPlugins()
    {
        return 
$this->plugins;
    }

    
/**
     * Gets global composer or null when main composer is not fully loaded
     *
     * @return Composer|null
     */
    
public function getGlobalComposer()
    {
        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 PackageInterface $package
     * @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
     *
     * @return void
     *
     * @throws \UnexpectedValueException
     */
    
public function registerPackage(PackageInterface $package$failOnMissingClasses false$isGlobalPlugin false)
    {
        if (
$this->disablePlugins) {
            return;
        }

        if (!
$this->isPluginAllowed($package->getName(), $isGlobalPlugin)) {
            
$this->io->writeError('Skipped loading "'.$package->getName() . '" '.($isGlobalPlugin '(installed globally) ' '').'as it is not in config.allow-plugins'trueIOInterface::DEBUG);
            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 '(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 '(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;
            }
        }

        
$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'] : array($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'] = array();
        
$rootPackage->setAutoload($rootPackageAutoloads);
        
$rootPackageAutoloads $rootPackage->getDevAutoload();
        
$rootPackageAutoloads['files'] = array();
        
$rootPackage->setDevAutoload($rootPackageAutoloads);
        unset(
$rootPackageAutoloads);

        
$rootPackageRepo = new RootPackageRepository($rootPackage);
        
$installedRepo = new InstalledRepository(array($localRepo$rootPackageRepo));
        if (
$globalRepo) {
            
$installedRepo->addRepository($globalRepo);
        }

        
$autoloadPackages = array($package->getName() => $package);
        
$autoloadPackages $this->collectDependencies($installedRepo$autoloadPackages$package);

        
$generator $this->composer->getAutoloadGenerator();
        
$autoloads = array(array($rootPackage''));
        foreach (
$autoloadPackages as $autoloadPackage) {
            if (
$autoloadPackage === $rootPackage) {
                continue;
            }

            
$downloadPath $this->getInstallPath($autoloadPackage$globalRepo && $globalRepo->hasPackage($autoloadPackage));
            
$autoloads[] = array($autoloadPackage$downloadPath);
        }

        
$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($classfalse)) {
                
$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\s+)?(?:\s*))class\s+('.preg_quote($className).')}mi''$1class $2_composer_tmp'.self::$classCounter$code1);
                
$code strtr($code, array(
                    
'__FILE__' => var_export($pathtrue),
                    
'__DIR__' => var_export(dirname($path), true),
                    
'__CLASS__' => var_export($classtrue),
                ));
                
$code Preg::replace('/^\s*<\?(php)?/i'''$code1);
                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 '(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
     *
     * @param PackageInterface $package
     *
     * @return void
     *
     * @throws \UnexpectedValueException
     */
    
public function deactivatePackage(PackageInterface $package)
    {
        if (
$this->disablePlugins) {
            return;
        }

        if (!isset(
$this->registeredPlugins[$package->getName()])) {
            return;
        }

        
$plugin $this->registeredPlugins[$package->getName()];
        unset(
$this->registeredPlugins[$package->getName()]);
        if (
$plugin instanceof InstallerInterface) {
            
$this->composer->getInstallationManager()->removeInstaller($plugin);
        } else {
            
$this->removePlugin($plugin);
        }
    }

    
/**
     * Uninstall a plugin package
     *
     * If it's of type composer-installer it is unregistered from the installers
     * instead for BC
     *
     * @param PackageInterface $package
     *
     * @return void
     *
     * @throws \UnexpectedValueException
     */
    
public function uninstallPackage(PackageInterface $package)
    {
        if (
$this->disablePlugins) {
            return;
        }

        if (!isset(
$this->registeredPlugins[$package->getName()])) {
            return;
        }

        
$plugin $this->registeredPlugins[$package->getName()];
        if (
$plugin instanceof InstallerInterface) {
            
$this->deactivatePackage($package);
        } else {
            unset(
$this->registeredPlugins[$package->getName()]);
            
$this->removePlugin($plugin);
            
$this->uninstallPlugin($plugin);
        }
    }

    
/**
     * Returns the version of the internal composer-plugin-api package.
     *
     * @return string
     */
    
protected function getPluginApiVersion()
    {
        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 bool              $isGlobalPlugin
     * @param ?PackageInterface $sourcePackage Package from which the plugin comes from
     *
     * @return void
     */
    
public function addPlugin(PluginInterface $plugin$isGlobalPlugin falsePackageInterface $sourcePackage null)
    {
        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)) {
            
$this->io->writeError('Skipped loading "'.get_class($plugin).' from '.$sourcePackage->getName() . '" '.($isGlobalPlugin '(installed globally) ' '').' as it is not in config.allow-plugins'trueIOInterface::DEBUG);
            return;
        }

        
$details = array();
        if (
$sourcePackage) {
            
$details[] = 'from '.$sourcePackage->getName();
        }
        if (
$isGlobalPlugin) {
            
$details[] = 'installed globally';
        }
        
$this->io->writeError('Loading plugin '.get_class($plugin).($details ' ('.implode(', '$details).')' ''), trueIOInterface::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
     *
     * @return void
     */
    
public function removePlugin(PluginInterface $plugin)
    {
        
$index array_search($plugin$this->pluginstrue);
        if (
$index === false) {
            return;
        }

        
$this->io->writeError('Unloading plugin '.get_class($plugin), trueIOInterface::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
     *
     * @return void
     */
    
public function uninstallPlugin(PluginInterface $plugin)
    {
        
$this->io->writeError('Uninstalling plugin '.get_class($plugin), trueIOInterface::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
     * @param bool                $isGlobalRepo
     *
     * @return void
     *
     * @throws \RuntimeException
     */
    
private function loadRepository(RepositoryInterface $repo$isGlobalRepo)
    {
        
$packages $repo->getPackages();
        
$sortedPackages PackageSorter::sortPackages($packages);
        foreach (
$sortedPackages as $package) {
            if (!(
$package instanceof CompletePackage)) {
                continue;
            }
            if (
'composer-plugin' === $package->getType()) {
                
$this->registerPackage($packagefalse$isGlobalRepo);
            
// Backward compatibility
            
} elseif ('composer-installer' === $package->getType()) {
                
$this->registerPackage($packagefalse$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
     * @param bool                $isGlobalRepo
     *
     * @return void
     */
    
private function deactivateRepository(RepositoryInterface $repo$isGlobalRepo)
    {
        
$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 $collectedPackageInterface $package)
    {
        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 PackageInterface $package
     * @param bool             $global  Whether this is a global package
     *
     * @return string Install path
     */
    
private function getInstallPath(PackageInterface $package$global false)
    {
        if (!
$global) {
            return 
$this->composer->getInstallationManager()->getInstallPath($package);
        }

        return 
$this->globalComposer->getInstallationManager()->getInstallPath($package);
    }

    
/**
     * @param  PluginInterface   $plugin
     * @param  string            $capability
     * @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$capability)
    {
        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  PluginInterface               $plugin
     * @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 null|Capability
     * @phpstan-param class-string<CapabilityClass> $capabilityClassName
     * @phpstan-return null|CapabilityClass
     */
    
public function getPluginCapability(PluginInterface $plugin$capabilityClassName, array $ctorArgs = array())
    {
        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 = array();
        foreach (
$this->getPlugins() as $plugin) {
            if (
$capability $this->getPluginCapability($plugin$capabilityClassName$ctorArgs)) {
                
$capabilities[] = $capability;
            }
        }

        return 
$capabilities;
    }

    
/**
     * @param  array<string, bool>|bool|null $allowPluginsConfig
     * @return array<string, bool>|null
     */
    
private function parseAllowedPlugins($allowPluginsConfig)
    {
        if (
null === $allowPluginsConfig) {
            return 
null;
        }

        if (
true === $allowPluginsConfig) {
            return array(
'{}' => true);
        }

        if (
false === $allowPluginsConfig) {
            return array(
'{^$}D' => false);
        }

        
$rules = array();
        foreach (
$allowPluginsConfig as $pattern => $allow) {
            
$rules[BasePackage::packageNameToRegexp($pattern)] = $allow;
        }

        return 
$rules;
    }

    
/**
     * @param string $package
     * @param bool $isGlobalPlugin
     * @return bool
     */
    
private function isPluginAllowed($package$isGlobalPlugin)
    {
        static 
$warned = array();
        
$rules $isGlobalPlugin $this->allowGlobalPluginRules $this->allowPluginRules;

        if (
$rules === null) {
            if (!
$this->io->isInteractive()) {
                if (!isset(
$warned['all'])) {
                    
$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>You have until July 2022 to add the setting. Composer will then switch the default behavior to disallow all plugins.</warning>');
                    
$warned['all'] = true;
                }

                
// if no config is defined we allow all plugins for BC
                
return true;
            }

            
// keep going and prompt the user
            
$rules = array();
        }

        foreach (
$rules as $pattern => $allow) {
            if (
Preg::isMatch($pattern$package)) {
                return 
$allow === true;
            }
        }

        if (
$package === 'composer/package-versions-deprecated') {
            return 
false;
        }

        if (!isset(
$warned[$package])) {
            if (
$this->io->isInteractive()) {
                
$composer $isGlobalPlugin && $this->globalComposer !== null $this->globalComposer $this->composer;

                
$this->io->writeError('<warning>'.$package.($isGlobalPlugin ' (installed globally)' '').' contains a Composer plugin which is currently not in your allow-plugins config. See https://getcomposer.org/allow-plugins</warning>');
                while (
true) {
                    switch (
$answer $this->io->ask('Do you trust "<info>'.$package.'</info>" to execute code and wish to enable it now? (writes "allow-plugins" to composer.json) [<comment>y,n,d,?</comment>] ''?')) {
                        case 
'y':
                        case 
'n':
                        case 
'd':
                            
$allow $answer === 'y';

                            
// persist answer in current rules to avoid prompting again if the package gets reloaded
                            
if ($isGlobalPlugin) {
                                
$this->allowGlobalPluginRules[BasePackage::packageNameToRegexp($package)] = $allow;
                            } else {
                                
$this->allowPluginRules[BasePackage::packageNameToRegexp($package)] = $allow;
                            }

                            
// persist answer in composer.json if it wasn't simply discarded
                            
if ($answer === 'y' || $answer === 'n') {
                                
$composer->getConfig()->getConfigSource()->addConfigSetting('allow-plugins.'.$package$allow);
                            }

                            return 
$allow;

                        case 
'?':
                        default:
                            
$this->io->writeError(array(
                                
'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;
                    }
                }
            } else {
                
$this->io->writeError('<warning>'.$package.($isGlobalPlugin ' (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. See https://getcomposer.org/allow-plugins</warning>');
                
$this->io->writeError('<warning>You can run "composer '.($isGlobalPlugin 'global ' '').'config --no-plugins allow-plugins.'.$package.' [true|false]" to enable it (true) or keep it disabled and suppress this warning (false)</warning>');
            }
            
$warned[$package] = true;
        }

        return 
false;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class PreCommandRunEvent extends Event
{
    
/**
     * @var InputInterface
     */
    
private $input;

    
/**
     * @var string
     */
    
private $command;

    
/**
     * Constructor.
     *
     * @param string         $name    The event name
     * @param InputInterface $input
     * @param string         $command The command about to be executed
     */
    
public function __construct($nameInputInterface $input$command)
    {
        
parent::__construct($name);
        
$this->input $input;
        
$this->command $command;
    }

    
/**
     * Returns the console input
     *
     * @return InputInterface
     */
    
public function getInput()
    {
        return 
$this->input;
    }

    
/**
     * Returns the command about to be executed
     *
     * @return string
     */
    
public function getCommand()
    {
        return 
$this->command;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 = array();

    
/**
     * @var (null|object)[]
     */
    
protected $rules = array();

    
/**
     * @var bool
     */
    
protected $noproxy;

    
/**
     * @param string $pattern NO_PROXY pattern
     */
    
public function __construct($pattern)
    {
        
$this->hostNames Preg::split('{[\s,]+}'$pattern, -1PREG_SPLIT_NO_EMPTY);
        
$this->noproxy = empty($this->hostNames) || '*' === $this->hostNames[0];
    }

    
/**
     * Returns true if a URL matches the NO_PROXY pattern
     *
     * @param string $url
     *
     * @return bool
     */
    
public function test($url)
    {
        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
     *
     * @param string $url
     *
     * @return bool|stdClass
     */
    
protected function getUrlData($url)
    {
        if (!
$host parse_url($urlPHP_URL_HOST)) {
            return 
false;
        }

        
$port parse_url($urlPHP_URL_PORT);

        if (empty(
$port)) {
            switch (
parse_url($urlPHP_URL_SCHEME)) {
                case 
'http':
                    
$port 80;
                    break;
                case 
'https':
                    
$port 443;
                    break;
            }
        }

        
$hostName $host . ($port ':' $port '');
        list(
$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
     *
     * @param int      $index
     * @param string   $hostName
     * @param stdClass $url
     *
     * @return bool
     */
    
protected function match($index$hostName$url)
    {
        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
     *
     * @param stdClass $network
     * @param stdClass $target
     *
     * @return bool
     */
    
protected function matchRange(stdClass $networkstdClass $target)
    {
        
$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
     *
     * @param int    $index
     * @param string $hostName
     *
     * @return null|stdClass Null if the hostname is invalid
     */
    
private function getRule($index$hostName)
    {
        if (
array_key_exists($index$this->rules)) {
            return 
$this->rules[$index];
        }

        
$this->rules[$index] = null;
        list(
$host$port$err) = $this->splitHostPort($hostName);

        if (
$err || !$this->ipCheckData($host$ipdatatrue)) {
            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 string        $host
     * @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($host, &$ipdata$allowPrefix false)
    {
        
$ipdata null;
        
$netmask null;
        
$prefix null;
        
$modified false;

        
// Check for a CIDR prefix-length
        
if (strpos($host'/') !== false) {
            list(
$host$prefix) = explode('/'$host);

            if (!
$allowPrefix || !$this->validateInt($prefix0128)) {
                return 
false;
            }
            
$prefix = (int) $prefix;
            
$modified true;
        }

        
// See if this is an ip address
        
if (!filter_var($hostFILTER_VALIDATE_IP)) {
            return !
$modified;
        }

        list(
$ip$size) = $this->ipGetAddr($host);

        if (
$prefix !== null) {
            
// Check for a valid prefix
            
if ($prefix $size 8) {
                return 
false;
            }

            list(
$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.
     *
     * @param string $host
     *
     * @return mixed[] in_addr, size
     */
    
private function ipGetAddr($host)
    {
        
$ip inet_pton($host);
        
$size strlen($ip);
        
$mapped $this->ipMapTo6($ip$size);

        return array(
$mapped$size);
    }

    
/**
     * Returns the binary network mask mapped to IPv6
     *
     * @param int $prefix CIDR prefix-length
     * @param int $size   Byte size of in_addr
     *
     * @return string
     */
    
private function ipGetMask($prefix$size)
    {
        
$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$sizechr(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($rangeIp$size$prefix)
    {
        
$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 array(
$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($binary$size)
    {
        if (
$size === 4) {
            
$prefix str_repeat(chr(0), 10) . str_repeat(chr(255), 2);
            
$binary $prefix $binary;
        }

        return 
$binary;
    }

    
/**
     * Creates a rule data object
     *
     * @param string        $host
     * @param int           $port
     * @param null|stdClass $ipdata
     *
     * @return stdClass
     */
    
private function makeData($host$port$ipdata)
    {
        return (object) array(
            
'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
     *
     * @return stdClass
     */
    
private function makeIpData($ip$size$netmask)
    {
        return (object) array(
            
'ip' => $ip,
            
'size' => $size,
            
'netmask' => $netmask,
        );
    }

    
/**
     * Splits the hostname into host and port components
     *
     * @param string $hostName
     *
     * @return mixed[] host, port, if there was error
     */
    
private function splitHostPort($hostName)
    {
        
// host, port, err
        
$error = array(''''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($hostName1$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($hostName0$index);

            if (!
$this->validateInt($port165535)) {
                return 
$error;
            }

            
$port = (int) $port;
        }

        
$host $ip6 $hostName;

        return array(
$host$portfalse);
    }

    
/**
     * Wrapper around filter_var FILTER_VALIDATE_INT
     *
     * @param string $int
     * @param int    $min
     * @param int    $max
     *
     * @return bool
     */
    
private function validateInt($int$min$max)
    {
        
$options = array(
            
'options' => array(
                
'min_range' => $min,
                
'max_range' => $max,
            ),
        );

        return 
false !== filter_var($intFILTER_VALIDATE_INT$options);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\Process;
use 
Symfony\Component\Process\Exception\RuntimeException;
use 
React\Promise\Promise;
use 
React\Promise\PromiseInterface;

/**
 * @author Robert Schönthal <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 */
class ProcessExecutor
{
    const 
STATUS_QUEUED 1;
    const 
STATUS_STARTED 2;
    const 
STATUS_COMPLETED 3;
    const 
STATUS_FAILED 4;
    const 
STATUS_ABORTED 5;

    
/** @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 = array();
    
/** @var int */
    
private $runningJobs 0;
    
/** @var int */
    
private $maxJobs 10;
    
/** @var int */
    
private $idGen 0;
    
/** @var bool */
    
private $allowAsync false;

    public function 
__construct(IOInterface $io null)
    {
        
$this->io $io;
    }

    
/**
     * runs a process on the commandline
     *
     * @param  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  ?string $cwd     the working directory
     * @return int     statuscode
     */
    
public function execute($command, &$output null$cwd null)
    {
        if (
func_num_args() > 1) {
            return 
$this->doExecute($command$cwdfalse$output);
        }

        return 
$this->doExecute($command$cwdfalse);
    }

    
/**
     * runs a process on the commandline in TTY mode
     *
     * @param  string  $command the command to execute
     * @param  ?string $cwd     the working directory
     * @return int     statuscode
     */
    
public function executeTty($command$cwd null)
    {
        if (
Platform::isTty()) {
            return 
$this->doExecute($command$cwdtrue);
        }

        return 
$this->doExecute($command$cwdfalse);
    }

    
/**
     * @param  string  $command
     * @param  ?string $cwd
     * @param  bool    $tty
     * @param  mixed   $output
     * @return int
     */
    
private function doExecute($command$cwd$tty, &$output null)
    {
        
$this->outputCommandRun($command$cwdfalse);

        
// TODO in 2.2, these two checks can be dropped as Symfony 4+ supports them out of the box
        // make sure that null translate to the proper directory in case the dir is a symlink
        // and we call a git command, because msysgit does not handle symlinks properly
        
if (null === $cwd && Platform::isWindows() && false !== strpos($command'git') && getcwd()) {
            
$cwd realpath(getcwd());
        }
        if (
null !== $cwd && !is_dir($cwd)) {
            throw new 
\RuntimeException('The given CWD for the process does not exist: '.$cwd);
        }

        
$this->captureOutput func_num_args() > 3;
        
$this->errorOutput '';

        
// TODO in v3, commands should be passed in as arrays of cmd + args
        
if (method_exists('Symfony\Component\Process\Process''fromShellCommandline')) {
            
$process Process::fromShellCommandline($command$cwdnullnull, static::getTimeout());
        } else {
            
/** @phpstan-ignore-next-line */
            
$process = new Process($command$cwdnullnull, static::getTimeout());
        }
        if (!
Platform::isWindows() && $tty) {
            try {
                
$process->setTty(true);
            } catch (
RuntimeException $e) {
                
// ignore TTY enabling errors
            
}
        }

        
$callback is_callable($output) ? $output : array($this'outputHandler');
        
$process->run($callback);

        if (
$this->captureOutput && !is_callable($output)) {
            
$output $process->getOutput();
        }

        
$this->errorOutput $process->getErrorOutput();

        return 
$process->getExitCode();
    }

    
/**
     * starts a process on the commandline in async mode
     *
     * @param  string           $command the command to execute
     * @param  string           $cwd     the working directory
     * @return PromiseInterface
     */
    
public function executeAsync($command$cwd null)
    {
        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 = array(
            
'id' => $this->idGen++,
            
'status' => self::STATUS_QUEUED,
            
'command' => $command,
            
'cwd' => $cwd,
        );

        
$resolver = function ($resolve$reject) use (&$job) {
            
$job['status'] = ProcessExecutor::STATUS_QUEUED;
            
$job['resolve'] = $resolve;
            
$job['reject'] = $reject;
        };

        
$self $this;

        
$canceler = function () use (&$job) {
            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$self) {
            if (
$job['process']->isSuccessful()) {
                
$job['status'] = ProcessExecutor::STATUS_COMPLETED;
            } else {
                
$job['status'] = ProcessExecutor::STATUS_FAILED;
            }

            
// TODO 3.0 this should be done directly on $this when PHP 5.3 is dropped
            
$self->markJobDone();

            return 
$job['process'];
        }, function (
$e) use (&$job$self) {
            
$job['status'] = ProcessExecutor::STATUS_FAILED;

            
$self->markJobDone();

            throw 
$e;
        });
        
$this->jobs[$job['id']] = &$job;

        if (
$this->runningJobs $this->maxJobs) {
            
$this->startJob($job['id']);
        }

        return 
$promise;
    }

    
/**
     * @param  int  $id
     * @return void
     */
    
private function startJob($id)
    {
        
$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$cwdtrue);

        
// TODO in 2.2, these two checks can be dropped as Symfony 4+ supports them out of the box
        // make sure that null translate to the proper directory in case the dir is a symlink
        // and we call a git command, because msysgit does not handle symlinks properly
        
if (null === $cwd && Platform::isWindows() && false !== strpos($command'git') && getcwd()) {
            
$cwd realpath(getcwd());
        }
        if (
null !== $cwd && !is_dir($cwd)) {
            throw new 
\RuntimeException('The given CWD for the process does not exist: '.$cwd);
        }

        try {
            
// TODO in v3, commands should be passed in as arrays of cmd + args
            
if (method_exists('Symfony\Component\Process\Process''fromShellCommandline')) {
                
$process Process::fromShellCommandline($command$cwdnullnull, static::getTimeout());
            } else {
                
$process = new Process($command$cwdnullnull, static::getTimeout());
            }
        } catch (
\Exception $e) {
            
call_user_func($job['reject'], $e);

            return;
        } catch (
\Throwable $e) {
            
call_user_func($job['reject'], $e);

            return;
        }

        
$job['process'] = $process;

        try {
            
$process->start();
        } catch (
\Exception $e) {
            
call_user_func($job['reject'], $e);

            return;
        } catch (
\Throwable $e) {
            
call_user_func($job['reject'], $e);

            return;
        }
    }

    
/**
     * @param  ?int $index job id
     * @return void
     */
    
public function wait($index null)
    {
        while (
true) {
            if (!
$this->countActiveJobs($index)) {
                return;
            }

            
usleep(1000);
        }
    }

    
/**
     * @internal
     *
     * @return void
     */
    
public function enableAsync()
    {
        
$this->allowAsync true;
    }

    
/**
     * @internal
     *
     * @param  ?int $index job id
     * @return int         number of active (queued or started) jobs
     */
    
public function countActiveJobs($index null)
    {
        
// 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 0;
        }

        
$active 0;
        foreach (
$this->jobs as $job) {
            if (
$job['status'] < self::STATUS_COMPLETED) {
                
$active++;
            } else {
                unset(
$this->jobs[$job['id']]);
            }
        }

        return 
$active;
    }

    
/**
     * @private
     *
     * @return void
     */
    
public function markJobDone()
    {
        
$this->runningJobs--;
    }

    
/**
     * @param  ?string  $output
     * @return string[]
     */
    
public function splitLines($output)
    {
        
$output trim((string) $output);

        return 
$output === '' ? array() : Preg::split('{\r?\n}'$output);
    }

    
/**
     * Get any error output from the last command
     *
     * @return string
     */
    
public function getErrorOutput()
    {
        return 
$this->errorOutput;
    }

    
/**
     * @private
     *
     * @param Process::ERR|Process::OUT $type
     * @param string                    $buffer
     *
     * @return void
     */
    
public function outputHandler($type$buffer)
    {
        if (
$this->captureOutput) {
            return;
        }

        if (
null === $this->io) {
            echo 
$buffer;

            return;
        }

        if (
Process::ERR === $type) {
            
$this->io->writeErrorRaw($bufferfalse);
        } else {
            
$this->io->writeRaw($bufferfalse);
        }
    }

    
/**
     * @return int the timeout in seconds
     */
    
public static function getTimeout()
    {
        return static::
$timeout;
    }

    
/**
     * @param  int  $timeout the timeout in seconds
     * @return void
     */
    
public static function setTimeout($timeout)
    {
        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)
    {
        return 
self::escapeArgument($argument);
    }

    
/**
     * @param string  $command
     * @param ?string $cwd
     * @param bool    $async
     * @return void
     */
    
private function outputCommandRun($command$cwd$async)
    {
        if (
null === $this->io || !$this->io->isDebug()) {
            return;
        }

        
$safeCommand Preg::replaceCallback('{://(?P<user>[^:/\s]+):(?P<password>[^@\s/]+)@}i', function ($m) {
            
// if the username looks like a long (12char+) hex string, or a modern github token (e.g. ghp_xxx) we obfuscate that
            
if (Preg::isMatch('{^([a-f0-9]{12,}|gh[a-z]_[a-zA-Z0-9_]+)$}'$m['user'])) {
                return 
'://***:***@';
            }
            if (
Preg::isMatch('{^[a-f0-9]{12,}$}'$m['user'])) {
                return 
'://***:***@';
            }

            return 
'://'.$m['user'].':***@';
        }, 
$command);
        
$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 <[email protected]>
     *
     * @param string|false|null $argument
     *
     * @return string
     */
    
private static function escapeArgument($argument)
    {
        if (
'' === ($argument = (string) $argument)) {
            return 
escapeshellarg($argument);
        }

        if (!
Platform::isWindows()) {
            return 
"'".str_replace("'""'\\''"$argument)."'";
        }

        
// New lines break cmd.exe command parsing
        
$argument strtr($argument"\n"' ');

        
$quote strpbrk($argument" \t") !== false;
        
$argument Preg::replace('/(\\\\*)"/''$1$1\\"'$argument, -1$dquotes);
        
$meta $dquotes || 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;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
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 $ioConfig $configProcessExecutor $process)
    {
        
$this->io $io;
        
$this->config $config;
        
$this->process $process;
    }

    
/**
     * @param callable    $commandCallable
     * @param string      $url
     * @param string|null $cwd
     *
     * @return void
     */
    
public function runCommand($commandCallable$url$cwd)
    {
        
$this->config->prohibitUrlByConfig($url$this->io);

        
// Try as is
        
$command call_user_func($commandCallable$url);

        if (
=== $this->process->execute($command$ignoredOutput$cwd)) {
            return;
        }

        
// Try with the authentication information available
        
if (Preg::isMatch('{^(https?)://((.+)(?:\:(.+))?@)?([^/]+)(/.*)?}mi'$url$match) && $this->io->hasAuthentication($match[5])) {
            
$auth $this->io->getAuthentication($match[5]);
            
$authenticatedUrl $match[1] . '://' rawurlencode($auth['username']) . ':' rawurlencode($auth['password']) . '@' $match[5] . (!empty($match[6]) ? $match[6] : null);

            
$command call_user_func($commandCallable$authenticatedUrl);

            if (
=== $this->process->execute($command$ignoredOutput$cwd)) {
                return;
            }

            
$error $this->process->getErrorOutput();
        } else {
            
$error 'The given URL (' $url ') does not match the required format (http(s)://(username:password@)example.com/path-to-repository)';
        }

        
$this->throwException('Failed to clone ' $url ', ' "\n\n" $error$url);
    }

    
/**
     * @param non-empty-string $message
     * @param string           $url
     *
     * @return never
     */
    
private function throwException($message$url)
    {
        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)
    {
        if (
false === self::$version) {
            
self::$version null;
            if (
=== $process->execute('hg --version'$output) && Preg::isMatch('/^.+? (\d+(?:\.\d+)+)\)?\r?\n/'$output$matches)) {
                
self::$version $matches[1];
            }
        }

        return 
self::$version;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\Process;

/**
 * @author Matt Whittom <[email protected]>
 *
 * @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
     * @param string             $port
     * @param string             $path
     * @param ProcessExecutor    $process
     * @param bool               $isWindows
     * @param IOInterface        $io
     */
    
public function __construct($repoConfig$port$pathProcessExecutor $process$isWindowsIOInterface $io)
    {
        
$this->windowsFlag $isWindows;
        
$this->p4Port $port;
        
$this->initializePath($path);
        
$this->process $process;
        
$this->initialize($repoConfig);
        
$this->io $io;
    }

    
/**
     * @phpstan-param RepoConfig $repoConfig
     * @param string             $port
     * @param string             $path
     * @param ProcessExecutor    $process
     * @param IOInterface        $io
     *
     * @return self
     */
    
public static function create($repoConfig$port$pathProcessExecutor $processIOInterface $io)
    {
        return new 
Perforce($repoConfig$port$path$processPlatform::isWindows(), $io);
    }

    
/**
     * @param string          $url
     * @param ProcessExecutor $processExecutor
     *
     * @return bool
     */
    
public static function checkServerExists($urlProcessExecutor $processExecutor)
    {
        
$output null;

        return  
=== $processExecutor->execute('p4 -p ' ProcessExecutor::escape($url) . ' info -s'$output);
    }

    
/**
     * @phpstan-param RepoConfig $repoConfig
     *
     * @return void
     */
    
public function initialize($repoConfig)
    {
        
$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'];
        }
    }

    
/**
     * @param string|null $depot
     * @param string|null $branch
     *
     * @return void
     */
    
public function initializeDepotAndBranch($depot$branch)
    {
        if (isset(
$depot)) {
            
$this->p4Depot $depot;
        }
        if (isset(
$branch)) {
            
$this->p4Branch $branch;
        }
    }

    
/**
     * @return non-empty-string
     */
    
public function generateUniquePerforceClientName()
    {
        return 
gethostname() . "_" time();
    }

    
/**
     * @return void
     */
    
public function cleanupClientSpec()
    {
        
$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
     *
     * @return int
     */
    
protected function executeCommand($command)
    {
        
$this->commandResult '';

        return 
$this->process->execute($command$this->commandResult);
    }

    
/**
     * @return string
     */
    
public function getClient()
    {
        if (!isset(
$this->p4Client)) {
            
$cleanStreamName str_replace(array('//''/''@'), array('''_'''), $this->getStream());
            
$this->p4Client 'composer_perforce_' $this->uniquePerforceClientName '_' $cleanStreamName;
        }

        return 
$this->p4Client;
    }

    
/**
     * @return string
     */
    
protected function getPath()
    {
        return 
$this->path;
    }

    
/**
     * @param string $path
     *
     * @return void
     */
    
public function initializePath($path)
    {
        
$this->path $path;
        
$fs $this->getFilesystem();
        
$fs->ensureDirectoryExists($path);
    }

    
/**
     * @return string
     */
    
protected function getPort()
    {
        return 
$this->p4Port;
    }

    
/**
     * @param string $stream
     *
     * @return void
     */
    
public function setStream($stream)
    {
        
$this->p4Stream $stream;
        
$index strrpos($stream'/');
        
//Stream format is //depot/stream, while non-streaming depot is //depot
        
if ($index 2) {
            
$this->p4DepotType 'stream';
        }
    }

    
/**
     * @return bool
     */
    
public function isStream()
    {
        return 
is_string($this->p4DepotType) && (strcmp($this->p4DepotType'stream') === 0);
    }

    
/**
     * @return string
     */
    
public function getStream()
    {
        if (!isset(
$this->p4Stream)) {
            if (
$this->isStream()) {
                
$this->p4Stream '//' $this->p4Depot '/' $this->p4Branch;
            } else {
                
$this->p4Stream '//' $this->p4Depot;
            }
        }

        return 
$this->p4Stream;
    }

    
/**
     * @param string $stream
     *
     * @return string
     */
    
public function getStreamWithoutLabel($stream)
    {
        
$index strpos($stream'@');
        if (
$index === false) {
            return 
$stream;
        }

        return 
substr($stream0$index);
    }

    
/**
     * @return non-empty-string
     */
    
public function getP4ClientSpec()
    {
        return 
$this->path '/' $this->getClient() . '.p4.spec';
    }

    
/**
     * @return string|null
     */
    
public function getUser()
    {
        return 
$this->p4User;
    }

    
/**
     * @param string|null $user
     *
     * @return void
     */
    
public function setUser($user)
    {
        
$this->p4User $user;
    }

    
/**
     * @return void
     */
    
public function queryP4User()
    {
        
$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 'p4 set P4USER=' $this->p4User;
        } else {
            
$command 'export P4USER=' $this->p4User;
        }
        
$this->executeCommand($command);
    }

    
/**
     * @param  string  $name
     * @return ?string
     */
    
protected function getP4variable($name)
    {
        if (
$this->windowsFlag) {
            
$command 'p4 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;
    }

    
/**
     * @return string|null
     */
    
public function queryP4Password()
    {
        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;
    }

    
/**
     * @param string $command
     * @param bool   $useClient
     *
     * @return non-empty-string
     */
    
public function generateP4Command($command$useClient true)
    {
        
$p4Command 'p4 ';
        
$p4Command .= '-u ' $this->getUser() . ' ';
        if (
$useClient) {
            
$p4Command .= '-c ' $this->getClient() . ' ';
        }
        
$p4Command .= '-p ' $this->getPort() . ' ' $command;

        return 
$p4Command;
    }

    
/**
     * @return bool
     */
    
public function isLoggedIn()
    {
        
$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;
    }

    
/**
     * @return void
     */
    
public function connectClient()
    {
        
$p4CreateClientCommand $this->generateP4Command(
            
'client -i < ' str_replace(" ""\\ "$this->getP4ClientSpec())
        );
        
$this->executeCommand($p4CreateClientCommand);
    }

    
/**
     * @param string|null $sourceReference
     *
     * @return void
     */
    
public function syncCodeBase($sourceReference)
    {
        
$prevDir getcwd();
        
chdir($this->path);
        
$p4SyncCommand $this->generateP4Command('sync -f ');
        if (
null !== $sourceReference) {
            
$p4SyncCommand .= '@' $sourceReference;
        }
        
$this->executeCommand($p4SyncCommand);
        
chdir($prevDir);
    }

    
/**
     * @param resource|false $spec
     *
     * @return void
     */
    
public function writeClientSpecToFile($spec)
    {
        
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
            
);
        }
    }

    
/**
     * @return void
     */
    
public function writeP4ClientSpec()
    {
        
$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
     *
     * @return void
     */
    
protected function read($pipe$name)
    {
        if (
feof($pipe)) {
            return;
        }
        
$line fgets($pipe);
        while (
$line !== false) {
            
$line fgets($pipe);
        }
    }

    
/**
     * @param string|null $password
     *
     * @return int
     */
    
public function windowsLogin($password)
    {
        
$command $this->generateP4Command(' login -a');

        
// TODO in v3 generate command as an array
        
if (method_exists('Symfony\Component\Process\Process''fromShellCommandline')) {
            
$process Process::fromShellCommandline($commandnullnull$password);
        } else {
            
// @phpstan-ignore-next-line
            
$process = new Process($commandnullnull$password);
        }

        return 
$process->run();
    }

    
/**
     * @return void
     */
    
public function p4Login()
    {
        
$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());
                }
            }
        }
    }

    
/**
     * @param string $identifier
     *
     * @return mixed|void
     */
    
public function getComposerInformation($identifier)
    {
        
$composerFileContent $this->getFileContent('composer.json'$identifier);

        if (!
$composerFileContent) {
            return;
        }

        return 
json_decode($composerFileContenttrue);
    }

    
/**
     * @param string $file
     * @param string $identifier
     *
     * @return string|null
     */
    
public function getFileContent($file$identifier)
    {
        
$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;
    }

    
/**
     * @param string $file
     * @param string $identifier
     *
     * @return string|null
     */
    
public function getFilePath($file$identifier)
    {
        
$index strpos($identifier'@');
        if (
$index === false) {
            return 
$identifier'/' $file;
        }

        
$path substr($identifier0$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($identifier0$index) . '/' $file '@' $fields[1];
            }
        }

        return 
null;
    }

    
/**
     * @return array{master: string}
     */
    
public function getBranches()
    {
        
$possibleBranches = array();
        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 array(
'master' => $possibleBranches[$this->p4Branch] . '@'$lastCommitNum);
    }

    
/**
     * @return array<string, string>
     */
    
public function getTags()
    {
        
$command $this->generateP4Command('labels');
        
$this->executeCommand($command);
        
$result $this->commandResult;
        
$resArray explode(PHP_EOL$result);
        
$tags = array();
        foreach (
$resArray as $line) {
            if (
strpos($line'Label') !== false) {
                
$fields explode(' '$line);
                
$tags[$fields[1]] = $this->getStream() . '@' $fields[1];
            }
        }

        return 
$tags;
    }

    
/**
     * @return bool
     */
    
public function checkStream()
    {
        
$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;
    }

    
/**
     * @param  string     $reference
     * @return mixed|null
     */
    
protected function getChangeList($reference)
    {
        
$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];
    }

    
/**
     * @param  string     $fromReference
     * @param  string     $toReference
     * @return mixed|null
     */
    
public function getCommitLogs($fromReference$toReference)
    {
        
$fromChangeList $this->getChangeList($fromReference);
        if (
$fromChangeList === null) {
            return 
null;
        }
        
$toChangeList $this->getChangeList($toReference);
        if (
$toChangeList === null) {
            return 
null;
        }
        
$index strpos($fromReference'@');
        
$main substr($fromReference0$index) . '/...';
        
$command $this->generateP4Command('filelog ' ProcessExecutor::escape($main '@' $fromChangeList',' $toChangeList));
        
$this->executeCommand($command);

        return 
$this->commandResult;
    }

    
/**
     * @return Filesystem
     */
    
public function getFilesystem()
    {
        if (
null === $this->filesystem) {
            
$this->filesystem = new Filesystem($this->process);
        }

        return 
$this->filesystem;
    }

    
/**
     * @return void
     */
    
public function setFilesystem(Filesystem $fs)
    {
        
$this->filesystem $fs;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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;

/**
 * Convert PHP errors into exceptions
 *
 * @author Artem Lopata <[email protected]>
 */
class ErrorHandler
{
    
/** @var ?IOInterface */
    
private static $io;

    
/**
     * 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
     * @return bool
     */
    
public static function handle($level$message$file$line)
    {
        
// error code is not included in error_reporting
        
if (!(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 (
$level !== E_DEPRECATED && $level !== E_USER_DEPRECATED) {
            throw new 
\ErrorException($message0$level$file$line);
        }

        if (
self::$io) {
            
// ignore symfony/* deprecation warnings
            // TODO remove in 2.3
            
if (Preg::isMatch('{^Return type of Symfony\\\\.*ReturnTypeWillChange}is'$message)) {
                return 
true;
            }
            if (
strpos(strtr($file'\\''/'), 'vendor/symfony/') !== false) {
                return 
true;
            }

            
self::$io->writeError('<warning>Deprecation Notice: '.$message.' in '.$file.':'.$line.'</warning>');
            if (
self::$io->isVerbose()) {
                
self::$io->writeError('<warning>Stack trace:</warning>');
                
self::$io->writeError(array_filter(array_map(function ($a) {
                    if (isset(
$a['line'], $a['file'])) {
                        return 
'<warning> '.$a['file'].':'.$a['line'].'</warning>';
                    }

                    return 
null;
                }, 
array_slice(debug_backtrace(), 2))));
            }
        }

        return 
true;
    }

    
/**
     * Register error handler.
     *
     * @param IOInterface|null $io
     *
     * @return void
     */
    
public static function register(IOInterface $io null)
    {
        
set_error_handler(array(__CLASS__'handle'));
        
error_reporting(E_ALL E_STRICT);
        
self::$io $io;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 */
class Svn
{
    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 string                   $url
     * @param \Composer\IO\IOInterface $io
     * @param Config                   $config
     * @param ProcessExecutor          $process
     */
    
public function __construct($urlIOInterface $ioConfig $configProcessExecutor $process null)
    {
        
$this->url $url;
        
$this->io $io;
        
$this->config $config;
        
$this->process $process ?: new ProcessExecutor($io);
    }

    
/**
     * @return void
     */
    
public static function cleanEnv()
    {
        
// 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 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
     * @return string
     */
    
public function execute($command$url$cwd null$path null$verbose false)
    {
        
// 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 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
     * @return string
     */
    
public function executeLocal($command$path$cwd null$verbose false)
    {
        
// A local command has no remote url
        
return $this->executeWithAuthRetry($command$cwd''$path$verbose);
    }

    
/**
     * @param  string $svnCommand
     * @param  string $cwd
     * @param  string $url
     * @param  string $path
     * @param  bool   $verbose
     *
     * @return ?string
     */
    
private function executeWithAuthRetry($svnCommand$cwd$url$path$verbose)
    {
        
// 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 = 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($bufferfalse);
            }
        };
        
$status $this->process->execute($command$handler$cwd);
        if (
=== $status) {
            return 
$output;
        }

        
$errorOutput $this->process->getErrorOutput();
        
$fullOutput implode("\n", array($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.')'
        
);
    }

    
/**
     * @param  bool $cacheCredentials
     * @return void
     */
    
public function setCacheCredentials($cacheCredentials)
    {
        
$this->cacheCredentials $cacheCredentials;
    }

    
/**
     * Repositories requests credentials, let's put them in.
     *
     * @throws \RuntimeException
     * @return \Composer\Util\Svn
     */
    
protected function doAuthDance()
    {
        
// 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 = array(
            
'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 string $cmd  Usually 'svn ls' or something like that.
     * @param string $url  Repo URL.
     * @param string $path Target for a checkout
     *
     * @return string
     */
    
protected function getCommand($cmd$url$path null)
    {
        
$cmd sprintf(
            
'%s %s%s -- %s',
            
$cmd,
            
'--non-interactive ',
            
$this->getCredentialString(),
            
ProcessExecutor::escape($url)
        );

        if (
$path) {
            
$cmd .= ' ' ProcessExecutor::escape($path);
        }

        return 
$cmd;
    }

    
/**
     * Return the credential string for the svn command.
     *
     * Adds --no-auth-cache when credentials are present.
     *
     * @return string
     */
    
protected function getCredentialString()
    {
        if (!
$this->hasAuth()) {
            return 
'';
        }

        return 
sprintf(
            
' %s--username %s --password %s ',
            
$this->getAuthCache(),
            
ProcessExecutor::escape($this->getUsername()),
            
ProcessExecutor::escape($this->getPassword())
        );
    }

    
/**
     * Get the password for the svn command. Can be empty.
     *
     * @throws \LogicException
     * @return string
     */
    
protected function getPassword()
    {
        if (
$this->credentials === null) {
            throw new 
\LogicException("No svn auth detected.");
        }

        return 
$this->credentials['password'];
    }

    
/**
     * Get the username for the svn command.
     *
     * @throws \LogicException
     * @return string
     */
    
protected function getUsername()
    {
        if (
$this->credentials === null) {
            throw new 
\LogicException("No svn auth detected.");
        }

        return 
$this->credentials['username'];
    }

    
/**
     * Detect Svn Auth.
     *
     * @return bool
     */
    
protected function hasAuth()
    {
        if (
null !== $this->hasAuth) {
            return 
$this->hasAuth;
        }

        if (
false === $this->createAuthFromConfig()) {
            
$this->createAuthFromUrl();
        }

        return (bool) 
$this->hasAuth;
    }

    
/**
     * Return the no-auth-cache switch.
     *
     * @return string
     */
    
protected function getAuthCache()
    {
        return 
$this->cacheCredentials '' '--no-auth-cache ';
    }

    
/**
     * Create the auth params from the configuration file.
     *
     * @return bool
     */
    
private function createAuthFromConfig()
    {
        if (!
$this->config->has('http-basic')) {
            return 
$this->hasAuth false;
        }

        
$authConfig $this->config->get('http-basic');

        
$host parse_url($this->urlPHP_URL_HOST);
        if (isset(
$authConfig[$host])) {
            
$this->credentials = array(
                
'username' => $authConfig[$host]['username'],
                
'password' => $authConfig[$host]['password'],
            );

            return 
$this->hasAuth true;
        }

        return 
$this->hasAuth false;
    }

    
/**
     * Create the auth params from the url
     *
     * @return bool
     */
    
private function createAuthFromUrl()
    {
        
$uri parse_url($this->url);
        if (empty(
$uri['user'])) {
            return 
$this->hasAuth false;
        }

        
$this->credentials = array(
            
'username' => $uri['user'],
            
'password' => !empty($uri['pass']) ? $uri['pass'] : '',
        );

        return 
$this->hasAuth true;
    }

    
/**
     * Returns the version of the svn binary contained in PATH
     *
     * @return string|null
     */
    
public function binaryVersion()
    {
        if (!
self::$version) {
            if (
=== $this->process->execute('svn --version'$output)) {
                if (
Preg::isMatch('{(\d+(?:\.\d+)+)}'$output$match)) {
                    
self::$version $match[1];
                }
            }
        }

        return 
self::$version;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
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 $ioConfig $configProcessExecutor $process nullHttpDownloader $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($originUrl)
    {
        
// 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 (=== $this->process->execute('git config gitlab.accesstoken'$output)) {
            
$this->io->setAuthentication($originUrltrim($output), 'oauth2');

            return 
true;
        }

        
// if available use deploy token from git config
        
if (=== $this->process->execute('git config gitlab.deploytoken.user'$tokenUser) && === $this->process->execute('git config gitlab.deploytoken.token'$tokenPassword)) {
            
$this->io->setAuthentication($originUrltrim($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) && array_key_exists("username"$token) ? $token["username"] : $token;
            
$password is_array($token) && array_key_exists("token"$token) ? $token["token"] : 'private-token';
            
$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($scheme$originUrl$message null)
    {
        if (
$message) {
            
$this->io->writeError($message);
        }

        
$this->io->writeError(sprintf('A token will be created and stored in "%s", your password will never be stored'$this->config->getAuthConfigSource()->getName()));
        
$this->io->writeError('To revoke access to this token you can visit '.$scheme.'://'.$originUrl.'/-/profile/personal_access_tokens');

        
$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(), array(403401))) {
                    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 '.$scheme.'://'.$originUrl.'/profile/personal_access_tokens');
                    
$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');

            
// store value in user config in auth file
            
$this->config->getAuthConfigSource()->addConfigSetting('gitlab-oauth.'.$originUrl$response['access_token']);

            return 
true;
        }

        throw new 
\RuntimeException('Invalid GitLab credentials 5 times in a row, aborting.');
    }

    
/**
     * @param string $scheme
     * @param string $originUrl
     *
     * @return array{access_token: non-empty-string, token_type: non-empty-string, expires_in: positive-int}
     *
     * @see https://docs.gitlab.com/ee/api/oauth2.html#resource-owner-password-credentials-flow
     */
    
private function createToken($scheme$originUrl)
    {
        
$username $this->io->ask('Username: ');
        
$password $this->io->askAndHideAnswer('Password: ');

        
$headers = array('Content-Type: application/x-www-form-urlencoded');

        
$apiUrl $originUrl;
        
$data http_build_query(array(
            
'username' => $username,
            
'password' => $password,
            
'grant_type' => 'password',
        ), 
'''&');
        
$options = array(
            
'retry-auth-failure' => false,
            
'http' => array(
                
'method' => 'POST',
                
'header' => $headers,
                
'content' => $data,
            ),
        );

        
$token $this->httpDownloader->get($scheme.'://'.$apiUrl.'/oauth/token'$options)->decodeJson();

        
$this->io->writeError('Token successfully created');

        return 
$token;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class Zip
{
    
/**
     * Gets content of the root composer.json inside a ZIP archive.
     *
     * @param string $pathToZip
     *
     * @return string|null
     */
    
public static function getComposerJson($pathToZip)
    {
        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 (
== $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.
     *
     * @param  \ZipArchive       $zip
     * @param  string            $filename
     * @throws \RuntimeException
     *
     * @return int
     */
    
private static function locateFile(\ZipArchive $zip$filename)
    {
        
// 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 = array();
        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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
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;

    public function 
__construct(IOInterface $ioConfig $configProcessExecutor $processFilesystem $fs)
    {
        
$this->io $io;
        
$this->config $config;
        
$this->process $process;
        
$this->filesystem $fs;
    }

    
/**
     * @param callable    $commandCallable
     * @param string      $url
     * @param string|null $cwd
     * @param bool        $initialClone
     *
     * @return void
     */
    
public function runCommand($commandCallable$url$cwd$initialClone false)
    {
        
// Ensure we are allowed to use this URL by config
        
$this->config->prohibitUrlByConfig($url$this->io);

        if (
$initialClone) {
            
$origCwd $cwd;
            
$cwd null;
        }

        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://[email protected]:22/path or just [email protected]: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::isMatch('{^(?: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');
        if (!
is_array($protocols)) {
            throw new 
\RuntimeException('Config value "github-protocols" must be an array, got ' gettype($protocols));
        }
        
// public github, autoswitch protocols
        
if (Preg::isMatch('{^(?:https?|git)://' self::getGitHubDomainsRegex($this->config) . '/(.*)}'$url$match)) {
            
$messages = array();
            foreach (
$protocols as $protocol) {
                if (
'ssh' === $protocol) {
                    
$protoUrl "git@" $match[1] . ":" $match[2];
                } else {
                    
$protoUrl $protocol "://" $match[1] . "/" $match[2];
                }

                if (
=== $this->process->execute(call_user_func($commandCallable$protoUrl), $ignoredOutput$cwd)) {
                    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'$protocolstrue);

        
$command call_user_func($commandCallable$url);

        
$auth null;
        
$credentials = array();
        if (
$bypassSshForGitHub || !== $this->process->execute($command$ignoredOutput$cwd)) {
            
$errorMsg $this->process->getErrorOutput();
            
// private github repository without ssh key access, try https with auth
            
if (Preg::isMatch('{^git@' self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i'$url$match)
                || 
Preg::isMatch('{^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';
                    
$command call_user_func($commandCallable$authUrl);
                    if (
=== $this->process->execute($command$ignoredOutput$cwd)) {
                        return;
                    }

                    
$credentials = array(rawurlencode($auth['username']), rawurlencode($auth['password']));
                    
$errorMsg $this->process->getErrorOutput();
                }
            } elseif (
Preg::isMatch('{^https://(bitbucket\.org)/(.*?)(?:\.git)?$}i'$url$match)) { //bitbucket oauth
                
$bitbucketUtil = new Bitbucket($this->io$this->config$this->process);

                if (!
$this->io->hasAuthentication($match[1])) {
                    
$message 'Enter your Bitbucket credentials to access private repos';

                    if (!
$bitbucketUtil->authorizeOAuth($match[1]) && $this->io->isInteractive()) {
                        
$bitbucketUtil->authorizeOAuthInteractively($match[1], $message);
                        
$accessToken $bitbucketUtil->getToken();
                        
$this->io->setAuthentication($match[1], 'x-token-auth'$accessToken);
                    }
                } else { 
//We're authenticating with a locally stored consumer.
                    
$auth $this->io->getAuthentication($match[1]);

                    
//We already have an access_token from a previous request.
                    
if ($auth['username'] !== 'x-token-auth') {
                        
$accessToken $bitbucketUtil->requestToken($match[1], $auth['username'], $auth['password']);
                        if (!empty(
$accessToken)) {
                            
$this->io->setAuthentication($match[1], 'x-token-auth'$accessToken);
                        }
                    }
                }

                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';

                    
$command call_user_func($commandCallable$authUrl);
                    if (
=== $this->process->execute($command$ignoredOutput$cwd)) {
                        return;
                    }

                    
$credentials = array(rawurlencode($auth['username']), rawurlencode($auth['password']));
                    
$errorMsg $this->process->getErrorOutput();
                } else { 
// Falling back to ssh
                    
$sshUrl '[email protected]:' $match[2] . '.git';
                    
$this->io->writeError('    No bitbucket authentication configured. Falling back to ssh.');
                    
$command call_user_func($commandCallable$sshUrl);
                    if (
=== $this->process->execute($command$ignoredOutput$cwd)) {
                        return;
                    }

                    
$errorMsg $this->process->getErrorOutput();
                }
            } elseif (
                
Preg::isMatch('{^(git)@' self::getGitLabDomainsRegex($this->config) . ':(.+?\.git)$}i'$url$match)
                || 
Preg::isMatch('{^(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($auth['username']) . '@' $match[2] . '/' $match[3]; // swap username and password
                    
} else {
                        
$authUrl $match[1] . '://' rawurlencode($auth['username']) . ':' rawurlencode($auth['password']) . '@' $match[2] . '/' $match[3];
                    }

                    
$command call_user_func($commandCallable$authUrl);
                    if (
=== $this->process->execute($command$ignoredOutput$cwd)) {
                        return;
                    }

                    
$credentials = array(rawurlencode($auth['username']), rawurlencode($auth['password']));
                    
$errorMsg $this->process->getErrorOutput();
                }
            } elseif (
$this->isAuthenticationFailure($url$match)) { // private non-github/gitlab/bitbucket repo that failed to authenticate
                
if (strpos($match[2], '@')) {
                    list(
$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 (
false !== strpos($authParts':')) {
                            list(
$defaultUsername, ) = explode(':'$authParts2);
                        } else {
                            
$defaultUsername $authParts;
                        }
                    }

                    
$this->io->writeError('    Authentication required (<info>' $match[2] . '</info>):');
                    
$auth = array(
                        
'username' => $this->io->ask('      Username: '$defaultUsername),
                        
'password' => $this->io->askAndHideAnswer('      Password: '),
                    );
                    
$storeAuth $this->config->get('store-auths');
                }

                if (
$auth) {
                    
$authUrl $match[1] . rawurlencode($auth['username']) . ':' rawurlencode($auth['password']) . '@' $match[2] . $match[3];

                    
$command call_user_func($commandCallable$authUrl);
                    if (
=== $this->process->execute($command$ignoredOutput$cwd)) {
                        
$this->io->setAuthentication($match[2], $auth['username'], $auth['password']);
                        
$authHelper = new AuthHelper($this->io$this->config);
                        
$authHelper->storeAuth($match[2], $storeAuth);

                        return;
                    }

                    
$credentials = array(rawurlencode($auth['username']), rawurlencode($auth['password']));
                    
$errorMsg $this->process->getErrorOutput();
                }
            }

            if (
$initialClone && isset($origCwd)) {
                
$this->filesystem->removeDirectory($origCwd);
            }

            if (
count($credentials) > 0) {
                
$command $this->maskCredentials($command$credentials);
                
$errorMsg $this->maskCredentials($errorMsg$credentials);
            }
            
$this->throwException('Failed to execute ' $command "\n\n" $errorMsg$url);
        }
    }

    
/**
     * @param string $url
     * @param string $dir
     *
     * @return bool
     */
    
public function syncMirror($url$dir)
    {
        if (
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) && === $this->process->execute('git rev-parse --git-dir'$output$dir) && trim($output) === '.') {
            try {
                
$commandCallable = function ($url) {
                    
$sanitizedUrl Preg::replace('{://([^@]+?):(.+?)@}''://'$url);

                    return 
sprintf('git remote set-url origin -- %s && git remote update --prune origin && git remote set-url origin -- %s && git gc --auto'ProcessExecutor::escape($url), ProcessExecutor::escape($sanitizedUrl));
                };
                
$this->runCommand($commandCallable$url$dir);
            } catch (
\Exception $e) {
                
$this->io->writeError('<error>Sync mirror failed: ' $e->getMessage() . '</error>'trueIOInterface::DEBUG);

                return 
false;
            }

            return 
true;
        }

        
// clean up directory and do a fresh clone into it
        
$this->filesystem->removeDirectory($dir);

        
$commandCallable = function ($url) use ($dir) {
            return 
sprintf('git clone --mirror -- %s %s'ProcessExecutor::escape($url), ProcessExecutor::escape($dir));
        };

        
$this->runCommand($commandCallable$url$dirtrue);

        return 
true;
    }

    
/**
     * @param string $url
     * @param string $dir
     * @param string $ref
     *
     * @return bool
     */
    
public function fetchRefOrSyncMirror($url$dir$ref)
    {
        if (
$this->checkRefIsInMirror($dir$ref)) {
            return 
true;
        }

        if (
$this->syncMirror($url$dir)) {
            return 
$this->checkRefIsInMirror($dir$ref);
        }

        return 
false;
    }

    
/**
     * @return string
     */
    
public static function getNoShowSignatureFlag(ProcessExecutor $process)
    {
        
$gitVersion self::getVersion($process);
        if (
$gitVersion && version_compare($gitVersion'2.10.0-rc0''>=')) {
            return 
' --no-show-signature';
        }

        return 
'';
    }

    
/**
     * @param string $dir
     * @param string $ref
     *
     * @return bool
     */
    
private function checkRefIsInMirror($dir$ref)
    {
        if (
is_dir($dir) && === $this->process->execute('git rev-parse --git-dir'$output$dir) && trim($output) === '.') {
            
$escapedRef ProcessExecutor::escape($ref.'^{commit}');
            
$exitCode $this->process->execute(sprintf('git rev-parse --quiet --verify %s'$escapedRef), $ignoredOutput$dir);
            if (
$exitCode === 0) {
                return 
true;
            }
        }

        return 
false;
    }

    
/**
     * @param string   $url
     * @param string[] $match
     *
     * @return bool
     */
    
private function isAuthenticationFailure($url, &$match)
    {
        if (!
Preg::isMatch('{^(https?://)([^/]+)(.*)$}i'$url$match)) {
            return 
false;
        }

        
$authFailures = array(
            
'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 
true;
            }
        }

        return 
false;
    }

    
/**
     * @return void
     */
    
public static function cleanEnv()
    {
        if (
PHP_VERSION_ID 50400 && ini_get('safe_mode') && false === strpos(ini_get('safe_mode_allowed_env_vars'), 'GIT_ASKPASS')) {
            throw new 
\RuntimeException('safe_mode is enabled and safe_mode_allowed_env_vars does not contain GIT_ASKPASS, can not set env var. You can disable safe_mode with "-dsafe_mode=0" when running composer');
        }

        
// 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)
    {
        return 
'(' implode('|'array_map('preg_quote'$config->get('github-domains'))) . ')';
    }

    
/**
     * @return non-empty-string
     */
    
public static function getGitLabDomainsRegex(Config $config)
    {
        return 
'(' implode('|'array_map('preg_quote'$config->get('gitlab-domains'))) . ')';
    }

    
/**
     * @param non-empty-string $message
     * @param string           $url
     *
     * @return never
     */
    
private function throwException($message$url)
    {
        
// git might delete a directory when it fails and php will not know
        
clearstatcache();

        if (
!== $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)
    {
        if (
false === self::$version) {
            
self::$version null;
            if (
=== $process->execute('git --version'$output) && Preg::isMatch('/^git version (\d+(?:\.\d+)+)/m'$output$matches)) {
                
self::$version $matches[1];
            }
        }

        return 
self::$version;
    }

    
/**
     * @param string   $error
     * @param string[] $credentials
     *
     * @return string
     */
    
private function maskCredentials($error, array $credentials)
    {
        
$maskedCredentials = array();

        foreach (
$credentials as $credential) {
            if (
in_array($credential, array('private-token''x-token-auth''oauth2''gitlab-ci-token''x-oauth-basic'))) {
                
$maskedCredentials[] = $credential;
            } elseif (
strlen($credential) > 6) {
                
$maskedCredentials[] = substr($credential03) . '...' substr($credential, -3);
            } elseif (
strlen($credential) > 3) {
                
$maskedCredentials[] = substr($credential03) . '...';
            } else {
                
$maskedCredentials[] = 'XXX';
            }
        }

        return 
str_replace($credentials$maskedCredentials$error);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\MaxFileSizeExceededException;
use 
Composer\IO\IOInterface;
use 
Composer\Downloader\TransportException;
use 
Composer\CaBundle\CaBundle;
use 
Composer\Pcre\Preg;
use 
Composer\Util\Http\Response;
use 
Composer\Util\Http\ProxyManager;

/**
 * @internal
 * @author François Pluchino <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 * @author Nils Adermann <[email protected]>
 */
class RemoteFilesystem
{
    
/** @var IOInterface */
    
private $io;
    
/** @var Config */
    
private $config;
    
/** @var string */
    
private $scheme;
    
/** @var int */
    
private $bytesMax;
    
/** @var string */
    
private $originUrl;
    
/** @var string */
    
private $fileUrl;
    
/** @var ?string */
    
private $fileName;
    
/** @var bool */
    
private $retry false;
    
/** @var bool */
    
private $progress;
    
/** @var ?int */
    
private $lastProgress;
    
/** @var mixed[] */
    
private $options = array();
    
/** @var array<string, array{cn: string, fp: string}> */
    
private $peerCertificateMap = array();
    
/** @var bool */
    
private $disableTls false;
    
/** @var 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;
    
/** @var ProxyManager */
    
private $proxyManager;

    
/**
     * Constructor.
     *
     * @param IOInterface $io         The IO instance
     * @param Config      $config     The config
     * @param mixed[]     $options    The options
     * @param bool        $disableTls
     * @param AuthHelper  $authHelper
     */
    
public function __construct(IOInterface $ioConfig $config, array $options = array(), $disableTls falseAuthHelper $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 = isset($authHelper) ? $authHelper : new AuthHelper($io$config);
        
$this->proxyManager ProxyManager::getInstance();
    }

    
/**
     * Copy the remote file in local.
     *
     * @param string  $originUrl The origin URL
     * @param 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($originUrl$fileUrl$fileName$progress true$options = array())
    {
        return 
$this->get($originUrl$fileUrl$options$fileName$progress);
    }

    
/**
     * Get the content.
     *
     * @param string  $originUrl The origin URL
     * @param 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($originUrl$fileUrl$progress true$options = array())
    {
        return 
$this->get($originUrl$fileUrl$optionsnull$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 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 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($originUrl$fileUrl$additionalOptions = array(), $fileName null$progress true)
    {
        
$this->scheme parse_url($fileUrlPHP_URL_SCHEME);
        
$this->bytesMax 0;
        
$this->originUrl $originUrl;
        
$this->fileUrl $fileUrl;
        
$this->fileName $fileName;
        
$this->progress $progress;
        
$this->lastProgress null;
        
$retryAuthFailure true;
        
$this->lastHeaders = array();
        
$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['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($fileUrl20);
            
$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, array('notification' => array($this'callbackGet')));

        
$proxy $this->proxyManager->getProxyForRequest($fileUrl);
        
$usingProxy $proxy->getFormattedUrl(' using proxy (%s)');
        
$this->io->writeError((strpos($origFileUrl'http') === 'Downloading ' 'Reading ') . Url::sanitize($origFileUrl) . $usingProxytrueIOInterface::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(function ($code$msg) use (&$errorMessage) {
            if (
$errorMessage) {
                
$errorMessage .= "\n";
            }
            
$errorMessage .= Preg::replace('{^file_get_contents\(.*?\): }'''$msg);

            return 
true;
        });
        
$http_response_header = array();
        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 >= 400 && Response::findHeaderValue($http_response_header'content-type') === 'application/json') {
                    
HttpDownloader::outputWarnings($this->io$originUrljson_decode($resulttrue));
                }

                if (
in_array($statusCode, array(401403)) && $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).')'trueIOInterface::DEBUG);

                throw 
$e;
            }

            if (
PHP_VERSION_ID 50600 && !empty($options['ssl']['peer_fingerprint'])) {
                
// Emulate fingerprint validation on PHP < 5.6
                
$params stream_context_get_params($ctx);
                
$expectedPeerFingerprint $options['ssl']['peer_fingerprint'];
                
$peerFingerprint TlsHelper::getCertificateFingerprint($params['options']['ssl']['peer_certificate']);

                
// Constant time compare??!
                
if ($expectedPeerFingerprint !== $peerFingerprint) {
                    throw new 
TransportException('Peer fingerprint did not match');
                }
            }
        } 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(array(
                    
'<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($locationHeaderPHP_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') === && !$hasFollowedRedirect) {
            try {
                
$result $this->decodeResult($result$http_response_header);
            } catch (
\Exception $e) {
                if (
$this->degradedMode) {
                    throw 
$e;
                }

                
$this->degradedMode true;
                
$this->io->writeError(array(
                    
'',
                    
'<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(function ($code$msg) use (&$errorMessage) {
                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);
            }
        }

        
// Handle SSL cert match issues
        
if (false === $result && false !== strpos($errorMessage'Peer certificate') && PHP_VERSION_ID 50600) {
            
// Certificate name error, PHP doesn't support subjectAltName on PHP < 5.6
            // The procedure to handle sAN for older PHP's is:
            //
            // 1. Open socket to remote server and fetch certificate (disabling peer
            //    validation because PHP errors without giving up the certificate.)
            //
            // 2. Verifying the domain in the URL against the names in the sAN field.
            //    If there is a match record the authority [host/port], certificate
            //    common name, and certificate fingerprint.
            //
            // 3. Retry the original request but changing the CN_match parameter to
            //    the common name extracted from the certificate in step 2.
            //
            // 4. To prevent any attempt at being hoodwinked by switching the
            //    certificate between steps 2 and 3 the fingerprint of the certificate
            //    presented in step 3 is compared against the one recorded in step 2.
            
if (CaBundle::isOpensslParseSafe()) {
                
$certDetails $this->getCertificateCnAndFp($this->fileUrl$options);

                if (
$certDetails) {
                    
$this->peerCertificateMap[$this->getUrlAuthority($this->fileUrl)] = $certDetails;

                    
$this->retry true;
                }
            } else {
                
$this->io->writeError('');
                
$this->io->writeError(sprintf(
                    
'<error>Your version of PHP, %s, is affected by CVE-2013-6420 and cannot safely perform certificate validation, we strongly suggest you upgrade.</error>',
                    
PHP_VERSION
                
));
            }
        }

        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(array(
                    
'<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
     */
    
protected function getRemoteContents($originUrl$fileUrl$context, array &$responseHeaders null$maxFileSize null)
    {
        
$result false;

        try {
            
$e null;
            if (
$maxFileSize !== null) {
                
$result file_get_contents($fileUrlfalse$context0$maxFileSize);
            } else {
                
// passing `null` to file_get_contents will convert `null` to `0` and return 0 bytes
                
$result file_get_contents($fileUrlfalse$context);
            }
        } catch (
\Exception $e) {
        } catch (
\Throwable $e) {
        }

        if (
$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
        
$responseHeaders = isset($http_response_header) ? $http_response_header : array();

        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($notificationCode$severity$message$messageCode$bytesTransferred$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 && $this->progress) {
                    
$progression min(100, (int) round($bytesTransferred $this->bytesMax 100));

                    if ((
=== $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|null  $reason
     * @param string[]     $headers
     *
     * @return void
     */
    
protected function promptAuthAndRetry($httpStatus$reason null$headers = array())
    {
        
$result $this->authHelper->promptAuthIfNeeded($this->fileUrl$this->originUrl$httpStatus$reason$headers);

        
$this->storeAuth $result['storeAuth'];
        
$this->retry $result['retry'];

        if (
$this->retry) {
            throw new 
TransportException('RETRY');
        }
    }

    
/**
     * @param string  $originUrl
     * @param mixed[] $additionalOptions
     *
     * @return mixed[]
     */
    
protected function getOptionsForUrl($originUrl$additionalOptions)
    {
        
$tlsOptions = array();

        
// Setup remaining TLS options - the matching may need monitoring, esp. www vs none in CN
        
if ($this->disableTls === false && PHP_VERSION_ID 50600 && !stream_is_local($this->fileUrl)) {
            
$host parse_url($this->fileUrlPHP_URL_HOST);

            if (
PHP_VERSION_ID 50304) {
                
// PHP < 5.3.4 does not support follow_location, for those people
                // do some really nasty hard coded transformations. These will
                // still breakdown if the site redirects to a domain we don't
                // expect.

                
if ($host === 'github.com' || $host === 'api.github.com') {
                    
$host '*.github.com';
                }
            }

            
$tlsOptions['ssl']['CN_match'] = $host;
            
$tlsOptions['ssl']['SNI_server_name'] = $host;

            
$urlAuthority $this->getUrlAuthority($this->fileUrl);

            if (isset(
$this->peerCertificateMap[$urlAuthority])) {
                
// Handle subjectAltName on lesser PHP's.
                
$certMap $this->peerCertificateMap[$urlAuthority];

                
$this->io->writeError(''trueIOInterface::DEBUG);
                
$this->io->writeError(sprintf(
                    
'Using <info>%s</info> as CN for subjectAltName enabled host <info>%s</info>',
                    
$certMap['cn'],
                    
$urlAuthority
                
), trueIOInterface::DEBUG);

                
$tlsOptions['ssl']['CN_match'] = $certMap['cn'];
                
$tlsOptions['ssl']['peer_fingerprint'] = $certMap['fp'];
            } elseif (!
CaBundle::isOpensslParseSafe() && $host === 'repo.packagist.org') {
                
// handle subjectAltName for packagist.org's repo domain on very old PHPs
                
$tlsOptions['ssl']['CN_match'] = 'packagist.org';
            }
        }

        
$headers = array();

        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($locationHeaderPHP_URL_SCHEME)) {
                
// Absolute URL; e.g. https://example.com/composer
                
$targetUrl $locationHeader;
            } elseif (
parse_url($locationHeaderPHP_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->fileUrlPHP_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(''trueIOInterface::DEBUG);
            
$this->io->writeError(sprintf('Following redirect (%u) %s'$this->redirectsUrl::sanitize($targetUrl)), trueIOInterface::DEBUG);

            
$additionalOptions['redirects'] = $this->redirects;

            return 
$this->get(parse_url($targetUrlPHP_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;
    }

    
/**
     * Fetch certificate common name and fingerprint for validation of SAN.
     *
     * @todo Remove when PHP 5.6 is minimum supported version.
     *
     * @param string  $url
     * @param mixed[] $options
     *
     * @return ?array{cn: string, fp: string}
     */
    
private function getCertificateCnAndFp($url$options)
    {
        if (
PHP_VERSION_ID >= 50600) {
            throw new 
\BadMethodCallException(sprintf(
                
'%s must not be used on PHP >= 5.6',
                
__METHOD__
            
));
        }

        
$context StreamContextFactory::getContext($url$options, array('options' => array(
            
'ssl' => array(
                
'capture_peer_cert' => true,
                
'verify_peer' => false// Yes this is fucking insane! But PHP is lame.
            
), ),
        ));

        
// Ideally this would just use stream_socket_client() to avoid sending a
        // HTTP request but that does not capture the certificate.
        
if (false === $handle = @fopen($url'rb'false$context)) {
            return 
null;
        }

        
// Close non authenticated connection without reading any content.
        
fclose($handle);
        
$handle null;

        
$params stream_context_get_params($context);

        if (!empty(
$params['options']['ssl']['peer_certificate'])) {
            
$peerCertificate $params['options']['ssl']['peer_certificate'];

            if (
TlsHelper::checkCertificateHost($peerCertificateparse_url($urlPHP_URL_HOST), $commonName)) {
                return array(
                    
'cn' => $commonName,
                    
'fp' => TlsHelper::getCertificateFingerprint($peerCertificate),
                );
            }
        }

        return 
null;
    }

    
/**
     * @param string $url
     *
     * @return string
     */
    
private function getUrlAuthority($url)
    {
        
$defaultPorts = array(
            
'ftp' => 21,
            
'http' => 80,
            
'https' => 443,
            
'ssh2.sftp' => 22,
            
'ssh2.scp' => 22,
        );

        
$scheme parse_url($urlPHP_URL_SCHEME);

        if (!isset(
$defaultPorts[$scheme])) {
            throw new 
\InvalidArgumentException(sprintf(
                
'Could not get default port for unknown scheme: %s',
                
$scheme
            
));
        }

        
$defaultPort $defaultPorts[$scheme];
        
$port parse_url($urlPHP_URL_PORT) ?: $defaultPort;

        return 
parse_url($urlPHP_URL_HOST).':'.$port;
    }

    
/**
     * @param string|false $result
     * @param string[]     $http_response_header
     *
     * @return string|null
     */
    
private function decodeResult($result$http_response_header)
    {
        
// decode gzip
        
if ($result && extension_loaded('zlib')) {
            
$contentEncoding Response::findHeaderValue($http_response_header'content-encoding');
            
$decode $contentEncoding && 'gzip' === strtolower($contentEncoding);

            if (
$decode) {
                if (
PHP_VERSION_ID >= 50400) {
                    
$result zlib_decode($result);
                } else {
                    
// work around issue with gzuncompress & co that do not work with all gzip checksums
                    
$result file_get_contents('compress.zlib://data:application/octet-stream;base64,'.base64_encode($result));
                }

                if (
$result === false) {
                    throw new 
TransportException('Failed to decode zlib stream');
                }
            }
        }

        return 
$this->normalizeResult($result);
    }

    
/**
     * @param string|false $result
     *
     * @return string|null
     */
    
private function normalizeResult($result)
    {
        if (
$result === false) {
            return 
null;
        }

        return 
$result;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class AuthHelper
{
    
/** @var IOInterface */
    
protected $io;
    
/** @var Config */
    
protected $config;
    
/** @var array<string, string> Map of origins to message displayed */
    
private $displayedOriginAuthentications = array();

    public function 
__construct(IOInterface $ioConfig $config)
    {
        
$this->io $io;
        
$this->config $config;
    }

    
/**
     * @param string      $origin
     * @param string|bool $storeAuth
     *
     * @return void
     */
    
public function storeAuth($origin$storeAuth)
    {
        
$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] ',
                function (
$value) {
                    
$input strtolower(substr(trim($value), 01));
                    if (
in_array($input, array('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  string      $url
     * @param  string      $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
     * @return array|null  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: string|bool}
     */
    
public function promptAuthIfNeeded($url$origin$statusCode$reason null$headers = array())
    {
        
$storeAuth false;

        if (
in_array($origin$this->config->get('github-domains'), true)) {
            
$gitHubUtil = new GitHub($this->io$this->confignull);
            
$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 ' $origin403);
                }
                
$this->io->ask('After authorizing your token, confirm that you would like to retry the request');

                return array(
'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 '.$origin401);
            }
        } 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->confignull);

            if (
$this->io->hasAuthentication($origin)) {
                
$auth $this->io->getAuthentication($origin);
                if (
in_array($auth['password'], array('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($urlPHP_URL_SCHEME), $origin$message))
            ) {
                throw new 
TransportException('Could not authenticate against '.$origin401);
            }
        } 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;
                    }
                } else {
                    throw new 
TransportException('Could not authenticate against ' $origin401);
                }
            }

            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 ' $origin401);
                }
            }
        } else {
            
// 404s are only handled for github
            
if ($statusCode === 404) {
                return 
null;
            }

            
// fail if the console is not interactive
            
if (!$this->io->isInteractive()) {
                if (
$statusCode === 401) {
                    
$message "The '" $url "' URL required authentication.\nYou must be using the interactive console to authenticate";
                } elseif (
$statusCode === 403) {
                    
$message "The '" $url "' URL could not be accessed: " $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)) {
                throw new 
TransportException("Invalid credentials 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 array(
'retry' => true'storeAuth' => $storeAuth);
    }

    
/**
     * @param string[] $headers
     * @param string   $origin
     * @param string   $url
     *
     * @return string[] updated headers array
     */
    
public function addAuthenticationHeader(array $headers$origin$url)
    {
        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'], array('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($authenticationDisplayMessagetrueIOInterface::DEBUG);
                
$this->displayedOriginAuthentications[$origin] = $authenticationDisplayMessage;
            }
        } elseif (
in_array($origin, array('api.bitbucket.org''api.github.com'), true)) {
            return 
$this->addAuthenticationHeader($headersstr_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($urlToBitBucketFile)
    {
        
$domain parse_url($urlToBitBucketFilePHP_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($urlToBitBucketFilePHP_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) >= && $pathParts[3] == 'downloads';
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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;

/**
 * Validates a composer configuration.
 *
 * @author Robert Schönthal <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 */
class ConfigValidator
{
    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($file$arrayLoaderValidationFlags ValidatingArrayLoader::CHECK_ALL$flags self::CHECK_VERSION)
    {
        
$errors = array();
        
$publishErrors = array();
        
$warnings = array();

        
// validate json schema
        
$laxValid false;
        try {
            
$json = new JsonFile($filenull$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 array(
$errors$publishErrors$warnings);
        }

        
// 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 (array('provide''replace') as $linkType) {
            if (isset(
$manifest[$linkType])) {
                foreach (array(
'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 = isset($manifest['require']) ? $manifest['require'] : array();
        
$requireDev = isset($manifest['require-dev']) ? $manifest['require-dev'] : array();
        
$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 = isset($manifest['scripts-descriptions']) ? $manifest['scripts-descriptions'] : array();
        
$scripts = isset($manifest['scripts']) ? $manifest['scripts'] : array();
        foreach (
$scriptsDescriptions as $scriptName => $scriptDescription) {
            if (!
array_key_exists($scriptName$scripts)) {
                
$warnings[] = sprintf(
                    
'Description for non-existent script "%s" found in "scripts-descriptions"',
                    
$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(), truenull$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 array(
$errors$publishErrors$warnings);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class Silencer
{
    
/**
     * @var int[] Unpop stack
     */
    
private static $stack = array();

    
/**
     * 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($mask null)
    {
        if (!isset(
$mask)) {
            
$mask E_WARNING E_NOTICE E_USER_WARNING E_USER_NOTICE E_DEPRECATED E_USER_DEPRECATED E_STRICT;
        }
        
$old error_reporting();
        
self::$stack[] = $old;
        
error_reporting($old & ~$mask);

        return 
$old;
    }

    
/**
     * Restores a single state.
     *
     * @return void
     */
    
public static function restore()
    {
        if (!empty(
self::$stack)) {
            
error_reporting(array_pop(self::$stack));
        }
    }

    
/**
     * Calls a specified function while silencing warnings and below.
     *
     * Future improvement: when PHP requirements are raised add Callable type hint (5.4) and variadic parameters (5.6)
     *
     * @param  callable   $callable Function to execute.
     * @throws \Exception Any exceptions from the callback are rethrown.
     * @return mixed      Return value of the callback.
     */
    
public static function call($callable /*, ...$parameters */)
    {
        try {
            
self::suppress();
            
$result call_user_func_array($callablearray_slice(func_get_args(), 1));
            
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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
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
     *
     * @return bool
     */
    
public static function checkCertificateHost($certificate$hostname, &$cn null)
    {
        
$names self::getCertificateNames($certificate);

        if (empty(
$names)) {
            return 
false;
        }

        
$combinedNames array_merge($names['san'], array($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)
    {
        if (
is_array($certificate)) {
            
$info $certificate;
        } elseif (
CaBundle::isOpensslParseSafe()) {
            
$info openssl_x509_parse($certificatefalse);
        }

        if (!isset(
$info['subject']['commonName'])) {
            return 
null;
        }

        
$commonName strtolower($info['subject']['commonName']);
        
$subjectAltNames = array();

        if (isset(
$info['extensions']['subjectAltName'])) {
            
$subjectAltNames Preg::split('{\s*,\s*}'$info['extensions']['subjectAltName']);
            
$subjectAltNames array_filter(array_map(function ($name) {
                if (
=== strpos($name'DNS:')) {
                    return 
strtolower(ltrim(substr($name4)));
                }

                return 
null;
            }, 
$subjectAltNames));
            
$subjectAltNames array_values($subjectAltNames);
        }

        return array(
            
'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 <[email protected]>
     *
     * 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 string $certificate
     * @return string
     */
    
public static function getCertificateFingerprint($certificate)
    {
        
$pubkeydetails openssl_pkey_get_details(openssl_get_publickey($certificate));
        
$pubkeypem $pubkeydetails['key'];
        
//Convert PEM to DER before SHA1'ing
        
$start '-----BEGIN PUBLIC KEY-----';
        
$end '-----END PUBLIC KEY-----';
        
$pemtrim substr($pubkeypemstrpos($pubkeypem$start) + strlen($start), (strlen($pubkeypem) - strpos($pubkeypem$end)) * (-1));
        
$der base64_decode($pemtrim);

        return 
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.
     *
     * @return bool
     */
    
public static function isOpensslParseSafe()
    {
        return 
CaBundle::isOpensslParseSafe();
    }

    
/**
     * Convert certificate name into matching function.
     *
     * @param string $certName CN/SAN
     *
     * @return callable|null
     */
    
private static function certNameMatcher($certName)
    {
        
$wildcards substr_count($certName'*');

        if (
=== $wildcards) {
            
// Literal match.
            
return function ($hostname) use ($certName) {
                return 
$hostname === $certName;
            };
        }

        if (
=== $wildcards) {
            
$components explode('.'$certName);

            if (
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 function (
$hostname) use ($wildcardRegex) {
                return 
Preg::isMatch($wildcardRegex$hostname);
            };
        }

        return 
null;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
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;

    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 $ioConfig $configProcessExecutor $process nullHttpDownloader $httpDownloader null$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;
    }

    
/**
     * @return string
     */
    
public function getToken()
    {
        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($originUrl)
    {
        if (
$originUrl !== 'bitbucket.org') {
            return 
false;
        }

        
// if available use token from git config
        
if (=== $this->process->execute('git config bitbucket.accesstoken'$output)) {
            
$this->io->setAuthentication($originUrl'x-token-auth'trim($output));

            return 
true;
        }

        return 
false;
    }

    
/**
     * @return bool
     */
    
private function requestAccessToken()
    {
        try {
            
$response $this->httpDownloader->get(self::OAUTH2_ACCESS_TOKEN_URL, array(
                
'retry-auth-failure' => false,
                
'http' => array(
                    
'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 two 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');

                return 
false;
            }
            if (
in_array($e->getCode(), array(403401))) {
                
$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($originUrl$message null)
    {
        if (
$message) {
            
$this->io->writeError($message);
        }

        
$url 'https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/';
        
$this->io->writeError(sprintf('Follow the instructions on %s'$url));
        
$this->io->writeError(sprintf('to create a consumer. It will be stored in "%s" for future use by Composer.'$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)');

        
$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
        
$this->storeInAuthConfig($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.
     *
     * @param  string $originUrl
     * @param  string $consumerKey
     * @param  string $consumerSecret
     * @return string
     */
    
public function requestToken($originUrl$consumerKey$consumerSecret)
    {
        if (
$this->token !== null || $this->getTokenFromConfig($originUrl)) {
            return 
$this->token['access_token'];
        }

        
$this->io->setAuthentication($originUrl$consumerKey$consumerSecret);
        if (!
$this->requestAccessToken()) {
            return 
'';
        }

        
$this->storeInAuthConfig($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
     *
     * @param string $originUrl
     * @param string $consumerKey
     * @param string $consumerSecret
     *
     * @return void
     */
    
private function storeInAuthConfig($originUrl$consumerKey$consumerSecret)
    {
        
$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 = array(
            
"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);
    }

    
/**
     * @param  string $originUrl
     * @return bool
     */
    
private function getTokenFromConfig($originUrl)
    {
        
$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 = array(
            
'access_token' => $authConfig[$originUrl]['access-token'],
        );

        return 
true;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @phpstan-type Request array{url: string, options?: mixed[], copyTo?: ?string}
 * @phpstan-type Job array{id: int, status: int, request: Request, sync: bool, origin: string, resolve?: callable, reject?: callable, curl_id?: int, response?: Response, exception?: TransportException}
 */
class HttpDownloader
{
    const 
STATUS_QUEUED 1;
    const 
STATUS_STARTED 2;
    const 
STATUS_COMPLETED 3;
    const 
STATUS_FAILED 4;
    const 
STATUS_ABORTED 5;

    
/** @var IOInterface */
    
private $io;
    
/** @var Config */
    
private $config;
    
/** @var array<Job> */
    
private $jobs = array();
    
/** @var mixed[] */
    
private $options = array();
    
/** @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
     * @param bool        $disableTls
     */
    
public function __construct(IOInterface $ioConfig $config, array $options = array(), $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(1min(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($url$options = array())
    {
        list(
$job) = $this->addJob(array('url' => $url'options' => $options'copyTo' => null), true);
        
$this->wait($job['id']);

        
$response $this->getResponse($job['id']);

        
// check for failed curl response (empty body but successful looking response)
        
if (
            
$this->curl
            
&& PHP_VERSION_ID 70000
            
&& $response->getBody() === null
            
&& $response->getStatusCode() === 200
            
&& $response->getHeader('content-length') !== '0'
        
) {
            
$this->io->writeError('<warning>cURL downloader failed to return a response, disabling it and proceeding in slow mode.</warning>');

            
$this->curl null;

            list(
$job) = $this->addJob(array('url' => $url'options' => $options'copyTo' => null), true);
            
$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
     */
    
public function add($url$options = array())
    {
        list(, 
$promise) = $this->addJob(array('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($url$to$options = array())
    {
        list(
$job) = $this->addJob(array('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
     */
    
public function addCopy($url$to$options = array())
    {
        list(, 
$promise) = $this->addJob(array('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);
    }

    
/**
     * @param Request $request
     * @param bool    $sync
     *
     * @return array{Job, PromiseInterface}
     */
    
private function addJob($request$sync false)
    {
        
$request['options'] = array_replace_recursive($this->options$request['options']);

        
/** @var Job */
        
$job = array(
            
'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::isMatch('{^https?://([^:/]+):([^@/]+)@([^/]+)}i'$request['url'], $match)) {
            
$this->io->setAuthentication($job['origin'], rawurldecode($match[1]), rawurldecode($match[2]));
        }

        
$rfs $this->rfs;

        if (
$this->canUseCurl($job)) {
            
$resolver = function ($resolve$reject) use (&$job) {
                
$job['status'] = HttpDownloader::STATUS_QUEUED;
                
$job['resolve'] = $resolve;
                
$job['reject'] = $reject;
            };
        } else {
            
$resolver = function ($resolve$reject) use (&$job$rfs) {
                
// 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'], $urlfalse /* TODO progress */$options);
                    
$headers $rfs->getLastHeaders();
                    
$response = new Http\Response($job['request'], $rfs->findStatusCode($headers), $headers$body);

                    
$resolve($response);
                }
            };
        }

        
$downloader $this;
        
$curl $this->curl;

        
$canceler = function () use (&$job$curl) {
            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$downloader) {
            
$job['status'] = HttpDownloader::STATUS_COMPLETED;
            
$job['response'] = $response;

            
// TODO 3.0 this should be done directly on $this when PHP 5.3 is dropped
            
$downloader->markJobDone();

            return 
$response;
        }, function (
$e) use (&$job$downloader) {
            
$job['status'] = HttpDownloader::STATUS_FAILED;
            
$job['exception'] = $e;

            
$downloader->markJobDone();

            throw 
$e;
        });
        
$this->jobs[$job['id']] = &$job;

        if (
$this->runningJobs $this->maxJobs) {
            
$this->startJob($job['id']);
        }

        return array(
$job$promise);
    }

    
/**
     * @param  int  $id
     * @return void
     */
    
private function startJob($id)
    {
        
$job = &$this->jobs[$id];
        if (
$job['status'] !== self::STATUS_QUEUED) {
            return;
        }

        
// start job
        
$job['status'] = self::STATUS_STARTED;
        
$this->runningJobs++;

        
$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(array('url' => $url), 304, array(), ''));
            } 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
     * @return void
     */
    
public function markJobDone()
    {
        
$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($index null)
    {
        do {
            
$jobCount $this->countActiveJobs($index);
        } while (
$jobCount);
    }

    
/**
     * @internal
     *
     * @return void
     */
    
public function enableAsync()
    {
        
$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($index null)
    {
        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 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
     * @return Response
     */
    
private function getResponse($index)
    {
        if (!isset(
$this->jobs[$index])) {
            throw new 
\LogicException('Invalid request id');
        }

        if (
$this->jobs[$index]['status'] === self::STATUS_FAILED) {
            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  string                                                                                    $url
     * @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
     * @return void
     */
    
public static function outputWarnings(IOInterface $io$url$data)
    {
        
// legacy warning/info keys
        
foreach (array('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).': '.$data[$type].'</'.$type.'>');
        }

        
// modern Composer 2.2+ format with support for multiple warning/info messages
        
foreach (array('warnings''infos') as $key) {
            if (empty(
$data[$key])) {
                continue;
            }

            
$versionParser = new VersionParser();
            foreach (
$data[$key] as $spec) {
                
$type substr($key0, -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).': '.$spec['message'].'</'.$type.'>');
            }
        }
    }

    
/**
     * @internal
     *
     * @return ?string[]
     */
    
public static function getExceptionHints(\Exception $e)
    {
        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'falsestream_context_create(array(
                
'ssl' => array('verify_peer' => false),
                
'http' => array('follow_location' => false'ignore_errors' => true),
            )));
            
Silencer::restore();
            if (
false !== $testConnectivity) {
                return array(
                    
'<error>The following exception probably indicates you have misconfigured DNS resolver(s)</error>',
                );
            }

            return array(
                
'<error>The following exception probably indicates you are offline or have misconfigured DNS resolver(s)</error>',
            );
        }

        return 
null;
    }

    
/**
     * @param  Job  $job
     * @return bool
     */
    
private function canUseCurl(array $job)
    {
        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
     * @return bool
     */
    
public static function isCurlEnabled()
    {
        return 
\extension_loaded('curl') && \function_exists('curl_multi_exec') && \function_exists('curl_multi_init');
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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-import-type Request from HttpDownloader
 */
class Response
{
    
/** @var Request */
    
private $request;
    
/** @var int */
    
private $code;
    
/** @var string[] */
    
private $headers;
    
/** @var ?string */
    
private $body;

    
/**
     * @param Request  $request
     * @param int      $code
     * @param string[] $headers
     * @param ?string  $body
     */
    
public function __construct(array $request$code, array $headers$body)
    {
        if (!isset(
$request['url'])) { // @phpstan-ignore-line
            
throw new \LogicException('url key missing from request array');
        }
        
$this->request $request;
        
$this->code = (int) $code;
        
$this->headers $headers;
        
$this->body $body;
    }

    
/**
     * @return int
     */
    
public function getStatusCode()
    {
        return 
$this->code;
    }

    
/**
     * @return string|null
     */
    
public function getStatusMessage()
    {
        
$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()
    {
        return 
$this->headers;
    }

    
/**
     * @param  string  $name
     * @return ?string
     */
    
public function getHeader($name)
    {
        return 
self::findHeaderValue($this->headers$name);
    }

    
/**
     * @return ?string
     */
    
public function getBody()
    {
        return 
$this->body;
    }

    
/**
     * @return mixed
     */
    
public function decodeJson()
    {
        return 
JsonFile::parseJson($this->body$this->request['url']);
    }

    
/**
     * @return void
     * @phpstan-impure
     */
    
public function collect()
    {
        
/** @phpstan-ignore-next-line */
        
$this->request $this->code $this->headers $this->body null;
    }

    
/**
     * @param  string[]    $headers array of returned headers like from getLastHeaders()
     * @param  string      $name    header name (case insensitive)
     * @return string|null
     */
    
public static function findHeaderValue(array $headers$name)
    {
        
$value null;
        foreach (
$headers as $header) {
            if (
Preg::isMatch('{^'.preg_quote($name).':\s*(.+?)\s*$}i'$header$match)) {
                
$value $match[1];
            }
        }

        return 
$value;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util\Http;

/**
 * Proxy discovery and helper class
 *
 * @internal
 * @author John Stevenson <[email protected]>
 */
class ProxyHelper
{
    
/**
     * Returns proxy environment values
     *
     * @return array{string|null, string|null, string|null} httpProxy, httpsProxy, noProxy values
     *
     * @throws \RuntimeException on malformed url
     */
    
public static function getProxyData()
    {
        
$httpProxy null;
        
$httpsProxy null;

        
// Handle http_proxy/HTTP_PROXY on CLI only for security reasons
        
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
            if (
$env self::getProxyEnv(array('http_proxy''HTTP_PROXY'), $name)) {
                
$httpProxy self::checkProxy($env$name);
            }
        }

        
// Prefer CGI_HTTP_PROXY if available
        
if ($env self::getProxyEnv(array('CGI_HTTP_PROXY'), $name)) {
            
$httpProxy self::checkProxy($env$name);
        }

        
// Handle https_proxy/HTTPS_PROXY
        
if ($env self::getProxyEnv(array('https_proxy''HTTPS_PROXY'), $name)) {
            
$httpsProxy self::checkProxy($env$name);
        } else {
            
$httpsProxy $httpProxy;
        }

        
// Handle no_proxy
        
$noProxy self::getProxyEnv(array('no_proxy''NO_PROXY'), $name);

        return array(
$httpProxy$httpsProxy$noProxy);
    }

    
/**
     * Returns http context options for the proxy url
     *
     * @param string $proxyUrl
     *
     * @return array{http: array{proxy: string, header?: string}}
     */
    
public static function getContextOptions($proxyUrl)
    {
        
$proxy parse_url($proxyUrl);

        
// Remove any authorization
        
$proxyUrl self::formatParsedUrl($proxyfalse);
        
$proxyUrl str_replace(array('http://''https://'), array('tcp://''ssl://'), $proxyUrl);

        
$options['http']['proxy'] = $proxyUrl;

        
// Handle any authorization
        
if (isset($proxy['user'])) {
            
$auth rawurldecode($proxy['user']);

            if (isset(
$proxy['pass'])) {
                
$auth .= ':' rawurldecode($proxy['pass']);
            }
            
$auth base64_encode($auth);
            
// Set header as a string
            
$options['http']['header'] = "Proxy-Authorization: Basic {$auth}";
        }

        return 
$options;
    }

    
/**
     * Sets/unsets request_fulluri value in http context options array
     *
     * @param string  $requestUrl
     * @param mixed[] $options Set by method
     *
     * @return void
     */
    
public static function setRequestFullUri($requestUrl, array &$options)
    {
        if (
'http' === parse_url($requestUrlPHP_URL_SCHEME)) {
            
$options['http']['request_fulluri'] = true;
        } else {
            unset(
$options['http']['request_fulluri']);
        }
    }

    
/**
     * Searches $_SERVER for case-sensitive values
     *
     * @param string[]    $names Names to search for
     * @param string|null $name  Name of any found value
     *
     * @return string|null The found value
     */
    
private static function getProxyEnv(array $names, &$name)
    {
        foreach (
$names as $name) {
            if (!empty(
$_SERVER[$name])) {
                return 
$_SERVER[$name];
            }
        }

        return 
null;
    }

    
/**
     * Checks and formats a proxy url from the environment
     *
     * @param  string            $proxyUrl
     * @param  string            $envName
     * @throws \RuntimeException on malformed url
     * @return string            The formatted proxy url
     */
    
private static function checkProxy($proxyUrl$envName)
    {
        
$error sprintf('malformed %s url'$envName);
        
$proxy parse_url($proxyUrl);

        
// We need parse_url to have identified a host
        
if (!isset($proxy['host'])) {
            throw new 
\RuntimeException($error);
        }

        
$proxyUrl self::formatParsedUrl($proxytrue);

        
// We need a port because streams and curl use different defaults
        
if (!parse_url($proxyUrlPHP_URL_PORT)) {
            throw new 
\RuntimeException($error);
        }

        return 
$proxyUrl;
    }

    
/**
     * Formats a url from its component parts
     *
     * @param  array{scheme?: string, host: string, port?: int, user?: string, pass?: string} $proxy
     * @param  bool                                                                           $includeAuth
     *
     * @return string The formatted value
     */
    
private static function formatParsedUrl(array $proxy$includeAuth)
    {
        
$proxyUrl = isset($proxy['scheme']) ? strtolower($proxy['scheme']) . '://' '';

        if (
$includeAuth && isset($proxy['user'])) {
            
$proxyUrl .= $proxy['user'];

            if (isset(
$proxy['pass'])) {
                
$proxyUrl .= ':' $proxy['pass'];
            }
            
$proxyUrl .= '@';
        }

        
$proxyUrl .= $proxy['host'];

        if (isset(
$proxy['port'])) {
            
$proxyUrl .= ':' $proxy['port'];
        } elseif (
strpos($proxyUrl'http://') === 0) {
            
$proxyUrl .= ':80';
        } elseif (
strpos($proxyUrl'https://') === 0) {
            
$proxyUrl .= ':443';
        }

        return 
$proxyUrl;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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;
use 
Composer\Util\Url;

/**
 * @internal
 * @author John Stevenson <[email protected]>
 */
class ProxyManager
{
    
/** @var ?string */
    
private $error null;
    
/** @var array{http: ?string, https: ?string} */
    
private $fullProxy;
    
/** @var array{http: ?string, https: ?string} */
    
private $safeProxy;
    
/** @var array{http: array{options: mixed[]|null}, https: array{options: mixed[]|null}} */
    
private $streams;
    
/** @var bool */
    
private $hasProxy;
    
/** @var ?string */
    
private $info null;
    
/** @var ?NoProxyPattern */
    
private $noProxyHandler null;

    
/** @var ?ProxyManager */
    
private static $instance null;

    private function 
__construct()
    {
        
$this->fullProxy $this->safeProxy = array(
            
'http' => null,
            
'https' => null,
        );

        
$this->streams['http'] = $this->streams['https'] = array(
            
'options' => null,
        );

        
$this->hasProxy false;
        
$this->initProxyData();
    }

    
/**
     * @return ProxyManager
     */
    
public static function getInstance()
    {
        if (!
self::$instance) {
            
self::$instance = new self();
        }

        return 
self::$instance;
    }

    
/**
     * Clears the persistent instance
     *
     * @return void
     */
    
public static function reset()
    {
        
self::$instance null;
    }

    
/**
     * Returns a RequestProxy instance for the request url
     *
     * @param  string       $requestUrl
     * @return RequestProxy
     */
    
public function getProxyForRequest($requestUrl)
    {
        if (
$this->error) {
            throw new 
TransportException('Unable to use a proxy: '.$this->error);
        }

        
$scheme parse_url($requestUrlPHP_URL_SCHEME) ?: 'http';
        
$proxyUrl '';
        
$options = array();
        
$formattedProxyUrl '';

        if (
$this->hasProxy && in_array($scheme, array('http''https'), true) && $this->fullProxy[$scheme]) {
            if (
$this->noProxy($requestUrl)) {
                
$formattedProxyUrl 'excluded by no_proxy';
            } else {
                
$proxyUrl $this->fullProxy[$scheme];
                
$options $this->streams[$scheme]['options'];
                
ProxyHelper::setRequestFullUri($requestUrl$options);
                
$formattedProxyUrl $this->safeProxy[$scheme];
            }
        }

        return new 
RequestProxy($proxyUrl$options$formattedProxyUrl);
    }

    
/**
     * Returns true if a proxy is being used
     *
     * @return bool If false any error will be in $message
     */
    
public function isProxying()
    {
        return 
$this->hasProxy;
    }

    
/**
     * Returns proxy configuration info which can be shown to the user
     *
     * @return string|null Safe proxy URL or an error message if setting up proxy failed or null if no proxy was configured
     */
    
public function getFormattedProxy()
    {
        return 
$this->hasProxy $this->info $this->error;
    }

    
/**
     * Initializes proxy values from the environment
     *
     * @return void
     */
    
private function initProxyData()
    {
        try {
            list(
$httpProxy$httpsProxy$noProxy) = ProxyHelper::getProxyData();
        } catch (
\RuntimeException $e) {
            
$this->error $e->getMessage();

            return;
        }

        
$info = array();

        if (
$httpProxy) {
            
$info[] = $this->setData($httpProxy'http');
        }
        if (
$httpsProxy) {
            
$info[] = $this->setData($httpsProxy'https');
        }
        if (
$this->hasProxy) {
            
$this->info implode(', '$info);
            if (
$noProxy) {
                
$this->noProxyHandler = new NoProxyPattern($noProxy);
            }
        }
    }

    
/**
     * Sets initial data
     *
     * @param non-empty-string $url    Proxy url
     * @param 'http'|'https'   $scheme Environment variable scheme
     *
     * @return non-empty-string
     */
    
private function setData($url$scheme)
    {
        
$safeProxy Url::sanitize($url);
        
$this->fullProxy[$scheme] = $url;
        
$this->safeProxy[$scheme] = $safeProxy;
        
$this->streams[$scheme]['options'] = ProxyHelper::getContextOptions($url);
        
$this->hasProxy true;

        return 
sprintf('%s=%s'$scheme$safeProxy);
    }

    
/**
     * Returns true if a url matches no_proxy value
     *
     * @param  string $requestUrl
     * @return bool
     */
    
private function noProxy($requestUrl)
    {
        return 
$this->noProxyHandler && $this->noProxyHandler->test($requestUrl);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\Util\Url;

/**
 * @internal
 * @author John Stevenson <[email protected]>
 */
class RequestProxy
{
    
/** @var mixed[] */
    
private $contextOptions;
    
/** @var bool */
    
private $isSecure;
    
/** @var string */
    
private $formattedUrl;
    
/** @var string */
    
private $url;

    
/**
     * @param string  $url
     * @param mixed[] $contextOptions
     * @param string  $formattedUrl
     */
    
public function __construct($url, array $contextOptions$formattedUrl)
    {
        
$this->url $url;
        
$this->contextOptions $contextOptions;
        
$this->formattedUrl $formattedUrl;
        
$this->isSecure === strpos($url'https://');
    }

    
/**
     * Returns an array of context options
     *
     * @return mixed[]
     */
    
public function getContextOptions()
    {
        return 
$this->contextOptions;
    }

    
/**
     * Returns the safe proxy url from the last request
     *
     * @param  string|null $format Output format specifier
     * @return string      Safe proxy, no proxy or empty
     */
    
public function getFormattedUrl($format '')
    {
        
$result '';
        if (
$this->formattedUrl) {
            
$format $format ?: '%s';
            
$result sprintf($format$this->formattedUrl);
        }

        return 
$result;
    }

    
/**
     * Returns the proxy url
     *
     * @return string Proxy url or empty
     */
    
public function getUrl()
    {
        return 
$this->url;
    }

    
/**
     * Returns true if this is a secure-proxy
     *
     * @return bool False if not secure or there is no proxy
     */
    
public function isSecure()
    {
        return 
$this->isSecure;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\StreamContextFactory;
use 
Composer\Util\AuthHelper;
use 
Composer\Util\Url;
use 
Composer\Util\HttpDownloader;
use 
React\Promise\Promise;

/**
 * @internal
 * @author Jordi Boggiano <[email protected]>
 * @author Nicolas Grekas <[email protected]>
 * @phpstan-type Attributes array{retryAuthFailure: bool, redirects: int, retries: int, storeAuth: bool}
 * @phpstan-type Job array{url: string, origin: string, attributes: Attributes, options: mixed[], progress: mixed[], curlHandle: resource, filename: string|false, headerHandle: resource, bodyHandle: resource, resolve: callable, reject: callable}
 */
class CurlDownloader
{
    
/** @var ?resource */
    
private $multiHandle;
    
/** @var ?resource */
    
private $shareHandle;
    
/** @var Job[] */
    
private $jobs = array();
    
/** @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 ProxyManager */
    
private $proxyManager;
    
/** @var bool */
    
private $supportsSecureProxy;
    
/** @var array<int, string[]> */
    
protected $multiErrors = array(
        
CURLM_BAD_HANDLE => array('CURLM_BAD_HANDLE''The passed-in handle is not a valid CURLM handle.'),
        
CURLM_BAD_EASY_HANDLE => array('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 => array('CURLM_OUT_OF_MEMORY''You are doomed.'),
        
CURLM_INTERNAL_ERROR => array('CURLM_INTERNAL_ERROR''This can only be returned if libcurl bugs. Please report it to us!'),
    );

    
/** @var mixed[] */
    
private static $options = array(
        
'http' => array(
            
'method' => CURLOPT_CUSTOMREQUEST,
            
'content' => CURLOPT_POSTFIELDS,
            
'header' => CURLOPT_HTTPHEADER,
            
'timeout' => CURLOPT_TIMEOUT,
        ),
        
'ssl' => array(
            
'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 = array(
        
'total_time' => true,
        
'namelookup_time' => true,
        
'connect_time' => true,
        
'pretransfer_time' => true,
        
'starttransfer_time' => true,
        
'redirect_time' => true,
    );

    
/**
     * @param mixed[] $options
     * @param bool    $disableTls
     */
    
public function __construct(IOInterface $ioConfig $config, array $options = array(), $disableTls false)
    {
        
$this->io $io;
        
$this->config $config;

        
$this->multiHandle $mh curl_multi_init();
        if (
function_exists('curl_multi_setopt')) {
            
curl_multi_setopt($mhCURLMOPT_PIPELININGPHP_VERSION_ID >= 70400 /* CURLPIPE_MULTIPLEX */ /*CURLPIPE_HTTP1 | CURLPIPE_MULTIPLEX*/ 3);
            if (
defined('CURLMOPT_MAX_HOST_CONNECTIONS') && !defined('HHVM_VERSION')) {
                
curl_multi_setopt($mhCURLMOPT_MAX_HOST_CONNECTIONS8);
            }
        }

        if (
function_exists('curl_share_init')) {
            
$this->shareHandle $sh curl_share_init();
            
curl_share_setopt($shCURLSHOPT_SHARECURL_LOCK_DATA_COOKIE);
            
curl_share_setopt($shCURLSHOPT_SHARECURL_LOCK_DATA_DNS);
            
curl_share_setopt($shCURLSHOPT_SHARECURL_LOCK_DATA_SSL_SESSION);
        }

        
$this->authHelper = new AuthHelper($io$config);
        
$this->proxyManager ProxyManager::getInstance();

        
$version curl_version();
        
$features $version['features'];
        
$this->supportsSecureProxy defined('CURL_VERSION_HTTPS_PROXY') && ($features CURL_VERSION_HTTPS_PROXY);
    }

    
/**
     * @param callable $resolve
     * @param callable $reject
     * @param string   $origin
     * @param string   $url
     * @param mixed[]  $options
     * @param ?string  $copyTo
     *
     * @return int internal job id
     */
    
public function download($resolve$reject$origin$url$options$copyTo null)
    {
        
$attributes = array();
        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 callable $resolve
     * @param callable $reject
     * @param string   $origin
     * @param string   $url
     * @param mixed[]  $options
     * @param ?string  $copyTo
     *
     * @param array{retryAuthFailure?: bool, redirects?: int, retries?: int, storeAuth?: bool} $attributes
     *
     * @return int internal job id
     */
    
private function initDownload($resolve$reject$origin$url$options$copyTo null, array $attributes = array())
    {
        
$attributes array_merge(array(
            
'retryAuthFailure' => true,
            
'redirects' => 0,
            
'retries' => 0,
            
'storeAuth' => false,
        ), 
$attributes);

        
$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);
        }

        
$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) {
            
$errorMessage '';
            
// @phpstan-ignore-next-line
            
set_error_handler(function ($code$msg) use (&$errorMessage) {
                if (
$errorMessage) {
                    
$errorMessage .= "\n";
                }
                
$errorMessage .= Preg::replace('{^fopen\(.*?\): }'''$msg);
            });
            
$bodyHandle fopen($copyTo.'~''w+b');
            
restore_error_handler();
            if (!
$bodyHandle) {
                throw new 
TransportException('The "'.$url.'" file could not be written to '.$copyTo.': '.$errorMessage);
            }
        } else {
            
$bodyHandle = @fopen('php://temp/maxmemory:524288''w+b');
        }

        
curl_setopt($curlHandleCURLOPT_URL$url);
        
curl_setopt($curlHandleCURLOPT_FOLLOWLOCATIONfalse);
        
curl_setopt($curlHandleCURLOPT_CONNECTTIMEOUT10);
        
curl_setopt($curlHandleCURLOPT_TIMEOUTmax((int) ini_get("default_socket_timeout"), 300));
        
curl_setopt($curlHandleCURLOPT_WRITEHEADER$headerHandle);
        
curl_setopt($curlHandleCURLOPT_FILE$bodyHandle);
        
curl_setopt($curlHandleCURLOPT_ENCODING""); // let cURL set the Accept-Encoding header to what it supports
        
curl_setopt($curlHandleCURLOPT_PROTOCOLSCURLPROTO_HTTP CURLPROTO_HTTPS);

        if (
function_exists('curl_share_init')) {
            
curl_setopt($curlHandleCURLOPT_SHARE$this->shareHandle);
        }

        if (!isset(
$options['http']['header'])) {
            
$options['http']['header'] = array();
        }

        
$options['http']['header'] = array_diff($options['http']['header'], array('Connection: close'));
        
$options['http']['header'][] = 'Connection: keep-alive';

        
$version curl_version();
        
$features $version['features'];
        if (
=== strpos($url'https://') && \defined('CURL_VERSION_HTTP2') && \defined('CURL_HTTP_VERSION_2_0') && (CURL_VERSION_HTTP2 $features)) {
            
curl_setopt($curlHandleCURLOPT_HTTP_VERSIONCURL_HTTP_VERSION_2_0);
        }

        
$options['http']['header'] = $this->authHelper->addAuthenticationHeader($options['http']['header'], $origin$url);
        
$options StreamContextFactory::initOptions($url$optionstrue);

        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 $options[$type][$name]);
                    } else {
                        
curl_setopt($curlHandle$curlOption$options[$type][$name]);
                    }
                }
            }
        }

        
// Always set CURLOPT_PROXY to enable/disable proxy handling
        // Any proxy authorization is included in the proxy url
        
$proxy $this->proxyManager->getProxyForRequest($url);
        
curl_setopt($curlHandleCURLOPT_PROXY$proxy->getUrl());

        
// Curl needs certificate locations for secure proxies.
        // CURLOPT_PROXY_SSL_VERIFY_PEER/HOST are enabled by default
        
if ($proxy->isSecure()) {
            if (!
$this->supportsSecureProxy) {
                throw new 
TransportException('Connecting to a secure proxy using curl is not supported on PHP versions below 7.3.0.');
            }
            if (!empty(
$options['ssl']['cafile'])) {
                
curl_setopt($curlHandleCURLOPT_PROXY_CAINFO$options['ssl']['cafile']);
            }
            if (!empty(
$options['ssl']['capath'])) {
                
curl_setopt($curlHandleCURLOPT_PROXY_CAPATH$options['ssl']['capath']);
            }
        }

        
$progress array_diff_key(curl_getinfo($curlHandle), self::$timeInfo);

        
$this->jobs[(int) $curlHandle] = array(
            
'url' => $url,
            
'origin' => $origin,
            
'attributes' => $attributes,
            
'options' => $originalOptions,
            
'progress' => $progress,
            
'curlHandle' => $curlHandle,
            
'filename' => $copyTo,
            
'headerHandle' => $headerHandle,
            
'bodyHandle' => $bodyHandle,
            
'resolve' => $resolve,
            
'reject' => $reject,
        );

        
$usingProxy $proxy->getFormattedUrl(' using proxy (%s)');
        
$ifModified false !== stripos(implode(','$options['http']['header']), 'if-modified-since:') ? ' if modified' '';
        if (
$attributes['redirects'] === && $attributes['retries'] === 0) {
            
$this->io->writeError('Downloading ' Url::sanitize($url) . $usingProxy $ifModifiedtrueIOInterface::DEBUG);
        }

        
$this->checkCurlResult(curl_multi_add_handle($this->multiHandle$curlHandle));
        
// TODO progress

        
return (int) $curlHandle;
    }

    
/**
     * @param  int  $id
     * @return void
     */
    
public function abortRequest($id)
    {
        if (isset(
$this->jobs[$id], $this->jobs[$id]['curlHandle'])) {
            
$job $this->jobs[$id];
            
curl_multi_remove_handle($this->multiHandle$job['curlHandle']);
            
curl_close($job['curlHandle']);
            if (
is_resource($job['headerHandle'])) {
                
fclose($job['headerHandle']);
            }
            if (
is_resource($job['bodyHandle'])) {
                
fclose($job['bodyHandle']);
            }
            if (
$job['filename']) {
                @
unlink($job['filename'].'~');
            }
            unset(
$this->jobs[$id]);
        }
    }

    
/**
     * @return void
     */
    
public function tick()
    {
        static 
$timeoutWarning false;

        if (!
$this->jobs) {
            return;
        }

        
$active true;
        
$this->checkCurlResult(curl_multi_exec($this->multiHandle$active));
        if (-
=== 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);
            
$job $this->jobs[$i];
            unset(
$this->jobs[$i]);
            
$error curl_error($curlHandle);
            
$errno curl_errno($curlHandle);
            
curl_multi_remove_handle($this->multiHandle$curlHandle);
            
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, array(/* CURLE_COULDNT_CONNECT */16 /* CURLE_HTTP2 */92 /* CURLE_HTTP2_STREAM */), true)
                            || (
$errno === 35 /* CURLE_SSL_CONNECT_ERROR */ && false !== strpos($error'Connection reset by peer'))
                        ) && 
$job['attributes']['retries'] < $this->maxRetries
                    
) {
                        
$this->io->writeError('Retrying ('.($job['attributes']['retries'] + 1).') ' Url::sanitize($job['url']) . ' due to curl error '$errnotrueIOInterface::DEBUG);
                        
$this->restartJob($job$job['url'], array('retries' => $job['attributes']['retries'] + 1));
                        continue;
                    }

                    if (
$errno === 28 /* CURLE_OPERATION_TIMEDOUT */ && isset($progress['namelookup_time']) && $progress['namelookup_time'] == && !$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($headerstrue).' curl info '.var_export($progresstrue));
                }

                
// prepare response object
                
if ($job['filename']) {
                    
$contents $job['filename'].'~';
                    if (
$statusCode >= 300) {
                        
rewind($job['bodyHandle']);
                        
$contents stream_get_contents($job['bodyHandle']);
                    }
                    
$response = new CurlResponse(array('url' => $progress['url']), $statusCode$headers$contents$progress);
                    
$this->io->writeError('['.$statusCode.'] '.Url::sanitize($progress['url']), trueIOInterface::DEBUG);
                } else {
                    
rewind($job['bodyHandle']);
                    
$contents stream_get_contents($job['bodyHandle']);
                    
$response = new CurlResponse(array('url' => $progress['url']), $statusCode$headers$contents$progress);
                    
$this->io->writeError('['.$statusCode.'] '.Url::sanitize($progress['url']), trueIOInterface::DEBUG);
                }
                
fclose($job['bodyHandle']);

                if (
$response->getStatusCode() >= 400 && $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'], array('storeAuth' => $result['storeAuth']));
                    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, array('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, array(423425500502503504507510), true)
                        && 
$job['attributes']['retries'] < $this->maxRetries
                    
) {
                        
$this->io->writeError('Retrying ('.($job['attributes']['retries'] + 1).') ' Url::sanitize($job['url']) . ' due to status code '$statusCodetrueIOInterface::DEBUG);
                        
$this->restartJob($job$job['url'], array('retries' => $job['attributes']['retries'] + 1));
                        continue;
                    }

                    throw 
$this->failResponse($job$response$response->getStatusMessage());
                }

                if (
$job['attributes']['storeAuth']) {
                    
$this->authHelper->storeAuth($job['origin'], $job['attributes']['storeAuth']);
                }

                
// resolve promise
                
if ($job['filename']) {
                    
rename($job['filename'].'~'$job['filename']);
                    
call_user_func($job['resolve'], $response);
                } else {
                    
call_user_func($job['resolve'], $response);
                }
            } catch (
\Exception $e) {
                if (
$e instanceof TransportException && $headers) {
                    
$e->setHeaders($headers);
                    
$e->setStatusCode($statusCode);
                }
                if (
$e instanceof TransportException && $response) {
                    
$e->setResponse($response->getBody());
                }
                if (
$e instanceof TransportException && $progress) {
                    
$e->setResponseInfo($progress);
                }

                
$this->rejectJob($job$e);
            }
        }

        foreach (
$this->jobs as $i => $curlHandle) {
            if (!isset(
$this->jobs[$i])) {
                continue;
            }
            
$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'));
                    }
                }

                
// TODO progress
            
}
        }
    }

    
/**
     * @param  Job    $job
     * @return string
     */
    
private function handleRedirect(array $jobResponse $response)
    {
        if (
$locationHeader $response->getHeader('location')) {
            if (
parse_url($locationHeaderPHP_URL_SCHEME)) {
                
// Absolute URL; e.g. https://example.com/composer
                
$targetUrl $locationHeader;
            } elseif (
parse_url($locationHeaderPHP_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'] + 1Url::sanitize($targetUrl)), trueIOInterface::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: string|bool}
     */
    
private function isAuthenticatedRetryNeeded(array $jobResponse $response)
    {
        if (
in_array($response->getStatusCode(), array(401403)) && $job['attributes']['retryAuthFailure']) {
            
$result $this->authHelper->promptAuthIfNeeded($job['url'], $job['origin'], $response->getStatusCode(), $response->getStatusMessage(), $response->getHeaders());

            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);
                if (
$result['retry']) {
                    return 
$result;
                }
            }

            throw 
$this->failResponse($job$response$needsAuthRetry);
        }

        return array(
'retry' => false'storeAuth' => false);
    }

    
/**
     * @param  Job    $job
     * @param  string $url
     *
     * @param  array{retryAuthFailure?: bool, redirects?: int, storeAuth?: bool} $attributes
     *
     * @return void
     */
    
private function restartJob(array $job$url, array $attributes = array())
    {
        if (
$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  string             $errorMessage
     * @return TransportException
     */
    
private function failResponse(array $jobResponse $response$errorMessage)
    {
        if (
$job['filename']) {
            @
unlink($job['filename'].'~');
        }

        
$details '';
        if (
in_array(strtolower($response->getHeader('content-type')), array('application/json''application/json; charset=utf-8'), true)) {
            
$details ':'.PHP_EOL.substr($response->getBody(), 0200).(strlen($response->getBody()) > 200 '...' '');
        }

        return new 
TransportException('The "'.$job['url'].'" file could not be downloaded ('.$errorMessage.')' $details$response->getStatusCode());
    }

    
/**
     * @param  Job                $job
     * @return void
     */
    
private function rejectJob(array $job\Exception $e)
    {
        if (
is_resource($job['headerHandle'])) {
            
fclose($job['headerHandle']);
        }
        if (
is_resource($job['bodyHandle'])) {
            
fclose($job['bodyHandle']);
        }
        if (
$job['filename']) {
            @
unlink($job['filename'].'~');
        }
        
call_user_func($job['reject'], $e);
    }

    
/**
     * @param  int  $code
     * @return void
     */
    
private function checkCurlResult($code)
    {
        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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 CurlInfo
     */
    
private $curlInfo;

    
/**
     * @param CurlInfo $curlInfo
     */
    
public function __construct(array $request$code, array $headers$body, array $curlInfo)
    {
        
parent::__construct($request$code$headers$body);
        
$this->curlInfo $curlInfo;
    }

    
/**
     * @return CurlInfo
     */
    
public function getCurlInfo()
    {
        return 
$this->curlInfo;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class Tar
{
    
/**
     * @param string $pathToArchive
     *
     * @return string|null
     */
    
public static function getComposerJson($pathToArchive)
    {
        
$phar = new \PharData($pathToArchive);

        if (!
$phar->valid()) {
            return 
null;
        }

        return 
self::extractComposerJsonFromFolder($phar);
    }

    
/**
     * @param \PharData $phar
     *
     * @throws \RuntimeException
     *
     * @return string
     */
    
private static function extractComposerJsonFromFolder(\PharData $phar)
    {
        if (isset(
$phar['composer.json'])) {
            return 
$phar['composer.json']->getContent();
        }

        
$topLevelPaths = array();
        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 (
$topLevelPaths && 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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 
React\Promise\PromiseInterface;
use 
RecursiveDirectoryIterator;
use 
RecursiveIteratorIterator;
use 
Symfony\Component\Filesystem\Exception\IOException;
use 
Symfony\Component\Finder\Finder;

/**
 * @author Jordi Boggiano <[email protected]>
 * @author Johannes M. Schmitt <[email protected]>
 */
class Filesystem
{
    
/** @var ?ProcessExecutor */
    
private $processExecutor;

    public function 
__construct(ProcessExecutor $executor null)
    {
        
$this->processExecutor $executor;
    }

    
/**
     * @param string $file
     *
     * @return bool
     */
    
public function remove($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
     *
     * @param  string $dir
     * @return bool
     */
    
public function isDirEmpty($dir)
    {
        
$finder Finder::create()
            ->
ignoreVCS(false)
            ->
ignoreDotFiles(false)
            ->
depth(0)
            ->
in($dir);

        return 
\count($finder) === 0;
    }

    
/**
     * @param string $dir
     * @param bool   $ensureDirectoryExists
     *
     * @return void
     */
    
public function emptyDirectory($dir$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.
     *
     * @param  string            $directory
     * @throws \RuntimeException
     * @return bool
     */
    
public function removeDirectory($directory)
    {
        
$edgeCaseResult $this->removeEdgeCases($directory);
        if (
$edgeCaseResult !== null) {
            return 
$edgeCaseResult;
        }

        if (
Platform::isWindows()) {
            
$cmd sprintf('rmdir /S /Q %s'ProcessExecutor::escape(realpath($directory)));
        } else {
            
$cmd sprintf('rm -rf %s'ProcessExecutor::escape($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.
     *
     * @param  string            $directory
     * @throws \RuntimeException
     * @return PromiseInterface
     */
    
public function removeDirectoryAsync($directory)
    {
        
$edgeCaseResult $this->removeEdgeCases($directory);
        if (
$edgeCaseResult !== null) {
            return 
\React\Promise\resolve($edgeCaseResult);
        }

        if (
Platform::isWindows()) {
            
$cmd sprintf('rmdir /S /Q %s'ProcessExecutor::escape(realpath($directory)));
        } else {
            
$cmd sprintf('rm -rf %s'ProcessExecutor::escape($directory));
        }

        
$promise $this->getProcess()->executeAsync($cmd);

        
$self $this;

        return 
$promise->then(function ($process) use ($directory$self) {
            
// 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($self->removeDirectoryPhp($directory));
        });
    }

    
/**
     * @param string $directory
     * @param bool   $fallbackToPhp
     *
     * @return bool|null Returns null, when no edge case was hit. Otherwise a bool whether removal was successful
     */
    
private function removeEdgeCases($directory$fallbackToPhp true)
    {
        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.
     *
     * @param  string $directory
     * @return bool
     */
    
public function removeDirectoryPhp($directory)
    {
        
$edgeCaseResult $this->removeEdgeCases($directoryfalse);
        if (
$edgeCaseResult !== null) {
            return 
$edgeCaseResult;
        }

        try {
            
$it = new RecursiveDirectoryIterator($directoryRecursiveDirectoryIterator::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($directoryRecursiveDirectoryIterator::SKIP_DOTS);
        }
        
$ri = new RecursiveIteratorIterator($itRecursiveIteratorIterator::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);
    }

    
/**
     * @param string $directory
     *
     * @return void
     */
    
public function ensureDirectoryExists($directory)
    {
        if (!
is_dir($directory)) {
            if (
file_exists($directory)) {
                throw new 
\RuntimeException(
                    
$directory.' exists and is not a directory.'
                
);
            }
            if (!@
mkdir($directory0777true)) {
                throw new 
\RuntimeException(
                    
$directory.' does not exist and could not be created.'
                
);
            }
        }
    }

    
/**
     * Attempts to unlink a file and in case of failure retries after 350ms on windows
     *
     * @param  string            $path
     * @throws \RuntimeException
     * @return bool
     */
    
public function unlink($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
     *
     * @param  string            $path
     * @throws \RuntimeException
     * @return bool
     */
    
public function rmdir($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.
     *
     * @param string $source
     * @param string $target
     *
     * @return void
     */
    
public function copyThenRemove($source$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.
     *
     * @param  string $source
     * @param  string $target
     * @return bool
     */
    
public function copy($source$target)
    {
        if (!
is_dir($source)) {
            return 
copy($source$target);
        }

        
$it = new RecursiveDirectoryIterator($sourceRecursiveDirectoryIterator::SKIP_DOTS);
        
$ri = new RecursiveIteratorIterator($itRecursiveIteratorIterator::SELF_FIRST);
        
$this->ensureDirectoryExists($target);

        
$result true;
        
/** @var RecursiveDirectoryIterator $ri */
        
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;
    }

    
/**
     * @param string $source
     * @param string $target
     *
     * @return void
     */
    
public function rename($source$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.
            
$command sprintf('xcopy %s %s /E /I /Q /Y'ProcessExecutor::escape($source), ProcessExecutor::escape($target));
            
$result $this->getProcess()->execute($command$output);

            
// clear stat cache because external processes aren't tracked by the php stat cache
            
clearstatcache();

            if (
=== $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.
            
$command sprintf('mv %s %s'ProcessExecutor::escape($source), ProcessExecutor::escape($target));
            
$result $this->getProcess()->execute($command$output);

            
// clear stat cache because external processes aren't tracked by the php stat cache
            
clearstatcache();

            if (
=== $result) {
                return;
            }
        }

        
$this->copyThenRemove($source$target);
    }

    
/**
     * Returns the shortest path from $from to $to
     *
     * @param  string                    $from
     * @param  string                    $to
     * @param  bool                      $directories if true, the source/target are considered to be directories
     * @throws \InvalidArgumentException
     * @return string
     */
    
public function findShortestPath($from$to$directories false)
    {
        if (!
$this->isAbsolutePath($from) || !$this->isAbsolutePath($to)) {
            throw new 
\InvalidArgumentException(sprintf('$from (%s) and $to (%s) must be absolute paths.'$from$to));
        }

        
$from lcfirst($this->normalizePath($from));
        
$to lcfirst($this->normalizePath($to));

        if (
$directories) {
            
$from rtrim($from'/') . '/dummy_file';
        }

        if (
\dirname($from) === \dirname($to)) {
            return 
'./'.basename($to);
        }

        
$commonPath $to;
        while (
strpos($from.'/'$commonPath.'/') !== && '/' !== $commonPath && !Preg::isMatch('{^[a-z]:/?$}i'$commonPath)) {
            
$commonPath strtr(\dirname($commonPath), '\\''/');
        }

        if (
!== strpos($from$commonPath) || '/' === $commonPath) {
            return 
$to;
        }

        
$commonPath rtrim($commonPath'/') . '/';
        
$sourcePathDepth substr_count(substr($from\strlen($commonPath)), '/');
        
$commonPathCode str_repeat('../'$sourcePathDepth);

        return (
$commonPathCode substr($to\strlen($commonPath))) ?: './';
    }

    
/**
     * Returns PHP code that, when executed in $from, will return the path to $to
     *
     * @param  string                    $from
     * @param  string                    $to
     * @param  bool                      $directories if true, the source/target are considered to be directories
     * @param  bool                      $staticCode
     * @throws \InvalidArgumentException
     * @return string
     */
    
public function findShortestPathCode($from$to$directories false$staticCode false)
    {
        if (!
$this->isAbsolutePath($from) || !$this->isAbsolutePath($to)) {
            throw new 
\InvalidArgumentException(sprintf('$from (%s) and $to (%s) must be absolute paths.'$from$to));
        }

        
$from lcfirst($this->normalizePath($from));
        
$to lcfirst($this->normalizePath($to));

        if (
$from === $to) {
            return 
$directories '__DIR__' '__FILE__';
        }

        
$commonPath $to;
        while (
strpos($from.'/'$commonPath.'/') !== && '/' !== $commonPath && !Preg::isMatch('{^[a-z]:/?$}i'$commonPath) && '.' !== $commonPath) {
            
$commonPath strtr(\dirname($commonPath), '\\''/');
        }

        if (
!== strpos($from$commonPath) || '/' === $commonPath || '.' === $commonPath) {
            return 
var_export($totrue);
        }

        
$commonPath rtrim($commonPath'/') . '/';
        if (
strpos($to$from.'/') === 0) {
            return 
'__DIR__ . '.var_export(substr($to\strlen($from)), true);
        }
        
$sourcePathDepth substr_count(substr($from\strlen($commonPath)), '/') + $directories;
        if (
$staticCode) {
            
$commonPathCode "__DIR__ . '".str_repeat('/..'$sourcePathDepth)."'";
        } else {
            
$commonPathCode str_repeat('dirname('$sourcePathDepth).'__DIR__'.str_repeat(')'$sourcePathDepth);
        }
        
$relTarget substr($to\strlen($commonPath));

        return 
$commonPathCode . (\strlen($relTarget) ? '.' var_export('/' $relTargettrue) : '');
    }

    
/**
     * Checks if the given path is absolute
     *
     * @param  string $path
     * @return bool
     */
    
public function isAbsolutePath($path)
    {
        return 
strpos($path'/') === || substr($path11) === ':' || 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($path)
    {
        if (!
file_exists($path)) {
            throw new 
\RuntimeException("$path does not exist.");
        }
        if (
is_dir($path)) {
            return 
$this->directorySize($path);
        }

        return 
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($path)
    {
        
$parts = array();
        
$path strtr($path'\\''/');
        
$prefix '';
        
$absolute '';

        
// extract windows UNC paths e.g. \\foo\bar
        
if (strpos($path'//') === && \strlen($path) > 2) {
            
$absolute '//';
            
$path substr($path2);
        }

        
// extract a prefix being a protocol://, protocol:, protocol://drive: or simply drive:
        
if (Preg::isMatch('{^( [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($path1);
        }

        
$up false;
        foreach (
explode('/'$path) as $chunk) {
            if (
'..' === $chunk && ($absolute !== '' || $up)) {
                
array_pop($parts);
                
$up = !(empty($parts) || '..' === end($parts));
            } elseif (
'.' !== $chunk && '' !== $chunk) {
                
$parts[] = $chunk;
                
$up '..' !== $chunk;
            }
        }

        return 
$prefix.((string) $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
     *
     * @param  string $path
     * @return string
     */
    
public static function trimTrailingSlash($path)
    {
        if (!
Preg::isMatch('{^[/\\\\]+$}'$path)) {
            
$path rtrim($path'/\\');
        }

        return 
$path;
    }

    
/**
     * Return if the given path is local
     *
     * @param  string $path
     * @return bool
     */
    
public static function isLocalPath($path)
    {
        return 
Preg::isMatch('{^(file://(?!//)|/(?!/)|/?[a-z]:[\\\\/]|\.\.[\\\\/]|[a-z0-9_.-]+[\\\\/])}i'$path);
    }

    
/**
     * @param string $path
     *
     * @return string
     */
    
public static function getPlatformPath($path)
    {
        if (
Platform::isWindows()) {
            
$path Preg::replace('{^(?:file:///([a-z]):?/)}i''file://$1:/'$path);
        }

        return (string) 
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
     *
     * @param  string $path
     * @return bool
     */
    
public static function isReadable($path)
    {
        if (
is_readable($path)) {
            return 
true;
        }

        if (
is_file($path)) {
            return 
false !== Silencer::call('file_get_contents'$pathfalsenull01);
        }

        if (
is_dir($path)) {
            return 
false !== Silencer::call('opendir'$path);
        }

        
// assume false otherwise
        
return false;
    }

    
/**
     * @param string $directory
     *
     * @return int
     */
    
protected function directorySize($directory)
    {
        
$it = new RecursiveDirectoryIterator($directoryRecursiveDirectoryIterator::SKIP_DOTS);
        
$ri = new RecursiveIteratorIterator($itRecursiveIteratorIterator::CHILD_FIRST);

        
$size 0;
        foreach (
$ri as $file) {
            if (
$file->isFile()) {
                
$size += $file->getSize();
            }
        }

        return 
$size;
    }

    
/**
     * @return ProcessExecutor
     */
    
protected function getProcess()
    {
        if (!
$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
     *
     * @param string $path
     *
     * @return bool
     */
    
private function unlinkImplementation($path)
    {
        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($target$link)
    {
        if (!
function_exists('symlink')) {
            return 
false;
        }

        
$cwd 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.
     *
     * @param string $directory
     *
     * @return bool
     */
    
public function isSymlinkedDirectory($directory)
    {
        if (!
is_dir($directory)) {
            return 
false;
        }

        
$resolved $this->resolveSymlinkedDirectorySymlink($directory);

        return 
is_link($resolved);
    }

    
/**
     * @param string $directory
     *
     * @return bool
     */
    
private function unlinkSymlinkedDirectory($directory)
    {
        
$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($pathname)
    {
        if (!
is_dir($pathname)) {
            return 
$pathname;
        }

        
$resolved rtrim($pathname'/');

        if (!
\strlen($resolved)) {
            return 
$pathname;
        }

        return 
$resolved;
    }

    
/**
     * Creates an NTFS junction.
     *
     * @param string $target
     * @param string $junction
     *
     * @return void
     */
    
public function junction($target$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), 0null$target);
        }
        
$cmd sprintf(
            
'mklink /J %s %s',
            
ProcessExecutor::escape(str_replace('/'DIRECTORY_SEPARATOR$junction)),
            
ProcessExecutor::escape(realpath($target))
        );
        if (
$this->getProcess()->execute($cmd$output) !== 0) {
            throw new 
IOException(sprintf('Failed to create junction to "%s" at "%s".'$target$junction), 0null$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($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 $stat 0x4000 !== ($stat['mode'] & 0xF000) : false;
    }

    
/**
     * Removes a Windows NTFS junction.
     *
     * @param  string $junction
     * @return bool
     */
    
public function removeJunction($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);
    }

    
/**
     * @param string $path
     * @param string $content
     *
     * @return int|false
     */
    
public function filePutContentsIfModified($path$content)
    {
        
$currentContent = @file_get_contents($path);
        if (!
$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
     *
     * @param string $source
     * @param string $target
     *
     * @return void
     */
    
public function safeCopy($source$target)
    {
        if (!
file_exists($target) || !file_exists($source) || !$this->filesAreEqual($source$target)) {
            
$source fopen($source'r');
            
$target fopen($target'w+');

            
stream_copy_to_stream($source$target);
            
fclose($source);
            
fclose($target);
        }
    }

    
/**
     * compare 2 files
     * https://stackoverflow.com/questions/3060125/can-i-use-file-get-contents-to-compare-two-files
     *
     * @param string $a
     * @param string $b
     *
     * @return bool
     */
    
private function filesAreEqual($a$b)
    {
        
// Check if filesize is different
        
if (filesize($a) !== filesize($b)) {
            return 
false;
        }

        
// Check if content is different
        
$ah fopen($a'rb');
        
$bh fopen($b'rb');

        
$result true;
        while (!
feof($ah)) {
            if (
fread($ah8192) != fread($bh8192)) {
                
$result false;
                break;
            }
        }

        
fclose($ah);
        
fclose($bh);

        return 
$result;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class Platform
{
    
/** @var ?bool */
    
private static $isVirtualBoxGuest null;
    
/** @var ?bool */
    
private static $isWindowsSubsystemForLinux null;

    
/**
     * getenv() equivalent but reads from the runtime global variables first
     *
     * @param  string $name
     * @return string|false
     */
    
public static function getEnv($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
     *
     * @param  string $name
     * @param  string $value
     * @return void
     */
    
public static function putEnv($name$value)
    {
        
$value = (string) $value;
        
putenv($name '=' $value);
        
$_SERVER[$name] = $_ENV[$name] = $value;
    }

    
/**
     * putenv('X') equivalent but updates the runtime global variables too
     *
     * @param  string $name
     * @return void
     */
    
public static function clearEnv($name)
    {
        
putenv($name);
        unset(
$_SERVER[$name], $_ENV[$name]);
    }

    
/**
     * Parses tildes and environment variables in paths.
     *
     * @param  string $path
     * @return string
     */
    
public static function expandPath($path)
    {
        if (
Preg::isMatch('#^~[\\/]#'$path)) {
            return 
self::getUserDirectory() . substr($path1);
        }

        return 
Preg::replaceCallback('#^(\$|(?P<percent>%))(?P<var>\w++)(?(percent)%)(?P<path>.*)#', function ($matches) {
            
// Treat HOME as an alias for USERPROFILE on Windows for legacy reasons
            
if (Platform::isWindows() && $matches['var'] == 'HOME') {
                return (
Platform::getEnv('HOME') ?: 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()
    {
        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());

            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()
    {
        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 (
                !
ini_get('open_basedir')
                && 
is_readable('/proc/version')
                && 
false !== stripos(Silencer::call('file_get_contents''/proc/version'), 'microsoft')
                && !
file_exists('/.dockerenv'// docker 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()
    {
        return 
\defined('PHP_WINDOWS_VERSION_BUILD');
    }

    
/**
     * @param  string $str
     * @return int    return a guaranteed binary length of the string, regardless of silly mbstring configs
     */
    
public static function strlen($str)
    {
        static 
$useMbString null;
        if (
null === $useMbString) {
            
$useMbString \function_exists('mb_strlen') && 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
     * @return bool
     */
    
public static function isTty($fd null)
    {
        if (
$fd === null) {
            
$fd defined('STDOUT') ? STDOUT fopen('php://stdout''w');
        }

        
// 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(self::getEnv('MSYSTEM') ?: ''), array('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);
        
// Check if formatted mode is S_IFCHR
        
return $stat 0020000 === ($stat['mode'] & 0170000) : false;
    }

    
/**
     * @return void
     */
    
public static function workaroundFilesystemIssues()
    {
        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
     *
     * @return bool
     */
    
private static function isVirtualBoxGuest()
    {
        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 (
$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 (
=== $process->execute('lsmod | grep vboxguest'$ignoredOutput)) {
                        return 
self::$isVirtualBoxGuest true;
                    }
                } catch (
\Exception $e) {
                    
// noop
                
}
            }
        }

        return 
self::$isVirtualBoxGuest;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @author Markus Tacker <[email protected]>
 */
final class StreamContextFactory
{
    
/**
     * Creates a context supporting HTTP proxies
     *
     * @param 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($url, array $defaultOptions = array(), array $defaultParams = array())
    {
        
$options = array('http' => array(
            
// specify defaults again to try and work better with curlwrappers enabled
            
'follow_location' => 1,
            
'max_redirects' => 20,
        ));

        
$options array_replace_recursive($optionsself::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 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($url, array $options$forCurl false)
    {
        
// Make sure the headers are in an array form
        
if (!isset($options['http']['header'])) {
            
$options['http']['header'] = array();
        }
        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);
            if (
$proxyOptions $proxy->getContextOptions()) {
                
$isHttpsRequest === 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 $optionsLoggerInterface $logger null)
    {
        
$ciphers implode(':', array(
            
'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 = array(
            
'ssl' => array(
                
'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.
         */
        
if (PHP_VERSION_ID >= 50413) {
            
$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)
    {
        if (!
is_array($header)) {
            
$header explode("\r\n"$header);
        }
        
uasort($header, function ($el) {
            return 
stripos($el'content-type') === : -1;
        });

        return 
$header;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\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   $downloader  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
     *
     * @return void
     */
    
public static function downloadAndInstallPackageSync(Loop $loopDownloaderInterface $downloader$pathPackageInterface $packagePackageInterface $prevPackage null)
    {
        
$type $prevPackage 'update' 'install';

        try {
            
self::await($loop$downloader->download($package$path$prevPackage));

            
self::await($loop$downloader->prepare($type$package$path$prevPackage));

            if (
$type === 'update') {
                
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()
     * @param PromiseInterface|null $promise
     *
     * @return void
     */
    
public static function await(Loop $loopPromiseInterface $promise null)
    {
        if (
$promise) {
            
$loop->wait(array($promise));
        }
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class GitHub
{
    
/** @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 $ioConfig $configProcessExecutor $process nullHttpDownloader $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($originUrl)
    {
        if (!
in_array($originUrl$this->config->get('github-domains'))) {
            return 
false;
        }

        
// if available use token from git config
        
if (=== $this->process->execute('git config github.accesstoken'$output)) {
            
$this->io->setAuthentication($originUrltrim($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($originUrl$message null)
    {
        if (
$message) {
            
$this->io->writeError($message);
        }

        
$note 'Composer';
        if (
$this->config->get('github-expose-hostname') === true && === $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(sprintf('When working with _public_ GitHub repositories only, head to %s to retrieve a token.'$url));
        
$this->io->writeError('This token will have read-only permission for public information only.');

        
$url 'https://'.$originUrl.'/settings/tokens/new?scopes=repo&description=' str_replace('%20''+'rawurlencode($note));
        
$this->io->writeError(sprintf('When you need to access _private_ GitHub repositories as well, go to %s'$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.'$this->config->getAuthConfigSource()->getName()));
        
$this->io->writeError('For additional information, check https://getcomposer.org/doc/articles/authentication-for-private-packages.md#github-oauth');

        
$token trim($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, array(
                
'retry-auth-failure' => false,
            ));
        } catch (
TransportException $e) {
            if (
in_array($e->getCode(), array(403401))) {
                
$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 user config
        
$this->config->getConfigSource()->removeConfigSetting('github-oauth.'.$originUrl);
        
$this->config->getAuthConfigSource()->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)
    {
        
$rateLimit = array(
            
'limit' => '?',
            
'reset' => '?',
        );

        foreach (
$headers as $header) {
            
$header trim($header);
            if (
false === strpos($header'X-RateLimit-')) {
                continue;
            }
            list(
$type$value) = explode(':'$header2);
            switch (
$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.
     *
     * @return string|null
     */
    
public function getSsoUrl(array $headers)
    {
        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.
     *
     * @return bool
     */
    
public function isRateLimited(array $headers)
    {
        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.
     *
     * @return bool
     */
    
public function requiresSso(array $headers)
    {
        foreach (
$headers as $header) {
            if (
Preg::isMatch('{^X-GitHub-SSO: required}i'trim($header))) {
                return 
true;
            }
        }

        return 
false;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
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()
    {
        return 
XdebugHandler::getAllIniFiles();
    }

    
/**
     * Describes the location of the loaded php.ini file(s)
     *
     * @return string
     */
    
public static function getMessage()
    {
        
$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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class ComposerMirror
{
    
/**
     * @param string      $mirrorUrl
     * @param string      $packageName
     * @param string      $version
     * @param string|null $reference
     * @param string|null $type
     * @param string|null $prettyVersion
     *
     * @return string
     */
    
public static function processUrl($mirrorUrl$packageName$version$reference$type$prettyVersion null)
    {
        if (
$reference) {
            
$reference Preg::isMatch('{^([a-f0-9]*|%reference%)$}'$reference) ? $reference md5($reference);
        }
        
$version strpos($version'/') === false $version md5($version);

        
$from = array('%package%''%version%''%reference%''%type%');
        
$to = array($packageName$version$reference$type);
        if (
null !== $prettyVersion) {
            
$from[] = '%prettyVersion%';
            
$to[] = $prettyVersion;
        }

        return 
str_replace($from$to$mirrorUrl);
    }

    
/**
     * @param string      $mirrorUrl
     * @param string      $packageName
     * @param string      $url
     * @param string|null $type
     *
     * @return string
     */
    
public static function processGitUrl($mirrorUrl$packageName$url$type)
    {
        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(
            array(
'%package%''%normalizedUrl%''%type%'),
            array(
$packageName$url$type),
            
$mirrorUrl
        
);
    }

    
/**
     * @param string $mirrorUrl
     * @param string $packageName
     * @param string $url
     * @param string $type
     *
     * @return string
     */
    
public static function processHgUrl($mirrorUrl$packageName$url$type)
    {
        return 
self::processGitUrl($mirrorUrl$packageName$url$type);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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;

class 
PackageSorter
{
    
/**
     * Sorts packages by dependency weight
     *
     * Packages of equal weight retain the original order
     *
     * @param  PackageInterface[] $packages
     * @return PackageInterface[] sorted array
     */
    
public static function sortPackages(array $packages)
    {
        
$usageList = array();

        foreach (
$packages as $package) {
            foreach (
array_merge($package->getRequires(), $package->getDevRequires()) as $link) {
                
$target $link->getTarget();
                
$usageList[$target][] = $package->getName();
            }
        }
        
$computing = array();
        
$computed = array();
        
$computeImportance = function ($name) use (&$computeImportance, &$computing, &$computed$usageList) {
            
// reusing computed importance
            
if (isset($computed[$name])) {
                return 
$computed[$name];
            }

            
// canceling circular dependency
            
if (isset($computing[$name])) {
                return 
0;
            }

            
$computing[$name] = true;
            
$weight 0;

            if (isset(
$usageList[$name])) {
                foreach (
$usageList[$name] as $user) {
                    
$weight -= $computeImportance($user);
                }
            }

            unset(
$computing[$name]);
            
$computed[$name] = $weight;

            return 
$weight;
        };

        
$weightList = array();

        foreach (
$packages as $index => $package) {
            
$weight $computeImportance($package->getName());
            
$weightList[$index] = $weight;
        }

        
$stable_sort = function (&$array) {
            static 
$transform$restore;

            
$i 0;

            if (!
$transform) {
                
$transform = function (&$v$k) use (&$i) {
                    
$v = array($v, ++$i$k$v);
                };

                
$restore = function (&$v) {
                    
$v $v[3];
                };
            }

            
array_walk($array$transform);
            
asort($array);
            
array_walk($array$restore);
        };

        
$stable_sort($weightList);

        
$sortedPackages = array();

        foreach (
array_keys($weightList) as $index) {
            
$sortedPackages[] = $packages[$index];
        }

        return 
$sortedPackages;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class Loop
{
    
/** @var HttpDownloader */
    
private $httpDownloader;
    
/** @var ProcessExecutor|null */
    
private $processExecutor;
    
/** @var PromiseInterface[][] */
    
private $currentPromises = array();
    
/** @var int */
    
private $waitIndex 0;

    public function 
__construct(HttpDownloader $httpDownloaderProcessExecutor $processExecutor null)
    {
        
$this->httpDownloader $httpDownloader;
        
$this->httpDownloader->enableAsync();

        
$this->processExecutor $processExecutor;
        if (
$this->processExecutor) {
            
$this->processExecutor->enableAsync();
        }
    }

    
/**
     * @return HttpDownloader
     */
    
public function getHttpDownloader()
    {
        return 
$this->httpDownloader;
    }

    
/**
     * @return ProcessExecutor|null
     */
    
public function getProcessExecutor()
    {
        return 
$this->processExecutor;
    }

    
/**
     * @param  PromiseInterface[] $promises
     * @param  ?ProgressBar       $progress
     * @return void
     */
    
public function wait(array $promisesProgressBar $progress null)
    {
        
/** @var \Exception|null */
        
$uncaught null;

        
\React\Promise\all($promises)->then(
            function () {
            },
            function (
$e) use (&$uncaught) {
                
$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 (
$uncaught) {
            throw 
$uncaught;
        }
    }

    
/**
     * @return void
     */
    
public function abortJobs()
    {
        foreach (
$this->currentPromises as $promiseGroup) {
            foreach (
$promiseGroup as $promise) {
                if (
$promise instanceof CancellablePromiseInterface) {
                    
$promise->cancel();
                }
            }
        }
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class Url
{
    
/**
     * @param  Config $config
     * @param  string $url
     * @param  string $ref
     * @return string the updated URL
     */
    
public static function updateDistReference(Config $config$url$ref)
    {
        
$host parse_url($urlPHP_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);
        }

        return 
$url;
    }

    
/**
     * @param  string $url
     * @return string
     */
    
public static function getOrigin(Config $config$url)
    {
        if (
=== strpos($url'file://')) {
            return 
$url;
        }

        
$origin = (string) parse_url($urlPHP_URL_HOST);
        if (
$port parse_url($urlPHP_URL_PORT)) {
            
$origin .= ':'.$port;
        }

        if (
strpos($origin'.github.com') === (strlen($origin) - 11)) {
            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 (
            
is_array($config->get('gitlab-domains'))
            && 
false === strpos($origin'/')
            && !
in_array($origin$config->get('gitlab-domains'))
        ) {
            foreach (
$config->get('gitlab-domains') as $gitlabDomain) {
                if (
=== strpos($gitlabDomain$origin)) {
                    return 
$gitlabDomain;
                }
            }
        }

        return 
$origin;
    }

    
/**
     * @param  string $url
     * @return string
     */
    
public static function sanitize($url)
    {
        
// 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', function ($m) {
            
// if the username looks like a long (12char+) hex string, or a modern github token (e.g. ghp_xxx) we obfuscate that
            
if (Preg::isMatch('{^([a-f0-9]{12,}|gh[a-z]_[a-zA-Z0-9_]+)$}'$m['user'])) {
                return 
$m['prefix'].'***:***@';
            }

            return 
$m['prefix'].$m['user'].':***@';
        }, 
$url);

        return 
$url;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer;

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 <[email protected]>
 */
class Config
{
    const 
SOURCE_DEFAULT 'default';
    const 
SOURCE_COMMAND 'command';
    const 
SOURCE_UNKNOWN 'unknown';

    const 
RELATIVE_PATHS 1;

    
/** @var array<string, mixed> */
    
public static $defaultConfig = array(
        
'process-timeout' => 300,
        
'use-include-path' => false,
        
'allow-plugins' => null// null for BC for now, will become array() after July 2022
        
'use-parent-dir' => 'prompt',
        
'preferred-install' => 'dist',
        
'notify-on-install' => true,
        
'github-protocols' => array('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' => array('github.com'),
        
'bitbucket-expose-hostname' => true,
        
'disable-tls' => false,
        
'secure-http' => true,
        
'secure-svn-domains' => array(),
        
'cafile' => null,
        
'capath' => null,
        
'github-expose-hostname' => true,
        
'gitlab-domains' => array('gitlab.com'),
        
'store-auths' => 'prompt',
        
'platform' => array(),
        
'archive-format' => 'tar',
        
'archive-dir' => '.',
        
'htaccess-protect' => true,
        
'use-github-api' => true,
        
'lock' => true,
        
'platform-check' => 'php-only',
        
// valid keys without defaults (auth config stuff):
        // bitbucket-oauth
        // github-oauth
        // gitlab-oauth
        // gitlab-token
        // http-basic
        // bearer
    
);

    
/** @var array<string, mixed> */
    
public static $defaultRepositories = array(
        
'packagist.org' => array(
            
'type' => 'composer',
            
'url' => 'https://repo.packagist.org',
        ),
    );

    
/** @var array<string, mixed> */
    
private $config;
    
/** @var ?string */
    
private $baseDir;
    
/** @var array<int|string, mixed> */
    
private $repositories;
    
/** @var ConfigSourceInterface */
    
private $configSource;
    
/** @var ConfigSourceInterface */
    
private $authConfigSource;
    
/** @var bool */
    
private $useEnvironment;
    
/** @var array<string, true> */
    
private $warnedHosts = array();
    
/** @var array<string, string> */
    
private $sourceOfConfigValue = array();

    
/**
     * @param bool   $useEnvironment Use COMPOSER_ environment variables to replace config settings
     * @param string $baseDir        Optional base directory of the config
     */
    
public function __construct($useEnvironment true$baseDir null)
    {
        
// load defaults
        
$this->config = static::$defaultConfig;

        
// TODO after July 2022 remove this and update the default value above in self::$defaultConfig + remove note from 06-config.md
        
if (strtotime('2022-07-01') < time()) {
            
$this->config['allow-plugins'] = array();
        }

        
$this->repositories = static::$defaultRepositories;
        
$this->useEnvironment = (bool) $useEnvironment;
        
$this->baseDir $baseDir;

        foreach (
$this->config as $configKey => $configValue) {
            
$this->setSourceOfConfigValue($configValue$configKeyself::SOURCE_DEFAULT);
        }

        foreach (
$this->repositories as $configKey => $configValue) {
            
$this->setSourceOfConfigValue($configValue'repositories.' $configKeyself::SOURCE_DEFAULT);
        }
    }

    
/**
     * @return void
     */
    
public function setConfigSource(ConfigSourceInterface $source)
    {
        
$this->configSource $source;
    }

    
/**
     * @return ConfigSourceInterface
     */
    
public function getConfigSource()
    {
        return 
$this->configSource;
    }

    
/**
     * @return void
     */
    
public function setAuthConfigSource(ConfigSourceInterface $source)
    {
        
$this->authConfigSource $source;
    }

    
/**
     * @return ConfigSourceInterface
     */
    
public function getAuthConfigSource()
    {
        return 
$this->authConfigSource;
    }

    
/**
     * Merges new config values with the existing ones (overriding)
     *
     * @param array{config?: array<string, mixed>, repositories?: array<mixed>} $config
     * @param string $source
     *
     * @return void
     */
    
public function merge($config$source self::SOURCE_UNKNOWN)
    {
        
// override defaults with given config
        
if (!empty($config['config']) && is_array($config['config'])) {
            foreach (
$config['config'] as $key => $val) {
                if (
in_array($key, array('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, array('allow-plugins'), true) && isset($this->config[$key]) && is_array($this->config[$key])) {
                    
// 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, array('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 = array('*' => $val);
                        }
                        if (
is_string($this->config[$key])) {
                            
$this->config[$key] = array('*' => $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);
                    }
                } else {
                    
$this->config[$key] = $val;
                    
$this->setSourceOfConfigValue($val$key$source);
                }
            }
        }

        if (!empty(
$config['repositories']) && is_array($config['repositories'])) {
            
$this->repositories array_reverse($this->repositoriestrue);
            
$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) && === 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->repositoriestrue), $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->repositoriestrue);
        }
    }

    
/**
     * @return array<int|string, mixed>
     */
    
public function getRepositories()
    {
        return 
$this->repositories;
    }

    
/**
     * Returns a setting
     *
     * @param  string            $key
     * @param  int               $flags Options (see class constants)
     * @throws \RuntimeException
     *
     * @return mixed
     */
    
public function get($key$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);
                }

                
$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 (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'$this->config[$key], $matches)) {
                    throw new 
\RuntimeException(
                        
"Could not parse the value of '$key': {$this->config[$key]}"
                    
);
                }
                
$size $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 
$size;

            
// special cases below
            
case 'cache-files-ttl':
                if (isset(
$this->config[$key])) {
                    return (int) 
$this->config[$key];
                }

                return (int) 
$this->config['cache-ttl'];

            case 
'home':
                
$val Preg::replace('#^(\$HOME|~)(/|$)#'rtrim(Platform::getEnv('HOME') ?: Platform::getEnv('USERPROFILE'), '/\\') . '/'$this->config[$key]);

                return 
rtrim($this->process($val$flags), '/\\');

            case 
'bin-compat':
                
$value $this->getComposerEnv('COMPOSER_BIN_COMPAT') ?: $this->config[$key];

                if (!
in_array($value, array('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':
                if (
$env $this->getComposerEnv('COMPOSER_DISCARD_CHANGES')) {
                    if (!
in_array($env, array('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], array(truefalse'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;

            default:
                if (!isset(
$this->config[$key])) {
                    return 
null;
                }

                return 
$this->process($this->config[$key], $flags);
        }
    }

    
/**
     * @param int $flags
     *
     * @return array<string, mixed[]>
     */
    
public function all($flags 0)
    {
        
$all = array(
            
'repositories' => $this->getRepositories(),
        );
        foreach (
array_keys($this->config) as $key) {
            
$all['config'][$key] = $this->get($key$flags);
        }

        return 
$all;
    }

    
/**
     * @param string $key
     * @return string
     */
    
public function getSourceOfValue($key)
    {
        
$this->get($key);

        return isset(
$this->sourceOfConfigValue[$key]) ? $this->sourceOfConfigValue[$key] : self::SOURCE_UNKNOWN;
    }

    
/**
     * @param mixed  $configValue
     * @param string $path
     * @param string $source
     *
     * @return void
     */
    
private function setSourceOfConfigValue($configValue$path$source)
    {
        
$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()
    {
        return array(
            
'repositories' => $this->getRepositories(),
            
'config' => $this->config,
        );
    }

    
/**
     * Checks whether a setting exists
     *
     * @param  string $key
     * @return bool
     */
    
public function has($key)
    {
        return 
array_key_exists($key$this->config);
    }

    
/**
     * Replaces {$refs} inside a config string
     *
     * @param  string|int|null $value a config string that can contain {$refs-to-other-config}
     * @param  int             $flags Options (see class constants)
     *
     * @return string|int|null
     */
    
private function process($value$flags)
    {
        
$config $this;

        if (!
is_string($value)) {
            return 
$value;
        }

        return 
Preg::replaceCallback('#\{\$(.+)\}#', function ($match) use ($config$flags) {
            return 
$config->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.
     *
     * @param  string $path
     * @return string
     */
    
private function realpath($path)
    {
        if (
Preg::isMatch('{^(?:/|[a-z]:|[a-z0-9.]+://)}i'$path)) {
            return 
$path;
        }

        return 
$this->baseDir $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  string      $var
     * @return string|bool
     */
    
private function getComposerEnv($var)
    {
        if (
$this->useEnvironment) {
            return 
Platform::getEnv($var);
        }

        return 
false;
    }

    
/**
     * @param string $name
     *
     * @return void
     */
    
private function disableRepoByName($name)
    {
        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 string      $url
     * @param IOInterface $io
     *
     * @return void
     */
    
public function prohibitUrlByConfig($urlIOInterface $io null)
    {
        
// Return right away if the URL is malformed or custom (see issue #5173)
        
if (false === filter_var($urlFILTER_VALIDATE_URL)) {
            return;
        }

        
// Extract scheme and throw exception on known insecure protocols
        
$scheme parse_url($urlPHP_URL_SCHEME);
        
$hostname parse_url($urlPHP_URL_HOST);
        if (
in_array($scheme, array('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) {
                
$host parse_url($urlPHP_URL_HOST);
                if (
is_string($host)) {
                    if (!isset(
$this->warnedHosts[$host])) {
                        
$io->writeError("<warning>Warning: Accessing $host over $scheme which is an insecure protocol.</warning>");
                    }
                    
$this->warnedHosts[$host] = true;
                }
            }
        }
    }

    
/**
     * Used by long-running custom scripts in composer.json
     *
     * "scripts": {
     *   "watch": [
     *     "Composer\\Config::disableProcessTimeout",
     *     "vendor/bin/long-running-script --watch"
     *   ]
     * }
     *
     * @return void
     */
    
public static function disableProcessTimeout()
    {
        
// Override global timeout set earlier by environment or config
        
ProcessExecutor::setTimeout(0);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @author Nils Adermann <[email protected]>
 */
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
     */
    
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($nameComposer $composerIOInterface $io$devMode false, array $args = array(), array $flags = array())
    {
        
parent::__construct($name$args$flags);
        
$this->composer $composer;
        
$this->io $io;
        
$this->devMode $devMode;
    }

    
/**
     * Returns the composer instance.
     *
     * @return Composer
     */
    
public function getComposer()
    {
        return 
$this->composer;
    }

    
/**
     * Returns the IO instance.
     *
     * @return IOInterface
     */
    
public function getIO()
    {
        return 
$this->io;
    }

    
/**
     * Return the dev mode flag
     *
     * @return bool
     */
    
public function isDevMode()
    {
        return 
$this->devMode;
    }

    
/**
     * Set the originating event.
     *
     * @return ?BaseEvent
     */
    
public function getOriginatingEvent()
    {
        return 
$this->originatingEvent;
    }

    
/**
     * Set the originating event.
     *
     * @param  BaseEvent $event
     * @return $this
     */
    
public function setOriginatingEvent(BaseEvent $event)
    {
        
$this->originatingEvent $this->calculateOriginatingEvent($event);

        return 
$this;
    }

    
/**
     * Returns the upper-most event in chain.
     *
     * @param  BaseEvent $event
     * @return BaseEvent
     */
    
private function calculateOriginatingEvent(BaseEvent $event)
    {
        if (
$event instanceof Event && $event->getOriginatingEvent()) {
            return 
$this->calculateOriginatingEvent($event->getOriginatingEvent());
        }

        return 
$event;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 */
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
     */
    
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
     */
    
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
     */
    
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
     */
    
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
     */
    
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
     */
    
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
     */
    
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
     */
    
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
     */
    
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
     */
    
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
     */
    
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
     */
    
const POST_ARCHIVE_CMD 'post-archive-cmd';
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 
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 <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 */
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
     *
     * @return void
     *
     * @throws \RuntimeException
     */
    
public function compile($pharFile 'composer.phar')
    {
        if (
file_exists($pharFile)) {
            
unlink($pharFile);
        }

        
// TODO in v2.3 always call with an array
        
if (method_exists('Symfony\Component\Process\Process''fromShellCommandline')) {
            
$process = new Process(array('git''log''--pretty="%H"''-n1''HEAD'), __DIR__);
        } else {
            
// @phpstan-ignore-next-line
            
$process = new Process('git log --pretty="%H" -n1 HEAD'__DIR__);
        }
        if (
$process->run() != 0) {
            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($process->getOutput());

        
// TODO in v2.3 always call with an array
        
if (method_exists('Symfony\Component\Process\Process''fromShellCommandline')) {
            
$process = new Process(array('git''log''-n1''--pretty=%ci''HEAD'), __DIR__);
        } else {
            
// @phpstan-ignore-next-line
            
$process = new Process('git log -n1 --pretty=%ci HEAD'__DIR__);
        }
        if (
$process->run() != 0) {
            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($process->getOutput()));
        
$this->versionDate->setTimezone(new \DateTimeZone('UTC'));

        
// TODO in v2.3 always call with an array
        
if (method_exists('Symfony\Component\Process\Process''fromShellCommandline')) {
            
$process = new Process(array('git''describe''--tags''--exact-match''HEAD'), __DIR__);
        } else {
            
// @phpstan-ignore-next-line
            
$process = new Process('git describe --tags --exact-match HEAD');
        }
        if (
$process->run() == 0) {
            
$this->version trim($process->getOutput());
        } 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'];
            }
        }

        
$phar = new \Phar($pharFile0'composer.phar');
        
$phar->setSignatureAlgorithm(\Phar::SHA512);

        
$phar->startBuffering();

        
$finderSort = function ($a$b) {
            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$filefalse);
        }

        
// Add vendor files
        
$finder = new Finder();
        
$finder->files()
            ->
ignoreVCS(true)
            ->
notPath('/\/(composer\.(json|lock)|[A-Z]+\.md|\.gitignore|appveyor.yml|phpunit\.xml\.dist|phpstan\.neon\.dist|phpstan-config\.neon)$/')
            ->
notPath('/bin\/(jsonlint|validate-json|simple-phpunit)(\.bat)?$/')
            ->
notPath('symfony/debug/Resources/ext/')
            ->
notPath('justinrainbow/json-schema/demo/')
            ->
notPath('justinrainbow/json-schema/dist/')
            ->
notPath('composer/installed.json')
            ->
notPath('composer/LICENSE')
            ->
exclude('Tests')
            ->
exclude('tests')
            ->
exclude('docs')
            ->
in(__DIR__.'/../../vendor/')
            ->
sort($finderSort)
        ;

        
$extraFiles = array(
            
realpath(__DIR__ '/../../vendor/composer/spdx-licenses/res/spdx-exceptions.json'),
            
realpath(__DIR__ '/../../vendor/composer/spdx-licenses/res/spdx-licenses.json'),
            
realpath(CaBundle::getBundledCaBundlePath()),
            
realpath(__DIR__ '/../../vendor/symfony/console/Resources/bin/hiddeninput.exe'),
            
realpath(__DIR__ '/../../vendor/symfony/polyfill-mbstring/Resources/mb_convert_variables.php8'),
        );
        
$unexpectedFiles = array();

        foreach (
$finder as $file) {
            if (
in_array(realpath($file), $extraFilestrue)) {
                unset(
$extraFiles[array_search(realpath($file), $extraFilestrue)]);
            } elseif (!
Preg::isMatch('{([/\\\\]LICENSE|\.php)$}'$file)) {
                
$unexpectedFiles[] = (string) $file;
            }

            if (
Preg::isMatch('{\.php[\d.]*$}'$file)) {
                
$this->addFile($phar$file);
            } else {
                
$this->addFile($phar$filefalse);
            }
        }

        if (
$extraFiles) {
            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.implode(PHP_EOL$extraFiles));
        }
        if (
$unexpectedFiles) {
            throw new 
\RuntimeException('These files were unexpectedly added to the phar, make sure they are excluded or listed in $extraFiles:'.PHP_EOL.implode(PHP_EOL$unexpectedFiles));
        }

        
// 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);
    }

    
/**
     * @param  \SplFileInfo $file
     * @return string
     */
    
private function getRelativeFilePath($file)
    {
        
$realPath $file->getRealPath();
        
$pathPrefix dirname(dirname(__DIR__)).DIRECTORY_SEPARATOR;

        
$pos strpos($realPath$pathPrefix);
        
$relativePath = ($pos !== false) ? substr_replace($realPath''$posstrlen($pathPrefix)) : $realPath;

        return 
strtr($relativePath'\\''/');
    }

    
/**
     * @param bool $strip
     *
     * @return void
     */
    
private function addFile(\Phar $phar\SplFileInfo $file$strip true)
    {
        
$path $this->getRelativeFilePath($file);
        
$content file_get_contents($file);
        if (
$strip) {
            
$content $this->stripWhitespace($content);
        } elseif (
'LICENSE' === basename($file)) {
            
$content "\n".$content."\n";
        }

        if (
$path === 'src/Composer/Composer.php') {
            
$content strtr(
                
$content,
                array(
                    
'@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);
    }

    
/**
     * @return void
     */
    
private function addComposerBin(\Phar $phar)
    {
        
$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($source)
    {
        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], array(T_COMMENTT_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;
    }

    
/**
     * @return string
     */
    
private function getStub()
    {
        
$stub = <<<'EOF'
#!/usr/bin/env php
<?php
/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class NoSslException extends \RuntimeException
{
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class IrrecoverableDownloadException extends \RuntimeException
{
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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`
 */
class InstalledVersions
{
    
/**
     * @var mixed[]|null
     * @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}|array{}|null
     */
    
private static $installed;

    
/**
     * @var bool|null
     */
    
private static $canGetVendors;

    
/**
     * @var array[]
     * @psalm-var array<string, array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: 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 (
=== \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 || empty($installed['versions'][$packageName]['dev_requirement']);
            }
        }

        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($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, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}
     */
    
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, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: 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__, -81) !== '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, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: 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, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>} $data
     */
    
public static function reload($data)
    {
        
self::$installed $data;
        
self::$installedByVendor = array();
    }

    
/**
     * @return array[]
     * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
     */
    
private static function getInstalled()
    {
        if (
null === self::$canGetVendors) {
            
self::$canGetVendors method_exists('Composer\Autoload\ClassLoader''getRegisteredLoaders');
        }

        
$installed = array();

        if (
self::$canGetVendors) {
            foreach (
ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
                if (isset(
self::$installedByVendor[$vendorDir])) {
                    
$installed[] = self::$installedByVendor[$vendorDir];
                } elseif (
is_file($vendorDir.'/composer/installed.php')) {
                    
$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
                    if (
null === self::$installed && strtr($vendorDir.'/composer''\\''/') === strtr(__DIR__'\\''/')) {
                        
self::$installed $installed[count($installed) - 1];
                    }
                }
            }
        }

        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__, -81) !== 'C') {
                
self::$installed = require __DIR__ '/installed.php';
            } else {
                
self::$installed = array();
            }
        }
        
$installed[] = self::$installed;

        return 
$installed;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
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 IOInterface $io
     * @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$cacheDir$allowlist 'a-z0-9.'Filesystem $filesystem null$readOnly false)
    {
        
$this->io $io;
        
$this->root rtrim($cacheDir'/\\') . '/';
        
$this->allowlist $allowlist;
        
$this->filesystem $filesystem ?: new Filesystem();
        
$this->readOnly = (bool) $readOnly;

        if (!
self::isUsable($cacheDir)) {
            
$this->enabled false;
        }
    }

    
/**
     * @param bool $readOnly
     *
     * @return void
     */
    
public function setReadOnly($readOnly)
    {
        
$this->readOnly = (bool) $readOnly;
    }

    
/**
     * @return bool
     */
    
public function isReadOnly()
    {
        return 
$this->readOnly;
    }

    
/**
     * @param string $path
     *
     * @return bool
     */
    
public static function isUsable($path)
    {
        return !
Preg::isMatch('{(^|[\\\\/])(\$null|nul|NUL|/dev/null)([\\\\/]|$)}'$path);
    }

    
/**
     * @return bool
     */
    
public function isEnabled()
    {
        if (
$this->enabled === null) {
            
$this->enabled true;

            if (
                (!
is_dir($this->root) && !Silencer::call('mkdir'$this->root0777true))
                || !
is_writable($this->root)
            ) {
                
$this->io->writeError('<warning>Cannot create cache directory ' $this->root ', or directory is not writable. Proceeding without cache</warning>');
                
$this->enabled false;
            }
        }

        return 
$this->enabled;
    }

    
/**
     * @return string
     */
    
public function getRoot()
    {
        return 
$this->root;
    }

    
/**
     * @param string $file
     *
     * @return string|false
     */
    
public function read($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'trueIOInterface::DEBUG);

                return 
file_get_contents($this->root $file);
            }
        }

        return 
false;
    }

    
/**
     * @param string $file
     * @param string $contents
     *
     * @return bool
     */
    
public function write($file$contents)
    {
        if (
$this->isEnabled() && !$this->readOnly) {
            
$file Preg::replace('{[^'.$this->allowlist.']}i''-'$file);

            
$this->io->writeError('Writing '.$this->root $file.' into cache'trueIOInterface::DEBUG);

            
$tempFileName $this->root $file uniqid('.'true) . '.tmp';
            try {
                return 
file_put_contents($tempFileName$contents) !== false && rename($tempFileName$this->root $file);
            } catch (
\ErrorException $e) {
                
$this->io->writeError('<warning>Failed to write into cache: '.$e->getMessage().'</warning>'trueIOInterface::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$u bytes of free space available</warning>',
                        
$tempFileName,
                        
$m[1],
                        
$m[2],
                        @
disk_free_space(dirname($tempFileName))
                    );

                    
$this->io->writeError($message);

                    return 
false;
                }

                throw 
$e;
            }
        }

        return 
false;
    }

    
/**
     * Copy a file into the cache
     *
     * @param string $file
     * @param string $source
     *
     * @return bool
     */
    
public function copyFrom($file$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 
copy($source$this->root $file);
        }

        return 
false;
    }

    
/**
     * Copy a file out of the cache
     *
     * @param string $file
     * @param string $target
     *
     * @return bool
     */
    
public function copyTo($file$target)
    {
        if (
$this->isEnabled()) {
            
$file Preg::replace('{[^'.$this->allowlist.']}i''-'$file);
            if (
file_exists($this->root $file)) {
                try {
                    
touch($this->root $filefilemtime($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'trueIOInterface::DEBUG);

                return 
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 (
PHP_VERSION_ID 70000) {
            return !
random_int(050);
        }

        return !
mt_rand(050);
    }

    
/**
     * @param string $file
     *
     * @return bool
     */
    
public function remove($file)
    {
        if (
$this->isEnabled()) {
            
$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->filesystem->emptyDirectory($this->root);

            return 
true;
        }

        return 
false;
    }

    
/**
     * @param string $file
     * @return int|false
     * @phpstan-return int<0, max>|false
     */
    
public function getAge($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;
    }

    
/**
     * @param int $ttl
     * @param int $maxSize
     *
     * @return bool
     */
    
public function gc($ttl$maxSize)
    {
        if (
$this->isEnabled()) {
            
$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;
    }

    
/**
     * @param string $file
     *
     * @return string|false
     */
    
public function sha1($file)
    {
        if (
$this->isEnabled()) {
            
$file Preg::replace('{[^'.$this->allowlist.']}i''-'$file);
            if (
file_exists($this->root $file)) {
                return 
sha1_file($this->root $file);
            }
        }

        return 
false;
    }

    
/**
     * @param string $file
     *
     * @return string|false
     */
    
public function sha256($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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class PlatformRepository extends ArrayRepository
{
    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 = array();

    
/**
     * Stores which packages have been disabled and their actual version
     *
     * @var array<string, CompletePackageInterface>
     */
    
private $disabledPackages = array();

    
/** @var Runtime */
    
private $runtime;
    
/** @var HhvmDetector */
    
private $hhvmDetector;

    
/**
     * @param array<string, string|false> $overrides
     */
    
public function __construct(array $packages = array(), array $overrides = array(), Runtime $runtime nullHhvmDetector $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($versiontrue));
            }
            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)] = array('name' => $name'version' => $version);
        }
        
parent::__construct($packages);
    }

    public function 
getRepoName()
    {
        return 
'platform repo';
    }

    
/**
     * @param  string  $name
     * @return boolean
     */
    
public function isPlatformPackageDisabled($name)
    {
        return isset(
$this->disabledPackages[$name]);
    }

    
/**
     * @return array<string, CompletePackageInterface>
     */
    
public function getDisabledPackages()
    {
        return 
$this->disabledPackages;
    }

    protected function 
initialize()
    {
        
parent::initialize();

        
$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(array($this->runtime'invoke'), 'inet_pton', array('::')) !== 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, array('standard''Core'))) {
                continue;
            }

            
$this->addExtension($name$this->runtime->getExtensionVersion($name));
        }

        
// Check for Xdebug in a restarted process
        
if (!in_array('xdebug'$loadedExtensionstrue) && ($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($name.'-librabbitmq'$librabbitmqMatches['version'], 'AMQP librabbitmq version');
                    }

                    
// AMQP protocol version => 0-9-1
                    
if (Preg::isMatch('/^AMQP protocol version => (?<version>.+)$/im'$info$protocolMatches)) {
                        
$this->addLibrary($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($name$matches['version']);
                    }
                    break;

                case 
'curl':
                    
$curlVersion $this->runtime->invoke('curl_version');
                    
$this->addLibrary($name$curlVersion['version']);

                    
$info $this->runtime->getExtensionInfo($name);

                    
// SSL Version => OpenSSL/1.0.1t
                    
if (Preg::isMatch('{^SSL Version => (?<library>[^/]+)/(?<version>.+)$}im'$info$sslMatches)) {
                        
$library strtolower($sslMatches['library']);
                        if (
$library === 'openssl') {
                            
$parsedVersion Version::parseOpenssl($sslMatches['version'], $isFips);
                            
$this->addLibrary($name.'-openssl'.($isFips '-fips' ''), $parsedVersion'curl OpenSSL version ('.$parsedVersion.')', array(), $isFips ? array('curl-openssl') : array());
                        } else {
                            
$this->addLibrary($name.'-'.$library$sslMatches['version'], 'curl '.$library.' version ('.$sslMatches['version'].')', array('curl-openssl'));
                        }
                    }

                    
// libSSH Version => libssh2/1.4.3
                    
if (Preg::isMatch('{^libSSH Version => (?<library>[^/]+)/(?<version>.+?)(?:/.*)?$}im'$info$sshMatches)) {
                        
$this->addLibrary($name.'-'.strtolower($sshMatches['library']), $sshMatches['version'], 'curl '.$sshMatches['library'].' version');
                    }

                    
// ZLib Version => 1.2.8
                    
if (Preg::isMatch('{^ZLib Version => (?<version>.+)$}im'$info$zlibMatches)) {
                        
$this->addLibrary($name.'-zlib'$zlibMatches['version'], 'curl zlib version');
                    }
                    break;

                case 
'date':
                    
$info $this->runtime->getExtensionInfo($name);

                    
// timelib version => 2018.03
                    
if (Preg::isMatch('/^timelib version => (?<version>.+)$/im'$info$timelibMatches)) {
                        
$this->addLibrary($name.'-timelib'$timelibMatches['version'], 'date timelib version');
                    }

                    
// Timezone Database => internal
                    
if (Preg::isMatch('/^Timezone Database => (?<source>internal|external)$/im'$info$zoneinfoSourceMatches)) {
                        
$external $zoneinfoSourceMatches['source'] === 'external';
                        if (
Preg::isMatch('/^"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'$loadedExtensionstrue)) {
                                
$this->addLibrary('timezonedb-zoneinfo'$zoneinfoMatches['version'], 'zoneinfo ("Olson") database for date (replaced by timezonedb)', array($name.'-zoneinfo'));
                            } else {
                                
$this->addLibrary($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($name.'-libmagic'$magicMatches['version'], 'fileinfo libmagic version');
                    }
                    break;

                case 
'gd':
                    
$this->addLibrary($name$this->runtime->getConstant('GD_VERSION'));

                    
$info $this->runtime->getExtensionInfo($name);

                    if (
Preg::isMatch('/^libJPEG Version => (?<version>.+?)(?: compatible)?$/im'$info$libjpegMatches)) {
                        
$this->addLibrary($name.'-libjpeg'Version::parseLibjpeg($libjpegMatches['version']), 'libjpeg version for gd');
                    }

                    if (
Preg::isMatch('/^libPNG Version => (?<version>.+)$/im'$info$libpngMatches)) {
                        
$this->addLibrary($name.'-libpng'$libpngMatches['version'], 'libpng version for gd');
                    }

                    if (
Preg::isMatch('/^FreeType Version => (?<version>.+)$/im'$info$freetypeMatches)) {
                        
$this->addLibrary($name.'-freetype'$freetypeMatches['version'], 'freetype version for gd');
                    }

                    if (
Preg::isMatch('/^libXpm Version => (?<versionId>\d+)$/im'$info$libxpmMatches)) {
                        
$this->addLibrary($name.'-libxpm'Version::convertLibxpmVersionId($libxpmMatches['versionId']), 'libxpm version for gd');
                    }

                    break;

                case 
'gmp':
                    
$this->addLibrary($name$this->runtime->getConstant('GMP_VERSION'));
                    break;

                case 
'iconv':
                    
$this->addLibrary($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('icu'$this->runtime->getConstant('INTL_ICU_VERSION'), $description);
                    } elseif (
Preg::isMatch('/^ICU version => (?<version>.+)$/im'$info$matches)) {
                        
$this->addLibrary('icu'$matches['version'], $description);
                    }

                    
// ICU TZData version => 2019c
                    
if (Preg::isMatch('/^ICU TZData version => (?<version>.*)$/im'$info$zoneinfoMatches)) {
                        
$this->addLibrary('icu-zoneinfo'Version::parseZoneinfoVersion($zoneinfoMatches['version']), 'zoneinfo ("Olson") database for icu');
                    }

                    
// Add a separate version for the CLDR library version
                    
if ($this->runtime->hasClass('ResourceBundle')) {
                        
$cldrVersion $this->runtime->invoke(array('ResourceBundle''create'), array('root''ICUDATA'false))->get('Version');
                        
$this->addLibrary('icu-cldr'$cldrVersion'ICU CLDR project version');
                    }

                    if (
$this->runtime->hasClass('IntlChar')) {
                        
$this->addLibrary('icu-unicode'implode('.'array_slice($this->runtime->invoke(array('IntlChar''getUnicodeVersion')), 03)), 'ICU unicode version');
                    }
                    break;

                case 
'imagick':
                    
$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
                    
Preg::match('/^ImageMagick (?<version>[\d.]+)(?:-(?<patch>\d+))?/'$imageMagickVersion['versionString'], $matches);
                    
$version $matches['version'];
                    if (isset(
$matches['patch'])) {
                        
$version .= '.'.$matches['patch'];
                    }

                    
$this->addLibrary($name.'-imagemagick'$versionnull, array('imagick'));
                    break;

                case 
'ldap':
                    
$info $this->runtime->getExtensionInfo($name);

                    if (
Preg::isMatch('/^Vendor Version => (?<versionId>\d+)$/im'$info$matches) && Preg::isMatch('/^Vendor Name => (?<vendor>.+)$/im'$info$vendorMatches)) {
                        
$this->addLibrary($name.'-'.strtolower($vendorMatches['vendor']), Version::convertOpenldapVersionId($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(function ($extension) {
                        return 
$extension '-libxml';
                    }, 
array_intersect($loadedExtensions, array('dom''simplexml''xml''xmlreader''xmlwriter')));
                    
$this->addLibrary($name$this->runtime->getConstant('LIBXML_DOTTED_VERSION'), 'libxml library version', array(), $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($name.'-libmbfl'$libmbflMatches['version'], 'mbstring libmbfl version');
                    }

                    if (
$this->runtime->hasConstant('MB_ONIGURUMA_VERSION')) {
                        
$this->addLibrary($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($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($name.'-libmemcached'$matches['version'], 'libmemcached version');
                    }
                    break;

                case 
'openssl':
                    
// OpenSSL 1.1.1g  21 Apr 2020
                    
if (Preg::isMatch('{^(?:OpenSSL|LibreSSL)?\s*(?<version>\S+)}i'$this->runtime->getConstant('OPENSSL_VERSION_TEXT'), $matches)) {
                        
$parsedVersion Version::parseOpenssl($matches['version'], $isFips);
                        
$this->addLibrary($name.($isFips '-fips' ''), $parsedVersion$this->runtime->getConstant('OPENSSL_VERSION_TEXT'), array(), $isFips ? array($name) : array());
                    }
                    break;

                case 
'pcre':
                    
$this->addLibrary($namePreg::replace('{^(\S+).*}''$1'$this->runtime->getConstant('PCRE_VERSION')));

                    
$info $this->runtime->getExtensionInfo($name);

                    
// PCRE Unicode Version => 12.1.0
                    
if (Preg::isMatch('/^PCRE Unicode Version => (?<version>.+)$/im'$info$pcreUnicodeMatches)) {
                        
$this->addLibrary($name.'-unicode'$pcreUnicodeMatches['version'], 'PCRE Unicode version support');
                    }

                    break;

                case 
'mysqlnd':
                case 
'pdo_mysql':
                    
$info $this->runtime->getExtensionInfo($name);

                    if (
Preg::isMatch('/^(?:Client API version|Version) => mysqlnd (?<version>.+?) /mi'$info$matches)) {
                        
$this->addLibrary($name.'-mysqlnd'$matches['version'], 'mysqlnd library version for '.$name);
                    }
                    break;

                case 
'mongodb':
                    
$info $this->runtime->getExtensionInfo($name);

                    if (
Preg::isMatch('/^libmongoc bundled version => (?<version>.+)$/im'$info$libmongocMatches)) {
                        
$this->addLibrary($name.'-libmongoc'$libmongocMatches['version'], 'libmongoc version of mongodb');
                    }

                    if (
Preg::isMatch('/^libbson bundled version => (?<version>.+)$/im'$info$libbsonMatches)) {
                        
$this->addLibrary($name.'-libbson'$libbsonMatches['version'], 'libbson version of mongodb');
                    }
                    break;

                case 
'pgsql':
                case 
'pdo_pgsql':
                    
$info $this->runtime->getExtensionInfo($name);

                    if (
Preg::isMatch('/^PostgreSQL\(libpq\) Version => (?<version>.*)$/im'$info$matches)) {
                        
$this->addLibrary($name.'-libpq'$matches['version'], 'libpq for '.$name);
                    }
                    break;

                case 
'libsodium':
                case 
'sodium':
                    if (
$this->runtime->hasConstant('SODIUM_LIBRARY_VERSION')) {
                        
$this->addLibrary('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($name.'-sqlite'$matches['version']);
                    }
                    break;

                case 
'ssh2':
                    
$info $this->runtime->getExtensionInfo($name);

                    if (
Preg::isMatch('/^libssh2 version => (?<version>.+)$/im'$info$matches)) {
                        
$this->addLibrary($name.'-libssh2'$matches['version']);
                    }
                    break;

                case 
'xsl':
                    
$this->addLibrary('libxslt'$this->runtime->getConstant('LIBXSLT_DOTTED_VERSION'), null, array('xsl'));

                    
$info $this->runtime->getExtensionInfo('xsl');
                    if (
Preg::isMatch('/^libxslt compiled against libxml Version => (?<version>.+)$/im'$info$matches)) {
                        
$this->addLibrary('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($name.'-libyaml'$matches['version'], 'libyaml version of yaml');
                    }
                    break;

                case 
'zip':
                    if (
$this->runtime->hasConstant('LIBZIP_VERSION''ZipArchive')) {
                        
$this->addLibrary($name.'-libzip'$this->runtime->getConstant('LIBZIP_VERSION''ZipArchive'), null, array('zip'));
                    }
                    break;

                case 
'zlib':
                    if (
$this->runtime->hasConstant('ZLIB_VERSION')) {
                        
$this->addLibrary($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($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)
    {
        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']) && === strpos($package->getName(), 'php-')) {
            if (isset(
$this->overrides[$package->getName()]) && $this->overrides[$package->getName()]['version'] === false) {
                
$this->addDisabledPackage($package);
                return;
            }

            
$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
     * @param string|null $name
     *
     * @return CompletePackage
     */
    
private function addOverriddenPackage(array $override$name null)
    {
        
$version $this->versionParser->normalize($override['version']);
        
$package = new CompletePackage($name ?: $override['name'], $version$override['version']);
        
$package->setDescription('Package overridden via config.platform');
        
$package->setExtra(array('config.platform' => true));
        
parent::addPackage($package);

        if (
$package->getName() === 'php') {
            
self::$lastSeenPlatformPhp implode('.'array_slice(explode('.'$package->getVersion()), 03));
        }

        return 
$package;
    }

    
/**
     * @return void
     */
    
private function addDisabledPackage(CompletePackage $package)
    {
        
$package->setDescription($package->getDescription().'. <warning>Package disabled via config.platform</warning>');
        
$package->setExtra(array('config.platform' => true));

        
$this->disabledPackages[$package->getName()] = $package;
    }

    
/**
     * Parses the version and adds a new package to the repository
     *
     * @param string      $name
     * @param null|string $prettyVersion
     *
     * @return void
     */
    
private function addExtension($name$prettyVersion)
    {
        
$extraDescription null;

        try {
            
$version $this->versionParser->normalize($prettyVersion);
        } catch (
\UnexpectedValueException $e) {
            
$extraDescription ' (actual version: '.$prettyVersion.')';
            if (
Preg::isMatch('{^(\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);

        if (
$name === 'uuid') {
            
$ext->setReplaces(array(
                
'lib-uuid' => new Link('ext-uuid''lib-uuid', new Constraint('='$version), Link::TYPE_REPLACE$ext->getPrettyVersion()),
            ));
        }

        
$this->addPackage($ext);
    }

    
/**
     * @param  string $name
     * @return string
     */
    
private function buildPackageName($name)
    {
        return 
'ext-' str_replace(' ''-'strtolower($name));
    }

    
/**
     * @param string      $name
     * @param string      $prettyVersion
     * @param string|null $description
     * @param string[]    $replaces
     * @param string[]    $provides
     *
     * @return void
     */
    
private function addLibrary($name$prettyVersion$description null, array $replaces = array(), array $provides = array())
    {
        try {
            
$version $this->versionParser->normalize($prettyVersion);
        } catch (
\UnexpectedValueException $e) {
            return;
        }

        if (
$description === null) {
            
$description 'The '.$name.' library';
        }

        
$lib = new CompletePackage('lib-'.$name$version$prettyVersion);
        
$lib->setDescription($description);

        
$replaceLinks = array();
        foreach (
$replaces as $replace) {
            
$replace strtolower($replace);
            
$replaceLinks[$replace] = new Link('lib-'.$name'lib-'.$replace, new Constraint('='$version), Link::TYPE_REPLACE$lib->getPrettyVersion());
        }
        
$provideLinks = array();
        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.
     *
     * @param  string $name
     * @return bool
     */
    
public static function isPlatformPackage($name)
    {
        static 
$cache = array();

        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
     * @return string|null
     */
    
public static function getPlatformPhpVersion()
    {
        return 
self::$lastSeenPlatformPhp;
    }

    public function 
search($query$mode 0$type null)
    {
        
// suppress vendor search as there are no vendors to match in platform packages
        
if ($mode === self::SEARCH_VENDOR) {
            return array();
        }

        return 
parent::search($query$mode$type);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\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;

/**
 * @author Jordi Boggiano <[email protected]>
 */
class ComposerRepository extends ArrayRepository implements ConfigurableRepositoryInterface
{
    
/**
     * @var mixed[]
     * @phpstan-var array{url: string, options?: mixed[], type?: 'composer', allow_ssl_downgrade?: bool}
     */
    
private $repoConfig;
    
/** @var mixed[] */
    
private $options;
    
/** @var string */
    
private $url;
    
/** @var string */
    
private $baseUrl;
    
/** @var IOInterface */
    
private $io;
    
/** @var HttpDownloader */
    
private $httpDownloader;
    
/** @var Loop */
    
private $loop;
    
/** @var Cache */
    
protected $cache;
    
/** @var ?string */
    
protected $notifyUrl null;
    
/** @var ?string */
    
protected $searchUrl null;
    
/** @var ?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 ?string */
    
protected $providersUrl null;
    
/** @var ?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<string> */
    
protected $availablePackagePatterns null;
    
/** @var ?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, array<int, array{url: string, preferred: bool}>> */
    
private $sourceMirrors;
    
/** @var ?array<int, array{url: 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;

    
/**
     * TODO v3 should make this private once we can drop PHP 5.3 support
     * @private
     * @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>
     */
    
public $freshMetadataUrls = array();

    
/**
     * TODO v3 should make this private once we can drop PHP 5.3 support
     * @private
     * @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>
     */
    
public $packagesNotFoundCache = array();
    
/**
     * TODO v3 should make this private once we can drop PHP 5.3 support
     * @private
     * @var VersionParser
     */
    
public $versionParser;

    
/**
     * @param array<string, mixed> $repoConfig
     * @phpstan-param array{url: string, options?: mixed[], type?: 'composer', allow_ssl_downgrade?: bool} $repoConfig
     */
    
public function __construct(array $repoConfigIOInterface $ioConfig $configHttpDownloader $httpDownloaderEventDispatcher $eventDispatcher null)
    {
        
parent::__construct();
        if (!
Preg::isMatch('{^[\w.]+\??://}'$repoConfig['url'])) {
            
// assume http as the default protocol
            
$repoConfig['url'] = 'http://'.$repoConfig['url'];
        }
        
$repoConfig['url'] = rtrim($repoConfig['url'], '/');

        if (
strpos($repoConfig['url'], 'https?') === 0) {
            
$repoConfig['url'] = (extension_loaded('openssl') ? 'https' 'http') . substr($repoConfig['url'], 6);
        }

        
$urlBits parse_url($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'] = array();
        }
        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';
        }

        
$this->baseUrl rtrim(Preg::replace('{(?:/[^/\\\\]+\.json)?(?:[?#].*)?$}'''$this->url), '/');
        
$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($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), $constrainttrue);
            }

            if (
$this->hasAvailablePackageList && !$this->lazyProvidersRepoContains($name)) {
                return 
null;
            }

            
$packages $this->loadAsyncPackages(array($name => $constraint));

            return 
reset($packages['packages']);
        }

        if (
$hasProviders) {
            foreach (
$this->getProviderNames() as $providerName) {
                if (
$name === $providerName) {
                    return 
$this->filterPackages($this->whatProvides($providerName), $constrainttrue);
                }
            }

            return 
null;
        }

        return 
parent::findPackage($name$constraint);
    }

    
/**
     * @inheritDoc
     */
    
public function findPackages($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 array();
            }

            
$result $this->loadAsyncPackages(array($name => $constraint));

            return 
$result['packages'];
        }

        if (
$hasProviders) {
            foreach (
$this->getProviderNames() as $providerName) {
                if (
$name === $providerName) {
                    return 
$this->filterPackages($this->whatProvides($providerName), $constraint);
                }
            }

            return array();
        }

        return 
parent::findPackages($name$constraint);
    }

    
/**
     * @param array<BasePackage> $packages
     * @param ConstraintInterface|null $constraint
     * @param bool $returnFirstMatch
     *
     * @return BasePackage|array<BasePackage>|null
     */
    
private function filterPackages(array $packages$constraint null$returnFirstMatch false)
    {
        if (
null === $constraint) {
            if (
$returnFirstMatch) {
                return 
reset($packages);
            }

            return 
$packages;
        }

        
$filteredPackages = array();

        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 = array();
                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($packageFilter null)
    {
        
$hasProviders $this->hasProviders();

        
$filterResults =
            
/**
             * @param list<string> $results
             * @return list<string>
             */
            
function (array $results) {
                return 
$results;
            }
        ;
        if (
null !== $packageFilter && '' !== $packageFilter) {
            
$packageFilterRegex BasePackage::packageNameToRegexp($packageFilter);
            
$filterResults =
                
/**
                 * @param list<string> $results
                 * @return list<string>
                 */
                
function (array $results) use ($packageFilterRegex) {
                    
/** @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 array();
        }

        if (
$hasProviders) {
            return 
$filterResults($this->getProviderNames());
        }

        
$names = array();
        foreach (
$this->getPackages() as $package) {
            
$names[] = $package->getPrettyName();
        }

        return 
$filterResults($names);
    }

    
/**
     * @return list<string>
     */
    
private function getVendorNames()
    {
        
$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 = array();
        foreach (
$names as $name) {
            
// @phpstan-ignore-next-line
            
$uniques[substr($name0strpos($name'/'))] = true;
        }

        
$vendors array_keys($uniques);

        if (!
$this->cache->isReadOnly()) {
            
$this->cache->write($cacheKeyimplode("\n"$vendors));
        }

        return 
$vendors;
    }

    
/**
     * @param string|null $packageFilter
     * @return list<string>
     */
    
private function loadPackageList($packageFilter null)
    {
        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($cacheKeyimplode("\n"$result['packageNames']));
        }

        return 
$result['packageNames'];
    }

    public function 
loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = array())
    {
        
// this call initializes loadRootServerFile which is needed for the rest below to work
        
$hasProviders $this->hasProviders();

        if (!
$hasProviders && !$this->hasPartialPackages() && !$this->lazyProvidersUrl) {
            return 
parent::loadPackages($packageNameMap$acceptableStabilities$stabilityFlags$alreadyLoaded);
        }

        
$packages = array();
        
$namesFound = array();

        if (
$hasProviders || $this->hasPartialPackages()) {
            foreach (
$packageNameMap as $name => $constraint) {
                
$matches = array();

                
// 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 array(
'namesFound' => array_keys($namesFound), 'packages' => $packages);
    }

    
/**
     * @inheritDoc
     */
    
public function search($query$mode 0$type null)
    {
        
$this->loadRootServerFile(600);

        if (
$this->searchUrl && $mode === self::SEARCH_FULLTEXT) {
            
$url str_replace(array('%query%''%type%'), array($query$type), $this->searchUrl);

            
$search $this->httpDownloader->get($url$this->options)->decodeJson();

            if (empty(
$search['results'])) {
                return array();
            }

            
$results = array();
            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 = array();
            
$regex '{(?:'.implode('|'Preg::split('{\s+}'$query)).')}i';

            
$vendorNames $this->getVendorNames();
            foreach (
Preg::grep($regex$vendorNames) as $name) {
                
$results[] = array('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::isMatch('{^\^(?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 = array();
                foreach (
$result['packageNames'] as $name) {
                    
$results[] = array('name' => $name'description' => '');
                }

                return 
$results;
            }

            
$results = array();
            
$regex '{(?:'.implode('|'Preg::split('{\s+}'$query)).')}i';

            
$packageNames $this->getPackageNames();
            foreach (
Preg::grep($regex$packageNames) as $name) {
                
$results[] = array('name' => $name'description' => '');
            }

            return 
$results;
        }

        return 
parent::search($query$mode);
    }

    public function 
getProviders($packageName)
    {
        
$this->loadRootServerFile();
        
$result = array();

        if (
$this->providersApiUrl) {
            
$apiResult $this->httpDownloader->get(str_replace('%package%'$packageName$this->providersApiUrl), $this->options)->decodeJson();

            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']] = array(
                        
'name' => $candidate['name'],
                        
'description' => isset($candidate['description']) ? $candidate['description'] : '',
                        
'type' => isset($candidate['type']) ? $candidate['type'] : '',
                    );
                }
            }
        }

        if (
$this->packages) {
            
$result array_merge($resultparent::getProviders($packageName));
        }

        return 
$result;
    }

    
/**
     * @return string[]
     */
    
private function getProviderNames()
    {
        
$this->loadRootServerFile();

        if (
null === $this->providerListing) {
            
$this->loadProviderListings($this->loadRootServerFile());
        }

        if (
$this->lazyProvidersUrl) {
            
// Can not determine list of provided packages for lazy repositories
            
return array();
        }

        if (
$this->providersUrl) {
            return 
array_keys($this->providerListing);
        }

        return array();
    }

    
/**
     * @return void
     */
    
protected function configurePackageTransportOptions(PackageInterface $package)
    {
        foreach (
$package->getDistUrls() as $url) {
            if (
strpos($url$this->baseUrl) === 0) {
                
$package->setTransportOptions($this->options);

                return;
            }
        }
    }

    
/**
     * @return bool
     */
    
private function hasProviders()
    {
        
$this->loadRootServerFile();

        return 
$this->hasProviders;
    }

    
/**
     * @param  string      $name package name
     * @param array<string, int>|null $acceptableStabilities
     * @phpstan-param array<string, 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($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 array();
            }

            if (
null === $this->providerListing) {
                
$this->loadProviderListings($this->loadRootServerFile());
            }

            
$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 array();
                }

                
$hash $this->providerListing[$name]['sha256'];
                
$url str_replace(array('%package%''%hash%'), array($name$hash), $this->providersUrl);
                
$cacheKey 'provider-'.strtr($name'/''$').'.json';
            } else {
                return array();
            }

            
$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($contentstrue);
                    
// 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(), array(404499), true)) {
                        
$packages = array('packages' => array());
                        
$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 = array('packages' => array('versions' => $this->partialPackagesByName[$name]));
            
$packagesSource 'root file ('.Url::sanitize($this->getPackagesJsonUrl()).')';
            
$loadingPartialPackage true;
        }

        
$result = array();
        
$versionsToLoad = array();
        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
     *
     * @param PackageInterface $package
     */
    
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<string, 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 = array();
        
$namesFound = array();
        
$promises = array();
        
$repo $this;

        if (!
$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, array($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) === && 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;
            }

            
$url str_replace('%package%'$name$this->lazyProvidersUrl);
            
$cacheKey 'provider-'.strtr($name'/''~').'.json';

            
$lastModified null;
            if (
$contents $this->cache->read($cacheKey)) {
                
$contents json_decode($contentstrue);
                
$lastModified = isset($contents['last-modified']) ? $contents['last-modified'] : null;
            }

            
$promises[] = $this->asyncFetchFile($url$cacheKey$lastModified)
                ->
then(function ($response) use (&$packages, &$namesFound$url$cacheKey$contents$realName$constraint$repo$acceptableStabilities$stabilityFlags$alreadyLoaded) {
                    
$packagesSource 'downloaded file ('.Url::sanitize($url).')';

                    if (
true === $response) {
                        
$packagesSource 'cached file ('.$cacheKey.' originating from '.Url::sanitize($url).')';
                        
$response $contents;
                    }

                    if (!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 = array();
                    foreach (
$versions as $version) {
                        if (!isset(
$version['version_normalized'])) {
                            
$version['version_normalized'] = $repo->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'] = $repo->versionParser->normalize($version['version']);
                        }

                        
// avoid loading packages which have already been loaded
                        
if (isset($alreadyLoaded[$realName][$version['version_normalized']])) {
                            continue;
                        }

                        if (
$repo->isVersionAcceptable($constraint$realName$version$acceptableStabilities$stabilityFlags)) {
                            
$versionsToLoad[] = $version;
                        }
                    }

                    
$loadedPackages $repo->createPackages($versionsToLoad$packagesSource);
                    foreach (
$loadedPackages as $package) {
                        
$package->setRepository($repo);
                        
$packages[spl_object_hash($package)] = $package;

                        if (
$package instanceof AliasPackage && !isset($packages[spl_object_hash($package->getAliasOf())])) {
                            
$package->getAliasOf()->setRepository($repo);
                            
$packages[spl_object_hash($package->getAliasOf())] = $package->getAliasOf();
                        }
                    }
                });
        }

        
$this->loop->wait($promises);

        return array(
'namesFound' => $namesFound'packages' => $packages);
        
// RepositorySet should call loadMetadata, getMetadata when all promises resolved, then metadataComplete when done so we can GC the loaded json and whatnot then as needed
    
}

    
/**
     * TODO v3 should make this private once we can drop PHP 5.3 support
     *
     * @private
     *
     * @param ConstraintInterface|null $constraint
     * @param string $name package name (must be lowercased already)
     * @param array<string, mixed> $versionData
     * @param array<string, int>|null $acceptableStabilities
     * @phpstan-param array<string, 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
     *
     * @return bool
     */
    
public function isVersionAcceptable($constraint$name$versionData, array $acceptableStabilities null, array $stabilityFlags null)
    {
        
$versions = array($versionData['version_normalized']);

        if (
$alias $this->loader->getBranchAlias($versionData)) {
            
$versions[] = $alias;
        }

        foreach (
$versions as $version) {
            if (
null !== $acceptableStabilities && null !== $stabilityFlags && !StabilityFilter::isPackageAcceptable($acceptableStabilities$stabilityFlags, array($name), VersionParser::parseStability($version))) {
                continue;
            }

            if (
$constraint && !CompilingMatcher::match($constraintConstraint::OP_EQ$version)) {
                continue;
            }

            return 
true;
        }

        return 
false;
    }

    
/**
     * @return string
     */
    
private function getPackagesJsonUrl()
    {
        
$jsonUrlParts parse_url($this->url);

        if (isset(
$jsonUrlParts['path']) && false !== strpos($jsonUrlParts['path'], '.json')) {
            return 
$this->url;
        }

        return 
$this->url '/packages.json';
    }

    
/**
     * @param int|null $rootMaxAge
     * @return array<string, mixed>
     */
    
protected function loadRootServerFile($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($cachedDatatrue);
            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'nulltrue);
        }

        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'][] = array('url' => $mirror['git-url'], 'preferred' => !empty($mirror['preferred']));
                }
                if (!empty(
$mirror['hg-url'])) {
                    
$this->sourceMirrors['hg'][] = array('url' => $mirror['hg-url'], 'preferred' => !empty($mirror['preferred']));
                }
                if (!empty(
$mirror['dist-url'])) {
                    
$this->distMirrors[] = array(
                        
'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(function ($pattern) {
                    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 (
$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 string
     */
    
private function canonicalizeUrl($url)
    {
        if (
'/' === $url[0]) {
            if (
Preg::isMatch('{^[^:]++://[^/]*+}'$this->url$matches)) {
                return 
$matches[0] . $url;
            }

            return 
$this->url;
        }

        return 
$url;
    }

    
/**
     * @return mixed[]
     */
    
private function loadDataFromServer()
    {
        
$data $this->loadRootServerFile();

        return 
$this->loadIncludes($data);
    }

    
/**
     * @return bool
     */
    
private function hasPartialPackages()
    {
        if (
$this->hasPartialPackages && null === $this->partialPackagesByName) {
            
$this->initializePartialPackages();
        }

        return 
$this->hasPartialPackages;
    }

    
/**
     * @param array{providers?: mixed[], provider-includes?: mixed[]} $data
     *
     * @return void
     */
    
private function loadProviderListings($data)
    {
        if (isset(
$data['providers'])) {
            if (!
is_array($this->providerListing)) {
                
$this->providerListing = array();
            }
            
$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(array('%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($data)
    {
        
$packages = array();

        
// 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) {
                foreach (
$versions as $version => $metadata) {
                    
$packages[] = $metadata;
                }
            }
        }

        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;
    }

    
/**
     * TODO v3 should make this private once we can drop PHP 5.3 support
     * @private
     *
     * @param mixed[] $packages
     * @param string|null $source
     *
     * @return list<CompletePackage|CompleteAliasPackage>
     */
    
public function createPackages(array $packages$source null)
    {
        if (!
$packages) {
            return array();
        }

        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 '.(isset($packages[0]['name']) ? $packages[0]['name'] : json_encode($packages)).' in '.$this->getRepoName().($source ' from '.$source '').': ['.get_class($e).'] '.$e->getMessage(), 0$e);
        }
    }

    
/**
     * @param string $filename
     * @param string|null $cacheKey
     * @param string|null $sha256
     * @param bool $storeLastModifiedTime
     *
     * @return array<mixed>
     */
    
protected function fetchFile($filename$cacheKey null$sha256 null$storeLastModifiedTime false)
    {
        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($filename0$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', array('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_DOWNLOADnull$sha256$filename'metadata', array('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($data0);
                        }
                    }
                    
$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;
    }

    
/**
     * @param string $filename
     * @param string $cacheKey
     * @param string $lastModifiedTime
     *
     * @return array<mixed>|true
     */
    
private function fetchFileIfLastModified($filename$cacheKey$lastModifiedTime)
    {
        try {
            
$options $this->options;
            if (
$this->eventDispatcher) {
                
$preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD$this->httpDownloader$filename'metadata', array('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_DOWNLOADnullnull$filename'metadata', array('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($data0);
            }
            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;
        }
    }

    
/**
     * @param string $filename
     * @param string $cacheKey
     * @param string|null $lastModifiedTime
     *
     * @return \React\Promise\PromiseInterface
     */
    
private function asyncFetchFile($filename$cacheKey$lastModifiedTime null)
    {
        if (isset(
$this->packagesNotFoundCache[$filename])) {
            return 
\React\Promise\resolve(array('packages' => array()));
        }

        if (isset(
$this->freshMetadataUrls[$filename]) && $lastModifiedTime) {
            
// make it look like we got a 304 response
            
return \React\Promise\resolve(true);
        }

        
$httpDownloader $this->httpDownloader;
        
$options $this->options;
        if (
$this->eventDispatcher) {
            
$preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD$this->httpDownloader$filename'metadata', array('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;
        
$repo $this;

        
$accept = function ($response) use ($io$url$filename$cache$cacheKey$eventDispatcher$repo) {
            
// package not found is acceptable for a v2 protocol repository
            
if ($response->getStatusCode() === 404) {
                
$repo->packagesNotFoundCache[$filename] = true;

                return array(
'packages' => array());
            }

            
$json = (string) $response->getBody();
            if (
$json === '' && $response->getStatusCode() === 304) {
                
$repo->freshMetadataUrls[$filename] = true;

                return 
true;
            }

            if (
$eventDispatcher) {
                
$postFileDownloadEvent = new PostFileDownloadEvent(PluginEvents::POST_FILE_DOWNLOADnullnull$filename'metadata', array('response' => $response'repository' => $repo));
                
$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($dataJsonFile::JSON_UNESCAPED_SLASHES JsonFile::JSON_UNESCAPED_UNICODE);
            }
            if (!
$cache->isReadOnly()) {
                
$cache->write($cacheKey$json);
            }
            
$repo->freshMetadataUrls[$filename] = true;

            return 
$data;
        };

        
$reject = function ($e) use ($filename$accept$io$url, &$degradedMode$repo$lastModifiedTime) {
            if (
$e instanceof TransportException && $e->getStatusCode() === 404) {
                
$repo->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(array('url' => $url), 304, array(), ''));
            }

            
// special error code returned when network is being artificially disabled
            
if ($e instanceof TransportException && $e->getStatusCode() === 499) {
                return 
$accept(new Response(array('url' => $url), 404, array(), ''));
            }

            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
     *
     * @return void
     */
    
private function initializePartialPackages()
    {
        
$rootData $this->loadRootServerFile();

        
$this->partialPackagesByName = array();
        foreach (
$rootData['packages'] as $package => $versions) {
            foreach (
$versions as $version) {
                
$this->partialPackagesByName[strtolower($version['name'])][] = $version;
            }
        }

        
// 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
     *
     * @param  string $name
     * @return bool   true if the package name is present in availablePackages or matched by availablePackagePatterns
     */
    
protected function lazyProvidersRepoContains($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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class LockArrayRepository extends ArrayRepository
{
    public function 
getRepoName()
    {
        return 
'lock repo';
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class InstalledArrayRepository extends WritableArrayRepository implements InstalledRepositoryInterface
{
    public function 
getRepoName()
    {
        return 
'installed '.parent::getRepoName();
    }

    
/**
     * @inheritDoc
     */
    
public function isFresh()
    {
        
// 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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class RootPackageRepository extends ArrayRepository
{
    public function 
__construct(RootPackageInterface $package)
    {
        
parent::__construct(array($package));
    }

    public function 
getRepoName()
    {
        return 
'root package repo';
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\ProcessExecutor;
use 
Composer\Util\HttpDownloader;
use 
Composer\Util\Url;
use 
Composer\Semver\Constraint\Constraint;
use 
Composer\IO\IOInterface;
use 
Composer\Config;

/**
 * @author Jordi Boggiano <[email protected]>
 */
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 string[] */
    
private $emptyReferences = array();
    
/** @var array<'tags'|'branches', array<string, TransportException>> */
    
private $versionTransportExceptions = array();

    
/**
     * @param array{url: string, type?: string}&array<string, mixed> $repoConfig
     * @param array<string, class-string<VcsDriverInterface>>|null $drivers
     */
    
public function __construct(array $repoConfigIOInterface $ioConfig $configHttpDownloader $httpDownloaderEventDispatcher $dispatcher nullProcessExecutor $process null, array $drivers nullVersionCacheInterface $versionCache null)
    {
        
parent::__construct();
        
$this->drivers $drivers ?: array(
            
'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'];
        
$this->io $io;
        
$this->type = isset($repoConfig['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;
    }

    
/**
     * @return void
     */
    
public function setLoader(LoaderInterface $loader)
    {
        
$this->loader $loader;
    }

    
/**
     * @return VcsDriverInterface|null
     */
    
public function getDriver()
    {
        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->urltrue)) {
                
$this->driver = new $driver($this->repoConfig$this->io$this->config$this->httpDownloader$this->processExecutor);
                
$this->driver->initialize();

                return 
$this->driver;
            }
        }

        return 
null;
    }

    
/**
     * @return bool
     */
    
public function hadInvalidBranches()
    {
        return 
$this->branchErrorOccurred;
    }

    
/**
     * @return string[]
     */
    
public function getEmptyReferences()
    {
        return 
$this->emptyReferences;
    }

    
/**
     * @return array<'tags'|'branches', array<string, TransportException>>
     */
    
public function getVersionTransportExceptions()
    {
        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>)';
            if (
$isVeryVerbose) {
                
$this->io->writeError($msg);
            } elseif (
$isVerbose) {
                
$this->io->overwriteError($msgfalse);
            }

            
// 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;
            }

            try {
                if (!
$data $driver->getComposerInformation($identifier)) {
                    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 ?: (isset($data['name']) ? $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 = array($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($msgfalse);
            }

            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-') === || VersionParser::DEFAULT_BRANCH_ALIAS === $parsedBranch) {
                
$version 'dev-' $branch;
            } else {
                
$prefix strpos($branch'v') === '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 {
                if (!
$data $driver->getComposerInformation($identifier)) {
                    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 && $this->loader->getWarnings()) {
                    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 VcsDriverInterface $driver
     * @param array{name?: string, dist?: array{type: string, url: string, reference: string, shasum: string}, source?: array{type: string, url: string, reference: string}} $data
     * @param string $identifier
     *
     * @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$identifier)
    {
        
// 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 = isset($data['name']) ? $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);
        }

        return 
$data;
    }

    
/**
     * @param string $branch
     *
     * @return string|false
     */
    
private function validateBranch($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;
    }

    
/**
     * @param string $version
     *
     * @return string|false
     */
    
private function validateTag($version)
    {
        try {
            return 
$this->versionParser->normalize($version);
        } catch (
\Exception $e) {
        }

        return 
false;
    }

    
/**
     * @param string $version
     * @param string $identifier
     * @param bool $isVerbose
     * @param bool $isVeryVerbose
     * @param bool $isDefaultBranch
     *
     * @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($version$identifier$isVerbose$isVeryVerbose$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($msgfalse);
            }

            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;
    }

    
/**
     * @return bool
     */
    
private function shouldRethrowTransportException(TransportException $e)
    {
        return 
in_array($e->getCode(), array(401403429), true) || $e->getCode() >= 500;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
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 = array())
    {
        foreach (
$packages as $package) {
            
$this->addPackage($package);
        }
    }

    public function 
getRepoName()
    {
        return 
'array repo (defining '.$this->count().' package'.($this->count() > 's' '').')';
    }

    
/**
     * @inheritDoc
     */
    
public function loadPackages(array $packageMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = array())
    {
        
$packages $this->getPackages();

        
$result = array();
        
$namesFound = array();
        foreach (
$packages as $package) {
            if (
array_key_exists($package->getName(), $packageMap)) {
                if (
                    (!
$packageMap[$package->getName()] || $packageMap[$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 array(
'namesFound' => array_keys($namesFound), 'packages' => $result);
    }

    
/**
     * @inheritDoc
     */
    
public function findPackage($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($name$constraint null)
    {
        
// normalize name
        
$name strtolower($name);
        
$packages = array();

        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($query$mode 0$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 = array();
        foreach (
$this->getPackages() as $package) {
            
$name $package->getName();
            if (
$mode === self::SEARCH_VENDOR) {
                list(
$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($regeximplode(' ', (array) $package->getKeywords()) . ' ' $package->getDescription()))
            ) {
                if (
$mode === self::SEARCH_VENDOR) {
                    
$matches[$name] = array(
                        
'name' => $name,
                        
'description' => null,
                    );
                } else {
                    
$matches[$name] = array(
                        
'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 = array();
            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($packageName)
    {
        
$result = array();

        foreach (
$this->getPackages() as $candidate) {
            if (isset(
$result[$candidate->getName()])) {
                continue;
            }
            foreach (
$candidate->getProvides() as $link) {
                if (
$packageName === $link->getTarget()) {
                    
$result[$candidate->getName()] = array(
                        
'name' => $candidate->getName(),
                        
'description' => $candidate instanceof CompletePackageInterface $candidate->getDescription() : null,
                        
'type' => $candidate->getType(),
                    );
                    continue 
2;
                }
            }
        }

        return 
$result;
    }

    
/**
     * @param string $alias
     * @param string $prettyAlias
     *
     * @return AliasPackage|CompleteAliasPackage
     */
    
protected function createAliasPackage(BasePackage $package$alias$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$key1);

                
// 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 int Number of packages
     */
    
#[\ReturnTypeWillChange]
    public function 
count()
    {
        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 = array();
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class InstalledRepository extends CompositeRepository
{
    
/**
     * @param string $name
     * @param ConstraintInterface|string|null $constraint
     *
     * @return BasePackage[]
     */
    
public function findPackagesWithReplacersAndProviders($name$constraint null)
    {
        
$name strtolower($name);

        if (
null !== $constraint && !$constraint instanceof ConstraintInterface) {
            
$versionParser = new VersionParser();
            
$constraint $versionParser->parseConstraints($constraint);
        }

        
$matches = array();
        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: mixed[]|bool}>
     */
    
public function getDependents($needle$constraint null$invert false$recurse true$packagesFound null)
    {
        
$needles array_map('strtolower', (array) $needle);
        
$results = array();

        
// 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[] = array($package$linkfalse);
                                    continue;
                                }
                                
$packagesInTree[] = $link->getTarget();
                                
$dependents $recurse $this->getDependents($link->getTarget(), nullfalsetrue$packagesInTree) : array();
                                
$results[] = array($package$link$dependents);
                                
$needles[] = $link->getTarget();
                            }
                        }
                    }
                }
            }

            
// 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[] = array($package$linkfalse);
                                continue;
                            }
                            
$packagesInTree[] = $link->getSource();
                            
$dependents $recurse $this->getDependents($link->getSource(), nullfalsetrue$packagesInTree) : array();
                            
$results[] = array($package$link$dependents);
                        }
                    }
                }
            }

            
// When inverting, we need to check for conflicts of the needles against installed packages
            
if ($invert && in_array($package->getName(), $needles)) {
                foreach (
$package->getConflicts() as $link) {
                    foreach (
$this->findPackages($link->getTarget()) as $pkg) {
                        
$version = new Constraint('='$pkg->getVersion());
                        if (
$link->getConstraint()->matches($version) === $invert) {
                            
$results[] = array($package$linkfalse);
                        }
                    }
                }
            }

            
// 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)) {
                    foreach (
$this->findPackages($link->getTarget()) as $pkg) {
                        
$version = new Constraint('='$pkg->getVersion());
                        if (
$link->getConstraint()->matches($version) === $invert) {
                            
$results[] = array($package$linkfalse);
                        }
                    }
                }
            }

            
// When inverting, we need to check for conflicts of the needles' requirements against installed packages
            
if ($invert && $constraint && in_array($package->getName(), $needles) && $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[] = array($package, new Link($package->getName(), $link->getTarget(), new MatchAllConstraintLink::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[] = array($package$linkfalse);
                                        
$results[] = array($rootPackage$rootReqfalse);
                                        continue 
3;
                                    }
                                }

                                
$results[] = array($package$linkfalse);
                                
$results[] = array($rootPackage, new Link($rootPackage->getName(), $link->getTarget(), new MatchAllConstraintLink::TYPE_DOES_NOT_REQUIRE'but ' $pkg->getPrettyVersion() . ' is installed'), false);
                            } else {
                                
// no root so let's just print whatever we found
                                
$results[] = array($package$linkfalse);
                            }
                        }

                        continue 
2;
                    }
                }
            }
        }

        
ksort($results);

        return 
$results;
    }

    public function 
getRepoName()
    {
        return 
'installed repo ('.implode(', 'array_map(function ($repo) {
            return 
$repo->getRepoName();
        }, 
$this->getRepositories())).')';
    }

    
/**
     * @inheritDoc
     */
    
public function addRepository(RepositoryInterface $repository)
    {
        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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
interface ConfigurableRepositoryInterface
{
    
/**
     * @return mixed[]
     */
    
public function getRepoConfig();
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class FilterRepository implements RepositoryInterface
{
    
/** @var ?string */
    
private $only null;
    
/** @var ?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()
    {
        return 
$this->repo->getRepoName();
    }

    
/**
     * Returns the wrapped repositories
     *
     * @return RepositoryInterface
     */
    
public function getRepository()
    {
        return 
$this->repo;
    }

    
/**
     * @inheritDoc
     */
    
public function hasPackage(PackageInterface $package)
    {
        return 
$this->repo->hasPackage($package);
    }

    
/**
     * @inheritDoc
     */
    
public function findPackage($name$constraint)
    {
        if (!
$this->isAllowed($name)) {
            return 
null;
        }

        return 
$this->repo->findPackage($name$constraint);
    }

    
/**
     * @inheritDoc
     */
    
public function findPackages($name$constraint null)
    {
        if (!
$this->isAllowed($name)) {
            return array();
        }

        return 
$this->repo->findPackages($name$constraint);
    }

    
/**
     * @inheritDoc
     */
    
public function loadPackages(array $packageMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = array())
    {
        foreach (
$packageMap as $name => $constraint) {
            if (!
$this->isAllowed($name)) {
                unset(
$packageMap[$name]);
            }
        }

        if (!
$packageMap) {
            return array(
'namesFound' => array(), 'packages' => array());
        }

        
$result $this->repo->loadPackages($packageMap$acceptableStabilities$stabilityFlags$alreadyLoaded);
        if (!
$this->canonical) {
            
$result['namesFound'] = array();
        }

        return 
$result;
    }

    
/**
     * @inheritDoc
     */
    
public function search($query$mode 0$type null)
    {
        
$result = array();

        foreach (
$this->repo->search($query$mode$type) as $package) {
            if (
$this->isAllowed($package['name'])) {
                
$result[] = $package;
            }
        }

        return 
$result;
    }

    
/**
     * @inheritDoc
     */
    
public function getPackages()
    {
        
$result = array();
        foreach (
$this->repo->getPackages() as $package) {
            if (
$this->isAllowed($package->getName())) {
                
$result[] = $package;
            }
        }

        return 
$result;
    }

    
/**
     * @inheritDoc
     */
    
public function getProviders($packageName)
    {
        
$result = array();
        foreach (
$this->repo->getProviders($packageName) as $name => $provider) {
            if (
$this->isAllowed($provider['name'])) {
                
$result[$name] = $provider;
            }
        }

        return 
$result;
    }

    
/**
     * @inheritDoc
     */
    
#[\ReturnTypeWillChange]
    public function 
count()
    {
        if (
$this->repo->count() > 0) {
            return 
count($this->getPackages());
        }

        return 
0;
    }

    
/**
     * @param string $name
     *
     * @return bool
     */
    
private function isAllowed($name)
    {
        if (!
$this->only && !$this->exclude) {
            return 
true;
        }

        if (
$this->only) {
            return 
Preg::isMatch($this->only$name);
        }

        return !
Preg::isMatch($this->exclude$name);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\Tar;
use 
Composer\Util\Zip;

/**
 * @author Serge Smertin <[email protected]>
 */
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 $repoConfigIOInterface $io)
    {
        
parent::__construct();
        if (!
extension_loaded('zip')) {
            throw new 
\RuntimeException('The artifact repository requires PHP\'s zip extension');
        }

        
$this->loader = new ArrayLoader();
        
$this->lookup $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);
    }

    
/**
     * @param string $path
     *
     * @return void
     */
    
private function scanDirectory($path)
    {
        
$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"trueIOInterface::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()), trueIOInterface::VERBOSE);

            
$this->addPackage($package);
        }
    }

    
/**
     * @return ?BasePackage
     */
    
private function getComposerInformation(\SplFileInfo $file)
    {
        
$json null;
        
$fileType null;
        
$fileExtension pathinfo($file->getPathname(), PATHINFO_EXTENSION);
        if (
in_array($fileExtension, array('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(), falseIOInterface::VERBOSE);
        }

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

        
$package JsonFile::parseJson($json$file->getPathname().'#composer.json');
        
$package['dist'] = array(
            
'type' => $fileType,
            
'url' => strtr($file->getPathname(), '\\''/'),
            
'shasum' => sha1_file($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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class InvalidRepositoryException extends \Exception
{
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\Installer\InstallationManager;

/**
 * Writable array repository.
 *
 * @author Jordi Boggiano <[email protected]>
 */
class WritableArrayRepository extends ArrayRepository implements WritableRepositoryInterface
{
    
/**
     * @var string[]
     */
    
protected $devPackageNames = array();

    
/**
     * @inheritDoc
     */
    
public function setDevPackageNames(array $devPackageNames)
    {
        
$this->devPackageNames $devPackageNames;
    }

    
/**
     * @inheritDoc
     */
    
public function getDevPackageNames()
    {
        return 
$this->devPackageNames;
    }

    
/**
     * @inheritDoc
     */
    
public function write($devModeInstallationManager $installationManager)
    {
    }

    
/**
     * @inheritDoc
     */
    
public function reload()
    {
    }

    
/**
     * @inheritDoc
     */
    
public function getCanonicalPackages()
    {
        
$packages $this->getPackages();

        
// get at most one package of each name, preferring non-aliased ones
        
$packagesByName = array();
        foreach (
$packages as $package) {
            if (!isset(
$packagesByName[$package->getName()]) || $packagesByName[$package->getName()] instanceof AliasPackage) {
                
$packagesByName[$package->getName()] = $package;
            }
        }

        
$canonicalPackages = array();

        
// unfold aliased packages
        
foreach ($packagesByName as $package) {
            while (
$package instanceof AliasPackage) {
                
$package $package->getAliasOf();
            }

            
$canonicalPackages[] = $package;
        }

        return 
$canonicalPackages;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
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($devModeInstallationManager $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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

interface 
VersionCacheInterface
{
    
/**
     * @param  string             $version
     * @param  string             $identifier
     * @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($version$identifier);
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class RepositorySecurityException extends \Exception
{
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @author Konstantin Kudryashov <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 */
interface RepositoryInterface extends \Countable
{
    const 
SEARCH_FULLTEXT 0;
    const 
SEARCH_NAME 1;
    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($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($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, BasePackage::STABILITY_*>        $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<string, ConstraintInterface|null> $packageNameMap
     * @phpstan-return array{namesFound: array<string>, packages: array<BasePackage>}
     */
    
public function loadPackages(array $packageNameMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = array());

    
/**
     * 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}>
     */
    
public function search($query$mode 0$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, type: string}>
     */
    
public function getProviders($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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\IO\IOInterface;
use 
Composer\Json\JsonFile;
use 
Composer\Package\CompleteAliasPackage;
use 
Composer\Package\CompletePackage;
use 
Composer\Package\Loader\ArrayLoader;
use 
Composer\Package\Version\VersionGuesser;
use 
Composer\Package\Version\VersionParser;
use 
Composer\Pcre\Preg;
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
 *         }
 *     },
 * ]
 * @endcode
 *
 * @author Samuel Roze <[email protected]>
 * @author Johann Reinke <[email protected]>
 */
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, relative?: bool, versions?: array<string, string>}}
     */
    
private $repoConfig;

    
/**
     * @var ProcessExecutor
     */
    
private $process;

    
/**
     * @var array{symlink?: bool, relative?: bool, versions?: array<string, string>}
     */
    
private $options;

    
/**
     * Initializes path repository.
     *
     * @param array{url?: string, options?: array{symlink?: bool, relative?: bool, versions?: array<string, string>}} $repoConfig
     * @param IOInterface $io
     * @param Config      $config
     */
    
public function __construct(array $repoConfigIOInterface $ioConfig $config)
    {
        if (!isset(
$repoConfig['url'])) {
            throw new 
\RuntimeException('You must specify the `url` configuration for the path repository');
        }

        
$this->loader = new ArrayLoader(nulltrue);
        
$this->url Platform::expandPath($repoConfig['url']);
        
$this->process = new ProcessExecutor($io);
        
$this->versionGuesser = new VersionGuesser($config$this->process, new VersionParser());
        
$this->repoConfig $repoConfig;
        
$this->options = isset($repoConfig['options']) ? $repoConfig['options'] : array();
        if (!isset(
$this->options['relative'])) {
            
$filesystem = new Filesystem();
            
$this->options['relative'] = !$filesystem->isAbsolutePath($this->url);
        }

        
parent::__construct();
    }

    public function 
getRepoName()
    {
        return 
'path repo ('.Url::sanitize($this->repoConfig['url']).')';
    }

    public function 
getRepoConfig()
    {
        return 
$this->repoConfig;
    }

    
/**
     * Initializes path repository.
     *
     * This method will basically read the folder and add the found package.
     */
    
protected function initialize()
    {
        
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'] = array(
                
'type' => 'path',
                
'url' => $url,
                
'reference' => sha1($json serialize($this->options)),
            );
            
$package['transport-options'] = $this->options;
            unset(
$package['transport-options']['versions']);

            
// 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 (
                    
=== $this->process->execute('git rev-parse HEAD'$ref1$path)
                    && 
=== $this->process->execute('git rev-parse HEAD'$ref2)
                    && 
$ref1 === $ref2
                
) {
                    
$package['version'] = $rootVersion;
                }
            }

            
$output '';
            if (
is_dir($path DIRECTORY_SEPARATOR '.git') && === $this->process->execute('git log -n1 --pretty=%H'.GitUtil::getNoShowSignatureFlag($this->process), $output$path)) {
                
$package['dist']['reference'] = trim($output);
            }

            
$needsAlias false;
            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';
                    
$needsAlias true;
                }
            }

            
$package $this->loader->load($package);
            if (
$needsAlias && $package instanceof CompletePackage) {
                
// keep a dev-master alias to dev-main for BC
                
$package = new CompleteAliasPackage($package'dev-master''dev-master');
            }
            
$this->addPackage($package);
        }
    }

    
/**
     * Get a list of all (possibly relative) path names matching given url (supports globbing).
     *
     * @return string[]
     */
    
private function getUrlMatches()
    {
        
$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(function ($val) {
            return 
rtrim(str_replace(DIRECTORY_SEPARATOR'/'$val), '/');
        }, 
glob($this->url$flags));
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class InstalledFilesystemRepository extends FilesystemRepository implements InstalledRepositoryInterface
{
    public function 
getRepoName()
    {
        return 
'installed '.parent::getRepoName();
    }

    
/**
     * @inheritDoc
     */
    
public function isFresh()
    {
        return !
$this->file->exists();
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class RepositoryFactory
{
    
/**
     * @param  IOInterface $io
     * @param  Config      $config
     * @param  string      $repository
     * @param  bool        $allowFilesystem
     * @return array|mixed
     */
    
public static function configFromString(IOInterface $ioConfig $config$repository$allowFilesystem false)
    {
        if (
=== strpos($repository'http')) {
            
$repoConfig = array('type' => 'composer''url' => $repository);
        } elseif (
"json" === pathinfo($repositoryPATHINFO_EXTENSION)) {
            
$json = new JsonFile($repositoryFactory::createHttpDownloader($io$config));
            
$data $json->read();
            if (!empty(
$data['packages']) || !empty($data['includes']) || !empty($data['provider-includes'])) {
                
$repoConfig = array('type' => 'composer''url' => 'file://' strtr(realpath($repository), '\\''/'));
            } elseif (
$allowFilesystem) {
                
$repoConfig = array('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;
    }

    
/**
     * @param  IOInterface         $io
     * @param  Config              $config
     * @param  string              $repository
     * @param  bool                $allowFilesystem
     * @return RepositoryInterface
     */
    
public static function fromString(IOInterface $ioConfig $config$repository$allowFilesystem falseRepositoryManager $rm null)
    {
        
$repoConfig = static::configFromString($io$config$repository$allowFilesystem);

        return static::
createRepo($io$config$repoConfig$rm);
    }

    
/**
     * @param  IOInterface         $io
     * @param  Config              $config
     * @param  array<string, mixed> $repoConfig
     * @return RepositoryInterface
     */
    
public static function createRepo(IOInterface $ioConfig $config, array $repoConfigRepositoryManager $rm null)
    {
        if (!
$rm) {
            
$rm = static::manager($io$configFactory::createHttpDownloader($io$config));
        }
        
$repos self::createRepos($rm, array($repoConfig));

        return 
reset($repos);
    }

    
/**
     * @param  IOInterface|null       $io
     * @param  Config|null            $config
     * @param  RepositoryManager|null $rm
     * @return RepositoryInterface[]
     */
    
public static function defaultRepos(IOInterface $io nullConfig $config nullRepositoryManager $rm null)
    {
        if (!
$config) {
            
$config Factory::createConfig($io);
        }
        if (
$io) {
            
$io->loadConfiguration($config);
        }
        if (!
$rm) {
            if (!
$io) {
                throw new 
\InvalidArgumentException('This function requires either an IOInterface or a RepositoryManager');
            }
            
$rm = static::manager($io$configFactory::createHttpDownloader($io$config));
        }

        return 
self::createRepos($rm$config->getRepositories());
    }

    
/**
     * @param  IOInterface       $io
     * @param  Config            $config
     * @param  EventDispatcher   $eventDispatcher
     * @param  HttpDownloader    $httpDownloader
     * @return RepositoryManager
     */
    
public static function manager(IOInterface $ioConfig $configHttpDownloader $httpDownloaderEventDispatcher $eventDispatcher nullProcessExecutor $process null)
    {
        
$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;
    }

    
/**
     * @param array<int|string, mixed> $repoConfigs
     *
     * @return RepositoryInterface[]
     */
    
private static function createRepos(RepositoryManager $rm, array $repoConfigs)
    {
        
$repos = array();

        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$index);
            }
        }

        return 
$repos;
    }

    
/**
     * @param int|string $index
     * @param array{url?: string} $repo
     * @param array<string, mixed> $existingRepos
     *
     * @return string
     */
    
public static function generateRepositoryName($index, array $repo, array $existingRepos)
    {
        
$name is_int($index) && isset($repo['url']) ? Preg::replace('{^https?://}i'''$repo['url']) : $index;
        while (isset(
$existingRepos[$name])) {
            
$name .= '2';
        }

        return 
$name;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 * @deprecated
 * @private
 */
class PearRepository extends ArrayRepository
{
    public function 
__construct()
    {
        throw new 
\InvalidArgumentException('The PEAR repository has been removed from Composer 2.x');
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
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 = array($this->config);
        }
    }

    
/**
     * Initializes repository (reads file, or remote address).
     */
    
protected function initialize()
    {
        
parent::initialize();

        
$loader = new ValidatingArrayLoader(new ArrayLoader(nulltrue), 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()
    {
        return 
Preg::replace('{^array }''package 'parent::getRepoName());
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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;

/**
 * Composite repository.
 *
 * @author Beau Simensen <[email protected]>
 */
class CompositeRepository implements RepositoryInterface
{
    
/**
     * List of repositories
     * @var RepositoryInterface[]
     */
    
private $repositories;

    
/**
     * Constructor
     * @param RepositoryInterface[] $repositories
     */
    
public function __construct(array $repositories)
    {
        
$this->repositories = array();
        foreach (
$repositories as $repo) {
            
$this->addRepository($repo);
        }
    }

    public function 
getRepoName()
    {
        return 
'composite repo ('.implode(', 'array_map(function ($repo) {
            return 
$repo->getRepoName();
        }, 
$this->repositories)).')';
    }

    
/**
     * Returns all the wrapped repositories
     *
     * @return RepositoryInterface[]
     */
    
public function getRepositories()
    {
        return 
$this->repositories;
    }

    
/**
     * @inheritDoc
     */
    
public function hasPackage(PackageInterface $package)
    {
        foreach (
$this->repositories as $repository) {
            
/* @var $repository RepositoryInterface */
            
if ($repository->hasPackage($package)) {
                return 
true;
            }
        }

        return 
false;
    }

    
/**
     * @inheritDoc
     */
    
public function findPackage($name$constraint)
    {
        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)
    {
        
$packages = array();
        foreach (
$this->repositories as $repository) {
            
/* @var $repository RepositoryInterface */
            
$packages[] = $repository->findPackages($name$constraint);
        }

        return 
$packages call_user_func_array('array_merge'$packages) : array();
    }

    
/**
     * @inheritDoc
     */
    
public function loadPackages(array $packageMap, array $acceptableStabilities, array $stabilityFlags, array $alreadyLoaded = array())
    {
        
$packages = array();
        
$namesFound = array();
        foreach (
$this->repositories as $repository) {
            
/* @var $repository RepositoryInterface */
            
$result $repository->loadPackages($packageMap$acceptableStabilities$stabilityFlags$alreadyLoaded);
            
$packages[] = $result['packages'];
            
$namesFound[] = $result['namesFound'];
        }

        return array(
            
'packages' => $packages call_user_func_array('array_merge'$packages) : array(),
            
'namesFound' => $namesFound array_unique(call_user_func_array('array_merge'$namesFound)) : array(),
        );
    }

    
/**
     * @inheritDoc
     */
    
public function search($query$mode 0$type null)
    {
        
$matches = array();
        foreach (
$this->repositories as $repository) {
            
/* @var $repository RepositoryInterface */
            
$matches[] = $repository->search($query$mode$type);
        }

        return 
$matches call_user_func_array('array_merge'$matches) : array();
    }

    
/**
     * @inheritDoc
     */
    
public function getPackages()
    {
        
$packages = array();
        foreach (
$this->repositories as $repository) {
            
/* @var $repository RepositoryInterface */
            
$packages[] = $repository->getPackages();
        }

        return 
$packages call_user_func_array('array_merge'$packages) : array();
    }

    
/**
     * @inheritDoc
     */
    
public function getProviders($packageName)
    {
        
$results = array();
        foreach (
$this->repositories as $repository) {
            
/* @var $repository RepositoryInterface */
            
$results[] = $repository->getProviders($packageName);
        }

        return 
$results call_user_func_array('array_merge'$results) : array();
    }

    
/**
     * @return void
     */
    
public function removePackage(PackageInterface $package)
    {
        foreach (
$this->repositories as $repository) {
            if (
$repository instanceof WritableRepositoryInterface) {
                
$repository->removePackage($package);
            }
        }
    }

    
/**
     * @inheritDoc
     */
    
#[\ReturnTypeWillChange]
    public function 
count()
    {
        
$total 0;
        foreach (
$this->repositories as $repository) {
            
/* @var $repository RepositoryInterface */
            
$total += $repository->count();
        }

        return 
$total;
    }

    
/**
     * Add a repository.
     * @param RepositoryInterface $repository
     *
     * @return void
     */
    
public function addRepository(RepositoryInterface $repository)
    {
        if (
$repository instanceof self) {
            foreach (
$repository->getRepositories() as $repo) {
                
$this->addRepository($repo);
            }
        } else {
            
$this->repositories[] = $repository;
        }
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
interface InstalledRepositoryInterface extends WritableRepositoryInterface
{
    
/**
     * @return bool true if packages were never installed in this repository
     */
    
public function isFresh();
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\RootPackageInterface;
use 
Composer\Package\AliasPackage;
use 
Composer\Package\Dumper\ArrayDumper;
use 
Composer\Installer\InstallationManager;
use 
Composer\Util\Filesystem;

/**
 * Filesystem repository.
 *
 * @author Konstantin Kudryashov <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 */
class FilesystemRepository extends WritableArrayRepository
{
    
/** @var JsonFile */
    
protected $file;
    
/** @var bool */
    
private $dumpVersions;
    
/** @var ?RootPackageInterface */
    
private $rootPackage;
    
/** @var Filesystem */
    
private $filesystem;

    
/**
     * Initializes filesystem repository.
     *
     * @param JsonFile              $repositoryFile repository json file
     * @param bool                  $dumpVersions
     * @param ?RootPackageInterface $rootPackage    Must be provided if $dumpVersions is true
     */
    
public function __construct(JsonFile $repositoryFile$dumpVersions falseRootPackageInterface $rootPackage nullFilesystem $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');
        }
    }

    
/**
     * 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 (!
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(nulltrue);
        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($devModeInstallationManager $installationManager)
    {
        
$data = array('packages' => array(), 'dev' => $devMode'dev-package-names' => array());
        
$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 = array();

        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 getcwd() . '/' $path);
                
$installPath $this->filesystem->findShortestPath($repoDir$normalizedPathtrue);
            }
            
$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->devPackageNamestrue)) {
                
$data['dev-package-names'][] = $package->getName();
            }
        }

        
sort($data['dev-package-names']);
        
usort($data['packages'], function ($a$b) {
            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->filesystem->filePutContentsIfModified($repoDir.'/InstalledVersions.php'$installedVersionsClass);

            
\Composer\InstalledVersions::reload($versions);
        }
    }

    
/**
     * @param array<mixed> $array
     * @param int $level
     *
     * @return string
     */
    
private function dumpToPhpCode(array $array = array(), $level 0)
    {
        
$lines "array(\n";
        
$level++;

        foreach (
$array as $key => $value) {
            
$lines .= str_repeat('    '$level);
            
$lines .= is_int($key) ? $key ' => ' '\'' $key '\' => ';

            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($valuetrue) . ",\n";
                } else {
                    
$lines .= "__DIR__ . " var_export('/' $valuetrue) . ",\n";
                }
            } else {
                
$lines .= var_export($valuetrue) . ",\n";
            }
        }

        
$lines .= str_repeat('    '$level 1) . ')' . ($level == '' ",\n");

        return 
$lines;
    }

    
/**
     * @param array<string, string> $installPaths
     * @param bool $devMode
     * @param string $repoDir
     *
     * @return ?array<mixed>
     */
    
private function generateInstalledVersions(InstallationManager $installationManager, array $installPaths$devMode$repoDir)
    {
        if (!
$this->dumpVersions) {
            return 
null;
        }

        
$devPackages array_flip($this->devPackageNames);
        
$versions = array('versions' => array());
        
$packages $this->getPackages();
        
$packages[] = $rootPackage $this->rootPackage;
        while (
$rootPackage instanceof AliasPackage) {
            
$rootPackage $rootPackage->getAliasOf();
            
$packages[] = $rootPackage;
        }

        
// add real installed packages
        
foreach ($packages as $package) {
            if (
$package instanceof AliasPackage) {
                continue;
            }

            
$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(getcwd()));
                
$installPath $this->filesystem->findShortestPath($repoDir$totrue);
            } else {
                
$installPath $installPaths[$package->getName()];
            }

            
$versions['versions'][$package->getName()] = array(
                
'pretty_version' => $package->getPrettyVersion(),
                
'version' => $package->getVersion(),
                
'type' => $package->getType(),
                
'install_path' => $installPath,
                
'aliases' => array(),
                
'reference' => $reference,
                
'dev_requirement' => isset($devPackages[$package->getName()]),
            );
            if (
$package instanceof RootPackageInterface) {
                
$versions['root'] = $versions['versions'][$package->getName()];
                unset(
$versions['root']['dev_requirement']);
                
$versions['root']['name'] = $package->getName();
                
$versions['root']['dev'] = $devMode;
            }
        }

        
// 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);

        return 
$versions;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class GitHubDriver extends VcsDriver
{
    
/** @var string */
    
protected $owner;
    
/** @var string */
    
protected $repository;
    
/** @var array<string, string> Map of tag name to identifier */
    
protected $tags;
    
/** @var array<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()
    {
        
Preg::match('#^(?:(?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/(.+?)(?:\.git|/)?$#'$this->url$match);
        
$this->owner $match[3];
        
$this->repository $match[4];
        
$this->originUrl strtolower(!empty($match[1]) ? $match[1] : $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();
    }

    
/**
     * @return string
     */
    
public function getRepositoryUrl()
    {
        return 
'https://'.$this->originUrl.'/'.$this->owner.'/'.$this->repository;
    }

    
/**
     * @inheritDoc
     */
    
public function getRootIdentifier()
    {
        if (
$this->gitDriver) {
            return 
$this->gitDriver->getRootIdentifier();
        }

        return 
$this->rootIdentifier;
    }

    
/**
     * @inheritDoc
     */
    
public function getUrl()
    {
        if (
$this->gitDriver) {
            return 
$this->gitDriver->getUrl();
        }

        return 
'https://' $this->originUrl '/'.$this->owner.'/'.$this->repository.'.git';
    }

    
/**
     * @return string
     */
    
protected function getApiUrl()
    {
        if (
'github.com' === $this->originUrl) {
            
$apiUrl 'api.github.com';
        } else {
            
$apiUrl $this->originUrl '/api/v3';
        }

        return 
'https://' $apiUrl;
    }

    
/**
     * @inheritDoc
     */
    
public function getSource($identifier)
    {
        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 array(
'type' => 'git''url' => $url'reference' => $identifier);
    }

    
/**
     * @inheritDoc
     */
    
public function getDist($identifier)
    {
        
$url $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/zipball/'.$identifier;

        return array(
'type' => 'zip''url' => $url'reference' => $identifier'shasum' => '');
    }

    
/**
     * @inheritDoc
     */
    
public function getComposerInformation($identifier)
    {
        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($identifierjson_encode($composer));
                }
            }

            if (
$composer) {
                
// specials for github
                
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 (array(
$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, array(
                    
'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 = array();
        
$key null;
        foreach (
Preg::split('{\r?\n}'$funding) as $line) {
            
$line trim($line);
            if (
Preg::isMatch('{^(\w+)\s*:\s*(.+)$}'$line$match)) {
                if (
Preg::isMatch('{^\[(.*)\](?:\s*#.*)?$}'$match[2], $match2)) {
                    foreach (
array_map('trim'Preg::split('{[\'"]?\s*,\s*[\'"]?}'$match2[1])) as $item) {
                        
$result[] = array('type' => $match[1], 'url' => trim($item'"\' '));
                    }
                } elseif (
Preg::isMatch('{^([^#].*?)(\s+#.*)?$}'$match[2], $match2)) {
                    
$result[] = array('type' => $match[1], 'url' => trim($match2[1], '"\' '));
                }
                
$key null;
            } elseif (
Preg::isMatch('{^(\w+)\s*:\s*#\s*$}'$line$match)) {
                
$key $match[1];
            } elseif (
$key && Preg::isMatch('{^-\s*(.+)(\s+#.*)?$}'$line$match)) {
                
$result[] = array('type' => $key'url' => trim($match[1], '"\' '));
            }
        }

        foreach (
$result as $key => $item) {
            switch (
$item['type']) {
                case 
'tidelift':
                    
$result[$key]['url'] = 'https://tidelift.com/funding/github/' $item['url'];
                    break;
                case 
'github':
                    
$result[$key]['url'] = 'https://github.com/' basename($item['url']);
                    break;
                case 
'patreon':
                    
$result[$key]['url'] = 'https://www.patreon.com/' basename($item['url']);
                    break;
                case 
'otechie':
                    
$result[$key]['url'] = 'https://otechie.com/' basename($item['url']);
                    break;
                case 
'open_collective':
                    
$result[$key]['url'] = 'https://opencollective.com/' basename($item['url']);
                    break;
                case 
'liberapay':
                    
$result[$key]['url'] = 'https://liberapay.com/' basename($item['url']);
                    break;
                case 
'ko_fi':
                    
$result[$key]['url'] = 'https://ko-fi.com/' basename($item['url']);
                    break;
                case 
'issuehunt':
                    
$result[$key]['url'] = 'https://issuehunt.io/r/' $item['url'];
                    break;
                case 
'community_bridge':
                    
$result[$key]['url'] = 'https://funding.communitybridge.org/projects/' basename($item['url']);
                    break;
            }
        }

        return 
$this->fundingInfo $result;
    }

    
/**
     * @inheritDoc
     */
    
public function getFileContent($file$identifier)
    {
        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();
        if (empty(
$resource['content']) || $resource['encoding'] !== 'base64' || !($content base64_decode($resource['content']))) {
            throw new 
\RuntimeException('Could not retrieve ' $file ' for '.$identifier);
        }

        return 
$content;
    }

    
/**
     * @inheritDoc
     */
    
public function getChangeDate($identifier)
    {
        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 
\DateTime($commit['commit']['committer']['date']);
    }

    
/**
     * @inheritDoc
     */
    
public function getTags()
    {
        if (
$this->gitDriver) {
            return 
$this->gitDriver->getTags();
        }
        if (
null === $this->tags) {
            
$tags = array();
            
$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()
    {
        if (
$this->gitDriver) {
            return 
$this->gitDriver->getBranches();
        }
        if (
null === $this->branches) {
            
$branches = array();
            
$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 $ioConfig $config$url$deep false)
    {
        if (!
Preg::isMatch('#^((?:https?|git)://([^/]+)/|git@([^:]+):/?)([^/]+)/(.+?)(?:\.git|/)?$#'$url$matches)) {
            return 
false;
        }

        
$originUrl = !empty($matches[2]) ? $matches[2] : $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.'trueIOInterface::VERBOSE);

            return 
false;
        }

        return 
true;
    }

    
/**
     * Gives back the loaded <github-api>/repos/<owner>/<repo> result
     *
     * @return mixed[]|null
     */
    
public function getRepoData()
    {
        
$this->fetchRootIdentifier();

        return 
$this->repoData;
    }

    
/**
     * Generate an SSH URL
     *
     * @return string
     */
    
protected function generateSshUrl()
    {
        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
     *
     * @param bool $fetchingRepoData
     */
    
protected function getContents($url$fetchingRepoData false)
    {
        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(array('url' => 'dummy'), 200, array(), 'null');
                    }

                    
$scopesIssued = array();
                    
$scopesNeeded = array();
                    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(array('url' => 'dummy'), 200, array(), '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
     *
     * @return void
     * @throws TransportException
     */
    
protected function fetchRootIdentifier()
    {
        if (
$this->repoData) {
            return;
        }

        
$repoDataUrl $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository;

        try {
            
$this->repoData $this->getContents($repoDataUrltrue)->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()
    {
        
$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;
        }
    }

    
/**
     * @param string $url
     *
     * @return void
     */
    
protected function setupGitDriver($url)
    {
        
$this->gitDriver = new GitDriver(
            array(
'url' => $url),
            
$this->io,
            
$this->config,
            
$this->httpDownloader,
            
$this->process
        
);
        
$this->gitDriver->initialize();
    }

    
/**
     * @return string|null
     */
    
protected function getNextPage(Response $response)
    {
        
$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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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;

/**
 * @author Matt Whittom <[email protected]>
 */
class PerforceDriver extends VcsDriver
{
    
/** @var string */
    
protected $depot;
    
/** @var string */
    
protected $branch;
    
/** @var ?Perforce */
    
protected $perforce null;

    
/**
     * @inheritDoc
     */
    
public function initialize()
    {
        
$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
     *
     * @return void
     */
    
private function initPerforce($repoConfig)
    {
        if (!empty(
$this->perforce)) {
            return;
        }

        if (!
Cache::isUsable((string) $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($file$identifier)
    {
        return 
$this->perforce->getFileContent($file$identifier);
    }

    
/**
     * @inheritDoc
     */
    
public function getChangeDate($identifier)
    {
        return 
null;
    }

    
/**
     * @inheritDoc
     */
    
public function getRootIdentifier()
    {
        return 
$this->branch;
    }

    
/**
     * @inheritDoc
     */
    
public function getBranches()
    {
        return 
$this->perforce->getBranches();
    }

    
/**
     * @inheritDoc
     */
    
public function getTags()
    {
        return 
$this->perforce->getTags();
    }

    
/**
     * @inheritDoc
     */
    
public function getDist($identifier)
    {
        return 
null;
    }

    
/**
     * @inheritDoc
     */
    
public function getSource($identifier)
    {
        return array(
            
'type' => 'perforce',
            
'url' => $this->repoConfig['url'],
            
'reference' => $identifier,
            
'p4user' => $this->perforce->getUser(),
        );
    }

    
/**
     * @inheritDoc
     */
    
public function getUrl()
    {
        return 
$this->url;
    }

    
/**
     * @inheritDoc
     */
    
public function hasComposerFile($identifier)
    {
        
$composerInfo $this->perforce->getComposerInformation('//' $this->depot '/' $identifier);

        return !empty(
$composerInfo);
    }

    
/**
     * @inheritDoc
     */
    
public function getContents($url)
    {
        throw new 
\BadMethodCallException('Not implemented/used in PerforceDriver');
    }

    
/**
     * @inheritDoc
     */
    
public static function supports(IOInterface $ioConfig $config$url$deep false)
    {
        if (
$deep || Preg::isMatch('#\b(perforce|p4)\b#i'$url)) {
            return 
Perforce::checkServerExists($url, new ProcessExecutor($io));
        }

        return 
false;
    }

    
/**
     * @inheritDoc
     */
    
public function cleanup()
    {
        
$this->perforce->cleanupClientSpec();
        
$this->perforce null;
    }

    
/**
     * @return string
     */
    
public function getDepot()
    {
        return 
$this->depot;
    }

    
/**
     * @return string
     */
    
public function getBranch()
    {
        return 
$this->branch;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
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()
    {
        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((string) $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''-'$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://[email protected]:22/path or just [email protected]: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()
    {
        if (
null === $this->rootIdentifier) {
            
$this->rootIdentifier 'master';

            
// select currently checked out branch if master is not available
            
$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::isMatch('{^\* +(\S+)}'$branch$match)) {
                        
$this->rootIdentifier $match[1];
                        break;
                    }
                }
            }
        }

        return 
$this->rootIdentifier;
    }

    
/**
     * @inheritDoc
     */
    
public function getUrl()
    {
        return 
$this->url;
    }

    
/**
     * @inheritDoc
     */
    
public function getSource($identifier)
    {
        return array(
'type' => 'git''url' => $this->getUrl(), 'reference' => $identifier);
    }

    
/**
     * @inheritDoc
     */
    
public function getDist($identifier)
    {
        return 
null;
    }

    
/**
     * @inheritDoc
     */
    
public function getFileContent($file$identifier)
    {
        
$resource sprintf('%s:%s'ProcessExecutor::escape($identifier), ProcessExecutor::escape($file));
        
$this->process->execute(sprintf('git show %s'$resource), $content$this->repoDir);

        if (!
trim($content)) {
            return 
null;
        }

        return 
$content;
    }

    
/**
     * @inheritDoc
     */
    
public function getChangeDate($identifier)
    {
        
$this->process->execute(sprintf(
            
'git -c log.showSignature=false log -1 --format=%%at %s',
            
ProcessExecutor::escape($identifier)
        ), 
$output$this->repoDir);

        return new 
\DateTime('@'.trim($output), new \DateTimeZone('UTC'));
    }

    
/**
     * @inheritDoc
     */
    
public function getTags()
    {
        if (
null === $this->tags) {
            
$this->tags = array();

            
$this->process->execute('git show-ref --tags --dereference'$output$this->repoDir);
            foreach (
$output $this->process->splitLines($output) as $tag) {
                if (
$tag && Preg::isMatch('{^([a-f0-9]{40}) refs/tags/(\S+?)(\^\{\})?$}'$tag$match)) {
                    
$this->tags[$match[2]] = (string) $match[1];
                }
            }
        }

        return 
$this->tags;
    }

    
/**
     * @inheritDoc
     */
    
public function getBranches()
    {
        if (
null === $this->branches) {
            
$branches = array();

            
$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::isMatch('{^(?:\* )? *(\S+) *([a-f0-9]+)(?: .*)?$}'$branch$match)) {
                        
$branches[$match[1]] = $match[2];
                    }
                }
            }

            
$this->branches $branches;
        }

        return 
$this->branches;
    }

    
/**
     * @inheritDoc
     */
    
public static function supports(IOInterface $ioConfig $config$url$deep false)
    {
        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;
            }
        }

        if (!
$deep) {
            return 
false;
        }

        
$gitUtil = new GitUtil($io$config, new ProcessExecutor($io), new Filesystem());
        
GitUtil::cleanEnv();

        try {
            
$gitUtil->runCommand(function ($url) {
                return 
'git ls-remote --heads -- ' ProcessExecutor::escape($url);
            }, 
$urlsys_get_temp_dir());
        } catch (
\RuntimeException $e) {
            return 
false;
        }

        return 
true;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @author Till Klampaeckel <[email protected]>
 */
class SvnDriver extends VcsDriver
{
    
/** @var string */
    
protected $baseUrl;
    
/** @var array<string, string> Map of tag name to identifier */
    
protected $tags;
    
/** @var array<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()
    {
        
$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->url0$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()
    {
        return 
$this->rootIdentifier ?: $this->trunkPath;
    }

    
/**
     * @inheritDoc
     */
    
public function getUrl()
    {
        return 
$this->url;
    }

    
/**
     * @inheritDoc
     */
    
public function getSource($identifier)
    {
        return array(
'type' => 'svn''url' => $this->baseUrl'reference' => $identifier);
    }

    
/**
     * @inheritDoc
     */
    
public function getDist($identifier)
    {
        return 
null;
    }

    
/**
     * @inheritDoc
     */
    
protected function shouldCache($identifier)
    {
        return 
$this->cache && Preg::isMatch('{@\d+$}'$identifier);
    }

    
/**
     * @inheritDoc
     */
    
public function getComposerInformation($identifier)
    {
        if (!isset(
$this->infoCache[$identifier])) {
            if (
$this->shouldCache($identifier) && $res $this->cache->read($identifier.'.json')) {
                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 '';
            }

            if (
$this->shouldCache($identifier)) {
                
$this->cache->write($identifier.'.json'json_encode($composer));
            }

            
$this->infoCache[$identifier] = $composer;
        }

        return 
$this->infoCache[$identifier];
    }

    
/**
     * @param string $file
     * @param string $identifier
     */
    
public function getFileContent($file$identifier)
    {
        
$identifier '/' trim($identifier'/') . '/';

        
Preg::match('{^(.+?)(@\d+)?/$}'$identifier$match);
        if (!empty(
$match[2])) {
            
$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($identifier)
    {
        
$identifier '/' trim($identifier'/') . '/';

        
Preg::match('{^(.+?)(@\d+)?/$}'$identifier$match);
        if (!empty(
$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::isMatch('{^Last Changed Date: ([^(]+)}'$line$match)) {
                return new 
\DateTime($match[1], new \DateTimeZone('UTC'));
            }
        }

        return 
null;
    }

    
/**
     * @inheritDoc
     */
    
public function getTags()
    {
        if (
null === $this->tags) {
            
$tags = array();

            if (
$this->tagsPath !== false) {
                
$output $this->execute('svn ls --verbose'$this->baseUrl '/' $this->tagsPath);
                if (
$output) {
                    foreach (
$this->process->splitLines($output) as $line) {
                        
$line trim($line);
                        if (
$line && Preg::isMatch('{^\s*(\S+).*?(\S+)\s*$}'$line$match)) {
                            if (isset(
$match[1], $match[2]) && $match[2] !== './') {
                                
$tags[rtrim($match[2], '/')] = $this->buildIdentifier(
                                    
'/' $this->tagsPath '/' $match[2],
                                    
$match[1]
                                );
                            }
                        }
                    }
                }
            }

            
$this->tags $tags;
        }

        return 
$this->tags;
    }

    
/**
     * @inheritDoc
     */
    
public function getBranches()
    {
        if (
null === $this->branches) {
            
$branches = array();

            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 (isset(
$match[1], $match[2]) && $match[2] === './') {
                            
$branches['trunk'] = $this->buildIdentifier(
                                
'/' $this->trunkPath,
                                
$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) {
                    foreach (
$this->process->splitLines(trim($output)) as $line) {
                        
$line trim($line);
                        if (
$line && Preg::isMatch('{^\s*(\S+).*?(\S+)\s*$}'$line$match)) {
                            if (isset(
$match[1], $match[2]) && $match[2] !== './') {
                                
$branches[rtrim($match[2], '/')] = $this->buildIdentifier(
                                    
'/' $this->branchesPath '/' $match[2],
                                    
$match[1]
                                );
                            }
                        }
                    }
                }
            }

            
$this->branches $branches;
        }

        return 
$this->branches;
    }

    
/**
     * @inheritDoc
     */
    
public static function supports(IOInterface $ioConfig $config$url$deep false)
    {
        
$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 -- ".ProcessExecutor::escape($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.
     *
     * @param string $url
     *
     * @return string
     */
    
protected static function normalizeUrl($url)
    {
        
$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  string            $command The svn command to run.
     * @param  string            $url     The SVN URL.
     * @throws \RuntimeException
     * @return string
     */
    
protected function execute($command$url)
    {
        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
     *
     * @return string
     */
    
protected function buildIdentifier($baseDir$revision)
    {
        return 
rtrim($baseDir'/') . $this->packagePath '/@' $revision;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
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<string, mixed> */
    
protected $infoCache = array();
    
/** @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 $repoConfigIOInterface $ioConfig $configHttpDownloader $httpDownloaderProcessExecutor $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.
     *
     * @param  string $identifier
     * @return bool
     */
    
protected function shouldCache($identifier)
    {
        return 
$this->cache && Preg::isMatch('{^[a-f0-9]{40}$}iD'$identifier);
    }

    
/**
     * @inheritDoc
     */
    
public function getComposerInformation($identifier)
    {
        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($identifierJsonFile::encode($composer0));
            }

            
$this->infoCache[$identifier] = $composer;
        }

        return 
$this->infoCache[$identifier];
    }

    
/**
     * @param string $identifier
     *
     * @return array<string, mixed>|null
     */
    
protected function getBaseComposerInformation($identifier)
    {
        
$composerFileContent $this->getFileContent('composer.json'$identifier);

        if (!
$composerFileContent) {
            return 
null;
        }

        
$composer JsonFile::parseJson($composerFileContent$identifier ':composer.json');

        if (empty(
$composer['time']) && $changeDate $this->getChangeDate($identifier)) {
            
$composer['time'] = $changeDate->format(DATE_RFC3339);
        }

        return 
$composer;
    }

    
/**
     * @inheritDoc
     */
    
public function hasComposerFile($identifier)
    {
        try {
            return (bool) 
$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()
    {
        if (
extension_loaded('openssl')) {
            return 
'https';
        }

        return 
'http';
    }

    
/**
     * Get the remote content.
     *
     * @param string $url The URL of content
     *
     * @return Response
     * @throws TransportException
     */
    
protected function getContents($url)
    {
        
$options = isset($this->repoConfig['options']) ? $this->repoConfig['options'] : array();

        return 
$this->httpDownloader->get($url$options);
    }

    
/**
     * @inheritDoc
     */
    
public function cleanup()
    {
        return;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
interface VcsDriverInterface
{
    
/**
     * Initializes the driver (git clone, svn checkout, fetch info etc)
     *
     * @return void
     */
    
public function initialize();

    
/**
     * Return the composer.json file information
     *
     * @param  string  $identifier Any identifier to a specific branch/tag/commit
     * @return mixed[] containing all infos from the composer.json file
     */
    
public function getComposerInformation($identifier);

    
/**
     * Return the content of $file or null if the file does not exist.
     *
     * @param  string      $file
     * @param  string      $identifier
     * @return string|null
     */
    
public function getFileContent($file$identifier);

    
/**
     * Get the changedate for $identifier.
     *
     * @param  string         $identifier
     * @return \DateTime|null
     */
    
public function getChangeDate($identifier);

    
/**
     * Return the root identifier (trunk, master, default/tip ..)
     *
     * @return string Identifier
     */
    
public function getRootIdentifier();

    
/**
     * Return list of branches in the repository
     *
     * @return array<int|string, string> Branch names as keys, identifiers as values
     */
    
public function getBranches();

    
/**
     * Return list of tags in the repository
     *
     * @return array<int|string, string> Tag names as keys, identifiers as values
     */
    
public function getTags();

    
/**
     * @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($identifier);

    
/**
     * @param string $identifier Any identifier to a specific branch/tag/commit
     *
     * @return array{type: string, url: string, reference: string}
     */
    
public function getSource($identifier);

    
/**
     * Return the URL of the repository
     *
     * @return string
     */
    
public function getUrl();

    
/**
     * 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($identifier);

    
/**
     * Performs any cleanup necessary as the driver is not longer needed
     *
     * @return void
     */
    
public function cleanup();

    
/**
     * 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
     * @return bool
     */
    
public static function supports(IOInterface $ioConfig $config$url$deep false);
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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;

/**
 * @author Per Bernhardt <[email protected]>
 */
class HgDriver extends VcsDriver
{
    
/** @var array<string, string> Map of tag name to identifier */
    
protected $tags;
    
/** @var array<string, string> Map of branch name to identifier */
    
protected $branches;
    
/** @var string */
    
protected $rootIdentifier;
    
/** @var string */
    
protected $repoDir;

    
/**
     * @inheritDoc
     */
    
public function initialize()
    {
        if (
Filesystem::isLocalPath($this->url)) {
            
$this->repoDir $this->url;
        } else {
            if (!
Cache::isUsable((string) $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''-'$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) && === $this->process->execute('hg summary'$output$this->repoDir)) {
                if (
!== $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 = function ($url) use ($repoDir) {
                    return 
sprintf('hg clone --noupdate -- %s %s'ProcessExecutor::escape($url), ProcessExecutor::escape($repoDir));
                };

                
$hgUtils->runCommand($command$this->urlnull);
            }
        }

        
$this->getTags();
        
$this->getBranches();
    }

    
/**
     * @inheritDoc
     */
    
public function getRootIdentifier()
    {
        if (
null === $this->rootIdentifier) {
            
$this->process->execute(sprintf('hg tip --template "{node}"'), $output$this->repoDir);
            
$output $this->process->splitLines($output);
            
$this->rootIdentifier $output[0];
        }

        return 
$this->rootIdentifier;
    }

    
/**
     * @inheritDoc
     */
    
public function getUrl()
    {
        return 
$this->url;
    }

    
/**
     * @inheritDoc
     */
    
public function getSource($identifier)
    {
        return array(
'type' => 'hg''url' => $this->getUrl(), 'reference' => $identifier);
    }

    
/**
     * @inheritDoc
     */
    
public function getDist($identifier)
    {
        return 
null;
    }

    
/**
     * @inheritDoc
     */
    
public function getFileContent($file$identifier)
    {
        
$resource sprintf('hg cat -r %s %s'ProcessExecutor::escape($identifier), ProcessExecutor::escape($file));
        
$this->process->execute($resource$content$this->repoDir);

        if (!
trim($content)) {
            return 
null;
        }

        return 
$content;
    }

    
/**
     * @inheritDoc
     */
    
public function getChangeDate($identifier)
    {
        
$this->process->execute(
            
sprintf(
                
'hg log --template "{date|rfc3339date}" -r %s',
                
ProcessExecutor::escape($identifier)
            ),
            
$output,
            
$this->repoDir
        
);

        return new 
\DateTime(trim($output), new \DateTimeZone('UTC'));
    }

    
/**
     * @inheritDoc
     */
    
public function getTags()
    {
        if (
null === $this->tags) {
            
$tags = array();

            
$this->process->execute('hg tags'$output$this->repoDir);
            foreach (
$this->process->splitLines($output) as $tag) {
                if (
$tag && Preg::isMatch('(^([^\s]+)\s+\d+:(.*)$)'$tag$match)) {
                    
$tags[$match[1]] = $match[2];
                }
            }
            unset(
$tags['tip']);

            
$this->tags $tags;
        }

        return 
$this->tags;
    }

    
/**
     * @inheritDoc
     */
    
public function getBranches()
    {
        if (
null === $this->branches) {
            
$branches = array();
            
$bookmarks = array();

            
$this->process->execute('hg branches'$output$this->repoDir);
            foreach (
$this->process->splitLines($output) as $branch) {
                if (
$branch && Preg::isMatch('(^([^\s]+)\s+\d+:([a-f0-9]+))'$branch$match)) {
                    
$branches[$match[1]] = $match[2];
                }
            }

            
$this->process->execute('hg bookmarks'$output$this->repoDir);
            foreach (
$this->process->splitLines($output) as $branch) {
                if (
$branch && Preg::isMatch('(^(?:[\s*]*)([^\s]+)\s+\d+:(.*)$)'$branch$match)) {
                    
$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 $ioConfig $config$url$deep false)
    {
        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(sprintf('hg identify -- %s'ProcessExecutor::escape($url)), $ignored);

        return 
$exit === 0;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @author Jérôme Tamarelle <[email protected]>
 */
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;

    
/**
     * @var array<string|int, mixed[]> Keeps commits returned by GitLab API as commit id => info
     */
    
private $commits = array();

    
/** @var array<string, string> Map of tag name to identifier */
    
private $tags;

    
/** @var array<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;

    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()
    {
        if (!
Preg::isMatch(self::URL_REGEX$this->url$match)) {
            throw new 
\InvalidArgumentException('The URL provided is invalid. It must be the HTTP URL of a GitLab project.');
        }

        
$guessedDomain = !empty($match['domain']) ? $match['domain'] : $match['domain2'];
        
$configuredDomains $this->config->get('gitlab-domains');
        
$urlParts explode('/'$match['parts']);

        
$this->scheme = !empty($match['scheme'])
            ? 
$match['scheme']
            : (isset(
$this->repoConfig['secure-http']) && $this->repoConfig['secure-http'] === false 'http' 'https')
        ;
        
$this->originUrl self::determineOrigin($configuredDomains$guessedDomain$urlParts$match['port']);

        if (
$protocol $this->config->get('gitlab-protocol')) {
            
// https treated as a synonym for http.
            
if (!in_array($protocol, array('git''http''https'))) {
                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
     *
     * @return void
     */
    
public function setHttpDownloader(HttpDownloader $httpDownloader)
    {
        
$this->httpDownloader $httpDownloader;
    }

    
/**
     * @inheritDoc
     */
    
public function getComposerInformation($identifier)
    {
        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($identifierjson_encode($composer));
                }
            }

            if (
$composer) {
                
// specials for gitlab (this data is only available if authentication is provided)
                
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($file$identifier)
    {
        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($identifier)
    {
        if (
$this->gitDriver) {
            return 
$this->gitDriver->getChangeDate($identifier);
        }

        if (isset(
$this->commits[$identifier])) {
            return new 
\DateTime($this->commits[$identifier]['committed_date']);
        }

        return new 
\DateTime();
    }

    
/**
     * @return string
     */
    
public function getRepositoryUrl()
    {
        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()
    {
        if (
$this->gitDriver) {
            return 
$this->gitDriver->getUrl();
        }

        return 
$this->project['web_url'];
    }

    
/**
     * @inheritDoc
     */
    
public function getDist($identifier)
    {
        
$url $this->getApiUrl().'/repository/archive.zip?sha='.$identifier;

        return array(
'type' => 'zip''url' => $url'reference' => $identifier'shasum' => '');
    }

    
/**
     * @inheritDoc
     */
    
public function getSource($identifier)
    {
        if (
$this->gitDriver) {
            return 
$this->gitDriver->getSource($identifier);
        }

        return array(
'type' => 'git''url' => $this->getRepositoryUrl(), 'reference' => $identifier);
    }

    
/**
     * @inheritDoc
     */
    
public function getRootIdentifier()
    {
        if (
$this->gitDriver) {
            return 
$this->gitDriver->getRootIdentifier();
        }

        return 
$this->project['default_branch'];
    }

    
/**
     * @inheritDoc
     */
    
public function getBranches()
    {
        if (
$this->gitDriver) {
            return 
$this->gitDriver->getBranches();
        }

        if (!
$this->branches) {
            
$this->branches $this->getReferences('branches');
        }

        return 
$this->branches;
    }

    
/**
     * @inheritDoc
     */
    
public function getTags()
    {
        if (
$this->gitDriver) {
            return 
$this->gitDriver->getTags();
        }

        if (!
$this->tags) {
            
$this->tags $this->getReferences('tags');
        }

        return 
$this->tags;
    }

    
/**
     * @return string Base URL for GitLab API v3
     */
    
public function getApiUrl()
    {
        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 `.`
     *
     * @param  string $string
     * @return string
     */
    
private function urlEncodeAll($string)
    {
        
$encoded '';
        for (
$i 0; isset($string[$i]); $i++) {
            
$character $string[$i];
            if (!
ctype_alnum($character) && !in_array($character, array('-''_'), true)) {
                
$character '%' sprintf('%02X'ord($character));
            }
            
$encoded .= $character;
        }

        return 
$encoded;
    }

    
/**
     * @param string $type
     *
     * @return string[] where keys are named references like tags or branches and the value a sha
     */
    
protected function getReferences($type)
    {
        
$perPage 100;
        
$resource $this->getApiUrl().'/repository/'.$type.'?per_page='.$perPage;

        
$references = array();
        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;
    }

    
/**
     * @return void
     */
    
protected function fetchProject()
    {
        
// we need to fetch the default branch from the api
        
$resource $this->getApiUrl();
        
$this->project $this->getContents($resourcetrue)->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()
    {
        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
     *
     * @return string
     */
    
protected function generateSshUrl()
    {
        if (
$this->hasNonstandardOrigin) {
            return 
'ssh://git@'.$this->originUrl.'/'.$this->namespace.'/'.$this->repository.'.git';
        }

        return 
'git@' $this->originUrl ':'.$this->namespace.'/'.$this->repository.'.git';
    }

    
/**
     * @return string
     */
    
protected function generatePublicUrl()
    {
        return 
$this->scheme '://' $this->originUrl '/'.$this->namespace.'/'.$this->repository.'.git';
    }

    
/**
     * @param string $url
     *
     * @return void
     */
    
protected function setupGitDriver($url)
    {
        
$this->gitDriver = new GitDriver(
            array(
'url' => $url),
            
$this->io,
            
$this->config,
            
$this->httpDownloader,
            
$this->process
        
);
        
$this->gitDriver->initialize();
    }

    
/**
     * @inheritDoc
     *
     * @param bool $fetchingRepoData
     */
    
protected function getContents($url$fetchingRepoData false)
    {
        try {
            
$response parent::getContents($url);

            if (
$fetchingRepoData) {
                
$json $response->decodeJson();

                
// Accessing the API with a token with Guest (10) 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'] > 10) {
                            
$moreThanGuestAccess true;
                        }
                    }

                    if (!
$moreThanGuestAccess) {
                        
$this->io->writeError('<warning>GitLab token with Guest only access detected</warning>');

                        
$this->attemptCloneFallback();

                        return new 
Response(array('url' => 'dummy'), 200, array(), '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 (!
$this->io->isInteractive()) {
                        
$this->attemptCloneFallback();

                        return new 
Response(array('url' => 'dummy'), 200, array(), '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(array('url' => 'dummy'), 200, array(), '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 $ioConfig $config$url$deep false)
    {
        if (!
Preg::isMatch(self::URL_REGEX$url$match)) {
            return 
false;
        }

        
$scheme = !empty($match['scheme']) ? $match['scheme'] : null;
        
$guessedDomain = !empty($match['domain']) ? $match['domain'] : $match['domain2'];
        
$urlParts explode('/'$match['parts']);

        if (
false === self::determineOrigin((array) $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.'trueIOInterface::VERBOSE);

            return 
false;
        }

        return 
true;
    }

    
/**
     * @return string|null
     */
    
protected function getNextPage(Response $response)
    {
        
$header $response->getHeader('link');

        
$links explode(','$header);
        foreach (
$links as $link) {
            if (
Preg::isMatch('{<(.+?)>; *rel="next"}'$link$match)) {
                return 
$match[1];
            }
        }

        return 
null;
    }

    
/**
     * @param  array<string> $configuredDomains
     * @param  string        $guessedDomain
     * @param  array<string> $urlParts
     * @param string         $portNumber
     *
     * @return string|false
     */
    
private static function determineOrigin(array $configuredDomains$guessedDomain, array &$urlParts$portNumber)
    {
        
$guessedDomain strtolower($guessedDomain);

        if (
in_array($guessedDomain$configuredDomains) || ($portNumber && in_array($guessedDomain.':'.$portNumber$configuredDomains))) {
            if (
$portNumber) {
                return 
$guessedDomain.':'.$portNumber;
            }

            return 
$guessedDomain;
        }

        if (
$portNumber) {
            
$guessedDomain .= ':'.$portNumber;
        }

        while (
null !== ($part array_shift($urlParts))) {
            
$guessedDomain .= '/' $part;

            if (
in_array($guessedDomain$configuredDomains) || ($portNumber && in_array(Preg::replace('{:\d+}'''$guessedDomain), $configuredDomains))) {
                return 
$guessedDomain;
            }
        }

        return 
false;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class GitBitbucketDriver extends VcsDriver
{
    
/** @var string */
    
protected $owner;
    
/** @var string */
    
protected $repository;
    
/** @var bool */
    
private $hasIssues false;
    
/** @var ?string */
    
private $rootIdentifier;
    
/** @var array<string, string> Map of tag name to identifier */
    
private $tags;
    
/** @var array<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 ?VcsDriver
     */
    
protected $fallbackDriver null;
    
/** @var string|null if set either git or hg */
    
private $vcsType;

    
/**
     * @inheritDoc
     */
    
public function initialize()
    {
        
Preg::match('#^https?://bitbucket\.org/([^/]+)/([^/]+?)(\.git|/?)?$#i'$this->url$match);
        
$this->owner $match[1];
        
$this->repository $match[2];
        
$this->originUrl 'bitbucket.org';
        
$this->cache = new Cache(
            
$this->io,
            
implode('/', array(
                
$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()
    {
        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
     *
     * @return bool
     * @phpstan-impure
     */
    
protected function getRepoData()
    {
        
$resource sprintf(
            
'https://api.bitbucket.org/2.0/repositories/%s/%s?%s',
            
$this->owner,
            
$this->repository,
            
http_build_query(
                array(
'fields' => '-project,-owner'),
                
'',
                
'&'
            
)
        );

        
$repoData $this->fetchWithOAuthCredentials($resourcetrue)->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'];

        return 
true;
    }

    
/**
     * @inheritDoc
     */
    
public function getComposerInformation($identifier)
    {
        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($identifierjson_encode($composer));
                }
            }

            if (
$composer) {
                
// specials for bitbucket
                
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($file$identifier)
    {
        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($identifier)
    {
        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 
\DateTime($commit['date']);
    }

    
/**
     * @inheritDoc
     */
    
public function getSource($identifier)
    {
        if (
$this->fallbackDriver) {
            return 
$this->fallbackDriver->getSource($identifier);
        }

        return array(
'type' => $this->vcsType'url' => $this->getUrl(), 'reference' => $identifier);
    }

    
/**
     * @inheritDoc
     */
    
public function getDist($identifier)
    {
        if (
$this->fallbackDriver) {
            return 
$this->fallbackDriver->getDist($identifier);
        }

        
$url sprintf(
            
'https://bitbucket.org/%s/%s/get/%s.zip',
            
$this->owner,
            
$this->repository,
            
$identifier
        
);

        return array(
'type' => 'zip''url' => $url'reference' => $identifier'shasum' => '');
    }

    
/**
     * @inheritDoc
     */
    
public function getTags()
    {
        if (
$this->fallbackDriver) {
            return 
$this->fallbackDriver->getTags();
        }

        if (
null === $this->tags) {
            
$tags = array();
            
$resource sprintf(
                
'%s?%s',
                
$this->tagsUrl,
                
http_build_query(
                    array(
                        
'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()
    {
        if (
$this->fallbackDriver) {
            return 
$this->fallbackDriver->getBranches();
        }

        if (
null === $this->branches) {
            
$branches = array();
            
$resource sprintf(
                
'%s?%s',
                
$this->branchesUrl,
                
http_build_query(
                    array(
                        
'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
     * @param bool   $fetchingRepoData
     *
     * @return Response The result
     *
     * @phpstan-impure
     */
    
protected function fetchWithOAuthCredentials($url$fetchingRepoData false)
    {
        try {
            return 
parent::getContents($url);
        } catch (
TransportException $e) {
            
$bitbucketUtil = new Bitbucket($this->io$this->config$this->process$this->httpDownloader);

            if (
403 === $e->getCode() || (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(array('url' => 'dummy'), 200, array(), 'null');
                }
            }

            throw 
$e;
        }
    }

    
/**
     * Generate an SSH URL
     *
     * @return string
     */
    
protected function generateSshUrl()
    {
        return 
'git@' $this->originUrl ':' $this->owner.'/'.$this->repository.'.git';
    }

    
/**
     * @phpstan-impure
     *
     * @return true
     * @throws \RuntimeException
     */
    
protected function attemptCloneFallback()
    {
        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;
        }
    }

    
/**
     * @param  string $url
     * @return void
     */
    
protected function setupFallbackDriver($url)
    {
        
$this->fallbackDriver = new GitDriver(
            array(
'url' => $url),
            
$this->io,
            
$this->config,
            
$this->httpDownloader,
            
$this->process
        
);
        
$this->fallbackDriver->initialize();
    }

    
/**
     * @param  array<array{name: string, href: string}> $cloneLinks
     * @return void
     */
    
protected function parseCloneUrls(array $cloneLinks)
    {
        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']);
            }
        }
    }

    
/**
     * @return (array{name: string}&mixed[])|null
     */
    
protected function getMainBranchData()
    {
        
$resource sprintf(
            
'https://api.bitbucket.org/2.0/repositories/%s/%s?fields=mainbranch',
            
$this->owner,
            
$this->repository
        
);

        
$data $this->fetchWithOAuthCredentials($resource)->decodeJson();
        if (isset(
$data['mainbranch'])) {
            return 
$data['mainbranch'];
        }

        return 
null;
    }

    
/**
     * @inheritDoc
     */
    
public function getRootIdentifier()
    {
        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'
                
);
            }

            
$mainBranchData $this->getMainBranchData();
            
$this->rootIdentifier = !empty($mainBranchData['name']) ? $mainBranchData['name'] : 'master';
        }

        return 
$this->rootIdentifier;
    }

    
/**
     * @inheritDoc
     */
    
public static function supports(IOInterface $ioConfig $config$url$deep false)
    {
        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.'trueIOInterface::VERBOSE);

            return 
false;
        }

        return 
true;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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<string, string> Map of tag name to identifier */
    
protected $tags;
    
/** @var array<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()
    {
        
// 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((string) $this->config->get('cache-repo-dir')) || !Cache::isUsable((string) $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.
     *
     * @return void
     */
    
protected function checkFossil()
    {
        if (
!== $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.
     *
     * @return void
     */
    
protected function updateLocalRepo()
    {
        
$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) && === $this->process->execute('fossil info'$output$this->checkoutDir)) {
            if (
!== $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 (
!== $this->process->execute(sprintf('fossil clone -- %s %s'ProcessExecutor::escape($this->url), ProcessExecutor::escape($this->repoFile)), $output)) {
                
$output $this->process->getErrorOutput();

                throw new 
\RuntimeException('Failed to clone '.$this->url.' to repository ' $this->repoFile "\n\n" .$output);
            }

            if (
!== $this->process->execute(sprintf('fossil open --nested -- %s'ProcessExecutor::escape($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()
    {
        if (
null === $this->rootIdentifier) {
            
$this->rootIdentifier 'trunk';
        }

        return 
$this->rootIdentifier;
    }

    
/**
     * @inheritDoc
     */
    
public function getUrl()
    {
        return 
$this->url;
    }

    
/**
     * @inheritDoc
     */
    
public function getSource($identifier)
    {
        return array(
'type' => 'fossil''url' => $this->getUrl(), 'reference' => $identifier);
    }

    
/**
     * @inheritDoc
     */
    
public function getDist($identifier)
    {
        return 
null;
    }

    
/**
     * @inheritDoc
     */
    
public function getFileContent($file$identifier)
    {
        
$command sprintf('fossil cat -r %s -- %s'ProcessExecutor::escape($identifier), ProcessExecutor::escape($file));
        
$this->process->execute($command$content$this->checkoutDir);

        if (!
trim($content)) {
            return 
null;
        }

        return 
$content;
    }

    
/**
     * @inheritDoc
     */
    
public function getChangeDate($identifier)
    {
        
$this->process->execute('fossil finfo -b -n 1 composer.json'$output$this->checkoutDir);
        list(, 
$date) = explode(' 'trim($output), 3);

        return new 
\DateTime($date, new \DateTimeZone('UTC'));
    }

    
/**
     * @inheritDoc
     */
    
public function getTags()
    {
        if (
null === $this->tags) {
            
$tags = array();

            
$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()
    {
        if (
null === $this->branches) {
            
$branches = array();

            
$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 $ioConfig $config$url$deep false)
    {
        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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @author Konstantin Kudryashov <[email protected]>
 * @author François Pluchino <[email protected]>
 */
class RepositoryManager
{
    
/** @var InstalledRepositoryInterface */
    
private $localRepository;
    
/** @var list<RepositoryInterface> */
    
private $repositories = array();
    
/** @var array<string, class-string<RepositoryInterface>> */
    
private $repositoryClasses = array();
    
/** @var IOInterface */
    
private $io;
    
/** @var Config */
    
private $config;
    
/** @var HttpDownloader */
    
private $httpDownloader;
    
/** @var ?EventDispatcher */
    
private $eventDispatcher;
    
/** @var ProcessExecutor */
    
private $process;

    public function 
__construct(IOInterface $ioConfig $configHttpDownloader $httpDownloaderEventDispatcher $eventDispatcher nullProcessExecutor $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
     *
     * @return PackageInterface|null
     */
    
public function findPackage($name$constraint)
    {
        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($name$constraint)
    {
        
$packages = array();

        foreach (
$this->getRepositories() as $repository) {
            
$packages array_merge($packages$repository->findPackages($name$constraint));
        }

        return 
$packages;
    }

    
/**
     * Adds repository
     *
     * @param RepositoryInterface $repository repository instance
     *
     * @return void
     */
    
public function addRepository(RepositoryInterface $repository)
    {
        
$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
     *
     * @return void
     */
    
public function prependRepository(RepositoryInterface $repository)
    {
        
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
     * @return RepositoryInterface
     */
    
public function createRepository($type$config$name null)
    {
        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
     *
     * @return void
     */
    
public function setRepositoryClass($type$class)
    {
        
$this->repositoryClasses[$type] = $class;
    }

    
/**
     * Returns all repositories, except local one.
     *
     * @return RepositoryInterface[]
     */
    
public function getRepositories()
    {
        return 
$this->repositories;
    }

    
/**
     * Sets local repository for the project.
     *
     * @param InstalledRepositoryInterface $repository repository instance
     *
     * @return void
     */
    
public function setLocalRepository(InstalledRepositoryInterface $repository)
    {
        
$this->localRepository $repository;
    }

    
/**
     * Returns local repository for the project.
     *
     * @return InstalledRepositoryInterface
     */
    
public function getLocalRepository()
    {
        return 
$this->localRepository;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\PolicyInterface;
use 
Composer\DependencyResolver\Pool;
use 
Composer\DependencyResolver\PoolBuilder;
use 
Composer\DependencyResolver\Request;
use 
Composer\EventDispatcher\EventDispatcher;
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\Semver\Constraint\ConstraintInterface;
use 
Composer\Package\Version\StabilityFilter;

/**
 * @author Nils Adermann <[email protected]>
 */
class RepositorySet
{
    
/**
     * Packages are returned even though their stability does not match the required stability
     */
    
const ALLOW_UNACCEPTABLE_STABILITIES 1;
    
/**
     * Packages will be looked up in all repositories, even after they have been found in a higher prio one
     */
    
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 = array();

    
/**
     * @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 ConstraintInterface[]
     * @phpstan-var array<string, ConstraintInterface>
     */
    
private $rootRequires;

    
/** @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 string $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
     */
    
public function __construct($minimumStability 'stable', array $stabilityFlags = array(), array $rootAliases = array(), array $rootReferences = array(), array $rootRequires = array())
    {
        
$this->rootAliases self::getRootAliasesPerPackage($rootAliases);
        
$this->rootReferences $rootReferences;

        
$this->acceptableStabilities = array();
        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]);
            }
        }
    }

    
/**
     * @param bool $allow
     *
     * @return void
     */
    
public function allowInstalledRepositories($allow true)
    {
        
$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()
    {
        return 
$this->rootRequires;
    }

    
/**
     * 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
     *
     * @return void
     */
    
public function addRepository(RepositoryInterface $repo)
    {
        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 = array($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  string                   $name
     * @param  ConstraintInterface|null $constraint
     * @param  int                      $flags      any of the ALLOW_* constants from this class to tweak what is returned
     * @return BasePackage[]
     */
    
public function findPackages($nameConstraintInterface $constraint null$flags 0)
    {
        
$ignoreStability = ($flags self::ALLOW_UNACCEPTABLE_STABILITIES) !== 0;
        
$loadFromAllRepos = ($flags self::ALLOW_SHADOWED_REPOSITORIES) !== 0;

        
$packages = array();
        if (
$loadFromAllRepos) {
            foreach (
$this->repositories as $repository) {
                
$packages[] = $repository->findPackages($name$constraint) ?: array();
            }
        } else {
            foreach (
$this->repositories as $repository) {
                
$result $repository->loadPackages(array($name => $constraint), $ignoreStability BasePackage::$stabilities $this->acceptableStabilities$ignoreStability ? array() : $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 call_user_func_array('array_merge'$packages) : array();

        
// when using loadPackages above (!$loadFromAllRepos) the repos already filter for stability so no need to do it again
        
if ($ignoreStability || !$loadFromAllRepos) {
            return 
$candidates;
        }

        
$result = array();
        foreach (
$candidates as $candidate) {
            if (
$this->isPackageAcceptable($candidate->getNames(), $candidate->getStability())) {
                
$result[] = $candidate;
            }
        }

        return 
$result;
    }

    
/**
     * @param  string   $packageName
     *
     * @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, type: string}>
     */
    
public function getProviders($packageName)
    {
        
$providers = array();
        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  string   $stability one of 'stable', 'RC', 'beta', 'alpha' or 'dev'
     * @return bool
     */
    
public function isPackageAcceptable($names$stability)
    {
        return 
StabilityFilter::isPackageAcceptable($this->acceptableStabilities$this->stabilityFlags$names$stability);
    }

    
/**
     * Create a pool for dependency resolution from the packages in this repository set.
     *
     * @return Pool
     */
    
public function createPool(Request $requestIOInterface $ioEventDispatcher $eventDispatcher nullPoolOptimizer $poolOptimizer null)
    {
        
$poolBuilder = new PoolBuilder($this->acceptableStabilities$this->stabilityFlags$this->rootAliases$this->rootReferences$io$eventDispatcher$poolOptimizer);

        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.
     *
     * @return Pool
     */
    
public function createPoolWithAllPackages()
    {
        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 = array();
        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);
    }

    
/**
     * @param string $packageName
     *
     * @return Pool
     */
    
public function createPoolForPackage($packageNameLockArrayRepository $lockedRepo null)
    {
        
// TODO unify this with above in some simpler version without "request"?
        
return $this->createPoolForPackages(array($packageName), $lockedRepo);
    }

    
/**
     * @param string[] $packageNames
     *
     * @return Pool
     */
    
public function createPoolForPackages($packageNamesLockArrayRepository $lockedRepo null)
    {
        
$request = new Request($lockedRepo);

        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);
        }

        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)
    {
        
$normalizedAliases = array();

        foreach (
$aliases as $alias) {
            
$normalizedAliases[$alias['package']][$alias['version']] = array(
                
'alias' => $alias['alias'],
                
'alias_normalized' => $alias['alias_normalized'],
            );
        }

        return 
$normalizedAliases;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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ň <[email protected]>
 */
class MetapackageInstaller implements InstallerInterface
{
    
/** @var IOInterface */
    
private $io;

    public function 
__construct(IOInterface $io)
    {
        
$this->io $io;
    }

    
/**
     * @inheritDoc
     */
    
public function supports($packageType)
    {
        return 
$packageType === 'metapackage';
    }

    
/**
     * @inheritDoc
     */
    
public function isInstalled(InstalledRepositoryInterface $repoPackageInterface $package)
    {
        return 
$repo->hasPackage($package);
    }

    
/**
     * @inheritDoc
     */
    
public function download(PackageInterface $packagePackageInterface $prevPackage null)
    {
        
// noop
        
return \React\Promise\resolve();
    }

    
/**
     * @inheritDoc
     */
    
public function prepare($typePackageInterface $packagePackageInterface $prevPackage null)
    {
        
// noop
        
return \React\Promise\resolve();
    }

    
/**
     * @inheritDoc
     */
    
public function cleanup($typePackageInterface $packagePackageInterface $prevPackage null)
    {
        
// noop
        
return \React\Promise\resolve();
    }

    
/**
     * @inheritDoc
     */
    
public function install(InstalledRepositoryInterface $repoPackageInterface $package)
    {
        
$this->io->writeError("  - " InstallOperation::format($package));

        
$repo->addPackage(clone $package);

        return 
\React\Promise\resolve();
    }

    
/**
     * @inheritDoc
     */
    
public function update(InstalledRepositoryInterface $repoPackageInterface $initialPackageInterface $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();
    }

    
/**
     * @inheritDoc
     */
    
public function uninstall(InstalledRepositoryInterface $repoPackageInterface $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();
    }

    
/**
     * @inheritDoc
     */
    
public function getInstallPath(PackageInterface $package)
    {
        return 
'';
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\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 <[email protected]>
 * @author Konstantin Kudryashov <[email protected]>
 */
class LibraryInstaller implements InstallerInterfaceBinaryPresenceInterface
{
    
/** @var Composer */
    
protected $composer;
    
/** @var string */
    
protected $vendorDir;
    
/** @var DownloadManager */
    
protected $downloadManager;
    
/** @var IOInterface */
    
protected $io;
    
/** @var string */
    
protected $type;
    
/** @var Filesystem */
    
protected $filesystem;
    
/** @var BinaryInstaller */
    
protected $binaryInstaller;

    
/**
     * Initializes library installer.
     *
     * @param IOInterface     $io
     * @param Composer        $composer
     * @param string|null     $type
     * @param Filesystem      $filesystem
     * @param BinaryInstaller $binaryInstaller
     */
    
public function __construct(IOInterface $ioComposer $composer$type 'library'Filesystem $filesystem nullBinaryInstaller $binaryInstaller null)
    {
        
$this->composer $composer;
        
$this->downloadManager $composer->getDownloadManager();
        
$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->iortrim($composer->getConfig()->get('bin-dir'), '/'), $composer->getConfig()->get('bin-compat'), $this->filesystem$this->vendorDir);
    }

    
/**
     * @inheritDoc
     */
    
public function supports($packageType)
    {
        return 
$packageType === $this->type || null === $this->type;
    }

    
/**
     * @inheritDoc
     */
    
public function isInstalled(InstalledRepositoryInterface $repoPackageInterface $package)
    {
        if (!
$repo->hasPackage($package)) {
            return 
false;
        }

        
$installPath $this->getInstallPath($package);

        if (
Filesystem::isReadable($installPath)) {
            return 
true;
        }

        return (
Platform::isWindows() && $this->filesystem->isJunction($installPath)) || is_link($installPath);
    }

    
/**
     * @inheritDoc
     */
    
public function download(PackageInterface $packagePackageInterface $prevPackage null)
    {
        
$this->initializeVendorDir();
        
$downloadPath $this->getInstallPath($package);

        return 
$this->downloadManager->download($package$downloadPath$prevPackage);
    }

    
/**
     * @inheritDoc
     */
    
public function prepare($typePackageInterface $packagePackageInterface $prevPackage null)
    {
        
$this->initializeVendorDir();
        
$downloadPath $this->getInstallPath($package);

        return 
$this->downloadManager->prepare($type$package$downloadPath$prevPackage);
    }

    
/**
     * @inheritDoc
     */
    
public function cleanup($typePackageInterface $packagePackageInterface $prevPackage null)
    {
        
$this->initializeVendorDir();
        
$downloadPath $this->getInstallPath($package);

        return 
$this->downloadManager->cleanup($type$package$downloadPath$prevPackage);
    }

    
/**
     * @inheritDoc
     */
    
public function install(InstalledRepositoryInterface $repoPackageInterface $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();
        }

        
$binaryInstaller $this->binaryInstaller;
        
$installPath $this->getInstallPath($package);

        return 
$promise->then(function () use ($binaryInstaller$installPath$package$repo) {
            
$binaryInstaller->installBinaries($package$installPath);
            if (!
$repo->hasPackage($package)) {
                
$repo->addPackage(clone $package);
            }
        });
    }

    
/**
     * @inheritDoc
     */
    
public function update(InstalledRepositoryInterface $repoPackageInterface $initialPackageInterface $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();
        }

        
$binaryInstaller $this->binaryInstaller;
        
$installPath $this->getInstallPath($target);

        return 
$promise->then(function () use ($binaryInstaller$installPath$target$initial$repo) {
            
$binaryInstaller->installBinaries($target$installPath);
            
$repo->removePackage($initial);
            if (!
$repo->hasPackage($target)) {
                
$repo->addPackage(clone $target);
            }
        });
    }

    
/**
     * @inheritDoc
     */
    
public function uninstall(InstalledRepositoryInterface $repoPackageInterface $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();
        }

        
$binaryInstaller $this->binaryInstaller;
        
$downloadPath $this->getPackageBasePath($package);
        
$filesystem $this->filesystem;

        return 
$promise->then(function () use ($binaryInstaller$filesystem$downloadPath$package$repo) {
            
$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
     */
    
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
     *
     * @param  PackageInterface $package
     * @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
     */
    
protected function installCode(PackageInterface $package)
    {
        
$downloadPath $this->getInstallPath($package);

        return 
$this->downloadManager->install($package$downloadPath);
    }

    
/**
     * @return PromiseInterface|null
     */
    
protected function updateCode(PackageInterface $initialPackageInterface $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();
                }

                
$self $this;

                return 
$promise->then(function () use ($self$target) {
                    
$reflMethod = new \ReflectionMethod($self'installCode');
                    
$reflMethod->setAccessible(true);

                    
// equivalent of $this->installCode($target) with php 5.3 support
                    // TODO remove this once 5.3 support is dropped
                    
return $reflMethod->invoke($self$target);
                });
            }

            
$this->filesystem->rename($initialDownloadPath$targetDownloadPath);
        }

        return 
$this->downloadManager->update($initial$target$targetDownloadPath);
    }

    
/**
     * @return PromiseInterface|null
     */
    
protected function removeCode(PackageInterface $package)
    {
        
$downloadPath $this->getPackageBasePath($package);

        return 
$this->downloadManager->remove($package$downloadPath);
    }

    
/**
     * @return void
     */
    
protected function initializeVendorDir()
    {
        
$this->filesystem->ensureDirectoryExists($this->vendorDir);
        
$this->vendorDir realpath($this->vendorDir);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\Repository\InstalledRepositoryInterface;
use 
Composer\Package\PackageInterface;
use 
Composer\Util\Filesystem;
use 
Composer\Util\Platform;
use 
React\Promise\PromiseInterface;

/**
 * Installer for plugin packages
 *
 * @author Jordi Boggiano <[email protected]>
 * @author Nils Adermann <[email protected]>
 */
class PluginInstaller extends LibraryInstaller
{
    
/**
     * Initializes Plugin installer.
     *
     * @param IOInterface $io
     * @param Composer    $composer
     */
    
public function __construct(IOInterface $ioComposer $composerFilesystem $fs nullBinaryInstaller $binaryInstaller null)
    {
        
parent::__construct($io$composer'composer-plugin'$fs$binaryInstaller);
    }

    
/**
     * @inheritDoc
     */
    
public function supports($packageType)
    {
        return 
$packageType === 'composer-plugin' || $packageType === 'composer-installer';
    }

    
/**
     * @inheritDoc
     */
    
public function download(PackageInterface $packagePackageInterface $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 $repoPackageInterface $package)
    {
        
$promise parent::install($repo$package);
        if (!
$promise instanceof PromiseInterface) {
            
$promise \React\Promise\resolve();
        }

        
$pluginManager $this->composer->getPluginManager();
        
$self $this;

        return 
$promise->then(function () use ($self$pluginManager$package$repo) {
            try {
                
Platform::workaroundFilesystemIssues();
                
$pluginManager->registerPackage($packagetrue);
            } catch (
\Exception $e) {
                
$self->rollbackInstall($e$repo$package);
            }
        });
    }

    
/**
     * @inheritDoc
     */
    
public function update(InstalledRepositoryInterface $repoPackageInterface $initialPackageInterface $target)
    {
        
$promise parent::update($repo$initial$target);
        if (!
$promise instanceof PromiseInterface) {
            
$promise \React\Promise\resolve();
        }

        
$pluginManager $this->composer->getPluginManager();
        
$self $this;

        return 
$promise->then(function () use ($self$pluginManager$initial$target$repo) {
            try {
                
Platform::workaroundFilesystemIssues();
                
$pluginManager->deactivatePackage($initial);
                
$pluginManager->registerPackage($targettrue);
            } catch (
\Exception $e) {
                
$self->rollbackInstall($e$repo$target);
            }
        });
    }

    public function 
uninstall(InstalledRepositoryInterface $repoPackageInterface $package)
    {
        
$this->composer->getPluginManager()->uninstallPackage($package);

        return 
parent::uninstall($repo$package);
    }

    
/**
     * TODO v3 should make this private once we can drop PHP 5.3 support
     * @private
     *
     * @return void
     */
    
public function rollbackInstall(\Exception $eInstalledRepositoryInterface $repoPackageInterface $package)
    {
        
$this->io->writeError('Plugin initialization failed ('.$e->getMessage().'), uninstalling plugin');
        
parent::uninstall($repo$package);
        throw 
$e;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class NoopInstaller implements InstallerInterface
{
    
/**
     * @inheritDoc
     */
    
public function supports($packageType)
    {
        return 
true;
    }

    
/**
     * @inheritDoc
     */
    
public function isInstalled(InstalledRepositoryInterface $repoPackageInterface $package)
    {
        return 
$repo->hasPackage($package);
    }

    
/**
     * @inheritDoc
     */
    
public function download(PackageInterface $packagePackageInterface $prevPackage null)
    {
        return 
\React\Promise\resolve();
    }

    
/**
     * @inheritDoc
     */
    
public function prepare($typePackageInterface $packagePackageInterface $prevPackage null)
    {
        return 
\React\Promise\resolve();
    }

    
/**
     * @inheritDoc
     */
    
public function cleanup($typePackageInterface $packagePackageInterface $prevPackage null)
    {
        return 
\React\Promise\resolve();
    }

    
/**
     * @inheritDoc
     */
    
public function install(InstalledRepositoryInterface $repoPackageInterface $package)
    {
        if (!
$repo->hasPackage($package)) {
            
$repo->addPackage(clone $package);
        }

        return 
\React\Promise\resolve();
    }

    
/**
     * @inheritDoc
     */
    
public function update(InstalledRepositoryInterface $repoPackageInterface $initialPackageInterface $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();
    }

    
/**
     * @inheritDoc
     */
    
public function uninstall(InstalledRepositoryInterface $repoPackageInterface $package)
    {
        if (!
$repo->hasPackage($package)) {
            throw new 
\InvalidArgumentException('Package is not installed: '.$package);
        }
        
$repo->removePackage($package);

        return 
\React\Promise\resolve();
    }

    
/**
     * @inheritDoc
     */
    
public function getInstallPath(PackageInterface $package)
    {
        
$targetDir $package->getTargetDir();

        return 
$package->getPrettyName() . ($targetDir '/'.$targetDir '');
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 */
interface InstallerInterface
{
    
/**
     * Decides if the installer supports the given type
     *
     * @param  string $packageType
     * @return bool
     */
    
public function supports($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 $repoPackageInterface $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
     */
    
public function download(PackageInterface $packagePackageInterface $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
     */
    
public function prepare($typePackageInterface $packagePackageInterface $prevPackage null);

    
/**
     * Installs specific package.
     *
     * @param  InstalledRepositoryInterface $repo    repository in which to check
     * @param  PackageInterface             $package package instance
     * @return PromiseInterface|null
     */
    
public function install(InstalledRepositoryInterface $repoPackageInterface $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
     */
    
public function update(InstalledRepositoryInterface $repoPackageInterface $initialPackageInterface $target);

    
/**
     * Uninstalls specific package.
     *
     * @param  InstalledRepositoryInterface $repo    repository in which to check
     * @param  PackageInterface             $package package instance
     * @return PromiseInterface|null
     */
    
public function uninstall(InstalledRepositoryInterface $repoPackageInterface $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
     */
    
public function cleanup($typePackageInterface $packagePackageInterface $prevPackage null);

    
/**
     * Returns the installation path of a package
     *
     * @param  PackageInterface $package
     * @return string           path to install to, which MUST not end with a slash
     */
    
public function getInstallPath(PackageInterface $package);
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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.
     *
     * @param string      $eventName
     * @param Composer    $composer
     * @param IOInterface $io
     * @param bool        $devMode
     * @param bool        $executeOperations
     * @param Transaction $transaction
     */
    
public function __construct($eventNameComposer $composerIOInterface $io$devMode$executeOperationsTransaction $transaction)
    {
        
parent::__construct($eventName);

        
$this->composer $composer;
        
$this->io $io;
        
$this->devMode $devMode;
        
$this->executeOperations $executeOperations;
        
$this->transaction $transaction;
    }

    
/**
     * @return Composer
     */
    
public function getComposer()
    {
        return 
$this->composer;
    }

    
/**
     * @return IOInterface
     */
    
public function getIO()
    {
        return 
$this->io;
    }

    
/**
     * @return bool
     */
    
public function isDevMode()
    {
        return 
$this->devMode;
    }

    
/**
     * @return bool
     */
    
public function isExecutingOperations()
    {
        return 
$this->executeOperations;
    }

    
/**
     * @return Transaction|null
     */
    
public function getTransaction()
    {
        return 
$this->transaction;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @author Konstantin Kudryashov <[email protected]>
 * @author Helmut Hummel <[email protected]>
 */
class BinaryInstaller
{
    
/** @var string */
    
protected $binDir;
    
/** @var string */
    
protected $binCompat;
    
/** @var IOInterface */
    
protected $io;
    
/** @var Filesystem */
    
protected $filesystem;
    
/** @var string|null */
    
private $vendorDir;

    
/**
     * @param IOInterface $io
     * @param string      $binDir
     * @param string      $binCompat
     * @param Filesystem  $filesystem
     * @param string|null $vendorDir
     */
    
public function __construct(IOInterface $io$binDir$binCompatFilesystem $filesystem null$vendorDir null)
    {
        
$this->binDir $binDir;
        
$this->binCompat $binCompat;
        
$this->io $io;
        
$this->filesystem $filesystem ?: new Filesystem();
        
$this->vendorDir $vendorDir;
    }

    
/**
     * @param string $installPath
     * @param bool $warnOnOverwrite
     *
     * @return void
     */
    
public function installBinaries(PackageInterface $package$installPath$warnOnOverwrite true)
    {
        
$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'$binPath0777 & ~umask());
        }
    }

    
/**
     * @return void
     */
    
public function removeBinaries(PackageInterface $package)
    {
        
$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);
        }
    }

    
/**
     * @param string $bin
     *
     * @return string
     */
    
public static function determineBinaryCaller($bin)
    {
        if (
'.bat' === substr($bin, -4) || '.exe' === substr($bin, -4)) {
            return 
'call';
        }

        
$handle fopen($bin'r');
        
$line fgets($handle);
        
fclose($handle);
        if (
Preg::isMatch('{^#!/(?:usr/bin/env )?(?:[^/]+/)*(.+)$}m'$line$match)) {
            return 
trim($match[1]);
        }

        return 
'php';
    }

    
/**
     * @return string[]
     */
    
protected function getBinaries(PackageInterface $package)
    {
        return 
$package->getBinaries();
    }

    
/**
     * @param string $binPath
     * @param string $link
     * @param string $bin
     *
     * @return void
     */
    
protected function installFullBinaries($binPath$link$binPackageInterface $package)
    {
        
// 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'$link0777 & ~umask());
        }
    }

    
/**
     * @param string $binPath
     * @param string $link
     *
     * @return void
     */
    
protected function installUnixyProxyBinaries($binPath$link)
    {
        
file_put_contents($link$this->generateUnixyProxyCode($binPath$link));
        
Silencer::call('chmod'$link0777 & ~umask());
    }

    
/**
     * @return void
     */
    
protected function initializeBinDir()
    {
        
$this->filesystem->ensureDirectoryExists($this->binDir);
        
$this->binDir realpath($this->binDir);
    }

    
/**
     * @param string $bin
     * @param string $link
     *
     * @return string
     */
    
protected function generateWindowsProxyCode($bin$link)
    {
        
$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_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_BIN_DIR=%~dp0\r\n".
            
"{$caller} \"%BIN_TARGET%\" %*\r\n";
    }

    
/**
     * @param string $bin
     * @param string $link
     *
     * @return string
     */
    
protected function generateUnixyProxyCode($bin$link)
    {
        
$binPath $this->filesystem->findShortestPath($link$bin);

        
$binDir ProcessExecutor::escape(dirname($binPath));
        
$binFile basename($binPath);

        
$binContents file_get_contents($bin);
        
// 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 = empty($match[1]) ? '#!/usr/bin/env php' trim($match[1]);
            
$binPathExported $this->filesystem->findShortestPathCode($link$binfalsetrue);
            
$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) {
                
$globalsCode .= '$GLOBALS[\'_composer_autoload_path\'] = ' $this->filesystem->findShortestPathCode($link$this->vendorDir '/autoload.php'falsetrue).";\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_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) {
        include("phpvfscomposer://" . 
$binPathExported);
        exit(0);
    }
}

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
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_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

"\${dir}/
$binFile" "\$@"

PROXY;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
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 string               $eventName
     * @param Composer             $composer
     * @param IOInterface          $io
     * @param bool                 $devMode
     * @param RepositoryInterface  $localRepo
     * @param OperationInterface[] $operations
     * @param OperationInterface   $operation
     */
    
public function __construct($eventNameComposer $composerIOInterface $io$devModeRepositoryInterface $localRepo, array $operationsOperationInterface $operation)
    {
        
parent::__construct($eventName);

        
$this->composer $composer;
        
$this->io $io;
        
$this->devMode $devMode;
        
$this->localRepo $localRepo;
        
$this->operations $operations;
        
$this->operation $operation;
    }

    
/**
     * @return Composer
     */
    
public function getComposer()
    {
        return 
$this->composer;
    }

    
/**
     * @return IOInterface
     */
    
public function getIO()
    {
        return 
$this->io;
    }

    
/**
     * @return bool
     */
    
public function isDevMode()
    {
        return 
$this->devMode;
    }

    
/**
     * @return RepositoryInterface
     */
    
public function getLocalRepo()
    {
        return 
$this->localRepo;
    }

    
/**
     * @return OperationInterface[]
     */
    
public function getOperations()
    {
        return 
$this->operations;
    }

    
/**
     * Returns the package instance.
     *
     * @return OperationInterface
     */
    
public function getOperation()
    {
        return 
$this->operation;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\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 <[email protected]>
 */
class ProjectInstaller implements InstallerInterface
{
    
/** @var string */
    
private $installPath;
    
/** @var DownloadManager */
    
private $downloadManager;
    
/** @var Filesystem */
    
private $filesystem;

    
/**
     * @param string $installPath
     */
    
public function __construct($installPathDownloadManager $dmFilesystem $fs)
    {
        
$this->installPath rtrim(strtr($installPath'\\''/'), '/').'/';
        
$this->downloadManager $dm;
        
$this->filesystem $fs;
    }

    
/**
     * Decides if the installer supports the given type
     *
     * @param  string $packageType
     * @return bool
     */
    
public function supports($packageType)
    {
        return 
true;
    }

    
/**
     * @inheritDoc
     */
    
public function isInstalled(InstalledRepositoryInterface $repoPackageInterface $package)
    {
        return 
false;
    }

    
/**
     * @inheritDoc
     */
    
public function download(PackageInterface $packagePackageInterface $prevPackage null)
    {
        
$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($installPath0777true);
        }

        return 
$this->downloadManager->download($package$installPath$prevPackage);
    }

    
/**
     * @inheritDoc
     */
    
public function prepare($typePackageInterface $packagePackageInterface $prevPackage null)
    {
        return 
$this->downloadManager->prepare($type$package$this->installPath$prevPackage);
    }

    
/**
     * @inheritDoc
     */
    
public function cleanup($typePackageInterface $packagePackageInterface $prevPackage null)
    {
        return 
$this->downloadManager->cleanup($type$package$this->installPath$prevPackage);
    }

    
/**
     * @inheritDoc
     */
    
public function install(InstalledRepositoryInterface $repoPackageInterface $package)
    {
        return 
$this->downloadManager->install($package$this->installPath);
    }

    
/**
     * @inheritDoc
     */
    
public function update(InstalledRepositoryInterface $repoPackageInterface $initialPackageInterface $target)
    {
        throw new 
\InvalidArgumentException("not supported");
    }

    
/**
     * @inheritDoc
     */
    
public function uninstall(InstalledRepositoryInterface $repoPackageInterface $package)
    {
        throw new 
\InvalidArgumentException("not supported");
    }

    
/**
     * Returns the installation path of a package
     *
     * @param  PackageInterface $package
     * @return string           path
     */
    
public function getInstallPath(PackageInterface $package)
    {
        return 
$this->installPath;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
interface BinaryPresenceInterface
{
    
/**
     * Make sure binaries are installed for a given package.
     *
     * @param PackageInterface $package package instance
     *
     * @return void
     */
    
public function ensureBinariesPresence(PackageInterface $package);
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class SuggestedPackagesReporter
{
    const 
MODE_LIST 1;
    const 
MODE_BY_PACKAGE 2;
    const 
MODE_BY_SUGGESTION 4;

    
/**
     * @var array<array{source: string, target: string, reason: string}>
     */
    
protected $suggestedPackages = array();

    
/**
     * @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()
    {
        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
     * @return SuggestedPackagesReporter
     */
    
public function addPackage($source$target$reason)
    {
        
$this->suggestedPackages[] = array(
            
'source' => $source,
            
'target' => $target,
            
'reason' => $reason,
        );

        return 
$this;
    }

    
/**
     * Add all suggestions from a package.
     *
     * @param  PackageInterface          $package
     * @return SuggestedPackagesReporter
     */
    
public function addSuggestionsFromPackage(PackageInterface $package)
    {
        
$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
     * @return void
     */
    
public function output($modeInstalledRepository $installedRepo nullPackageInterface $onlyDependentsOf null)
    {
        
$suggestedPackages $this->getFilteredSuggestions($installedRepo$onlyDependentsOf);

        
$suggesters = array();
        
$suggested = array();
        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
     * @return void
     */
    
public function outputMinimalistic(InstalledRepository $installedRepo nullPackageInterface $onlyDependentsOf null)
    {
        
$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 nullPackageInterface $onlyDependentsOf null)
    {
        
$suggestedPackages $this->getPackages();
        
$installedNames = array();
        if (
null !== $installedRepo && !empty($suggestedPackages)) {
            foreach (
$installedRepo->getPackages() as $package) {
                
$installedNames array_merge(
                    
$installedNames,
                    
$package->getNames()
                );
            }
        }

        
$sourceFilter = array();
        if (
$onlyDependentsOf) {
            
$sourceFilter array_map(function ($link) {
                return 
$link->getTarget();
            }, 
array_merge($onlyDependentsOf->getRequires(), $onlyDependentsOf->getDevRequires()));
            
$sourceFilter[] = $onlyDependentsOf->getName();
        }

        
$suggestions = array();
        foreach (
$suggestedPackages as $suggestion) {
            if (
in_array($suggestion['target'], $installedNames) || ($sourceFilter && !in_array($suggestion['source'], $sourceFilter))) {
                continue;
            }

            
$suggestions[] = $suggestion;
        }

        return 
$suggestions;
    }

    
/**
     * @param  string $string
     * @return string
     */
    
private function escapeOutput($string)
    {
        return 
OutputFormatter::escape(
            
$this->removeControlCharacters($string)
        );
    }

    
/**
     * @param  string $string
     * @return string
     */
    
private function removeControlCharacters($string)
    {
        return 
Preg::replace(
            
'/[[:cntrl:]]/',
            
'',
            
str_replace("\n"' '$string)
        );
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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;

/**
 * Package operation manager.
 *
 * @author Konstantin Kudryashov <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 * @author Nils Adermann <[email protected]>
 */
class InstallationManager
{
    
/** @var array<InstallerInterface> */
    
private $installers = array();
    
/** @var array<string, InstallerInterface> */
    
private $cache = array();
    
/** @var array<string, array<PackageInterface>> */
    
private $notifiablePackages = array();
    
/** @var Loop */
    
private $loop;
    
/** @var IOInterface */
    
private $io;
    
/** @var ?EventDispatcher */
    
private $eventDispatcher;
    
/** @var bool */
    
private $outputProgress;

    public function 
__construct(Loop $loopIOInterface $ioEventDispatcher $eventDispatcher null)
    {
        
$this->loop $loop;
        
$this->io $io;
        
$this->eventDispatcher $eventDispatcher;
    }

    
/**
     * @return void
     */
    
public function reset()
    {
        
$this->notifiablePackages = array();
        
FileDownloader::$downloadMetadata = array();
    }

    
/**
     * Adds installer
     *
     * @param InstallerInterface $installer installer instance
     *
     * @return void
     */
    
public function addInstaller(InstallerInterface $installer)
    {
        
array_unshift($this->installers$installer);
        
$this->cache = array();
    }

    
/**
     * Removes installer
     *
     * @param InstallerInterface $installer installer instance
     *
     * @return void
     */
    
public function removeInstaller(InstallerInterface $installer)
    {
        if (
false !== ($key array_search($installer$this->installerstrue))) {
            
array_splice($this->installers$key1);
            
$this->cache = array();
        }
    }

    
/**
     * Disables plugins.
     *
     * We prevent any plugins from being instantiated by simply
     * deactivating the installer for them. This ensure that no third-party
     * code is ever executed.
     *
     * @return void
     */
    
public function disablePlugins()
    {
        foreach (
$this->installers as $i => $installer) {
            if (!
$installer instanceof PluginInstaller) {
                continue;
            }

            unset(
$this->installers[$i]);
        }
    }

    
/**
     * Returns installer for a specific package type.
     *
     * @param string $type package type
     *
     * @throws \InvalidArgumentException if installer for provided type is not registered
     * @return InstallerInterface
     */
    
public function getInstaller($type)
    {
        
$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
     *
     * @return bool
     */
    
public function isPackageInstalled(InstalledRepositoryInterface $repoPackageInterface $package)
    {
        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
     *
     * @return void
     */
    
public function ensureBinariesPresence(PackageInterface $package)
    {
        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
     *
     * @return void
     */
    
public function execute(InstalledRepositoryInterface $repo, array $operations$devMode true$runScripts true)
    {
        
/** @var PromiseInterface[] */
        
$cleanupPromises = array();

        
$loop $this->loop;
        
$io $this->io;
        
$runCleanup = function () use (&$cleanupPromises$loop) {
            
$promises = array();

            
$loop->abortJobs();

            foreach (
$cleanupPromises as $cleanup) {
                
$promises[] = new \React\Promise\Promise(function ($resolve$reject) use ($cleanup) {
                    
$promise $cleanup();
                    if (!
$promise instanceof PromiseInterface) {
                        
$resolve();
                    } else {
                        
$promise->then(function () use ($resolve) {
                            
$resolve();
                        });
                    }
                });
            }

            if (!empty(
$promises)) {
                
$loop->wait($promises);
            }
        };

        
$handleInterruptsUnix function_exists('pcntl_async_signals') && function_exists('pcntl_signal');
        
$handleInterruptsWindows function_exists('sapi_windows_set_ctrl_handler') && PHP_SAPI === 'cli';
        
$prevHandler null;
        
$windowsHandler null;
        if (
$handleInterruptsUnix) {
            
pcntl_async_signals(true);
            
$prevHandler pcntl_signal_get_handler(SIGINT);
            
pcntl_signal(SIGINT, function ($sig) use ($runCleanup$prevHandler$io) {
                
$io->writeError('Received SIGINT, aborting'trueIOInterface::DEBUG);
                
$runCleanup();

                if (!
in_array($prevHandler, array(SIG_DFLSIG_IGN), true)) {
                    
call_user_func($prevHandler$sig);
                }

                exit(
130);
            });
        }
        if (
$handleInterruptsWindows) {
            
$windowsHandler = function ($event) use ($runCleanup$io) {
                if (
$event !== PHP_WINDOWS_EVENT_CTRL_C) {
                    return;
                }
                
$io->writeError('Received CTRL+C, aborting'trueIOInterface::DEBUG);
                
$runCleanup();

                exit(
130);
            };
            
sapi_windows_set_ctrl_handler($windowsHandler);
        }

        try {
            
// execute operations in batches to make sure download-modifying-plugins are installed
            // before the other packages get downloaded
            
$batches = array();
            
$batch = array();
            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()) && isset($extra['plugin-modifies-downloads']) && $extra['plugin-modifies-downloads'] === true) {
                        if (
$batch) {
                            
$batches[] = $batch;
                        }
                        
$batches[] = array($index => $operation);
                        
$batch = array();

                        continue;
                    }
                }
                
$batch[$index] = $operation;
            }

            if (
$batch) {
                
$batches[] = $batch;
            }

            foreach (
$batches as $batch) {
                
$this->downloadAndExecuteBatch($repo$batch$cleanupPromises$devMode$runScripts$operations);
            }
        } catch (
\Exception $e) {
            
$runCleanup();

            if (
$handleInterruptsUnix) {
                
pcntl_signal(SIGINT$prevHandler);
            }
            if (
$handleInterruptsWindows) {
                
sapi_windows_set_ctrl_handler($windowsHandlerfalse);
            }

            throw 
$e;
        }

        if (
$handleInterruptsUnix) {
            
pcntl_signal(SIGINT$prevHandler);
        }
        if (
$handleInterruptsWindows) {
            
sapi_windows_set_ctrl_handler($windowsHandlerfalse);
        }

        
// 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 PromiseInterface[] $cleanupPromises
     * @param bool $devMode
     * @param bool $runScripts
     * @param OperationInterface[] $allOperations Complete list of operations to be executed in the install job, used for event listeners
     *
     * @return void
     */
    
private function downloadAndExecuteBatch(InstalledRepositoryInterface $repo, array $operations, array &$cleanupPromises$devMode$runScripts, array $allOperations)
    {
        
$promises = array();

        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, array('update''install''uninstall'))) {
                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] = function () use ($opType$installer$package$initialPackage) {
                
// avoid calling cleanup if the download was not even initialized for a package
                // as without installation source configured nothing will work
                
if (!$package->getInstallationSource()) {
                    return;
                }

                return 
$installer->cleanup($opType$package$initialPackage);
            };

            if (
$opType !== 'uninstall') {
                
$promise $installer->download($package$initialPackage);
                if (
$promise) {
                    
$promises[] = $promise;
                }
            }
        }

        
// execute all downloads first
        
if (count($promises)) {
            
$this->waitOnPromises($promises);
        }

        
// 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 = array();
        
$batch = array();
        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 (
$batch) {
                        
$batches[] = $batch;
                    }
                    
$batches[] = array($index => $operation);
                    
$batch = array();

                    continue;
                }
            }
            
$batch[$index] = $operation;
        }

        if (
$batch) {
            
$batches[] = $batch;
        }

        foreach (
$batches as $batch) {
            
$this->executeBatch($repo$batch$cleanupPromises$devMode$runScripts$allOperations);
        }
    }

    
/**
     * @param OperationInterface[] $operations    List of operations to execute in this batch
     * @param PromiseInterface[] $cleanupPromises
     * @param bool $devMode
     * @param bool $runScripts
     * @param OperationInterface[] $allOperations Complete list of operations to be executed in the install job, used for event listeners
     *
     * @return void
     */
    
private function executeBatch(InstalledRepositoryInterface $repo, array $operations, array $cleanupPromises$devMode$runScripts, array $allOperations)
    {
        
$promises = array();
        
$postExecCallbacks = array();

        foreach (
$operations as $index => $operation) {
            
$opType $operation->getOperationType();

            
// ignoring alias ops as they don't need to execute anything
            
if (!in_array($opType, array('update''install''uninstall'))) {
                
// 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());

            
$event 'Composer\Installer\PackageEvents::PRE_PACKAGE_'.strtoupper($opType);
            if (
defined($event) && $runScripts && $this->eventDispatcher) {
                
$this->eventDispatcher->dispatchPackageEvent(constant($event), $devMode$repo$allOperations$operation);
            }

            
$dispatcher $this->eventDispatcher;
            
$installManager $this;
            
$io $this->io;

            
$promise $installer->prepare($opType$package$initialPackage);
            if (!
$promise instanceof PromiseInterface) {
                
$promise \React\Promise\resolve();
            }

            
$promise $promise->then(function () use ($opType$installManager$repo$operation) {
                return 
$installManager->$opType($repo$operation);
            })->
then($cleanupPromises[$index])
            ->
then(function () use ($installManager$devMode$repo) {
                
$repo->write($devMode$installManager);
            }, function (
$e) use ($opType$package$io) {
                
$io->writeError('    <error>' ucfirst($opType) .' of '.$package->getPrettyName().' failed</error>');

                throw 
$e;
            });

            
$postExecCallbacks[] = function () use ($opType$runScripts$dispatcher$devMode$repo$allOperations$operation) {
                
$event 'Composer\Installer\PackageEvents::POST_PACKAGE_'.strtoupper($opType);
                if (
defined($event) && $runScripts && $dispatcher) {
                    
$dispatcher->dispatchPackageEvent(constant($event), $devMode$repo$allOperations$operation);
                }
            };

            
$promises[] = $promise;
        }

        
// execute all prepare => installs/updates/removes => cleanup steps
        
if (count($promises)) {
            
$this->waitOnPromises($promises);
        }

        
Platform::workaroundFilesystemIssues();

        foreach (
$postExecCallbacks as $cb) {
            
$cb();
        }
    }

    
/**
     * @param PromiseInterface[] $promises
     *
     * @return void
     */
    
private function waitOnPromises(array $promises)
    {
        
$progress null;
        if (
            
$this->outputProgress
            
&& $this->io instanceof ConsoleIO
            
&& !Platform::getEnv('CI')
            && !
$this->io->isDebug()
            && 
count($promises) > 1
        
) {
            
$progress $this->io->getProgressBar();
        }
        
$this->loop->wait($promises$progress);
        if (
$progress) {
            
$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 install operation.
     *
     * @param InstalledRepositoryInterface $repo      repository in which to check
     * @param InstallOperation             $operation operation instance
     *
     * @return PromiseInterface|null
     */
    
public function install(InstalledRepositoryInterface $repoInstallOperation $operation)
    {
        
$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
     *
     * @return PromiseInterface|null
     */
    
public function update(InstalledRepositoryInterface $repoUpdateOperation $operation)
    {
        
$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();
            }

            
$installer $this->getInstaller($targetType);
            
$promise $promise->then(function () use ($installer$repo$target) {
                return 
$installer->install($repo$target);
            });
        }

        return 
$promise;
    }

    
/**
     * Uninstalls package.
     *
     * @param InstalledRepositoryInterface $repo      repository in which to check
     * @param UninstallOperation           $operation operation instance
     *
     * @return PromiseInterface|null
     */
    
public function uninstall(InstalledRepositoryInterface $repoUninstallOperation $operation)
    {
        
$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
     *
     * @return void
     */
    
public function markAliasInstalled(InstalledRepositoryInterface $repoMarkAliasInstalledOperation $operation)
    {
        
$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
     *
     * @return void
     */
    
public function markAliasUninstalled(InstalledRepositoryInterface $repoMarkAliasUninstalledOperation $operation)
    {
        
$package $operation->getPackage();

        
$repo->removePackage($package);
    }

    
/**
     * Returns the installation path of a package
     *
     * @param  PackageInterface $package
     * @return string           path
     */
    
public function getInstallPath(PackageInterface $package)
    {
        
$installer $this->getInstaller($package->getType());

        return 
$installer->getInstallPath($package);
    }

    
/**
     * @param bool $outputProgress
     *
     * @return void
     */
    
public function setOutputProgress($outputProgress)
    {
        
$this->outputProgress $outputProgress;
    }

    
/**
     * @return void
     */
    
public function notifyInstalls(IOInterface $io)
    {
        
$promises = array();

        try {
            foreach (
$this->notifiablePackages as $repoUrl => $packages) {
                
// non-batch API, deprecated
                
if (strpos($repoUrl'%package%')) {
                    foreach (
$packages as $package) {
                        
$url str_replace('%package%'$package->getPrettyName(), $repoUrl);

                        
$params = array(
                            
'version' => $package->getPrettyVersion(),
                            
'version_normalized' => $package->getVersion(),
                        );
                        
$opts = array(
                            
'retry-auth-failure' => false,
                            
'http' => array(
                                
'method' => 'POST',
                                
'header' => array('Content-type: application/x-www-form-urlencoded'),
                                
'content' => http_build_query($params'''&'),
                                
'timeout' => 3,
                            ),
                        );

                        
$promises[] = $this->loop->getHttpDownloader()->add($url$opts);
                    }

                    continue;
                }

                
$postData = array('downloads' => array());
                foreach (
$packages as $package) {
                    
$packageNotification = array(
                        
'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 = array(
                    
'retry-auth-failure' => false,
                    
'http' => array(
                        
'method' => 'POST',
                        
'header' => array('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();
    }

    
/**
     * @return void
     */
    
private function markForNotification(PackageInterface $package)
    {
        if (
$package->getNotificationUrl()) {
            
$this->notifiablePackages[$package->getNotificationUrl()][$package->getName()] = $package;
        }
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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
     */
    
const PRE_OPERATIONS_EXEC 'pre-operations-exec';
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
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
     */
    
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
     */
    
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
     */
    
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
     */
    
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
     */
    
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
     */
    
const POST_PACKAGE_UNINSTALL 'post-package-uninstall';
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\SelfUpdate;

use 
Composer\Util\HttpDownloader;
use 
Composer\Config;

/**
 * @author Jordi Boggiano <[email protected]>
 */
class Versions
{
    
/** @var string[] */
    
public static $channels = array('stable''preview''snapshot''1''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}>> */
    
private $versionsData;

    public function 
__construct(Config $configHttpDownloader $httpDownloader)
    {
        
$this->httpDownloader $httpDownloader;
        
$this->config $config;
    }

    
/**
     * @return string
     */
    
public function getChannel()
    {
        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, array('stable''preview''snapshot'), true)) {
                return 
$this->channel $channel;
            }
        }

        return 
$this->channel 'stable';
    }

    
/**
     * @param string $channel
     *
     * @return void
     */
    
public function setChannel($channel)
    {
        if (!
in_array($channelself::$channelstrue)) {
            throw new 
\InvalidArgumentException('Invalid channel '.$channel.', must be one of: ' implode(', 'self::$channels));
        }

        
$channelFile $this->config->get('home').'/update-channel';
        
$this->channel $channel;
        
file_put_contents($channelFile, (is_numeric($channel) ? 'stable' $channel).PHP_EOL);
    }

    
/**
     * @param string|null $channel
     *
     * @return array{path: string, version: string, min-php: int}
     */
    
public function getLatest($channel null)
    {
        
$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}>>
     */
    
private function getVersionsData()
    {
        if (!
$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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class Keys
{
    
/**
     * @param string $path
     *
     * @return string
     */
    
public static function fingerprint($path)
    {
        
$hash strtoupper(hash('sha256'Preg::replace('{\s}'''file_get_contents($path))));

        return 
implode(' ', array(
            
substr($hash08),
            
substr($hash88),
            
substr($hash168),
            
substr($hash248),
            
''// Extra space
            
substr($hash328),
            
substr($hash408),
            
substr($hash488),
            
substr($hash568),
        ));
    }
}
<?
php

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 string
     */
    
private $ignoreRegex;

    
/**
     * @var string
     */
    
private $ignoreUpperBoundRegex;

    
/**
     * @param string[] $reqList
     */
    
public function __construct(array $reqList)
    {
        
$ignoreAll $ignoreUpperBound = array();
        foreach (
$reqList as $req) {
            if (
substr($req, -1) === '+') {
                
$ignoreUpperBound[] = substr($req0, -1);
            } else {
                
$ignoreAll[] = $req;
            }
        }
        
$this->ignoreRegex BasePackage::packageNamesToRegexp($ignoreAll);
        
$this->ignoreUpperBoundRegex BasePackage::packageNamesToRegexp($ignoreUpperBound);
    }

    
/**
     * @param string $req
     * @return bool
     */
    
public function isIgnored($req)
    {
        if (!
PlatformRepository::isPlatformPackage($req)) {
            return 
false;
        }

        return 
Preg::isMatch($this->ignoreRegex$req);
    }

    
/**
     * @param string $req
     * @return ConstraintInterface
     */
    
public function filterConstraint($reqConstraintInterface $constraint)
    {
        if (!
PlatformRepository::isPlatformPackage($req)) {
            return 
$constraint;
        }

        if (!
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(array($constraint, new Constraint('>='$last->getEnd()->getVersion())), false);
        }

        return 
$constraint;
    }
}
<?
php

namespace Composer\Filter\PlatformRequirementFilter;

final class 
PlatformRequirementFilterFactory
{
    
/**
     * @param mixed $boolOrList
     *
     * @return PlatformRequirementFilterInterface
     */
    
public static function fromBoolOrList($boolOrList)
    {
        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)
            )
        );
    }

    
/**
     * @return PlatformRequirementFilterInterface
     */
    
public static function ignoreAll()
    {
        return new 
IgnoreAllPlatformRequirementFilter();
    }

    
/**
     * @return PlatformRequirementFilterInterface
     */
    
public static function ignoreNothing()
    {
        return new 
IgnoreNothingPlatformRequirementFilter();
    }
}
<?
php

namespace Composer\Filter\PlatformRequirementFilter;

interface 
PlatformRequirementFilterInterface
{
    
/**
     * @param string $req
     * @return bool
     */
    
public function isIgnored($req);
}
<?
php

namespace Composer\Filter\PlatformRequirementFilter;

final class 
IgnoreNothingPlatformRequirementFilter implements PlatformRequirementFilterInterface
{
    
/**
     * @param string $req
     * @return false
     */
    
public function isIgnored($req)
    {
        return 
false;
    }
}
<?
php

namespace Composer\Filter\PlatformRequirementFilter;

use 
Composer\Repository\PlatformRepository;

final class 
IgnoreAllPlatformRequirementFilter implements PlatformRequirementFilterInterface
{
    
/**
     * @param string $req
     * @return bool
     */
    
public function isIgnored($req)
    {
        return 
PlatformRepository::isPlatformPackage($req);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\EventDispatcher;

/**
 * @author Jordi Boggiano <[email protected]>
 */
class ScriptExecutionException extends \RuntimeException
{
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
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($name, array $args = array(), array $flags = array())
    {
        
$this->name $name;
        
$this->args $args;
        
$this->flags $flags;
    }

    
/**
     * Returns the event's name.
     *
     * @return string The event name
     */
    
public function getName()
    {
        return 
$this->name;
    }

    
/**
     * Returns the event's arguments.
     *
     * @return string[] The event arguments
     */
    
public function getArguments()
    {
        return 
$this->args;
    }

    
/**
     * Returns the event's flags.
     *
     * @return mixed[] The event flags
     */
    
public function getFlags()
    {
        return 
$this->flags;
    }

    
/**
     * Checks if stopPropagation has been called
     *
     * @return bool Whether propagation has been stopped
     */
    
public function isPropagationStopped()
    {
        return 
$this->propagationStopped;
    }

    
/**
     * Prevents the event from being passed to further listeners
     *
     * @return void
     */
    
public function stopPropagation()
    {
        
$this->propagationStopped true;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @author Jonathan Wage <[email protected]>
 * @author Roman Borschel <[email protected]>
 * @author Bernhard Schussek <[email protected]>
 */
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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\IOInterface;
use 
Composer\Composer;
use 
Composer\Pcre\Preg;
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\Process\PhpExecutableFinder;
use 
Symfony\Component\Process\ExecutableFinder;

/**
 * The Event Dispatcher.
 *
 * Example in command:
 *     $dispatcher = new EventDispatcher($this->getComposer(), $this->getApplication()->getIO());
 *     // ...
 *     $dispatcher->dispatch(ScriptEvents::POST_INSTALL_CMD);
 *     // ...
 *
 * @author François Pluchino <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 * @author Nils Adermann <[email protected]>
 */
class EventDispatcher
{
    
/** @var Composer */
    
protected $composer;
    
/** @var IOInterface */
    
protected $io;
    
/** @var ?ClassLoader */
    
protected $loader;
    
/** @var ProcessExecutor */
    
protected $process;
    
/** @var array<string, array<int, array<callable|string>>> */
    
protected $listeners = array();
    
/** @var bool */
    
protected $runScripts true;
    
/** @var list<string> */
    
private $eventStack;

    
/**
     * Constructor.
     *
     * @param Composer        $composer The composer instance
     * @param IOInterface     $io       The IOInterface instance
     * @param ProcessExecutor $process
     */
    
public function __construct(Composer $composerIOInterface $ioProcessExecutor $process null)
    {
        
$this->composer $composer;
        
$this->io $io;
        
$this->process $process ?: new ProcessExecutor($io);
        
$this->eventStack = array();
    }

    
/**
     * Set whether script handlers are active or not
     *
     * @param bool $runScripts
     * @return $this
     */
    
public function setRunScripts($runScripts true)
    {
        
$this->runScripts = (bool) $runScripts;

        return 
$this;
    }

    
/**
     * Dispatch an event
     *
     * @param  string $eventName An event name
     * @param  Event  $event
     * @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($eventNameEvent $event null)
    {
        if (
null === $event) {
            
$event = new Event($eventName);
        }

        return 
$this->doDispatch($event);
    }

    
/**
     * Dispatch a script event.
     *
     * @param  string               $eventName      The constant in ScriptEvents
     * @param  bool                 $devMode
     * @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($eventName$devMode false$additionalArgs = array(), $flags = array())
    {
        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($eventName$devModeRepositoryInterface $localRepo, array $operationsOperationInterface $operation)
    {
        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($eventName$devMode$executeOperationsTransaction $transaction)
    {
        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();
            }
            
$this->io->writeError('Dispatching <info>'.$event->getName().'</info>'.($details ' ('.$details.')' '').' event');
        }

        
$listeners $this->getListeners($event);

        
$this->pushEvent($event);

        try {
            
$returnMax 0;
            foreach (
$listeners as $callable) {
                
$return 0;
                
$this->ensureBinDirIsInPath();

                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'$event->getName(), (is_object($callable[0]) ? get_class($callable[0]) : $callable[0]).'->'.$callable[1]), trueIOInterface::VERBOSE);
                    }
                    
$return false === call_user_func($callable$event) ? 0;
                } elseif (
$this->isComposerScript($callable)) {
                    
$this->io->writeError(sprintf('> %s: %s'$event->getName(), $callable), trueIOInterface::VERBOSE);

                    
$script explode(' 'substr($callable1));
                    
$scriptName $script[0];
                    unset(
$script[0]);

                    
$args array_merge($script$event->getArguments());
                    
$flags $event->getFlags();
                    if (
strpos($callable'@composer ') === 0) {
                        
$exec $this->getPhpExecCommand() . ' ' ProcessExecutor::escape(Platform::getEnv('COMPOSER_BINARY')) . ' ' implode(' '$args);
                        if (
!== ($exitCode $this->executeTty($exec))) {
                            
$this->io->writeError(sprintf('<error>Script %s handling the %s event returned with error code '.$exitCode.'</error>'$callable$event->getName()), trueIOInterface::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), trueIOInterface::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()), trueIOInterface::QUIET);
                            throw 
$e;
                        }
                    }
                } elseif (
$this->isPhpScript($callable)) {
                    
$className substr($callable0strpos($callable'::'));
                    
$methodName substr($callablestrpos($callable'::') + 2);

                    if (!
class_exists($className)) {
                        
$this->io->writeError('<warning>Class '.$className.' is not autoloadable, can not call '.$event->getName().' script</warning>'trueIOInterface::QUIET);
                        continue;
                    }
                    if (!
is_callable($callable)) {
                        
$this->io->writeError('<warning>Method '.$callable.' is not callable, can not call '.$event->getName().' script</warning>'trueIOInterface::QUIET);
                        continue;
                    }

                    try {
                        
$return false === $this->executeEventPhpScript($className$methodName$event) ? 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>'trueIOInterface::QUIET);
                        throw 
$e;
                    }
                } else {
                    
$args implode(' 'array_map(array('Composer\Util\ProcessExecutor''escape'), $event->getArguments()));
                    
$exec $callable . ($args === '' '' ' '.$args);
                    if (
$this->io->isVerbose()) {
                        
$this->io->writeError(sprintf('> %s: %s'$event->getName(), $exec));
                    } elseif (
$event->getName() !== '__exec_command') {
                        
// do not output the command being run when using `composer exec` as it is fairly obvious the user is running it
                        
$this->io->writeError(sprintf('> %s'$exec));
                    }

                    
$possibleLocalBinaries $this->composer->getPackage()->getBinaries();
                    if (
$possibleLocalBinaries) {
                        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($exec8));
                        } else {
                            list(
$var$value) = explode('='substr($exec8), 2);
                            
Platform::putEnv($var$value);
                        }

                        continue;
                    }
                    if (
strpos($exec'@php ') === 0) {
                        
$pathAndArgs substr($exec5);
                        if (
Platform::isWindows()) {
                            
$pathAndArgs Preg::replaceCallback('{^\S+}', 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::isMatch('{^[^\'"\s/\\\\]+}'$pathAndArgs$match);
                        if (
$matched && !file_exists($match[0])) {
                            
$finder = new ExecutableFinder;
                            if (
$pathToExec $finder->find($match[0])) {
                                
$pathAndArgs $pathToExec substr($pathAndArgsstrlen($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+}', 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($exec8);
                    }

                    if (
!== ($exitCode $this->executeTty($exec))) {
                        
$this->io->writeError(sprintf('<error>Script %s handling the %s event returned with error code '.$exitCode.'</error>'$callable$event->getName()), trueIOInterface::QUIET);

                        throw new 
ScriptExecutionException('Error Output: '.$this->process->getErrorOutput(), $exitCode);
                    }
                }

                
$returnMax max($returnMax$return);

                if (
$event->isPropagationStopped()) {
                    break;
                }
            }
        } catch (
\Exception $e) { // TODO Composer 2.2 turn all this into a finally
            
$this->popEvent();

            throw 
$e;
        } catch (
\Throwable $e) {
            
$this->popEvent();

            throw 
$e;
        }

        
$this->popEvent();

        return 
$returnMax;
    }

    
/**
     * @param string $exec
     *
     * @return int
     */
    
protected function executeTty($exec)
    {
        if (
$this->io->isInteractive()) {
            return 
$this->process->executeTty($exec);
        }

        return 
$this->process->execute($exec);
    }

    
/**
     * @return string
     */
    
protected function getPhpExecCommand()
    {
        
$finder = new PhpExecutableFinder();
        
$phpPath $finder->find(false);
        if (!
$phpPath) {
            throw new 
\RuntimeException('Failed to locate PHP binary to execute '.$phpPath);
        }
        
$phpArgs $finder->findArguments();
        
$phpArgs $phpArgs ' ' 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 string $className
     * @param string $methodName
     * @param Event  $event      Event invoking the PHP callable
     *
     * @return mixed
     */
    
protected function executeEventPhpScript($className$methodNameEvent $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 $listener  A callable expecting an event argument
     * @param int      $priority  A higher value represents a higher priority
     *
     * @return void
     */
    
public function addListener($eventName$listener$priority 0)
    {
        
$this->listeners[$eventName][$priority][] = $listener;
    }

    
/**
     * @param callable|object $listener A callable or an object instance for which all listeners should be removed
     *
     * @return void
     */
    
public function removeListener($listener)
    {
        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
     *
     * @param EventSubscriberInterface $subscriber
     *
     * @return void
     */
    
public function addSubscriber(EventSubscriberInterface $subscriber)
    {
        foreach (
$subscriber->getSubscribedEvents() as $eventName => $params) {
            if (
is_string($params)) {
                
$this->addListener($eventName, array($subscriber$params));
            } elseif (
is_string($params[0])) {
                
$this->addListener($eventName, array($subscriber$params[0]), isset($params[1]) ? $params[1] : 0);
            } else {
                foreach (
$params as $listener) {
                    
$this->addListener($eventName, array($subscriber$listener[0]), isset($listener[1]) ? $listener[1] : 0);
                }
            }
        }
    }

    
/**
     * Retrieves all listeners for a given event
     *
     * @param  Event $event
     * @return array<callable|string> All listeners: callables and scripts
     */
    
protected function getListeners(Event $event)
    {
        
$scriptListeners $this->runScripts $this->getScriptListeners($event) : array();

        if (!isset(
$this->listeners[$event->getName()][0])) {
            
$this->listeners[$event->getName()][0] = array();
        }
        
krsort($this->listeners[$event->getName()]);

        
$listeners $this->listeners;
        
$listeners[$event->getName()][0] = array_merge($listeners[$event->getName()][0], $scriptListeners);

        return 
call_user_func_array('array_merge'$listeners[$event->getName()]);
    }

    
/**
     * Checks if an event has listeners registered
     *
     * @param  Event $event
     * @return bool
     */
    
public function hasEventListeners(Event $event)
    {
        
$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)
    {
        
$package $this->composer->getPackage();
        
$scripts $package->getScripts();

        if (empty(
$scripts[$event->getName()])) {
            return array();
        }

        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
     *
     * @param  string $callable
     * @return bool
     */
    
protected function isPhpScript($callable)
    {
        return 
false === strpos($callable' ') && false !== strpos($callable'::');
    }

    
/**
     * Checks if string given references a composer run-script
     *
     * @param  string $callable
     * @return bool
     */
    
protected function isComposerScript($callable)
    {
        return 
strpos($callable'@') === && strpos($callable'@php ') !== && strpos($callable'@putenv ') !== 0;
    }

    
/**
     * Push an event to the stack of active event
     *
     * @param  Event             $event
     * @throws \RuntimeException
     * @return int
     */
    
protected function pushEvent(Event $event)
    {
        
$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
     *
     * @return string|null
     */
    
protected function popEvent()
    {
        return 
array_pop($this->eventStack);
    }

    
/**
     * @return void
     */
    
private function ensureBinDirIsInPath()
    {
        
$pathEnv 'PATH';
        if (
false === Platform::getEnv('PATH') && false !== Platform::getEnv('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 Platform::getEnv($pathEnv);
            if (!
Preg::isMatch('{(^|'.PATH_SEPARATOR.')'.preg_quote($binDir).'($|'.PATH_SEPARATOR.')}'$pathValue)) {
                
Platform::putEnv($pathEnv$binDir.PATH_SEPARATOR.$pathValue);
            }
        }
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class RuleWatchGraph
{
    
/** @var array<int, RuleWatchChain> */
    
protected $watchChains = array();

    
/**
     * 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
     * @return void
     */
    
public function insert(RuleWatchNode $node)
    {
        if (
$node->getRule()->isAssertion()) {
            return;
        }

        if (!
$node->getRule() instanceof MultiConflictRule) {
            foreach (array(
$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($decidedLiteral$levelDecisions $decisions)
    {
        
// 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, function ($ruleLiteral) use ($literal$otherWatch$decisions) {
                        return 
$literal !== $ruleLiteral &&
                            
$otherWatch !== $ruleLiteral &&
                            !
$decisions->conflict($ruleLiteral);
                    });

                    if (
$alternativeLiterals) {
                        
reset($alternativeLiterals);
                        
$this->moveWatch($literalcurrent($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
     * @return void
     */
    
protected function moveWatch($fromLiteral$toLiteralRuleWatchNode $node)
    {
        if (!isset(
$this->watchChains[$toLiteral])) {
            
$this->watchChains[$toLiteral] = new RuleWatchChain;
        }

        
$node->moveWatch($fromLiteral$toLiteral);
        
$this->watchChains[$fromLiteral]->remove();
        
$this->watchChains[$toLiteral]->unshift($node);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class PoolOptimizer
{
    
/**
     * @var PolicyInterface
     */
    
private $policy;

    
/**
     * @var array<int, true>
     */
    
private $irremovablePackages = array();

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

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

    
/**
     * @var array<int, true>
     */
    
private $packagesToRemove = array();

    
/**
     * @var array<int, BasePackage[]>
     */
    
private $aliasesPerPackage = array();

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

    public function 
__construct(PolicyInterface $policy)
    {
        
$this->policy $policy;
    }

    
/**
     * @return Pool
     */
    
public function optimize(Request $requestPool $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 = array();
        
$this->requireConstraintsPerPackage = array();
        
$this->conflictConstraintsPerPackage = array();
        
$this->packagesToRemove = array();
        
$this->aliasesPerPackage = array();
        
$this->removedVersionsByPackage = array();

        return 
$optimizedPool;
    }

    
/**
     * @return void
     */
    
private function prepare(Request $requestPool $pool)
    {
        
$irremovablePackageConstraintGroups = array();

        
// 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) {
            
$constraint Intervals::compactConstraint($constraint);
            
$this->requireConstraintsPerPackage[$require][(string) $constraint] = $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) {
                
$constraint Intervals::compactConstraint($link->getConstraint());
                
$this->requireConstraintsPerPackage[$link->getTarget()][(string) $constraint] = $constraint;
            }
            
// Extract package conflicts
            
foreach ($package->getConflicts() as $link) {
                
$constraint Intervals::compactConstraint($link->getConstraint());
                
$this->conflictConstraintsPerPackage[$link->getTarget()][(string) $constraint] = $constraint;
            }

            
// 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 = array();
        foreach (
$irremovablePackageConstraintGroups as $packageName => $constraints) {
            
$irremovablePackageConstraints[$packageName] = === \count($constraints) ? $constraints[0] : new MultiConstraint($constraintsfalse);
        }
        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);
            }
        }
    }

    
/**
     * @return void
     */
    
private function markPackageIrremovable(BasePackage $package)
    {
        
$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)
    {
        
$packages = array();
        
$removedVersions = array();
        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;
    }

    
/**
     * @return void
     */
    
private function optimizeByIdenticalDependencies(Request $requestPool $pool)
    {
        
$identicalDefinitionsPerPackage = array();
        
$packageIdenticalDefinitionLookup = array();

        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 = array();

                    if (
CompilingMatcher::match($requireConstraintConstraint::OP_EQ$package->getVersion())) {
                        
$groupHashParts[] = 'require:' . (string) $requireConstraint;
                    }

                    if (
$package->getReplaces()) {
                        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($conflictConstraintConstraint::OP_EQ$package->getVersion())) {
                                
$groupHashParts[] = 'conflict:' . (string) $conflictConstraint;
                            }
                        }
                    }

                    if (!
$groupHashParts) {
                        continue;
                    }

                    
$groupHash implode(''$groupHashParts);
                    
$identicalDefinitionsPerPackage[$packageName][$groupHash][$dependencyHash][] = $package;
                    
$packageIdenticalDefinitionLookup[$package->id][$packageName] = array('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 (=== \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 = array();

                    foreach (
$packages as $package) {
                        
$literals[] = $package->id;
                    }

                    foreach (
$this->policy->selectPreferredPackages($pool$literals) as $preferredLiteral) {
                        
$this->keepPackage($pool->literalToPackage($preferredLiteral), $identicalDefinitionsPerPackage$packageIdenticalDefinitionLookup);
                    }
                }
            }
        }
    }

    
/**
     * @return string
     */
    
private function calculateDependencyHash(BasePackage $package)
    {
        
$hash '';

        
$hashRelevantLinks = array(
            
'requires' => $package->getRequires(),
            
'conflicts' => $package->getConflicts(),
            
'replaces' => $package->getReplaces(),
            
'provides' => $package->getProvides()
        );

        foreach (
$hashRelevantLinks as $key => $links) {
            if (
=== \count($links)) {
                continue;
            }

            
// start new hash section
            
$hash .= $key ':';

            
$subhash = array();

            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;
    }

    
/**
     * @param int $id
     * @return void
     */
    
private function markPackageForRemoval($id)
    {
        
// 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
     * @return void
     */
    
private function keepPackage(BasePackage $package$identicalDefinitionsPerPackage$packageIdenticalDefinitionLookup)
    {
        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
     *
     * @return void
     */
    
private function optimizeImpossiblePackagesAway(Request $requestPool $pool)
    {
        if (
count($request->getLockedPackages()) === 0) {
            return;
        }

        
$packageIndex = array();

        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($linkConstraintConstraint::OP_EQ$requiredPkg->getVersion())) {
                        
$this->markPackageForRemoval($id);
                        unset(
$packageIndex[$require][$id]);
                    }
                }
            }
        }
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class Solver
{
    const 
BRANCH_LITERALS 0;
    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 mixed[] */
    
protected $branches = array();
    
/** @var Problem[] */
    
protected $problems = array();
    
/** @var array<Rule[]> */
    
protected $learnedPool = array();
    
/** @var array<string, int> */
    
protected $learnedWhy = array();

    
/** @var bool */
    
public $testFlagLearnedPositiveLiteral false;

    
/** @var IOInterface */
    
protected $io;

    public function 
__construct(PolicyInterface $policyPool $poolIOInterface $io)
    {
        
$this->io $io;
        
$this->policy $policy;
        
$this->pool $pool;
    }

    
/**
     * @return int
     */
    
public function getRuleSetSize()
    {
        return 
\count($this->rules);
    }

    
/**
     * @return Pool
     */
    
public function getPool()
    {
        return 
$this->pool;
    }

    
// aka solver_makeruledecisions

    /**
     * @return void
     */
    
private function makeAssertionRuleDecisions()
    {
        
$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($literal1$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 (
$conflict && 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;
        }
    }

    
/**
     * @return void
     */
    
protected function setupFixedMap(Request $request)
    {
        
$this->fixedMap = array();
        foreach (
$request->getFixedPackages() as $package) {
            
$this->fixedMap[$package->id] = $package;
        }
    }

    
/**
     * @return void
     */
    
protected function checkForRootRequireProblems(Request $requestPlatformRequirementFilterInterface $platformRequirementFilter)
    {
        foreach (
$request->getRequires() as $packageName => $constraint) {
            if (
$platformRequirementFilter->isIgnored($packageName)) {
                continue;
            } elseif (
$platformRequirementFilter instanceof IgnoreListPlatformRequirementFilter) {
                
$constraint $platformRequirementFilter->filterConstraint($packageName$constraint);
            }

            if (!
$this->pool->whatProvides($packageName$constraint)) {
                
$problem = new Problem();
                
$problem->addRule(new GenericRule(array(), Rule::RULE_ROOT_REQUIRE, array('packageName' => $packageName'constraint' => $constraint)));
                
$this->problems[] = $problem;
            }
        }
    }

    
/**
     * @return LockTransaction
     */
    
public function solve(Request $requestPlatformRequirementFilterInterface $platformRequirementFilter null)
    {
        
$platformRequirementFilter $platformRequirementFilter ?: PlatformRequirementFilterFactory::ignoreNothing();

        
$this->setupFixedMap($request);

        
$this->io->writeError('Generating rules'trueIOInterface::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'trueIOInterface::DEBUG);
        
$before microtime(true);
        
$this->runSat();
        
$this->io->writeError(''trueIOInterface::DEBUG);
        
$this->io->writeError(sprintf('Dependency resolution completed in %.3f seconds'microtime(true) - $before), trueIOInterface::VERBOSE);

        if (
$this->problems) {
            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
     *
     * @param  int       $level
     * @return Rule|null A rule on conflict, otherwise null.
     */
    
protected function propagate($level)
    {
        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) {
                return 
$conflict;
            }
        }

        return 
null;
    }

    
/**
     * Reverts a decision at the given level.
     *
     * @param int $level
     *
     * @return void
     */
    
private function revert($level)
    {
        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 (!empty(
$this->branches) && $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
     *
     * @param  int        $level
     * @param  string|int $literal
     * @return int
     */
    
private function setPropagateLearn($level$literalRule $rule)
    {
        
$level++;

        
$this->decisions->decide($literal$level$rule);

        while (
true) {
            
$rule $this->propagate($level);

            if (!
$rule) {
                break;
            }

            if (
$level == 1) {
                return 
$this->analyzeUnsolvable($rule);
            }

            
// conflict
            
list($learnLiteral$newLevel$newRule$why) = $this->analyze($level$rule);

            if (
$newLevel <= || $newLevel >= $level) {
                throw new 
SolverBugException(
                    
"Trying to revert to invalid level ".$newLevel." from level ".$level."."
                
);
            }

            
$level $newLevel;

            
$this->revert($level);

            
$this->rules->add($newRuleRuleSet::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  int   $level
     * @param  int[] $decisionQueue
     * @return int
     */
    
private function selectAndInstall($level, array $decisionQueueRule $rule)
    {
        
// 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)) {
            
$this->branches[] = array($literals$level);
        }

        return 
$this->setPropagateLearn($level$selectedLiteral$rule);
    }

    
/**
     * @param  int   $level
     * @return array{int, int, GenericRule, int}
     */
    
protected function analyze($levelRule $rule)
    {
        
$analyzedRule $rule;
        
$ruleLevel 1;
        
$num 0;
        
$l1num 0;
        
$seen = array();
        
$learnedLiterals = array(null);

        
$decisionId \count($this->decisions);

        
$this->learnedPool[] = array();

        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 (
=== $l) {
                    
$l1num++;
                } elseif (
$level === $l) {
                    
$num++;
                } else {
                    
// not level1 or conflict level, add to new rule
                    
$learnedLiterals[] = $literal;

                    if (
$l $ruleLevel) {
                        
$ruleLevel $l;
                    }
                }
            }
            unset(
$literal);

            
$l1retry true;
            while (
$l1retry) {
                
$l1retry false;

                if (
=== $num && === --$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 (
!== $num && === --$num) {
                    if (
$literal 0) {
                        
$this->testFlagLearnedPositiveLiteral true;
                    }
                    
$learnedLiterals[0] = -$literal;

                    if (!
$l1num) {
                        break 
2;
                    }

                    foreach (
$learnedLiterals as $i => $learnedLiteral) {
                        if (
$i !== 0) {
                            unset(
$seen[abs($learnedLiteral)]);
                        }
                    }
                    
// 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 multiconflict rule
                        
foreach ($rule->getLiterals() as $literal) {
                            if (!isset(
$seen[abs($literal)]) && $this->decisions->satisfy(-$literal)) {
                                
$this->learnedPool[\count($this->learnedPool) - 1][] = $rule;
                                
$l $this->decisions->decisionLevel($literal);
                                if (
=== $l) {
                                    
$l1num++;
                                } elseif (
$level === $l) {
                                    
$num++;
                                } else {
                                    
// not level1 or conflict level, add to new rule
                                    
$learnedLiterals[] = $literal;

                                    if (
$l $ruleLevel) {
                                        
$ruleLevel $l;
                                    }
                                }
                                
$seen[abs($literal)] = true;
                                break;
                            }
                        }

                        
$l1retry true;
                    }
                }
            }

            
$decision $this->decisions->atOffset($decisionId);
            
$rule $decision[Decisions::DECISION_REASON];
        }

        
$why \count($this->learnedPool) - 1;

        if (!
$learnedLiterals[0]) {
            throw new 
SolverBugException(
                
"Did not find a learnable literal in analyzed rule $analyzedRule."
            
);
        }

        
$newRule = new GenericRule($learnedLiteralsRule::RULE_LEARNED$why);

        return array(
$learnedLiterals[0], $ruleLevel$newRule$why);
    }

    
/**
     * @param array<string, true> $ruleSeen
     * @return void
     */
    
private function analyzeUnsolvableRule(Problem $problemRule $conflictRule, array &$ruleSeen)
    {
        
$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);
    }

    
/**
     * @return int
     */
    
private function analyzeUnsolvable(Rule $conflictRule)
    {
        
$problem = new Problem();
        
$problem->addRule($conflictRule);

        
$ruleSeen = array();

        
$this->analyzeUnsolvableRule($problem$conflictRule$ruleSeen);

        
$this->problems[] = $problem;

        
$seen = array();
        
$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) {
            
$literal $decision[Decisions::DECISION_LITERAL];

            
// skip literals that are not in this rule
            
if (!isset($seen[abs($literal)])) {
                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;
            }
        }

        return 
0;
    }

    
/**
     * enable/disable learnt rules
     *
     * we have enabled or disabled some of our rules. We now re-enable all
     * of our learnt rules except the ones that were learnt from rules that
     * are now disabled.
     *
     * @return void
     */
    
private function enableDisableLearnedRules()
    {
        foreach (
$this->rules->getIteratorFor(RuleSet::TYPE_LEARNED) as $rule) {
            
$why $this->learnedWhy[spl_object_hash($rule)];
            
$problemRules $this->learnedPool[$why];

            
$foundDisabled false;
            foreach (
$problemRules as $problemRule) {
                if (
$problemRule->isDisabled()) {
                    
$foundDisabled true;
                    break;
                }
            }

            if (
$foundDisabled && $rule->isEnabled()) {
                
$rule->disable();
            } elseif (!
$foundDisabled && $rule->isDisabled()) {
                
$rule->enable();
            }
        }
    }

    
/**
     * @return void
     */
    
private function runSat()
    {
        
$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 (
=== $level) {
                
$conflictRule $this->propagate($level);
                if (
null !== $conflictRule) {
                    if (
$this->analyzeUnsolvable($conflictRule)) {
                        continue;
                    }

                    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 = array();
                        
$noneSatisfied true;

                        foreach (
$rule->getLiterals() as $literal) {
                            if (
$this->decisions->satisfy($literal)) {
                                
$noneSatisfied false;
                                break;
                            }
                            if (
$literal && $this->decisions->undecided($literal)) {
                                
$decisionQueue[] = $literal;
                            }
                        }

                        if (
$noneSatisfied && \count($decisionQueue)) {
                            
// if any of the options in the decision queue are fixed, only use those
                            
$prunedQueue = array();
                            foreach (
$decisionQueue as $literal) {
                                if (isset(
$this->fixedMap[abs($literal)])) {
                                    
$prunedQueue[] = $literal;
                                }
                            }
                            if (!empty(
$prunedQueue)) {
                                
$decisionQueue $prunedQueue;
                            }
                        }

                        if (
$noneSatisfied && \count($decisionQueue)) {
                            
$oLevel $level;
                            
$level $this->selectAndInstall($level$decisionQueue$rule);

                            if (
=== $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.'trueIOInterface::DEBUG);
            for (
$i 0$n 0$n $rulesCount$i++, $n++) {
                if (
$i == $rulesCount) {
                    if (
=== $pass) {
                        
$this->io->writeError("Something's changed, looking at all rules again (pass #$pass)"falseIOInterface::DEBUG);
                    } else {
                        
$this->io->overwriteError("Something's changed, looking at all rules again (pass #$pass)"falsenullIOInterface::DEBUG);
                    }

                    
$i 0;
                    
$pass++;
                }

                
$rule $this->rules->ruleById[$i];
                
$literals $rule->getLiterals();

                if (
$rule->isDisabled()) {
                    continue;
                }

                
$decisionQueue = array();

                
// 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 (
=== $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)) {
                
$lastLiteral null;
                
$lastLevel null;
                
$lastBranchIndex 0;
                
$lastBranchOffset 0;

                for (
$i \count($this->branches) - 1$i >= 0$i--) {
                    list(
$literals$l) = $this->branches[$i];

                    foreach (
$literals as $offset => $literal) {
                        if (
$literal && $literal && $this->decisions->decisionLevel($literal) > $l 1) {
                            
$lastLiteral $literal;
                            
$lastBranchIndex $i;
                            
$lastBranchOffset $offset;
                            
$lastLevel $l;
                        }
                    }
                }

                if (
$lastLiteral) {
                    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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @author Ruben Gonzalez <[email protected]>
 * @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
    
const RULE_ROOT_REQUIRE 2// array{packageName: string, constraint: ConstraintInterface}
    
const RULE_FIXED 3// array{package: BasePackage}
    
const RULE_PACKAGE_CONFLICT 6// Link
    
const RULE_PACKAGE_REQUIRES 7// Link
    
const RULE_PACKAGE_SAME_NAME 10// string (package name)
    
const RULE_LEARNED 12// int (rule id)
    
const RULE_PACKAGE_ALIAS 13// BasePackage
    
const RULE_PACKAGE_INVERSE_ALIAS 14// BasePackage

    // bitfield defs
    
const BITFIELD_TYPE 0;
    const 
BITFIELD_REASON 8;
    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 = (<< self::BITFIELD_DISABLED) |
            (
$reason << self::BITFIELD_REASON) |
            (
255 << self::BITFIELD_TYPE);
    }

    
/**
     * @return int[]
     */
    
abstract public function getLiterals();

    
/**
     * @return int|string
     */
    
abstract public function getHash();

    abstract public function 
__toString();

    
/**
     * @param Rule $rule
     * @return bool
     */
    
abstract public function equals(Rule $rule);

    
/**
     * @return int
     */
    
public function getReason()
    {
        return (
$this->bitfield & (255 << self::BITFIELD_REASON)) >> self::BITFIELD_REASON;
    }

    
/**
     * @phpstan-return ReasonData
     */
    
public function getReasonData()
    {
        return 
$this->reasonData;
    }

    
/**
     * @return string|null
     */
    
public function getRequiredPackage()
    {
        
$reason $this->getReason();

        if (
$reason === self::RULE_ROOT_REQUIRE) {
            return 
$this->reasonData['packageName'];
        }

        if (
$reason === self::RULE_FIXED) {
            return 
$this->reasonData['package']->getName();
        }

        if (
$reason === self::RULE_PACKAGE_REQUIRES) {
            return 
$this->reasonData->getTarget();
        }

        return 
null;
    }

    
/**
     * @param RuleSet::TYPE_* $type
     * @return void
     */
    
public function setType($type)
    {
        
$this->bitfield = ($this->bitfield & ~(255 << self::BITFIELD_TYPE)) | ((255 $type) << self::BITFIELD_TYPE);
    }

    
/**
     * @return int
     */
    
public function getType()
    {
        return (
$this->bitfield & (255 << self::BITFIELD_TYPE)) >> self::BITFIELD_TYPE;
    }

    
/**
     * @return void
     */
    
public function disable()
    {
        
$this->bitfield = ($this->bitfield & ~(255 << self::BITFIELD_DISABLED)) | (<< self::BITFIELD_DISABLED);
    }

    
/**
     * @return void
     */
    
public function enable()
    {
        
$this->bitfield &= ~(255 << self::BITFIELD_DISABLED);
    }

    
/**
     * @return bool
     */
    
public function isDisabled()
    {
        return (bool) ((
$this->bitfield & (255 << self::BITFIELD_DISABLED)) >> self::BITFIELD_DISABLED);
    }

    
/**
     * @return bool
     */
    
public function isEnabled()
    {
        return !((
$this->bitfield & (255 << self::BITFIELD_DISABLED)) >> self::BITFIELD_DISABLED);
    }

    
/**
     * @return bool
     */
    
abstract public function isAssertion();

    
/**
     * @return bool
     */
    
public function isCausedByLock(RepositorySet $repositorySetRequest $requestPool $pool)
    {
        if (
$this->getReason() === self::RULE_PACKAGE_REQUIRES) {
            if (
PlatformRepository::isPlatformPackage($this->reasonData->getTarget())) {
                return 
false;
            }
            if (
$request->getLockedRepository()) {
                foreach (
$request->getLockedRepository()->getPackages() as $package) {
                    if (
$package->getName() === $this->reasonData->getTarget()) {
                        if (
$pool->isUnacceptableFixedOrLockedPackage($package)) {
                            return 
true;
                        }
                        if (!
$this->reasonData->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->reasonData['packageName'])) {
                return 
false;
            }
            if (
$request->getLockedRepository()) {
                foreach (
$request->getLockedRepository()->getPackages() as $package) {
                    if (
$package->getName() === $this->reasonData['packageName']) {
                        if (
$pool->isUnacceptableFixedOrLockedPackage($package)) {
                            return 
true;
                        }
                        if (!
$this->reasonData['constraint']->matches(new Constraint('='$package->getVersion()))) {
                            return 
true;
                        }
                        break;
                    }
                }
            }
        }

        return 
false;
    }

    
/**
     * @internal
     * @return BasePackage
     */
    
public function getSourcePackage(Pool $pool)
    {
        
$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]));

                if (
$reasonData $this->getReasonData()) {
                    
// swap literals if they are not in the right order with package2 being the conflicter
                    
if ($reasonData->getSource() === $package1->getName()) {
                        list(
$package2$package1) = array($package1$package2);
                    }
                }

                return 
$package2;

            case 
self::RULE_PACKAGE_REQUIRES:
                
$sourceLiteral array_shift($literals);
                
$sourcePackage $this->deduplicateDefaultBranchAlias($pool->literalToPackage($sourceLiteral));

                return 
$sourcePackage;

            default:
                throw new 
\LogicException('Not implemented');
        }
    }


    
/**
     * @param bool $isVerbose
     * @param BasePackage[] $installedMap
     * @param array<Rule[]> $learnedPool
     * @return string
     */
    
public function getPrettyString(RepositorySet $repositorySetRequest $requestPool $pool$isVerbose, array $installedMap = array(), array $learnedPool = array())
    {
        
$literals $this->getLiterals();

        switch (
$this->getReason()) {
            case 
self::RULE_ROOT_REQUIRE:
                
$packageName $this->reasonData['packageName'];
                
$constraint $this->reasonData['constraint'];

                
$packages $pool->whatProvides($packageName$constraint);
                if (!
$packages) {
                    return 
'No package found to satisfy root composer.json require '.$packageName.($constraint ' '.$constraint->getPrettyString() : '');
                }

                
$packagesNonAlias array_values(array_filter($packages, function ($p) {
                    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 ' '.$constraint->getPrettyString() : '').' -> satisfiable by '.$this->formatPackagesUnique($pool$packages$isVerbose$constraint).'.';

            case 
self::RULE_FIXED:
                
$package $this->deduplicateDefaultBranchAlias($this->reasonData['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();
                if (
$reasonData $this->getReasonData()) {
                    
assert($reasonData instanceof Link);

                    
// swap literals if they are not in the right order with package2 being the conflicter
                    
if ($reasonData->getSource() === $package1->getName()) {
                        list(
$package2$package1) = array($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:
                
$sourceLiteral array_shift($literals);
                
$sourcePackage $this->deduplicateDefaultBranchAlias($pool->literalToPackage($sourceLiteral));
                
/** @var Link */
                
$reasonData $this->reasonData;

                
$requires = array();
                foreach (
$literals as $literal) {
                    
$requires[] = $pool->literalToPackage($literal);
                }

                
$text $reasonData->getPrettyString($sourcePackage);
                if (
$requires) {
                    
$text .= ' -> satisfiable by ' $this->formatPackagesUnique($pool$requires$isVerbose$this->reasonData->getConstraint()) . '.';
                } else {
                    
$targetName $reasonData->getTarget();

                    
$reason Problem::getMissingPackageReason($repositorySet$request$pool$isVerbose$targetName$this->reasonData->getConstraint());

                    return 
$text ' -> ' $reason[1];
                }

                return 
$text;

            case 
self::RULE_PACKAGE_SAME_NAME:
                
$packageNames = array();
                foreach (
$literals as $literal) {
                    
$package $pool->literalToPackage($literal);
                    
$packageNames[$package->getName()] = true;
                }
                
$replacedName $this->reasonData;

                if (
count($packageNames) > 1) {
                    
$reason null;

                    if (!isset(
$packageNames[$replacedName])) {
                        
$reason 'They '.(count($literals) == '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 = array();
                    
$removablePackages = array();
                    foreach (
$literals as $literal) {
                        if (isset(
$installedMap[abs($literal)])) {
                            
$installedPackages[] = $pool->literalToPackage($literal);
                        } else {
                            
$removablePackages[] = $pool->literalToPackage($literal);
                        }
                    }

                    if (
$installedPackages && $removablePackages) {
                        return 
$this->formatPackagesUnique($pool$removablePackages$isVerbosenulltrue).' cannot be installed as that would require removing '.$this->formatPackagesUnique($pool$installedPackages$isVerbosenulltrue).'. '.$reason;
                    }

                    return 
'Only one of these can be installed: '.$this->formatPackagesUnique($pool$literals$isVerbosenulltrue).'. '.$reason;
                }

                return 
'You can only install one version of a package, so only one of these can be installed: ' $this->formatPackagesUnique($pool$literals$isVerbosenulltrue) . '.';
            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->reasonData])) {
                //     echo $this->reasonData."\n";
                //     $learnedString = ', learned rules:' . Problem::formatDeduplicatedRules($learnedPool[$this->reasonData], '        ', $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 = array();
                    foreach (
$literals as $literal) {
                        
$package $pool->literalToPackage($literal);
                        if (isset(
$installedMap[$package->id])) {
                            
$group $literal 'keep' 'remove';
                        } else {
                            
$group $literal 'install' 'don\'t install';
                        }

                        
$groups[$group][] = $this->deduplicateDefaultBranchAlias($package);
                    }
                    
$ruleTexts = array();
                    foreach (
$groups as $group => $packages) {
                        
$ruleTexts[] = $group . (count($packages) > ' 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> $packages An array containing packages or literals
     * @param bool $isVerbose
     * @param bool $useRemovedVersionGroup
     * @return string
     */
    
protected function formatPackagesUnique(Pool $pool, array $packages$isVerboseConstraintInterface $constraint null$useRemovedVersionGroup false)
    {
        foreach (
$packages as $index => $package) {
            if (!
\is_object($package)) {
                
$packages[$index] = $pool->literalToPackage($package);
            }
        }

        return 
Problem::getPackageList($packages$isVerbose$pool$constraint$useRemovedVersionGroup);
    }

    
/**
     * @return BasePackage
     */
    
private function deduplicateDefaultBranchAlias(BasePackage $package)
    {
        if (
$package instanceof AliasPackage && $package->getPrettyVersion() === VersionParser::DEFAULT_BRANCH_ALIAS) {
            
$package $package->getAliasOf();
        }

        return 
$package;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 *
 * @method self::ERROR_DEPENDENCY_RESOLUTION_FAILED getCode()
 */
class SolverProblemsException extends \RuntimeException
{
    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);
    }

    
/**
     * @param bool $isVerbose
     * @param bool $isDevExtraction
     * @return string
     */
    
public function getPrettyString(RepositorySet $repositorySetRequest $requestPool $pool$isVerbose$isDevExtraction false)
    {
        
$installedMap $request->getPresentMap(true);
        
$missingExtensions = array();
        
$isCausedByLock false;

        
$problems = array();
        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 = array();
        if (!
$isDevExtraction && (strpos($text'could not be found') || strpos($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 (!empty(
$missingExtensions)) {
            
$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 (
strpos($text'found composer-plugin-api[2.0.0] but it does not match') && strpos($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 (
strpos($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 (
$hints) {
            
$text .= "\n" implode("\n\n"$hints);
        }

        return 
$text;
    }

    
/**
     * @return Problem[]
     */
    
public function getProblems()
    {
        return 
$this->problems;
    }

    
/**
     * @param string[] $missingExtensions
     * @return string
     */
    
private function createExtensionHint(array $missingExtensions)
    {
        
$paths IniHelper::getAll();

        if (
count($paths) === && empty($paths[0])) {
            return 
'';
        }

        
$ignoreExtensionsArguments implode(" "array_map(function ($extension) {
            return 
"--ignore-platform-req=$extension";
        }, 
$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)
    {
        
$missingExtensions = array();
        foreach (
$reasonSets as $reasonSet) {
            foreach (
$reasonSet as $rule) {
                
$required $rule->getRequiredPackage();
                if (
null !== $required && === strpos($required'ext-')) {
                    
$missingExtensions[$required] = 1;
                }
            }
        }

        return 
array_keys($missingExtensions);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @phpstan-import-type ReasonData from Rule
 */
class Rule2Literals extends Rule
{
    
/** @var int */
    
protected $literal1;
    
/** @var int */
    
protected $literal2;

    
/**
     * @param int $literal1
     * @param int $literal2
     * @param Rule::RULE_* $reason A RULE_* constant
     * @param mixed $reasonData
     *
     * @phpstan-param ReasonData $reasonData
     */
    
public function __construct($literal1$literal2$reason$reasonData)
    {
        
parent::__construct($reason$reasonData);

        if (
$literal1 $literal2) {
            
$this->literal1 $literal1;
            
$this->literal2 $literal2;
        } else {
            
$this->literal1 $literal2;
            
$this->literal2 $literal1;
        }
    }

    
/** @return int[] */
    
public function getLiterals()
    {
        return array(
$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)
    {
        
// 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 (
!= \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()
    {
        return 
false;
    }

    
/**
     * Formats a rule as a string of the format (Literal1|Literal2|...)
     *
     * @return string
     */
    
public function __toString()
    {
        
$result $this->isDisabled() ? 'disabled(' '(';

        
$result .= $this->literal1 '|' $this->literal2 ')';

        return 
$result;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
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 $literals[0] : 0;
        
$this->watch2 $literalCount $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
     * @return void
     */
    
public function watch2OnHighest(Decisions $decisions)
    {
        
$literals $this->rule->getLiterals();

        
// if there are only 2 elements, both are being watched anyway
        
if (\count($literals) < || $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
     *
     * @return Rule
     */
    
public function getRule()
    {
        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($literal)
    {
        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
     * @return void
     */
    
public function moveWatch($from$to)
    {
        if (
$this->watch1 == $from) {
            
$this->watch1 $to;
        } else {
            
$this->watch2 $to;
        }
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @implements \IteratorAggregate<Rule>
 */
class RuleSet implements \IteratorAggregate\Countable
{
    
// highest priority => lowest number
    
const TYPE_PACKAGE 0;
    const 
TYPE_REQUEST 1;
    const 
TYPE_LEARNED 4;

    
/**
     * READ-ONLY: Lookup table for rule id to rule object
     *
     * @var array<int, Rule>
     */
    
public $ruleById = array();

    
/** @var array<0|1|4, string> */
    
protected static $types = array(
        
self::TYPE_PACKAGE => 'PACKAGE',
        
self::TYPE_REQUEST => 'REQUEST',
        
self::TYPE_LEARNED => 'LEARNED',
    );

    
/** @var array<self::TYPE_*, Rule[]> */
    
protected $rules;

    
/** @var int */
    
protected $nextRuleId 0;

    
/** @var array<int|string, Rule|Rule[]> */
    
protected $rulesByHash = array();

    public function 
__construct()
    {
        foreach (
$this->getTypes() as $type) {
            
$this->rules[$type] = array();
        }
    }

    
/**
     * @param self::TYPE_* $type
     * @return void
     */
    
public function add(Rule $rule$type)
    {
        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] = array();
        }

        
$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] = array($originalRule$rule);
        }
    }

    
/**
     * @return int
     */
    
#[\ReturnTypeWillChange]
    public function 
count()
    {
        return 
$this->nextRuleId;
    }

    
/**
     * @param int $id
     * @return Rule
     */
    
public function ruleById($id)
    {
        return 
$this->ruleById[$id];
    }

    
/** @return array<self::TYPE_*, Rule[]> */
    
public function getRules()
    {
        return 
$this->rules;
    }

    
/**
     * @return RuleSetIterator
     */
    
#[\ReturnTypeWillChange]
    public function 
getIterator()
    {
        return new 
RuleSetIterator($this->getRules());
    }

    
/**
     * @param  self::TYPE_*|array<self::TYPE_*> $types
     * @return RuleSetIterator
     */
    
public function getIteratorFor($types)
    {
        if (!
\is_array($types)) {
            
$types = array($types);
        }

        
$allRules $this->getRules();

        
/** @var array<self::TYPE_*, Rule[]> $rules */
        
$rules = array();

        foreach (
$types as $type) {
            
$rules[$type] = $allRules[$type];
        }

        return new 
RuleSetIterator($rules);
    }

    
/**
     * @param array<self::TYPE_*>|self::TYPE_* $types
     * @return RuleSetIterator
     */
    
public function getIteratorWithout($types)
    {
        if (!
\is_array($types)) {
            
$types = array($types);
        }

        
$rules $this->getRules();

        foreach (
$types as $type) {
            unset(
$rules[$type]);
        }

        return new 
RuleSetIterator($rules);
    }

    
/** @return array{0: 0, 1: 1, 2: 4} */
    
public function getTypes()
    {
        
$types self::$types;

        return 
array_keys($types);
    }

    
/**
     * @param bool $isVerbose
     * @return string
     */
    
public function getPrettyString(RepositorySet $repositorySet nullRequest $request nullPool $pool null$isVerbose false)
    {
        
$string "\n";
        foreach (
$this->rules as $type => $rules) {
            
$string .= str_pad(self::$types[$type], 8' ') . ": ";
            foreach (
$rules as $rule) {
                
$string .= ($repositorySet && $request && $pool $rule->getPrettyString($repositorySet$request$pool$isVerbose) : $rule)."\n";
            }
            
$string .= "\n\n";
        }

        return 
$string;
    }

    public function 
__toString()
    {
        return 
$this->getPrettyString();
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 */
class Pool implements \Countable
{
    
/** @var BasePackage[] */
    
protected $packages = array();
    
/** @var array<string, BasePackage[]> */
    
protected $packageByName = array();
    
/** @var VersionParser */
    
protected $versionParser;
    
/** @var array<string, array<string, BasePackage[]>> */
    
protected $providerCache = array();
    
/** @var BasePackage[] */
    
protected $unacceptableFixedOrLockedPackages;
    
/** @var array<string, array<string, string>> Map of package name => normalized version => pretty version */
    
protected $removedVersions = array();
    
/** @var array<string, array<string, string>> Map of package object hash => removed normalized versions => removed pretty version */
    
protected $removedVersionsByPackage = array();

    
/**
     * @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(), array $unacceptableFixedOrLockedPackages = array(), array $removedVersions = array(), array $removedVersionsByPackage = array())
    {
        
$this->versionParser = new VersionParser;
        
$this->setPackages($packages);
        
$this->unacceptableFixedOrLockedPackages $unacceptableFixedOrLockedPackages;
        
$this->removedVersions $removedVersions;
        
$this->removedVersionsByPackage $removedVersionsByPackage;
    }

    
/**
     * @param  string $name
     * @return array<string, string>
     */
    
public function getRemovedVersions($nameConstraintInterface $constraint)
    {
        if (!isset(
$this->removedVersions[$name])) {
            return array();
        }

        
$result = array();
        foreach (
$this->removedVersions[$name] as $version => $prettyVersion) {
            if (
$constraint->matches(new Constraint('=='$version))) {
                
$result[$version] = $prettyVersion;
            }
        }

        return 
$result;
    }

    
/**
     * @param  string $objectHash
     * @return array<string, string>
     */
    
public function getRemovedVersionsByPackage($objectHash)
    {
        if (!isset(
$this->removedVersionsByPackage[$objectHash])) {
            return array();
        }

        return 
$this->removedVersionsByPackage[$objectHash];
    }

    
/**
     * @param BasePackage[] $packages
     * @return void
     */
    
private function setPackages(array $packages)
    {
        
$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()
    {
        return 
$this->packages;
    }

    
/**
     * Retrieves the package object for a given package id.
     *
     * @param  int         $id
     * @return BasePackage
     */
    
public function packageById($id)
    {
        return 
$this->packages[$id 1];
    }

    
/**
     * Returns how many packages have been loaded into the pool
     * @return int
     */
    
#[\ReturnTypeWillChange]
    public function 
count()
    {
        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($nameConstraintInterface $constraint null)
    {
        
$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($nameConstraintInterface $constraint null)
    {
        if (!isset(
$this->packageByName[$name])) {
            return array();
        }

        
$matches = array();

        foreach (
$this->packageByName[$name] as $candidate) {
            if (
$this->match($candidate$name$constraint)) {
                
$matches[] = $candidate;
            }
        }

        return 
$matches;
    }

    
/**
     * @param int $literal
     * @return BasePackage
     */
    
public function literalToPackage($literal)
    {
        
$packageId abs($literal);

        return 
$this->packageById($packageId);
    }

    
/**
     * @param int $literal
     * @param array<int, BasePackage> $installedMap
     * @return string
     */
    
public function literalToPrettyString($literal$installedMap)
    {
        
$package $this->literalToPackage($literal);

        if (isset(
$installedMap[$package->id])) {
            
$prefix = ($literal 'keep' 'remove');
        } else {
            
$prefix = ($literal '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
     * @return bool
     */
    
public function match(BasePackage $candidate$nameConstraintInterface $constraint null)
    {
        
$candidateName $candidate->getName();
        
$candidateVersion $candidate->getVersion();

        if (
$candidateName === $name) {
            return 
$constraint === null || CompilingMatcher::match($constraintConstraint::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;
    }

    
/**
     * @return bool
     */
    
public function isUnacceptableFixedOrLockedPackage(BasePackage $package)
    {
        return 
\in_array($package$this->unacceptableFixedOrLockedPackagestrue);
    }

    
/**
     * @return BasePackage[]
     */
    
public function getUnacceptableFixedOrLockedPackages()
    {
        return 
$this->unacceptableFixedOrLockedPackages;
    }

    public function 
__toString()
    {
        
$str "Pool:\n";

        foreach (
$this->packages as $package) {
            
$str .= '- '.str_pad((string) $package->id6' 'STR_PAD_LEFT).': '.$package->getName()."\n";
        }

        return 
$str;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 *
 * MultiConflictRule([A, B, C]) acts as Rule([-A, -B]), Rule([-A, -C]), Rule([-B, -C])
 */
class MultiConflictRule extends Rule
{
    
/** @var int[] */
    
protected $literals;

    
/**
     * @param 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 int[]
     */
    
public function getLiterals()
    {
        return 
$this->literals;
    }

    
/**
     * @inheritDoc
     */
    
public function getHash()
    {
        
$data unpack('ihash'md5('c:'.implode(','$this->literals), true));

        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)
    {
        if (
$rule instanceof MultiConflictRule) {
            return 
$this->literals === $rule->getLiterals();
        }

        return 
false;
    }

    
/**
     * @return bool
     */
    
public function isAssertion()
    {
        return 
false;
    }

    
/**
     * @return never
     * @throws \RuntimeException
     */
    
public function disable()
    {
        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|...)
     *
     * @return string
     */
    
public function __toString()
    {
        
// 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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class MarkAliasUninstalledOperation extends SolverOperation implements OperationInterface
{
    const 
TYPE 'markAliasUninstalled';

    
/**
     * @var AliasPackage
     */
    
protected $package;

    public function 
__construct(AliasPackage $package)
    {
        
$this->package $package;
    }

    
/**
     * Returns package instance.
     *
     * @return AliasPackage
     */
    
public function getPackage()
    {
        return 
$this->package;
    }

    
/**
     * @inheritDoc
     */
    
public function show($lock)
    {
        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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
abstract class SolverOperation implements OperationInterface
{
    const 
TYPE null;

    
/**
     * Returns operation type.
     *
     * @return string
     */
    
public function getOperationType()
    {
        return static::
TYPE;
    }

    
/**
     * @inheritDoc
     */
    
public function __toString()
    {
        return 
$this->show(false);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class UpdateOperation extends SolverOperation implements OperationInterface
{
    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 $initialPackageInterface $target)
    {
        
$this->initialPackage $initial;
        
$this->targetPackage $target;
    }

    
/**
     * Returns initial package.
     *
     * @return PackageInterface
     */
    
public function getInitialPackage()
    {
        return 
$this->initialPackage;
    }

    
/**
     * Returns target package.
     *
     * @return PackageInterface
     */
    
public function getTargetPackage()
    {
        return 
$this->targetPackage;
    }

    
/**
     * @inheritDoc
     */
    
public function show($lock)
    {
        return 
self::format($this->initialPackage$this->targetPackage$lock);
    }

    
/**
     * @param bool $lock
     * @return string
     */
    
public static function format(PackageInterface $initialPackagePackageInterface $targetPackage$lock false)
    {
        
$fromVersion $initialPackage->getFullPrettyVersion();
        
$toVersion $targetPackage->getFullPrettyVersion();

        if (
$fromVersion === $toVersion && $initialPackage->getSourceReference() !== $targetPackage->getSourceReference()) {
            
$fromVersion $initialPackage->getFullPrettyVersion(truePackageInterface::DISPLAY_SOURCE_REF);
            
$toVersion $targetPackage->getFullPrettyVersion(truePackageInterface::DISPLAY_SOURCE_REF);
        } elseif (
$fromVersion === $toVersion && $initialPackage->getDistReference() !== $targetPackage->getDistReference()) {
            
$fromVersion $initialPackage->getFullPrettyVersion(truePackageInterface::DISPLAY_DIST_REF);
            
$toVersion $targetPackage->getFullPrettyVersion(truePackageInterface::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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
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($lock);

    
/**
     * Serializes the operation in a human readable format
     *
     * @return string
     */
    
public function __toString();
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class MarkAliasInstalledOperation extends SolverOperation implements OperationInterface
{
    const 
TYPE 'markAliasInstalled';

    
/**
     * @var AliasPackage
     */
    
protected $package;

    public function 
__construct(AliasPackage $package)
    {
        
$this->package $package;
    }

    
/**
     * Returns package instance.
     *
     * @return AliasPackage
     */
    
public function getPackage()
    {
        return 
$this->package;
    }

    
/**
     * @inheritDoc
     */
    
public function show($lock)
    {
        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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class UninstallOperation extends SolverOperation implements OperationInterface
{
    const 
TYPE 'uninstall';

    
/**
     * @var PackageInterface
     */
    
protected $package;

    public function 
__construct(PackageInterface $package)
    {
        
$this->package $package;
    }

    
/**
     * Returns package instance.
     *
     * @return PackageInterface
     */
    
public function getPackage()
    {
        return 
$this->package;
    }

    
/**
     * @inheritDoc
     */
    
public function show($lock)
    {
        return 
self::format($this->package$lock);
    }

    
/**
     * @param bool $lock
     * @return string
     */
    
public static function format(PackageInterface $package$lock false)
    {
        return 
'Removing <info>'.$package->getPrettyName().'</info> (<comment>'.$package->getFullPrettyVersion().'</comment>)';
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class InstallOperation extends SolverOperation implements OperationInterface
{
    const 
TYPE 'install';

    
/**
     * @var PackageInterface
     */
    
protected $package;

    public function 
__construct(PackageInterface $package)
    {
        
$this->package $package;
    }

    
/**
     * Returns package instance.
     *
     * @return PackageInterface
     */
    
public function getPackage()
    {
        return 
$this->package;
    }

    
/**
     * @inheritDoc
     */
    
public function show($lock)
    {
        return 
self::format($this->package$lock);
    }

    
/**
     * @param bool $lock
     * @return string
     */
    
public static function format(PackageInterface $package$lock false)
    {
        return (
$lock 'Locking ' 'Installing ').'<info>'.$package->getPrettyName().'</info> (<comment>'.$package->getFullPrettyVersion().'</comment>)';
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @internal
 */
class LocalRepoTransaction extends Transaction
{
    public function 
__construct(RepositoryInterface $lockedRepositoryInstalledRepositoryInterface $localRepository)
    {
        
parent::__construct(
            
$localRepository->getPackages(),
            
$lockedRepository->getPackages()
        );
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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;

/**
 * @author Nils Adermann <[email protected]>
 * @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 $unlockableMapDecisions $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?
    /**
     * @return void
     */
    
public function setResultPackages(Pool $poolDecisions $decisions)
    {
        
$this->resultPackages = array('all' => array(), 'non-dev' => array(), 'dev' => array());
        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;
                }
            }
        }
    }

    
/**
     * @return void
     */
    
public function setNonDevPackages(LockTransaction $extractionResult)
    {
        
$packages $extractionResult->getNewLockPackages(false);

        
$this->resultPackages['dev'] = $this->resultPackages['non-dev'];
        
$this->resultPackages['non-dev'] = array();

        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?
    /**
     * @param bool $devMode
     * @param bool $updateMirrors
     * @return BasePackage[]
     */
    
public function getNewLockPackages($devMode$updateMirrors false)
    {
        
$packages = array();
        foreach (
$this->resultPackages[$devMode 'dev' 'non-dev'] as $package) {
            if (!
$package instanceof AliasPackage) {
                
// if we're just updating mirrors we need to reset references to the same as currently "present" packages' references to keep the lock file as-is
                // we do not reset references if the currently present package didn't have any, or if the type of VCS has changed
                
if ($updateMirrors && !isset($this->presentMap[spl_object_hash($package)])) {
                    foreach (
$this->presentMap as $presentPackage) {
                        if (
$package->getName() == $presentPackage->getName() && $package->getVersion() == $presentPackage->getVersion()) {
                            if (
$presentPackage->getSourceReference() && $presentPackage->getSourceType() === $package->getSourceType()) {
                                
$package->setSourceDistReferences($presentPackage->getSourceReference());
                            }
                            if (
$presentPackage->getReleaseDate() && $package instanceof Package) {
                                
$package->setReleaseDate($presentPackage->getReleaseDate());
                            }
                        }
                    }
                }
                
$packages[] = $package;
            }
        }

        return 
$packages;
    }

    
/**
     * Checks which of the given aliases from composer.json are actually in use for the lock file
     * @param array<array{package: string, version: string, alias: string, alias_normalized: string}> $aliases
     * @return array<array{package: string, version: string, alias: string, alias_normalized: string}>
     */
    
public function getAliases($aliases)
    {
        
$usedAliases = array();

        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, function ($a$b) {
            return 
strcmp($a['package'], $b['package']);
        });

        return 
$usedAliases;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @implements \Iterator<RuleSet::TYPE_*, 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();
    }

    
/**
     * @return Rule
     */
    
#[\ReturnTypeWillChange]
    public function 
current()
    {
        return 
$this->rules[$this->currentType][$this->currentOffset];
    }

    
/**
     * @return RuleSet::TYPE_*|-1
     */
    
#[\ReturnTypeWillChange]
    public function 
key()
    {
        return 
$this->currentType;
    }

    
/**
     * @return void
     */
    
#[\ReturnTypeWillChange]
    public function 
next()
    {
        
$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 (isset(
$this->types[$this->currentTypeOffset]) && !\count($this->rules[$this->currentType]));
        }
    }

    
/**
     * @return void
     */
    
#[\ReturnTypeWillChange]
    public function 
rewind()
    {
        
$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 (isset(
$this->types[$this->currentTypeOffset]) && !\count($this->rules[$this->currentType]));
    }

    
/**
     * @return bool
     */
    
#[\ReturnTypeWillChange]
    public function 
valid()
    {
        return isset(
$this->rules[$this->currentType], $this->rules[$this->currentType][$this->currentOffset]);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @extends \SplDoublyLinkedList<RuleWatchNode>
 */
class RuleWatchChain extends \SplDoublyLinkedList
{
    
/**
     * Moves the internal iterator to the specified offset
     *
     * @param int $offset The offset to seek to.
     * @return void
     */
    
public function seek($offset)
    {
        
$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.
     *
     * @return void
     */
    
public function remove()
    {
        
$offset $this->key();
        
$this->offsetUnset($offset);
        
$this->seek($offset);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class GenericRule extends Rule
{
    
/** @var int[] */
    
protected $literals;

    
/**
     * @param 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 int[]
     */
    
public function getLiterals()
    {
        return 
$this->literals;
    }

    
/**
     * @inheritDoc
     */
    
public function getHash()
    {
        
$data unpack('ihash'md5(implode(','$this->literals), true));

        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)
    {
        return 
$this->literals === $rule->getLiterals();
    }

    
/**
     * @return bool
     */
    
public function isAssertion()
    {
        return 
=== \count($this->literals);
    }

    
/**
     * Formats a rule as a string of the format (Literal1|Literal2|...)
     *
     * @return string
     */
    
public function __toString()
    {
        
$result $this->isDisabled() ? 'disabled(' '(';

        foreach (
$this->literals as $i => $literal) {
            if (
$i != 0) {
                
$result .= '|';
            }
            
$result .= $literal;
        }

        
$result .= ')';

        return 
$result;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @implements \Iterator<array{0: int, 1: Rule}>
 */
class Decisions implements \Iterator\Countable
{
    const 
DECISION_LITERAL 0;
    const 
DECISION_REASON 1;

    
/** @var Pool */
    
protected $pool;
    
/** @var array<int, int> */
    
protected $decisionMap;
    
/**
     * @var array<array{0: int, 1: Rule}>
     */
    
protected $decisionQueue = array();

    public function 
__construct(Pool $pool)
    {
        
$this->pool $pool;
        
$this->decisionMap = array();
    }

    
/**
     * @param int $literal
     * @param int $level
     * @return void
     */
    
public function decide($literal$levelRule $why)
    {
        
$this->addDecision($literal$level);
        
$this->decisionQueue[] = array(
            
self::DECISION_LITERAL => $literal,
            
self::DECISION_REASON => $why,
        );
    }

    
/**
     * @param int $literal
     * @return bool
     */
    
public function satisfy($literal)
    {
        
$packageId abs($literal);

        return (
            
$literal && isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > ||
            
$literal && isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] < 0
        
);
    }

    
/**
     * @param int $literal
     * @return bool
     */
    
public function conflict($literal)
    {
        
$packageId abs($literal);

        return (
            (isset(
$this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > && $literal 0) ||
            (isset(
$this->decisionMap[$packageId]) && $this->decisionMap[$packageId] < && $literal 0)
        );
    }

    
/**
     * @param int $literalOrPackageId
     * @return bool
     */
    
public function decided($literalOrPackageId)
    {
        return !empty(
$this->decisionMap[abs($literalOrPackageId)]);
    }

    
/**
     * @param int $literalOrPackageId
     * @return bool
     */
    
public function undecided($literalOrPackageId)
    {
        return empty(
$this->decisionMap[abs($literalOrPackageId)]);
    }

    
/**
     * @param int $literalOrPackageId
     * @return bool
     */
    
public function decidedInstall($literalOrPackageId)
    {
        
$packageId abs($literalOrPackageId);

        return isset(
$this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0;
    }

    
/**
     * @param int $literalOrPackageId
     * @return int
     */
    
public function decisionLevel($literalOrPackageId)
    {
        
$packageId abs($literalOrPackageId);
        if (isset(
$this->decisionMap[$packageId])) {
            return 
abs($this->decisionMap[$packageId]);
        }

        return 
0;
    }

    
/**
     * @param int $literalOrPackageId
     * @return Rule|null
     */
    
public function decisionRule($literalOrPackageId)
    {
        
$packageId abs($literalOrPackageId);

        foreach (
$this->decisionQueue as $decision) {
            if (
$packageId === abs($decision[self::DECISION_LITERAL])) {
                return 
$decision[self::DECISION_REASON];
            }
        }

        return 
null;
    }

    
/**
     * @param int $queueOffset
     * @return array{0: int, 1: Rule} a literal and decision reason
     */
    
public function atOffset($queueOffset)
    {
        return 
$this->decisionQueue[$queueOffset];
    }

    
/**
     * @param int $queueOffset
     * @return bool
     */
    
public function validOffset($queueOffset)
    {
        return 
$queueOffset >= && $queueOffset \count($this->decisionQueue);
    }

    
/**
     * @return Rule
     */
    
public function lastReason()
    {
        return 
$this->decisionQueue[\count($this->decisionQueue) - 1][self::DECISION_REASON];
    }

    
/**
     * @return int
     */
    
public function lastLiteral()
    {
        return 
$this->decisionQueue[\count($this->decisionQueue) - 1][self::DECISION_LITERAL];
    }

    
/**
     * @return void
     */
    
public function reset()
    {
        while (
$decision array_pop($this->decisionQueue)) {
            
$this->decisionMap[abs($decision[self::DECISION_LITERAL])] = 0;
        }
    }

    
/**
     * @param int $offset
     * @return void
     */
    
public function resetToOffset($offset)
    {
        while (
\count($this->decisionQueue) > $offset 1) {
            
$decision array_pop($this->decisionQueue);
            
$this->decisionMap[abs($decision[self::DECISION_LITERAL])] = 0;
        }
    }

    
/**
     * @return void
     */
    
public function revertLast()
    {
        
$this->decisionMap[abs($this->lastLiteral())] = 0;
        
array_pop($this->decisionQueue);
    }

    
/**
     * @return int
     */
    
#[\ReturnTypeWillChange]
    public function 
count()
    {
        return 
\count($this->decisionQueue);
    }

    
/**
     * @return void
     */
    
#[\ReturnTypeWillChange]
    public function 
rewind()
    {
        
end($this->decisionQueue);
    }

    
/**
     * @return array{0: int, 1: Rule}|false
     */
    
#[\ReturnTypeWillChange]
    public function 
current()
    {
        return 
current($this->decisionQueue);
    }

    
/**
     * @return ?int
     */
    
#[\ReturnTypeWillChange]
    public function 
key()
    {
        return 
key($this->decisionQueue);
    }

    
/**
     * @return void
     */
    
#[\ReturnTypeWillChange]
    public function 
next()
    {
        
prev($this->decisionQueue);
    }

    
/**
     * @return bool
     */
    
#[\ReturnTypeWillChange]
    public function 
valid()
    {
        return 
false !== current($this->decisionQueue);
    }

    
/**
     * @return bool
     */
    
public function isEmpty()
    {
        return 
\count($this->decisionQueue) === 0;
    }

    
/**
     * @param int $literal
     * @param int $level
     * @return void
     */
    
protected function addDecision($literal$level)
    {
        
$packageId abs($literal);

        
$previousDecision = isset($this->decisionMap[$packageId]) ? $this->decisionMap[$packageId] : null;
        if (
$previousDecision != 0) {
            
$literalString $this->pool->literalToPrettyString($literal, array());
            
$package $this->pool->literalToPackage($literal);
            throw new 
SolverBugException(
                
"Trying to decide $literalString on level $level, even though $package was previously decided as ".(int) $previousDecision."."
            
);
        }

        if (
$literal 0) {
            
$this->decisionMap[$packageId] = $level;
        } else {
            
$this->decisionMap[$packageId] = -$level;
        }
    }

    
/**
     * @return string
     */
    
public function toString(Pool $pool null)
    {
        
$decisionMap $this->decisionMap;
        
ksort($decisionMap);
        
$str '[';
        foreach (
$decisionMap as $packageId => $level) {
            
$str .= (($pool) ? $pool->literalToPackage($packageId) : $packageId).':'.$level.',';
        }
        
$str .= ']';

        return 
$str;
    }

    public function 
__toString()
    {
        return 
$this->toString();
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
interface PolicyInterface
{
    
/**
     * @param  string $operator
     * @return bool
     *
     * @phpstan-param Constraint::STR_OP_* $operator
     */
    
public function versionCompare(PackageInterface $aPackageInterface $b$operator);

    
/**
     * @param  int[]   $literals
     * @param  ?string $requiredPackage
     * @return int[]
     */
    
public function selectPreferredPackages(Pool $pool, array $literals$requiredPackage null);
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class SolverBugException extends \RuntimeException
{
    
/**
     * @param string $message
     */
    
public function __construct($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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class PoolBuilder
{
    
/**
     * @var int[]
     * @phpstan-var array<string, 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 ?EventDispatcher
     */
    
private $eventDispatcher;
    
/**
     * @var PoolOptimizer|null
     */
    
private $poolOptimizer;
    
/**
     * @var IOInterface
     */
    
private $io;
    
/**
     * @var array[]
     * @phpstan-var array<string, AliasPackage[]>
     */
    
private $aliasMap = array();
    
/**
     * @var ConstraintInterface[]
     * @phpstan-var array<string, ConstraintInterface>
     */
    
private $packagesToLoad = array();
    
/**
     * @var ConstraintInterface[]
     * @phpstan-var array<string, ConstraintInterface>
     */
    
private $loadedPackages = array();
    
/**
     * @var array[]
     * @phpstan-var array<int, array<string, array<string, PackageInterface>>>
     */
    
private $loadedPerRepo = array();
    
/**
     * @var BasePackage[]
     */
    
private $packages = array();
    
/**
     * @var BasePackage[]
     */
    
private $unacceptableFixedOrLockedPackages = array();
    
/** @var string[] */
    
private $updateAllowList = array();
    
/** @var array<string, array<PackageInterface>> */
    
private $skippedLoad = array();

    
/**
     * 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 = array();

    
/**
     * 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 = array();
    
/**
     * @var array
     * @phpstan-var array<string, bool>
     */
    
private $updateAllowWarned = array();

    
/** @var int */
    
private $indexCounter 0;

    
/**
     * @param int[] $acceptableStabilities array of stability => BasePackage::STABILITY_* value
     * @phpstan-param array<string, 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
     */
    
public function __construct(array $acceptableStabilities, array $stabilityFlags, array $rootAliases, array $rootReferencesIOInterface $ioEventDispatcher $eventDispatcher nullPoolOptimizer $poolOptimizer null)
    {
        
$this->acceptableStabilities $acceptableStabilities;
        
$this->stabilityFlags $stabilityFlags;
        
$this->rootAliases $rootAliases;
        
$this->rootReferences $rootReferences;
        
$this->eventDispatcher $eventDispatcher;
        
$this->poolOptimizer $poolOptimizer;
        
$this->io $io;
    }

    
/**
     * @param RepositoryInterface[] $repositories
     * @return Pool
     */
    
public function buildPool(array $repositoriesRequest $request)
    {
        if (
$request->getUpdateAllowList()) {
            
$this->updateAllowList $request->getUpdateAllowList();
            
$this->warnAboutNonMatchingUpdateAllowList($request);

            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$packagefalse);
            } 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 (!empty(
$this->packagesToLoad)) {
            
$this->loadPackagesMarkedForLoading($request$repositories);
        }

        foreach (
$this->packages as $i => $package) {
            
// we check all alias related packages at once, so no need to check individual aliases
            // isset also checks non-null value
            
if (!$package instanceof AliasPackage) {
                
$constraint = new Constraint('=='$package->getVersion());
                
$aliasedPackages = array($i => $package);
                if (isset(
$this->aliasMap[spl_object_hash($package)])) {
                    
$aliasedPackages += $this->aliasMap[spl_object_hash($package)];
                }

                
$found false;
                foreach (
$aliasedPackages as $packageOrAlias) {
                    if (
CompilingMatcher::match($constraintConstraint::OP_EQ$packageOrAlias->getVersion())) {
                        
$found true;
                    }
                }
                if (!
$found) {
                    foreach (
$aliasedPackages as $index => $packageOrAlias) {
                        unset(
$this->packages[$index]);
                    }
                }
            }
        }

        if (
$this->eventDispatcher) {
            
$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 = array();
        
$this->packagesToLoad = array();
        
$this->loadedPackages = array();
        
$this->loadedPerRepo = array();
        
$this->packages = array();
        
$this->unacceptableFixedOrLockedPackages = array();
        
$this->maxExtendedReqs = array();
        
$this->skippedLoad = array();
        
$this->indexCounter 0;

        
$pool $this->runOptimizer($request$pool);

        
Intervals::clear();

        return 
$pool;
    }

    
/**
     * @param string $name
     * @return void
     */
    
private function markPackageNameForLoading(Request $request$nameConstraintInterface $constraint)
    {
        
// 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(array($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(array($this->loadedPackages[$name], $constraint), false));
        unset(
$this->loadedPackages[$name]);
    }

    
/**
     * @param RepositoryInterface[] $repositories
     * @return void
     */
    
private function loadPackagesMarkedForLoading(Request $request, array $repositories)
    {
        foreach (
$this->packagesToLoad as $name => $constraint) {
            
$this->loadedPackages[$name] = $constraint;
        }

        
$packageBatch $this->packagesToLoad;
        
$this->packagesToLoad = array();

        foreach (
$repositories as $repoIndex => $repository) {
            if (empty(
$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, isset($this->loadedPerRepo[$repoIndex]) ? $this->loadedPerRepo[$repoIndex] : array());

            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;
                
$this->loadPackage($request$repositories$package, !isset($this->pathRepoUnlocked[$package->getName()]));
            }
        }
    }

    
/**
     * @param bool $propagateUpdate
     * @param RepositoryInterface[] $repositories
     * @return void
     */
    
private function loadPackage(Request $request, array $repositoriesBasePackage $package$propagateUpdate)
    {
        
$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
        
if ($propagateUpdate && 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() || !$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() || !$skippedRootRequires) {
                        
$this->unlockPackage($request$repositories$replace);
                        
$this->markPackageNameForLoading($request$replace$link->getConstraint());
                    } 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
     * @return bool
     */
    
private function isRootRequire(Request $request$name)
    {
        
$rootRequires $request->getRequires();

        return isset(
$rootRequires[$name]);
    }

    
/**
     * @param  string $name
     * @return string[]
     */
    
private function getSkippedRootRequires(Request $request$name)
    {
        if (!isset(
$this->skippedLoad[$name])) {
            return array();
        }

        
$rootRequires $request->getRequires();
        
$matches = array();

        if (isset(
$rootRequires[$name])) {
            return 
array_map(function (PackageInterface $package) use ($name) {
                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
     *
     * @return bool
     */
    
private function isUpdateAllowed(BasePackage $package)
    {
        foreach (
$this->updateAllowList as $pattern => $void) {
            
$patternRegexp BasePackage::packageNameToRegexp($pattern);
            if (
Preg::isMatch($patternRegexp$package->getName())) {
                return 
true;
            }
        }

        return 
false;
    }

    
/**
     * @return void
     */
    
private function warnAboutNonMatchingUpdateAllowList(Request $request)
    {
        foreach (
$this->updateAllowList as $pattern => $void) {
            
$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)) {
                    continue 
2;
                }
            }
            if (
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
     * @param string $name
     * @return void
     */
    
private function unlockPackage(Request $request, array $repositories$name)
    {
        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->packagestrue)) {
                    
$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
                    
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());
                            }
                        }
                    }
                }
            }
        }
    }

    
/**
     * @param RepositoryInterface[] $repositories
     * @param int $index
     * @return void
     */
    
private function removeLoadedPackage(Request $request, array $repositoriesBasePackage $package$index)
    {
        
$repoIndex array_search($package->getRepository(), $repositoriestrue);

        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)]);
        }
    }

    
/**
     * @return Pool
     */
    
private function runOptimizer(Request $requestPool $pool)
    {
        if (
null === $this->poolOptimizer) {
            return 
$pool;
        }

        
$total \count($pool->getPackages());

        
$pool $this->poolOptimizer->optimize($request$pool);

        
$filtered $total \count($pool->getPackages());

        if (
=== $filtered) {
            return 
$pool;
        }

        
$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)
        ), 
trueIOInterface::VERY_VERBOSE);

        return 
$pool;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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;

/**
 * Represents a problem detected while solving dependencies
 *
 * @author Nils Adermann <[email protected]>
 */
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 = array();

    
/** @var int */
    
protected $section 0;

    
/**
     * Add a rule as a reason
     *
     * @param Rule $rule A rule which is a reason for this problem
     * @return void
     */
    
public function addRule(Rule $rule)
    {
        
$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()
    {
        return 
$this->reasons;
    }

    
/**
     * A human readable textual representation of the problem's reasons
     *
     * @param bool $isVerbose
     * @param array<int|string, BasePackage> $installedMap A map of all present packages
     * @param array<Rule[]> $learnedPool
     * @return string
     */
    
public function getPrettyString(RepositorySet $repositorySetRequest $requestPool $pool$isVerbose, array $installedMap = array(), array $learnedPool = array())
    {
        
// TODO doesn't this entirely defeat the purpose of the problem sections? what's the point of sections?
        
$reasons call_user_func_array('array_merge'array_reverse($this->reasons));

        if (
count($reasons) === 1) {
            
reset($reasons);
            
$rule current($reasons);

            if (!
in_array($rule->getReason(), array(Rule::RULE_ROOT_REQUIRERule::RULE_FIXED), true)) {
                throw new 
\LogicException("Single reason problems must contain a request rule.");
            }

            
$reasonData $rule->getReasonData();
            
$packageName $reasonData['packageName'];
            
$constraint $reasonData['constraint'];

            if (isset(
$constraint)) {
                
$packages $pool->whatProvides($packageName$constraint);
            } else {
                
$packages = array();
            }

            if (empty(
$packages)) {
                return 
"\n    ".implode(self::getMissingPackageReason($repositorySet$request$pool$isVerbose$packageName$constraint));
            }
        }

        return 
self::formatDeduplicatedRules($reasons'    '$repositorySet$request$pool$isVerbose$installedMap$learnedPool);
    }

    
/**
     * @param Rule[] $rules
     * @param string $indent
     * @param bool $isVerbose
     * @param array<int|string, BasePackage> $installedMap A map of all present packages
     * @param array<Rule[]> $learnedPool
     * @return string
     * @internal
     */
    
public static function formatDeduplicatedRules($rules$indentRepositorySet $repositorySetRequest $requestPool $pool$isVerbose, array $installedMap = array(), array $learnedPool = array())
    {
        
$messages = array();
        
$templates = array();
        
$parser = new VersionParser;
        
$deduplicatableRuleTypes = array(Rule::RULE_PACKAGE_REQUIRESRule::RULE_PACKAGE_CONFLICT);
        foreach (
$rules as $rule) {
            
$message $rule->getPrettyString($repositorySet$request$pool$isVerbose$installedMap$learnedPool);
            if (
in_array($rule->getReason(), $deduplicatableRuleTypestrue) && Preg::isMatch('{^(?P<package>\S+) (?P<version>\S+) (?P<type>requires|conflicts)}'$message$m)) {
                
$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 = array();
        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($versions1);
                    }
                    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);
    }

    
/**
     * @return bool
     */
    
public function isCausedByLock(RepositorySet $repositorySetRequest $requestPool $pool)
    {
        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
     * @return void
     */
    
protected function addReason($idRule $reason)
    {
        
// 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;
        }
    }

    
/**
     * @return void
     */
    
public function nextSection()
    {
        
$this->section++;
    }

    
/**
     * @internal
     * @param bool $isVerbose
     * @param string $packageName
     * @return array{0: string, 1: string}
     */
    
public static function getMissingPackageReason(RepositorySet $repositorySetRequest $requestPool $pool$isVerbose$packageNameConstraintInterface $constraint null)
    {
        if (
PlatformRepository::isPlatformPackage($packageName)) {
            
// handle php/php-*/hhvm
            
if (=== stripos($packageName'php') || $packageName === 'hhvm') {
                
$version self::getPlatformPackageVersion($pool$packageNamephpversion());

                
$msg "- Root composer.json requires ".$packageName.self::constraintToText($constraint).' but ';

                if (
defined('HHVM_VERSION') || ($packageName === 'hhvm' && count($pool->whatProvides($packageName)) > 0)) {
                    return array(
$msg'your HHVM version does not satisfy that requirement.');
                }

                if (
$packageName === 'hhvm') {
                    return array(
$msg'HHVM was not detected on this machine, make sure it is in your PATH.');
                }

                if (
null === $version) {
                    return array(
$msg'the '.$packageName.' package is disabled by your platform config. Enable it again with "composer config platform.'.$packageName.' --unset".');
                }

                return array(
$msg'your '.$packageName.' version ('$version .') does not satisfy that requirement.');
            }

            
// handle php extensions
            
if (=== stripos($packageName'ext-')) {
                if (
false !== strpos($packageName' ')) {
                    return array(
'- '"PHP extension ".$packageName.' should be required as '.str_replace(' ''-'$packageName).'.');
                }

                
$ext substr($packageName4);
                
$msg "- Root composer.json requires PHP extension ".$packageName.self::constraintToText($constraint).' but ';

                
$version self::getPlatformPackageVersion($pool$packageNamephpversion($ext) ?: '0');
                if (
null === $version) {
                    if (
extension_loaded($ext)) {
                        return array(
                            
$msg,
                            
'the '.$packageName.' package is disabled by your platform config. Enable it again with "composer config platform.'.$packageName.' --unset".',
                        );
                    }

                    return array(
$msg'it is missing from your system. Install or enable PHP\'s '.$ext.' extension.');
                }

                return array(
$msg'it has the wrong version installed ('.$version.').');
            }

            
// handle linked libs
            
if (=== 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 array(
"- Root composer.json requires linked library ".$packageName.self::constraintToText($constraint).' but '$error);
                }

                return array(
"- 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.');
            }
        }

        
$lockedPackage null;
        foreach (
$request->getLockedPackages() as $package) {
            if (
$package->getName() === $packageName) {
                
$lockedPackage $package;
                if (
$pool->isUnacceptableFixedOrLockedPackage($package)) {
                    return array(
"- "$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;
            }
        }

        
// 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
        
if ($packages $repositorySet->findPackages($packageName$constraint)) {
            
$rootReqs $repositorySet->getRootRequires();
            if (isset(
$rootReqs[$packageName])) {
                
$filtered array_filter($packages, function ($p) use ($rootReqs$packageName) {
                    return 
$rootReqs[$packageName]->matches(new Constraint('=='$p->getVersion()));
                });
                if (
=== count($filtered)) {
                    return array(
"- 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().').');
                }
            }

            if (
$lockedPackage) {
                
$fixedConstraint = new Constraint('=='$lockedPackage->getVersion());
                
$filtered array_filter($packages, function ($p) use ($fixedConstraint) {
                    return 
$fixedConstraint->matches(new Constraint('=='$p->getVersion()));
                });
                if (
=== count($filtered)) {
                    return array(
"- 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, function ($p) {
                return !
$p->getRepository() instanceof LockArrayRepository;
            });

            if (!
$nonLockedPackages) {
                return array(
"- 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 array(
"- 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
        
if ($packages $repositorySet->findPackages($packageName$constraintRepositorySet::ALLOW_UNACCEPTABLE_STABILITIES)) {
            
// we must first verify if a valid package would be found in a lower priority repository
            
if ($allReposPackages $repositorySet->findPackages($packageName$constraintRepositorySet::ALLOW_SHADOWED_REPOSITORIES)) {
                return 
self::computeCheckForLowerPrioRepo($pool$isVerbose$packageName$packages$allReposPackages'minimum-stability'$constraint);
            }

            return array(
"- 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
        
if ($packages $repositorySet->findPackages($packageNamenullRepositorySet::ALLOW_UNACCEPTABLE_STABILITIES)) {
            
// we must first verify if a valid package would be found in a lower priority repository
            
if ($allReposPackages $repositorySet->findPackages($packageName$constraintRepositorySet::ALLOW_SHADOWED_REPOSITORIES)) {
                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(), array('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 array(
"- 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 array(
"- Root composer.json requires $packageName, it "'could not be found, it looks like its name is invalid, "'.$illegalChars.'" is not allowed in package names.');
        }

        if (
$providers $repositorySet->getProviders($packageName)) {
            
$maxProviders 20;
            
$providersStr implode(array_map(function ($p) {
                
$description $p['description'] ? ' '.substr($p['description'], 0100) : '';

                return 
"      - ${p['name']}".$description."\n";
            }, 
count($providers) > $maxProviders array_slice($providers0$maxProviders) : $providers));
            if (
count($providers) > $maxProviders 1) {
                
$providersStr .= '      ... and '.(count($providers) - $maxProviders).' more.'."\n";
            }

            return array(
"- 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 array(
"- 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
     * @param bool $isVerbose
     * @param bool $useRemovedVersionGroup
     * @return string
     */
    
public static function getPackageList(array $packages$isVerbosePool $pool nullConstraintInterface $constraint null$useRemovedVersionGroup false)
    {
        
$prepared = array();
        
$hasDefaultBranch = array();
        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 && $constraint) {
                foreach (
$pool->getRemovedVersions($package->getName(), $constraint) as $version => $prettyVersion) {
                    
$prepared[$package->getName()]['versions'][$version] = $prettyVersion;
                }
            }
            if (
$pool && $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 = array();
        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 $packageName
     * @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$packageName$version)
    {
        
$available $pool->whatProvides($packageName);

        if (
count($available)) {
            
$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 '''$selected->getDescription());
            }
        } else {
            return 
null;
        }

        return 
$version;
    }

    
/**
     * @param string[] $versions an array of pretty versions, with normalized versions as keys
     * @param int $max
     * @param int $maxDev
     * @return list<string> a list of pretty versions and '...' where versions were removed
     */
    
private static function condenseVersionList(array $versions$max$maxDev 16)
    {
        if (
count($versions) <= $max) {
            return 
$versions;
        }

        
$filtered = array();
        
$byMajor = array();
        foreach (
$versions as $version => $pretty) {
            if (
=== stripos($version'dev-')) {
                
$byMajor['dev'][] = $pretty;
            } else {
                
$byMajor[Preg::replace('{^(\d+)\..*}''$1'$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
     * @return bool
     */
    
private static function hasMultipleNames(array $packages)
    {
        
$name null;
        foreach (
$packages as $package) {
            if (
$name === null || $name === $package->getName()) {
                
$name $package->getName();
            } else {
                return 
true;
            }
        }

        return 
false;
    }

    
/**
     * @param bool $isVerbose
     * @param string $packageName
     * @param PackageInterface[] $higherRepoPackages
     * @param PackageInterface[] $allReposPackages
     * @param string $reason
     * @return array{0: string, 1: string}
     */
    
private static function computeCheckForLowerPrioRepo(Pool $pool$isVerbose$packageName, array $higherRepoPackages, array $allReposPackages$reasonConstraintInterface $constraint null)
    {
        
$nextRepoPackages = array();
        
$nextRepo null;

        foreach (
$allReposPackages as $package) {
            if (
$nextRepo === null || $nextRepo === $package->getRepository()) {
                
$nextRepoPackages[] = $package;
                
$nextRepo $package->getRepository();
            } else {
                break;
            }
        }

        if (
$higherRepoPackages) {
            
$topPackage reset($higherRepoPackages);
            if (
$topPackage instanceof RootPackageInterface) {
                return array(
                    
"- Root composer.json requires $packageName".self::constraintToText($constraint).', it is ',
                    
'satisfiable by '.self::getPackageList($nextRepoPackages$isVerbose$pool$constraint).' from '.$nextRepo->getRepoName().' but '.$topPackage->getPrettyName().' 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;

            return array(
"- Root composer.json requires $packageName".self::constraintToText($constraint) . ', it is ',
                
'found '.self::getPackageList($nextRepoPackages$isVerbose$pool$constraint).' in the lock file and '.self::getPackageList($higherRepoPackages$isVerbose$pool$constraint).' from '.reset($higherRepoPackages)->getRepository()->getRepoName().' but ' . ($singular 'it does' 'these do') . ' not match your '.$reason.' and ' . ($singular 'is' 'are') . ' therefore not installable. Make sure you either fix the '.$reason.' or avoid updating this package to keep the one from the lock file.', );
        }

        return array(
"- 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
     *
     * @return string
     */
    
protected static function constraintToText(ConstraintInterface $constraint null)
    {
        return 
$constraint ' '.$constraint->getPrettyString() : '';
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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;
use 
Composer\Repository\PlatformRepository;

/**
 * @author Nils Adermann <[email protected]>
 * @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 = array();
    
/** @var array<string, BasePackage[]> */
    
protected $addedPackagesByNames = array();

    public function 
__construct(PolicyInterface $policyPool $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 null)
    {
        
$literals = array(-$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  BasePackage[] $packages   The set of packages to choose from
     * @param  Rule::RULE_*  $reason     A RULE_* constant describing the reason for
     *                                   generating this rule
     * @param  array         $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)
    {
        
$literals = array();
        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 $issuerBasePackage $provider$reason$reasonData null)
    {
        
// ignore self conflict
        
if ($issuer === $provider) {
            return 
null;
        }

        return new 
Rule2Literals(-$issuer->id, -$provider->id$reason$reasonData);
    }

    
/**
     * @param BasePackage[] $packages
     * @param Rule::RULE_* $reason A RULE_* constant
     * @param mixed $reasonData
     * @return Rule
     *
     * @phpstan-param ReasonData $reasonData
     */
    
protected function createMultiConflictRule(array $packages$reason$reasonData)
    {
        
$literals = array();
        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
     *
     * @return void
     */
    
private function addRule($typeRule $newRule null)
    {
        if (!
$newRule) {
            return;
        }

        
$this->rules->add($newRule$type);
    }

    
/**
     * @return void
     */
    
protected function addRulesForPackage(BasePackage $packagePlatformRequirementFilterInterface $platformRequirementFilter)
    {
        
/** @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, array($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(), array($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$possibleRequiresRule::RULE_PACKAGE_REQUIRES$link));

                foreach (
$possibleRequires as $require) {
                    
$workQueue->enqueue($require);
                }
            }
        }
    }

    
/**
     * @return void
     */
    
protected function addConflictRules(PlatformRequirementFilterInterface $platformRequirementFilter)
    {
        
/** @var BasePackage $package */
        
foreach ($this->addedMap as $package) {
            foreach (
$package->getConflicts() as $link) {
                
// even if conlict 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);
                }

                
$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$conflictRule::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));
            }
        }
    }

    
/**
     * @return void
     */
    
protected function addRulesForRequest(Request $requestPlatformRequirementFilterInterface $platformRequirementFilter)
    {
        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(array($package), Rule::RULE_FIXED, array(
                
'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 (
$packages) {
                foreach (
$packages as $package) {
                    
$this->addRulesForPackage($package$platformRequirementFilter);
                }

                
$rule $this->createInstallOneOfRule($packagesRule::RULE_ROOT_REQUIRE, array(
                    
'packageName' => $packageName,
                    
'constraint' => $constraint,
                ));
                
$this->addRule(RuleSet::TYPE_REQUEST$rule);
            }
        }
    }

    
/**
     * @return void
     */
    
protected function addRulesForRootAliases(PlatformRequirementFilterInterface $platformRequirementFilter)
    {
        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);
            }
        }
    }

    
/**
     * @return RuleSet
     */
    
public function getRulesFor(Request $requestPlatformRequirementFilterInterface $platformRequirementFilter null)
    {
        
$platformRequirementFilter $platformRequirementFilter ?: PlatformRequirementFilterFactory::ignoreNothing();

        
$this->addRulesForRequest($request$platformRequirementFilter);

        
$this->addRulesForRootAliases($platformRequirementFilter);

        
$this->addConflictRules($platformRequirementFilter);

        
// Remove references to packages
        
$this->addedMap $this->addedPackagesByNames = array();

        
$rules $this->rules;

        
$this->rules = new RuleSet;

        return 
$rules;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @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 = array();

    
/**
     * @param PackageInterface[] $presentPackages
     * @param PackageInterface[] $resultPackages
     */
    
public function __construct($presentPackages$resultPackages)
    {
        
$this->presentPackages $presentPackages;
        
$this->setResultPackageMaps($resultPackages);
        
$this->operations $this->calculateOperations();
    }

    
/**
     * @return OperationInterface[]
     */
    
public function getOperations()
    {
        return 
$this->operations;
    }

    
/**
     * @param PackageInterface[] $resultPackages
     * @return void
     */
    
private function setResultPackageMaps($resultPackages)
    {
        
$packageSort = function (PackageInterface $aPackageInterface $b) {
            
// 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;
                }
                
// 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 = array();
        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()
    {
        
$operations = array();

        
$presentPackageMap = array();
        
$removeMap = array();
        
$presentAliasMap = array();
        
$removeAliasMap = array();
        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 = array();
        
$processed = array();

        while (!empty(
$stack)) {
            
$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()
    {
        
$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)
    {
        if (!isset(
$this->resultPackagesByName[$link->getTarget()])) {
            return array();
        }

        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)
    {
        
$dlModifyingPluginsNoDeps = array();
        
$dlModifyingPluginsWithDeps = array();
        
$dlModifyingPluginRequires = array();
        
$pluginsNoDeps = array();
        
$pluginsWithDeps = array();
        
$pluginRequires = array();

        foreach (
array_reverse($operationstrue) as $idx => $op) {
            if (
$op instanceof Operation\InstallOperation) {
                
$package $op->getPackage();
            } elseif (
$op instanceof Operation\UpdateOperation) {
                
$package $op->getTargetPackage();
            } else {
                continue;
            }

            
$isDownloadsModifyingPlugin $package->getType() === 'composer-plugin' && ($extra $package->getExtra()) && 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))) {
                
// get the package's requires, but filter out any platform requirements
                
$requires array_filter(array_keys($package->getRequires()), function ($req) {
                    return !
PlatformRepository::isPlatformPackage($req);
                });

                
// is this a plugin with no meaningful dependencies?
                
if ($isDownloadsModifyingPlugin && !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))) {
                
// get the package's requires, but filter out any platform requirements
                
$requires array_filter(array_keys($package->getRequires()), function ($req) {
                    return !
PlatformRepository::isPlatformPackage($req);
                });

                
// is this a plugin with no meaningful dependencies?
                
if ($isPlugin && !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)
    {
        
$uninstOps = array();
        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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class Request
{
    
/**
     * Identifies a partial update for listed packages only, all dependencies will remain at locked versions
     */
    
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.
     */
    
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.
     */
    
const UPDATE_LISTED_WITH_TRANSITIVE_DEPS 2;

    
/** @var ?LockArrayRepository */
    
protected $lockedRepository;
    
/** @var array<string, ConstraintInterface> */
    
protected $requires = array();
    
/** @var array<string, BasePackage> */
    
protected $fixedPackages = array();
    
/** @var array<string, BasePackage> */
    
protected $lockedPackages = array();
    
/** @var array<string, BasePackage> */
    
protected $fixedLockedPackages = array();
    
/** @var string[] */
    
protected $updateAllowList = array();
    
/** @var false|self::UPDATE_* */
    
protected $updateAllowTransitiveDependencies false;

    public function 
__construct(LockArrayRepository $lockedRepository null)
    {
        
$this->lockedRepository $lockedRepository;
    }

    
/**
     * @param string $packageName
     * @return void
     */
    
public function requireName($packageNameConstraintInterface $constraint null)
    {
        
$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.
     *
     * @return void
     */
    
public function fixPackage(BasePackage $package)
    {
        
$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.
     *
     * @return void
     */
    
public function lockPackage(BasePackage $package)
    {
        
$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.
     *
     * @return void
     */
    
public function fixLockedPackage(BasePackage $package)
    {
        
$this->fixedPackages[spl_object_hash($package)] = $package;
        
$this->fixedLockedPackages[spl_object_hash($package)] = $package;
    }

    
/**
     * @return void
     */
    
public function unlockPackage(BasePackage $package)
    {
        unset(
$this->lockedPackages[spl_object_hash($package)]);
    }

    
/**
     * @param string[] $updateAllowList
     * @param false|self::UPDATE_* $updateAllowTransitiveDependencies
     * @return void
     */
    
public function setUpdateAllowList($updateAllowList$updateAllowTransitiveDependencies)
    {
        
$this->updateAllowList $updateAllowList;
        
$this->updateAllowTransitiveDependencies $updateAllowTransitiveDependencies;
    }

    
/**
     * @return string[]
     */
    
public function getUpdateAllowList()
    {
        return 
$this->updateAllowList;
    }

    
/**
     * @return bool
     */
    
public function getUpdateAllowTransitiveDependencies()
    {
        return 
$this->updateAllowTransitiveDependencies !== self::UPDATE_ONLY_LISTED;
    }

    
/**
     * @return bool
     */
    
public function getUpdateAllowTransitiveRootDependencies()
    {
        return 
$this->updateAllowTransitiveDependencies === self::UPDATE_LISTED_WITH_TRANSITIVE_DEPS;
    }

    
/**
     * @return array<string, ConstraintInterface>
     */
    
public function getRequires()
    {
        return 
$this->requires;
    }

    
/**
     * @return array<string, BasePackage>
     */
    
public function getFixedPackages()
    {
        return 
$this->fixedPackages;
    }

    
/**
     * @return bool
     */
    
public function isFixedPackage(BasePackage $package)
    {
        return isset(
$this->fixedPackages[spl_object_hash($package)]);
    }

    
/**
     * @return array<string, BasePackage>
     */
    
public function getLockedPackages()
    {
        return 
$this->lockedPackages;
    }

    
/**
     * @return bool
     */
    
public function isLockedPackage(PackageInterface $package)
    {
        return isset(
$this->lockedPackages[spl_object_hash($package)]) || isset($this->fixedLockedPackages[spl_object_hash($package)]);
    }

    
/**
     * @return array<string, BasePackage>
     */
    
public function getFixedOrLockedPackages()
    {
        return 
array_merge($this->fixedPackages$this->lockedPackages);
    }

    
/**
     * @param bool $packageIds
     * @return array<int|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($packageIds false)
    {
        
$presentMap = array();

        if (
$this->lockedRepository) {
            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 BasePackage[]
     */
    
public function getFixedPackagesMap()
    {
        
$fixedPackagesMap = array();

        foreach (
$this->fixedPackages as $package) {
            
$fixedPackagesMap[$package->getId()] = $package;
        }

        return 
$fixedPackagesMap;
    }

    
/**
     * @return ?LockArrayRepository
     */
    
public function getLockedRepository()
    {
        return 
$this->lockedRepository;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\Constraint\Constraint;

/**
 * @author Nils Adermann <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 */
class DefaultPolicy implements PolicyInterface
{
    
/** @var bool */
    
private $preferStable;
    
/** @var bool */
    
private $preferLowest;

    
/**
     * @param bool $preferStable
     * @param bool $preferLowest
     */
    
public function __construct($preferStable false$preferLowest false)
    {
        
$this->preferStable $preferStable;
        
$this->preferLowest $preferLowest;
    }

    
/**
     * @param string $operator One of Constraint::STR_OP_*
     * @return bool
     *
     * @phpstan-param Constraint::STR_OP_* $operator
     */
    
public function versionCompare(PackageInterface $aPackageInterface $b$operator)
    {
        if (
$this->preferStable && ($stabA $a->getStability()) !== ($stabB $b->getStability())) {
            return 
BasePackage::$stabilities[$stabA] < BasePackage::$stabilities[$stabB];
        }

        
$constraint = new Constraint($operator$b->getVersion());
        
$version = new Constraint('=='$a->getVersion());

        return 
$constraint->matchSpecific($versiontrue);
    }

    
/**
     * @param  int[]  $literals
     * @param  string $requiredPackage
     * @return int[]
     */
    
public function selectPreferredPackages(Pool $pool, array $literals$requiredPackage null)
    {
        
$packages $this->groupLiteralsByName($pool$literals);
        
$policy $this;

        foreach (
$packages as &$nameLiterals) {
            
usort($nameLiterals, function ($a$b) use ($policy$pool$requiredPackage) {
                return 
$policy->compareByPriority($pool$pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackagetrue);
            });
        }

        foreach (
$packages as &$sortedLiterals) {
            
$sortedLiterals $this->pruneToBestVersion($pool$sortedLiterals);
            
$sortedLiterals $this->pruneRemoteAliases($pool$sortedLiterals);
        }

        
$selected \call_user_func_array('array_merge'array_values($packages));

        
// now sort the result across all packages to respect replaces across packages
        
usort($selected, function ($a$b) use ($policy$pool$requiredPackage) {
            return 
$policy->compareByPriority($pool$pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage);
        });

        return 
$selected;
    }

    
/**
     * @param  int[] $literals
     * @return array<string, int[]>
     */
    
protected function groupLiteralsByName(Pool $pool$literals)
    {
        
$packages = array();
        foreach (
$literals as $literal) {
            
$packageName $pool->literalToPackage($literal)->getName();

            if (!isset(
$packages[$packageName])) {
                
$packages[$packageName] = array();
            }
            
$packages[$packageName][] = $literal;
        }

        return 
$packages;
    }

    
/**
     * @protected
     * @param ?string $requiredPackage
     * @param bool $ignoreReplace
     * @return int
     */
    
public function compareByPriority(Pool $poolBasePackage $aBasePackage $b$requiredPackage null$ignoreReplace false)
    {
        
// 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 && false !== ($pos strpos($requiredPackage'/'))) {
                
$requiredVendor substr($requiredPackage0$pos);

                
$aIsSameVendor strpos($a->getName(), $requiredVendor) === 0;
                
$bIsSameVendor strpos($b->getName(), $requiredVendor) === 0;

                if (
$bIsSameVendor !== $aIsSameVendor) {
                    return 
$aIsSameVendor ? -1;
                }
            }
        }

        
// priority equal, sort by package id to make reproducible
        
if ($a->id === $b->id) {
            return 
0;
        }

        return (
$a->id $b->id) ? -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.
     *
     * @return bool
     */
    
protected function replaces(BasePackage $sourceBasePackage $target)
    {
        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  int[] $literals
     * @return int[]
     */
    
protected function pruneToBestVersion(Pool $pool$literals)
    {
        
$operator $this->preferLowest '<' '>';
        
$bestLiterals = array($literals[0]);
        
$bestPackage $pool->literalToPackage($literals[0]);
        foreach (
$literals as $i => $literal) {
            if (
=== $i) {
                continue;
            }

            
$package $pool->literalToPackage($literal);

            if (
$this->versionCompare($package$bestPackage$operator)) {
                
$bestPackage $package;
                
$bestLiterals = array($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  int[] $literals
     * @return int[]
     */
    
protected function pruneRemoteAliases(Pool $pool, array $literals)
    {
        
$hasLocalAlias false;

        foreach (
$literals as $literal) {
            
$package $pool->literalToPackage($literal);

            if (
$package instanceof AliasPackage && $package->isRootPackageAlias()) {
                
$hasLocalAlias true;
                break;
            }
        }

        if (!
$hasLocalAlias) {
            return 
$literals;
        }

        
$selected = array();
        foreach (
$literals as $literal) {
            
$package $pool->literalToPackage($literal);

            if (
$package instanceof AliasPackage && $package->isRootPackageAlias()) {
                
$selected[] = $literal;
            }
        }

        return 
$selected;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Console;

use 
Composer\Pcre\Preg;
use 
Symfony\Component\Console\Formatter\OutputFormatter;
use 
Symfony\Component\Console\Formatter\OutputFormatterStyle;

/**
 * @author Jordi Boggiano <[email protected]>
 */
class HtmlOutputFormatter extends OutputFormatter
{
    
/** @var array<int, string> */
    
private static $availableForegroundColors = array(
        
30 => 'black',
        
31 => 'red',
        
32 => 'green',
        
33 => 'yellow',
        
34 => 'blue',
        
35 => 'magenta',
        
36 => 'cyan',
        
37 => 'white',
    );
    
/** @var array<int, string> */
    
private static $availableBackgroundColors = array(
        
40 => 'black',
        
41 => 'red',
        
42 => 'green',
        
43 => 'yellow',
        
44 => 'blue',
        
45 => 'magenta',
        
46 => 'cyan',
        
47 => 'white',
    );
    
/** @var array<int, string> */
    
private static $availableOptions = array(
        
=> 'bold',
        
=> 'underscore',
        
//5 => 'blink',
        //7 => 'reverse',
        //8 => 'conceal'
    
);

    
/**
     * @param array<string, OutputFormatterStyle> $styles Array of "name => FormatterStyle" instances
     */
    
public function __construct(array $styles = array())
    {
        
parent::__construct(true$styles);
    }

    
/**
     * @param ?string $message
     *
     * @return string
     */
    
public function format($message)
    {
        
$formatted parent::format($message);

        
$clearEscapeCodes '(?:39|49|0|22|24|25|27|28)';

        
// TODO in 2.3 replace with Closure::fromCallable and then use Preg::replaceCallback
        
return preg_replace_callback("{\033\[([0-9;]+)m(.*?)\033\[(?:".$clearEscapeCodes.";)*?".$clearEscapeCodes."m}s", array($this'formatHtml'), $formatted);
    }

    
/**
     * @param string[] $matches
     *
     * @return string
     */
    
private function formatHtml($matches)
    {
        
$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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\NullIO;
use 
Composer\Util\Filesystem;
use 
Composer\Util\Platform;
use 
Composer\Util\Silencer;
use 
Symfony\Component\Console\Application as BaseApplication;
use 
Symfony\Component\Console\Exception\CommandNotFoundException;
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\OutputInterface;
use 
Seld\JsonLint\ParsingException;
use 
Composer\Command;
use 
Composer\Composer;
use 
Composer\Factory;
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 <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 * @author François Pluchino <[email protected]>
 */
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 Store the initial working directory at startup time
     */
    
private $initialWorkingDirectory;

    public function 
__construct()
    {
        static 
$shutdownRegistered false;

        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'));
        }

        if (!
$shutdownRegistered) {
            if (
function_exists('pcntl_async_signals') && function_exists('pcntl_signal')) {
                
pcntl_async_signals(true);
                
pcntl_signal(SIGINT, function ($sig) {
                    exit(
130);
                });
            }

            
$shutdownRegistered true;

            
register_shutdown_function(function () {
                
$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->io = new NullIO();

        
$this->initialWorkingDirectory getcwd();

        
parent::__construct('Composer'Composer::getVersion());
    }

    
/**
     * @return int
     */
    
public function run(InputInterface $input nullOutputInterface $output null)
    {
        if (
null === $output) {
            
$output Factory::createOutput();
        }

        return 
parent::run($input$output);
    }

    
/**
     * @return int
     */
    
public function doRun(InputInterface $inputOutputInterface $output)
    {
        
$this->disablePluginsByDefault $input->hasParameterOption('--no-plugins');
        
$this->disableScriptsByDefault $input->hasParameterOption('--no-scripts');

        if (
Platform::getEnv('COMPOSER_NO_INTERACTION') || !Platform::isTty(defined('STDIN') ? STDIN fopen('php://stdin''r'))) {
            
$input->setInteractive(false);
        }

        
$io $this->io = new ConsoleIO($input$output, new HelperSet(array(
            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'trueIOInterface::DEBUG);
            
Platform::putEnv('COMPOSER_CACHE_DIR'Platform::isWindows() ? 'nul' '/dev/null');
        }

        
// switch working dir
        
if ($newWorkDir $this->getNewWorkingDir($input)) {
            
$oldWorkingDir getcwd();
            
chdir($newWorkDir);
            
$this->initialWorkingDirectory $newWorkDir;
            
$io->writeError('Changed CWD to ' getcwd(), trueIOInterface::DEBUG);
        }

        
// determine command name to be executed without including plugin commands
        
$commandName '';
        if (
$name $this->getCommandName($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 ($io->isInteractive() && !$newWorkDir && !in_array($commandName, array('''list''init''about''help''diagnose''self-update''global''create-project''outdated'), true) && !file_exists(Factory::getComposerFile()) && ($useParentDirIfNoJsonAvailable $this->getUseParentDirConfigValue()) !== false) {
            
$dir dirname(getcwd());
            
$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->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 getcwd();
                        
chdir($dir);
                    }
                    break;
                }
                
$dir dirname($dir);
            }
        }

        
// 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(array('--version''-V'))
            && (
                
// not a composer command, so try loading plugin ones
                
false === $commandName
                
// list command requires plugin commands to show them
                
|| in_array($commandName, array('''list''help'), true)
            );

        if (
$mayNeedPluginCommand && !$this->disablePluginsByDefault && !$this->hasPluginCommands) {
            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 {
                        
$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;
        }

        
// determine command name to be executed incl plugin commands, and check if it's a proxy command
        
$isProxyCommand false;
        if (
$name $this->getCommandName($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'
            
), trueIOInterface::DEBUG);

            if (
PHP_VERSION_ID 50302) {
                
$io->writeError('<warning>Composer only officially supports PHP 5.3.2 and above, you will most likely encounter problems with your PHP '.PHP_VERSION.', upgrading is strongly recommended.</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 (
                !
Platform::isWindows()
                && 
function_exists('exec')
                && !
Platform::getEnv('COMPOSER_ALLOW_SUPERUSER')
                && (
ini_get('open_basedir') || !file_exists('/.dockerenv'))
            ) {
                if (
function_exists('posix_getuid') && posix_getuid() === 0) {
                    if (
$commandName !== 'self-update' && $commandName !== 'selfupdate') {
                        
$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;
                            }
                        }
                    }
                    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');
            }

            
// Check system temp folder for usability as it can cause weird runtime issues otherwise
            
Silencer::call(function () use ($io) {
                
$tempfile sys_get_temp_dir() . '/temp-' md5(microtime());
                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];
                                }

                                
$this->add(new Command\ScriptAliasCommand($script$description));
                            }
                        }
                    }
                }
            }
        }

        try {
            if (
$input->hasParameterOption('--profile')) {
                
$startTime microtime(true);
                
$this->io->enableDebugging($startTime);
            }

            
$result parent::doRun($input$output);

            
// chdir back to $oldWorkingDir if set
            
if (isset($oldWorkingDir)) {
                
Silencer::call('chdir'$oldWorkingDir);
            }

            if (isset(
$startTime)) {
                
$io->writeError('<info>Memory usage: '.round(memory_get_usage() / 1024 10242).'MiB (peak: '.round(memory_get_peak_usage() / 1024 10242).'MiB), time: '.round(microtime(true) - $startTime2).'s');
            }

            
restore_error_handler();

            return 
$result;
        } catch (
ScriptExecutionException $e) {
            return 
$e->getCode();
        } catch (
\Exception $e) {
            
$ghe = new GithubActionError($this->io);
            
$ghe->emit($e->getMessage());

            
$this->hintCommonErrors($e);

            
restore_error_handler();

            throw 
$e;
        }
    }

    
/**
     * @param  InputInterface    $input
     * @throws \RuntimeException
     * @return string
     */
    
private function getNewWorkingDir(InputInterface $input)
    {
        
$workingDir $input->getParameterOption(array('--working-dir''-d'));
        if (
false !== $workingDir && !is_dir($workingDir)) {
            throw new 
\RuntimeException('Invalid working directory specified, '.$workingDir.' does not exist.');
        }

        return 
$workingDir;
    }

    
/**
     * @return void
     */
    
private function hintCommonErrors(\Exception $exception)
    {
        
$io $this->getIO();

        
Silencer::suppress();
        try {
            
$composer $this->getComposer(falsetrue);
            if (
$composer) {
                
$config $composer->getConfig();

                
$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)
                    || ((
$df disk_free_space($dir sys_get_temp_dir())) !== false && $df $minSpaceFree)
                ) {
                    
$io->writeError('<error>The disk hosting '.$dir.' is full, this may be the cause of the following exception</error>'trueIOInterface::QUIET);
                }
            }
        } catch (
\Exception $e) {
        }
        
Silencer::restore();

        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>'trueIOInterface::QUIET);
            
$io->writeError('<error>Check https://getcomposer.org/doc/articles/troubleshooting.md#-the-system-cannot-find-the-path-specified-windows- for details</error>'trueIOInterface::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>'trueIOInterface::QUIET);
            
$io->writeError('<error>Check https://getcomposer.org/doc/articles/troubleshooting.md#proc-open-fork-failed-errors for details</error>'trueIOInterface::QUIET);
        }

        if (
$exception instanceof ProcessTimedOutException) {
            
$io->writeError('<error>The following exception is caused by a process timeout</error>'trueIOInterface::QUIET);
            
$io->writeError('<error>Check https://getcomposer.org/doc/06-config.md#process-timeout for details</error>'trueIOInterface::QUIET);
        }

        if (
$hints HttpDownloader::getExceptionHints($exception)) {
            foreach (
$hints as $hint) {
                
$io->writeError($hinttrueIOInterface::QUIET);
            }
        }
    }

    
/**
     * @param  bool                    $required
     * @param  bool|null               $disablePlugins
     * @param  bool|null               $disableScripts
     * @throws JsonValidationException
     * @throws \InvalidArgumentException
     * @return ?\Composer\Composer If $required is true then the return value is guaranteed
     */
    
public function getComposer($required true$disablePlugins null$disableScripts null)
    {
        if (
null === $disablePlugins) {
            
$disablePlugins $this->disablePluginsByDefault;
        }
        if (
null === $disableScripts) {
            
$disableScripts $this->disableScriptsByDefault;
        }

        if (
null === $this->composer) {
            try {
                
$this->composer Factory::create($this->ionull$disablePlugins$disableScripts);
            } catch (
\InvalidArgumentException $e) {
                if (
$required) {
                    
$this->io->writeError($e->getMessage());
                    
// TODO composer 2.3 simplify to $this->areExceptionsCaught()
                    
if (!method_exists($this'areExceptionsCaught') || $this->areExceptionsCaught()) {
                        exit(
1);
                    }
                    throw 
$e;
                }
            } catch (
JsonValidationException $e) {
                if (
$required) {
                    throw 
$e;
                }
            }
        }

        return 
$this->composer;
    }

    
/**
     * Removes the cached composer instance
     *
     * @return void
     */
    
public function resetComposer()
    {
        
$this->composer null;
        if (
method_exists($this->getIO(), 'resetAuthentications')) {
            
$this->getIO()->resetAuthentications();
        }
    }

    
/**
     * @return IOInterface
     */
    
public function getIO()
    {
        return 
$this->io;
    }

    
/**
     * @return string
     */
    
public function getHelp()
    {
        return 
self::$logo parent::getHelp();
    }

    
/**
     * Initializes all the composer commands.
     * @return \Symfony\Component\Console\Command\Command[]
     */
    
protected function getDefaultCommands()
    {
        
$commands array_merge(parent::getDefaultCommands(), array(
            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\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(),
        ));

        if (
strpos(__FILE__'phar:') === 0) {
            
$commands[] = new Command\SelfUpdateCommand();
        }

        return 
$commands;
    }

    
/**
     * @return string
     */
    
public function getLongVersion()
    {
        if (
Composer::BRANCH_ALIAS_VERSION && Composer::BRANCH_ALIAS_VERSION !== '@package_branch_alias_version'.'@') {
            return 
sprintf(
                
'<info>%s</info> version <comment>%s (%s)</comment> %s',
                
$this->getName(),
                
Composer::BRANCH_ALIAS_VERSION,
                
$this->getVersion(),
                
Composer::RELEASE_DATE
            
);
        }

        return 
parent::getLongVersion() . ' ' Composer::RELEASE_DATE;
    }

    
/**
     * @return InputDefinition
     */
    
protected function getDefaultInputDefinition()
    {
        
$definition parent::getDefaultInputDefinition();
        
$definition->addOption(new InputOption('--profile'nullInputOption::VALUE_NONE'Display timing and memory usage information'));
        
$definition->addOption(new InputOption('--no-plugins'nullInputOption::VALUE_NONE'Whether to disable plugins.'));
        
$definition->addOption(new InputOption('--no-scripts'nullInputOption::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'nullInputOption::VALUE_NONE'Prevent use of the cache'));

        return 
$definition;
    }

    
/**
     * @return Command\BaseCommand[]
     */
    
private function getPluginCommands()
    {
        
$commands = array();

        
$composer $this->getComposer(falsefalse);
        if (
null === $composer) {
            
$composer Factory::createGlobal($this->io);
        }

        if (
null !== $composer) {
            
$pm $composer->getPluginManager();
            foreach (
$pm->getPluginCapabilities('Composer\Plugin\Capability\CommandProvider', array('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
     */
    
public function getInitialWorkingDirectory()
    {
        return 
$this->initialWorkingDirectory;
    }

    
/**
     * @return 'prompt'|bool
     */
    
private function getUseParentDirConfigValue()
    {
        
$config Factory::createConfig($this->io);

        return 
$config->get('use-parent-dir');
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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;
    }

    
/**
     * @param string      $message
     * @param null|string $file
     * @param null|int    $line
     *
     * @return void
     */
    
public function emit($message$file null$line null)
    {
        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);
            }
        }
    }
    
    
/**
     * @param string $data
     * @return string
     */
    
private function escapeData($data) {
        
// 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;
    }
    
    
/**
     * @param string $property
     * @return string
     */
    
private function escapeProperty($property) {
        
// 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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 
Symfony\Component\Console\Input\InputOption;
use 
Symfony\Component\Console\Input\InputArgument;
use 
Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Fabien Potencier <[email protected]>
 */
class RunScriptCommand extends BaseCommand
{
    
/**
     * @var string[] Array with command events
     */
    
protected $scriptEvents = array(
        
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,
    );

    
/**
     * @return void
     */
    
protected function configure()
    {
        
$this
            
->setName('run-script')
            ->
setAliases(array('run'))
            ->
setDescription('Runs the scripts defined in composer.json.')
            ->
setDefinition(array(
                new 
InputArgument('script'InputArgument::OPTIONAL'Script name to run.'),
                new 
InputArgument('args'InputArgument::IS_ARRAY InputArgument::OPTIONAL''),
                new 
InputOption('timeout'nullInputOption::VALUE_REQUIRED'Sets script timeout in seconds, or 0 for never.'),
                new 
InputOption('dev'nullInputOption::VALUE_NONE'Sets the dev mode.'),
                new 
InputOption('no-dev'nullInputOption::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
EOT
            )
        ;
    }

    protected function 
execute(InputInterface $inputOutputInterface $output)
    {
        if (
$input->getOption('list')) {
            return 
$this->listScripts($output);
        }
        if (!
$input->getArgument('script')) {
            throw new 
\RuntimeException('Missing required argument "script"');
        }

        
$script $input->getArgument('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->getComposer();
        
$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);
    }

    
/**
     * @return int
     */
    
protected function listScripts(OutputInterface $output)
    {
        
$scripts $this->getComposer()->getPackage()->getScripts();

        if (!
count($scripts)) {
            return 
0;
        }

        
$io $this->getIO();
        
$io->writeError('<info>scripts:</info>');
        
$table = array();
        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
            
}
            
$table[] = array('  '.$name$description);
        }

        
$this->renderTable($table$output);

        return 
0;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\Json\JsonFile;
use 
Composer\Json\JsonValidationException;
use 
Composer\Package\BasePackage;
use 
Composer\Package\CompletePackageInterface;
use 
Composer\Package\Package;
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\Util\Filesystem;
use 
Composer\Util\ProcessExecutor;
use 
Composer\Semver\Constraint\Constraint;
use 
Composer\Util\Silencer;
use 
Symfony\Component\Console\Input\ArrayInput;
use 
Symfony\Component\Console\Input\InputInterface;
use 
Symfony\Component\Console\Input\InputOption;
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 <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 */
class InitCommand extends BaseCommand
{
    
/** @var ?CompositeRepository */
    
protected $repos;

    
/** @var array<string, string> */
    
private $gitConfig;

    
/** @var RepositorySet[] */
    
private $repositorySets;

    
/**
     * @inheritDoc
     *
     * @return void
     */
    
protected function configure()
    {
        
$this
            
->setName('init')
            ->
setDescription('Creates a basic composer.json file in current directory.')
            ->
setDefinition(array(
                new 
InputOption('name'nullInputOption::VALUE_REQUIRED'Name of the package'),
                new 
InputOption('description'nullInputOption::VALUE_REQUIRED'Description of package'),
                new 
InputOption('author'nullInputOption::VALUE_REQUIRED'Author name of package'),
                
// new InputOption('version', null, InputOption::VALUE_NONE, 'Version of package'),
                
new InputOption('type'nullInputOption::VALUE_OPTIONAL'Type of package (e.g. library, project, metapackage, composer-plugin)'),
                new 
InputOption('homepage'nullInputOption::VALUE_REQUIRED'Homepage of package'),
                new 
InputOption('require'nullInputOption::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"'),
                new 
InputOption('require-dev'nullInputOption::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"'),
                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'nullInputOption::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
            )
        ;
    }

    
/**
     * @inheritDoc
     *
     * @return int
     * @throws \Seld\JsonLint\ParsingException
     */
    
protected function execute(InputInterface $inputOutputInterface $output)
    {
        
$io $this->getIO();

        
$allowlist = array('name''description''author''type''homepage''require''require-dev''stability''license''autoload');
        
$options array_filter(array_intersect_key($input->getOptions(), array_flip($allowlist)));

        if (isset(
$options['name']) && !Preg::isMatch('{^[a-z0-9_.-]+/[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 (
$repositories) {
            
$config Factory::createConfig($io);
            foreach (
$repositories as $repo) {
                
$options['repositories'][] = RepositoryFactory::configFromString($io$config$repotrue);
            }
        }

        if (isset(
$options['stability'])) {
            
$options['minimum-stability'] = $options['stability'];
            unset(
$options['stability']);
        }

        
$options['require'] = isset($options['require']) ? $this->formatRequirements($options['require']) : new \stdClass;
        if (array() === 
$options['require']) {
            
$options['require'] = new \stdClass;
        }

        if (isset(
$options['require-dev'])) {
            
$options['require-dev'] = $this->formatRequirements($options['require-dev']);
            if (array() === 
$options['require-dev']) {
                
$options['require-dev'] = new \stdClass;
            }
        }

        
// --autoload - create autoload object
        
$autoloadPath null;
        if (isset(
$options['autoload'])) {
            
$autoloadPath $options['autoload'];
            
$namespace $this->namespaceFromPackageName($input->getOption('name'));
            
$options['autoload'] = (object) array(
                
'psr-4' => array(
                    
$namespace '\\' => $autoloadPath,
                ),
            );
        }

        
$file = new JsonFile(Factory::getComposerFile());
        
$json JsonFile::encode($options);

        if (
$input->isInteractive()) {
            
$io->writeError(array(''$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($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 $inputOutputInterface $output)
    {
        
$git $this->getGitConfig();
        
$io $this->getIO();
        
/** @var FormatterHelper $formatter */
        
$formatter $this->getHelperSet()->get('formatter');

        
// initialize repos if configured
        
$repositories $input->getOption('repository');
        if (
$repositories) {
            
$config Factory::createConfig($io);
            
$repos = array(new PlatformRepository);
            
$createDefaultPackagistRepo true;
            foreach (
$repositories as $repo) {
                
$repoConfig RepositoryFactory::configFromString($io$config$repotrue);
                if (
                    (isset(
$repoConfig['packagist']) && $repoConfig === array('packagist' => false))
                    || (isset(
$repoConfig['packagist.org']) && $repoConfig === array('packagist.org' => false))
                ) {
                    
$createDefaultPackagistRepo false;
                    continue;
                }
                
$repos[] = RepositoryFactory::createRepo($io$config$repoConfig);
            }

            if (
$createDefaultPackagistRepo) {
                
$repos[] = RepositoryFactory::createRepo($io$config, array(
                    
'type' => 'composer',
                    
'url' => 'https://repo.packagist.org',
                ));
            }

            
$this->repos = new CompositeRepository($repos);
            unset(
$repos$config$repositories);
        }

        
$io->writeError(array(
            
'',
            
$formatter->formatBlock('Welcome to the Composer config generator''bg=blue;fg=white'true),
            
'',
        ));

        
// namespace
        
$io->writeError(array(
            
'',
            
'This command will guide you through creating your composer.json config.',
            
'',
        ));

        
$cwd realpath(".");

        if (!
$name $input->getOption('name')) {
            
$name basename($cwd);
            
$name Preg::replace('{(?:([a-z])([A-Z])|([A-Z])([A-Z][a-z]))}''\\1\\3-\\2\\4'$name);
            
$name strtolower($name);
            if (!empty(
$_SERVER['COMPOSER_DEFAULT_VENDOR'])) {
                
$name $_SERVER['COMPOSER_DEFAULT_VENDOR'] . '/' $name;
            } elseif (isset(
$git['github.user'])) {
                
$name $git['github.user'] . '/' $name;
            } elseif (!empty(
$_SERVER['USERNAME'])) {
                
$name $_SERVER['USERNAME'] . '/' $name;
            } elseif (!empty(
$_SERVER['USER'])) {
                
$name $_SERVER['USER'] . '/' $name;
            } elseif (
get_current_user()) {
                
$name get_current_user() . '/' $name;
            } else {
                
// package names must be in the format foo/bar
                
$name .= '/' $name;
            }
            
$name strtolower($name);
        }

        
$name $io->askAndValidate(
            
'Package name (<vendor>/<name>) [<comment>'.$name.'</comment>]: ',
            function (
$value) use ($name) {
                if (
null === $value) {
                    return 
$name;
                }

                if (!
Preg::isMatch('{^[a-z0-9_.-]+/[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') ?: false;
        
$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);
            }
        }

        
$self $this;
        
$author $io->askAndValidate(
            
'Author [<comment>'.$author.'</comment>, n to skip]: ',
            function (
$value) use ($self$author) {
                if (
$value === 'n' || $value === 'no') {
                    return;
                }
                
$value $value ?: $author;
                
$author $self->parseAuthorString($value);

                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>]: ',
            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') ?: false;
        
$type $io->ask(
            
'Package Type (e.g. library, project, metapackage, composer-plugin) [<comment>'.$type.'</comment>]: ',
            
$type
        
);
        
$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
        
);
        
$input->setOption('license'$license);

        
$io->writeError(array('''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 = array();
        if (
$require || $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 = array();
        if (
$requireDev || $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($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]: ',
            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);
    }

    
/**
     * @private
     * @param  string $author
     * @return array{name: string, email: string}
     */
    
public function parseAuthorString($author)
    {
        if (
Preg::isMatch('/^(?P<name>[- .,\p{L}\p{N}\p{Mn}\'’"()]+) <(?P<email>.+?)>$/u'$author$match)) {
            if (
$this->isValidEmail($match['email'])) {
                return array(
                    
'name' => trim($match['name']),
                    
'email' => $match['email'],
                );
            }
        }

        throw new 
\InvalidArgumentException(
            
'Invalid author string.  Must be in the format: '.
            
'John Smith <[email protected]>'
        
);
    }

    
/**
     * @return CompositeRepository
     */
    
protected function getRepos()
    {
        if (!
$this->repos) {
            
$this->repos = new CompositeRepository(array_merge(
                array(new 
PlatformRepository),
                
RepositoryFactory::defaultRepos($this->getIO())
            ));
        }

        return 
$this->repos;
    }

    
/**
     * @param array<string> $requires
     * @param PlatformRepository|null $platformRepo
     * @param string $preferredStability
     * @param bool $checkProvidedVersions
     * @param bool $fixed
     *
     * @return array<string>
     * @throws \Exception
     */
    
final protected function determineRequirements(InputInterface $inputOutputInterface $output$requires = array(), PlatformRepository $platformRepo null$preferredStability 'stable'$checkProvidedVersions true$fixed false)
    {
        if (
$requires) {
            
$requires $this->normalizeRequirements($requires);
            
$result = array();
            
$io $this->getIO();

            foreach (
$requires as $requirement) {
                if (!isset(
$requirement['version'])) {
                    
// determine the best version automatically
                    
list($name$version) = $this->findBestVersionAndNameForPackage($input$requirement['name'], $platformRepo$preferredStabilitynullnull$fixed);
                    
$requirement['version'] = $version;

                    
// replace package name from packagist.org
                    
$requirement['name'] = $name;

                    
$io->writeError(sprintf(
                        
'Using version <info>%s</info> for <info>%s</info>',
                        
$requirement['version'],
                        
$requirement['name']
                    ));
                } else {
                    
// check that the specified version/constraint exists before we proceed
                    
list($name) = $this->findBestVersionAndNameForPackage($input$requirement['name'], $platformRepo$preferredStability$checkProvidedVersions $requirement['version'] : null'dev'$fixed);

                    
// replace package name from packagist.org
                    
$requirement['name'] = $name;
                }

                
$result[] = $requirement['name'] . ' ' $requirement['version'];
            }

            return 
$result;
        }

        
$versionParser = new VersionParser();

        
// Collect existing packages
        
$composer $this->getComposer(false);
        
$installedRepo $composer $composer->getRepositoryManager()->getLocalRepository() : null;
        
$existingPackages = array();
        if (
$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)) {
                
// Remove existing packages from search results.
                
foreach ($matches as $position => $foundPackage) {
                    if (
in_array($foundPackage['name'], $existingPackagestrue)) {
                        unset(
$matches[$position]);
                    }
                }
                
$matches array_values($matches);

                
$exactMatch null;
                
$choices = array();
                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);
                    if (
$foundPackage['name'] === $package) {
                        
$exactMatch true;
                        break;
                    }
                }

                
// no match, prompt which to pick
                
if (!$exactMatch) {
                    
$io->writeError(array(
                        
'',
                        
sprintf('Found <info>%s</info> packages matching <info>%s</info>'count($matches), $package),
                        
'',
                    ));

                    
$io->writeError($choices);
                    
$io->writeError('');

                    
$validator = function ($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,
                        
false
                    
);
                }

                
// no constraint yet, determine the best version automatically
                
if (false !== $package && false === strpos($package' ')) {
                    
$validator = function ($input) {
                        
$input trim($input);

                        return 
$input ?: false;
                    };

                    
$constraint $io->askAndValidate(
                        
'Enter the version constraint to require (or leave blank to use the latest version): ',
                        
$validator,
                        
3,
                        
false
                    
);

                    if (
false === $constraint) {
                        list(, 
$constraint) = $this->findBestVersionAndNameForPackage($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[] = substr($package0strpos($package' '));
                }
            }
        }

        return 
$requires;
    }

    
/**
     * @param string $author
     *
     * @return array<int, array{name: string, email: string}>
     */
    
protected function formatAuthors($author)
    {
        return array(
$this->parseAuthorString($author));
    }

    
/**
     * Extract namespace from package's vendor name.
     *
     * new_projects.acme-extra/package-name becomes "NewProjectsAcmeExtra\PackageName"
     *
     * @param string $packageName
     *
     * @return string|null
     */
    
public function namespaceFromPackageName($packageName)
    {
        if (!
$packageName || strpos($packageName'/') === false) {
            return 
null;
        }

        
$namespace array_map(
            function (
$part) {
                
$part Preg::replace('/[^a-z0-9]/i'' '$part);
                
$part ucwords($part);

                return 
str_replace(' '''$part);
            },
            
explode('/'$packageName)
        );

        return 
join('\\'$namespace);
    }

    
/**
     * @return array<string, string>
     */
    
protected function getGitConfig()
    {
        if (
null !== $this->gitConfig) {
            return 
$this->gitConfig;
        }

        
$finder = new ExecutableFinder();
        
$gitBin $finder->find('git');

        
// TODO in v2.3 always call with an array
        
if (method_exists('Symfony\Component\Process\Process''fromShellCommandline')) {
            
$cmd = new Process(array($gitBin'config''-l'));
        } else {
            
// @phpstan-ignore-next-line
            
$cmd = new Process(sprintf('%s config -l'ProcessExecutor::escape($gitBin)));
        }
        
$cmd->run();

        if (
$cmd->isSuccessful()) {
            
$this->gitConfig = array();
            
Preg::matchAll('{^([^=]+)=(.*)$}m'$cmd->getOutput(), $matches);
            foreach (
$matches[1] as $key => $match) {
                
$this->gitConfig[$match] = $matches[2][$key];
            }

            return 
$this->gitConfig;
        }

        return 
$this->gitConfig = array();
    }

    
/**
     * Checks the local .gitignore file for the Composer vendor directory.
     *
     * Tested patterns include:
     *  "/$vendor"
     *  "$vendor"
     *  "$vendor/"
     *  "/$vendor/"
     *  "/$vendor/*"
     *  "$vendor/*"
     *
     * @param string $ignoreFile
     * @param string $vendor
     *
     * @return bool
     */
    
protected function hasVendorIgnore($ignoreFile$vendor 'vendor')
    {
        if (!
file_exists($ignoreFile)) {
            return 
false;
        }

        
$pattern sprintf('{^/?%s(/\*?)?$}'preg_quote($vendor));

        
$lines file($ignoreFileFILE_IGNORE_NEW_LINES);
        foreach (
$lines as $line) {
            if (
Preg::isMatch($pattern$line)) {
                return 
true;
            }
        }

        return 
false;
    }

    
/**
     * @param string $ignoreFile
     * @param string $vendor
     *
     * @return void
     */
    
protected function addVendorIgnore($ignoreFile$vendor '/vendor/')
    {
        
$contents "";
        if (
file_exists($ignoreFile)) {
            
$contents file_get_contents($ignoreFile);

            if (
strpos($contents"\n") !== 0) {
                
$contents .= "\n";
            }
        }

        
file_put_contents($ignoreFile$contents $vendor"\n");
    }

    
/**
     * @param string $email
     *
     * @return bool
     */
    
protected function isValidEmail($email)
    {
        
// assume it's valid if we can't validate it
        
if (!function_exists('filter_var')) {
            return 
true;
        }

        
// php <5.3.3 has a very broken email validator, so bypass checks
        
if (PHP_VERSION_ID 50303) {
            return 
true;
        }

        return 
false !== filter_var($emailFILTER_VALIDATE_EMAIL);
    }

    
/**
     * @param string|null $minimumStability
     *
     * @return RepositorySet
     */
    
private function getRepositorySet(InputInterface $input$minimumStability null)
    {
        
$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 string
     */
    
private function getMinimumStability(InputInterface $input)
    {
        if (
$input->hasOption('stability')) {
            return 
VersionParser::normalizeStability($input->getOption('stability') ?: 'stable');
        }

        
$file Factory::getComposerFile();
        if (
is_file($file) && Filesystem::isReadable($file) && is_array($composer json_decode(file_get_contents($file), true))) {
            if (!empty(
$composer['minimum-stability'])) {
                return 
VersionParser::normalizeStability($composer['minimum-stability']);
            }
        }

        return 
'stable';
    }

    
/**
     * 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.
     *
     * @param  string                    $name
     * @param  PlatformRepository|null   $platformRepo
     * @param  string                    $preferredStability
     * @param  string|null               $requiredVersion
     * @param  string                    $minimumStability
     * @param  bool                      $fixed
     * @throws \InvalidArgumentException
     * @return array{string, string}     name version
     */
    
private function findBestVersionAndNameForPackage(InputInterface $input$namePlatformRepository $platformRepo null$preferredStability 'stable'$requiredVersion null$minimumStability null$fixed null)
    {
        
// handle ignore-platform-reqs flag if present
        
$ignorePlatformReqs false;
        if (
$input->hasOption('ignore-platform-reqs') && $input->hasOption('ignore-platform-req')) {
            
$ignorePlatformReqs $input->getOption('ignore-platform-reqs') ?: ($input->getOption('ignore-platform-req') ?: false);
        }
        
$platformRequirementFilter PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs);

        
// find the latest version allowed in this repo set
        
$versionSelector = new VersionSelector($this->getRepositorySet($input$minimumStability), $platformRepo);
        
$effectiveMinimumStability $minimumStability ?: $this->getMinimumStability($input);

        
$package $versionSelector->findBestCandidate($name$requiredVersion$preferredStability$platformRequirementFilter);

        if (!
$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 array(
$name$requiredVersion ?: '*');
            }

            
// Check whether the package requirements were the problem
            
if (!($platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter) && ($candidate $versionSelector->findBestCandidate($name$requiredVersion$preferredStabilityPlatformRequirementFilterFactory::ignoreAll()))) {
                throw new 
\InvalidArgumentException(sprintf(
                    
'Package %s%s has requirements incompatible with your PHP version, PHP extensions and Composer version' $this->getPlatformExceptionDetails($candidate$platformRepo),
                    
$name,
                    
$requiredVersion ' at version '.$requiredVersion ''
                
));
            }
            
// Check whether the minimum stability was the problem but the package exists
            
if ($package $versionSelector->findBestCandidate($name$requiredVersion$preferredStability$platformRequirementFilterRepositorySet::ALLOW_UNACCEPTABLE_STABILITIES)) {
                
// we must first verify if a valid package would be found in a lower priority repository
                
if ($allReposPackage $versionSelector->findBestCandidate($name$requiredVersion$preferredStability$platformRequirementFilterRepositorySet::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 required version was the problem
            
if ($requiredVersion && $package $versionSelector->findBestCandidate($namenull$preferredStability$platformRequirementFilter)) {
                
// we must first verify if a valid package would be found in a lower priority repository
                
if ($allReposPackage $versionSelector->findBestCandidate($name$requiredVersion$preferredStabilityPlatformRequirementFilterFactory::ignoreNothing(), 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 constraint 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 package %s in a version matching "%s" and a stability matching "'.$effectiveMinimumStability.'".',
                    
$name,
                    
$requiredVersion
                
));
            }
            
// Check whether the PHP version was the problem for all versions
            
if (!($platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter) && ($candidate $versionSelector->findBestCandidate($namenull$preferredStabilityPlatformRequirementFilterFactory::ignoreAll(), RepositorySet::ALLOW_UNACCEPTABLE_STABILITIES))) {
                
$additional '';
                if (
false === $versionSelector->findBestCandidate($namenull$preferredStabilityPlatformRequirementFilterFactory::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 (
$similar) {
                if (
in_array($name$similartrue)) {
                    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
                    
));
                }

                throw new 
\InvalidArgumentException(sprintf(
                    
"Could not find package %s.\n\nDid you mean " . (count($similar) > '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 array(
            
$package->getPrettyName(),
            
$fixed $package->getPrettyVersion() : $versionSelector->findRecommendedRequireVersion($package),
        );
    }

    
/**
     * @return string
     */
    
private function getPlatformExceptionDetails(PackageInterface $candidatePlatformRepository $platformRepo null)
    {
        
$details = array();
        if (!
$platformRepo) {
            return 
'';
        }

        foreach (
$candidate->getRequires() as $link) {
            if (!
PlatformRepository::isPlatformPackage($link->getTarget())) {
                continue;
            }
            
$platformPkg $platformRepo->findPackage($link->getTarget(), '*');
            if (!
$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 (!
$details) {
            return 
'';
        }

        return 
':'.PHP_EOL.'  - ' implode(PHP_EOL.'  - '$details);
    }

    
/**
     * @param string $package
     *
     * @return array<string>
     */
    
private function findSimilar($package)
    {
        try {
            
$results $this->repos->search($package);
        } catch (
\Exception $e) {
            
// ignore search errors
            
return array();
        }
        
$similarPackages = array();

        
$installedRepo $this->getComposer()->getRepositoryManager()->getLocalRepository();

        foreach (
$results as $result) {
            if (
$installedRepo->findPackage($result['name'], '*')) {
                
// Ignore installed package
                
continue;
            }
            
$similarPackages[$result['name']] = levenshtein($package$result['name']);
        }
        
asort($similarPackages);

        return 
array_keys(array_slice($similarPackages05));
    }

    
/**
     * @return void
     */
    
private function updateDependencies(OutputInterface $output)
    {
        try {
            
$updateCommand $this->getApplication()->find('update');
            
$this->getApplication()->resetComposer();
            
$updateCommand->run(new ArrayInput(array()), $output);
        } catch (
\Exception $e) {
            
$this->getIO()->writeError('Could not update dependencies. Run `composer update` to see more information.');
        }
    }

    
/**
     * @return void
     */
    
private function runDumpAutoloadCommand(OutputInterface $output)
    {
        try {
            
$command $this->getApplication()->find('dump-autoload');
            
$this->getApplication()->resetComposer();
            
$command->run(new ArrayInput(array()), $output);
        } catch (
\Exception $e) {
            
$this->getIO()->writeError('Could not run dump-autoload.');
        }
    }

    
/**
     * @param array<string, string|array<string>> $options
     * @return bool
     */
    
private function hasDependencies($options)
    {
        
$requires = (array) $options['require'];
        
$devRequires = isset($options['require-dev']) ? (array) $options['require-dev'] : array();

        return !empty(
$requires) || !empty($devRequires);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\Input\InputOption;
use 
Symfony\Component\Console\Input\InputArgument;
use 
Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <[email protected]>
 */
class ScriptAliasCommand extends BaseCommand
{
    
/** @var string */
    
private $script;
    
/** @var string */
    
private $description;

    
/**
     * @param string $script
     * @param string $description
     */
    
public function __construct($script$description)
    {
        
$this->script $script;
        
$this->description = empty($description) ? 'Runs the '.$script.' script as defined in composer.json.' $description;

        
parent::__construct();
    }

    
/**
     * @return void
     */
    
protected function configure()
    {
        
$this
            
->setName($this->script)
            ->
setDescription($this->description)
            ->
setDefinition(array(
                new 
InputOption('dev'nullInputOption::VALUE_NONE'Sets the dev mode.'),
                new 
InputOption('no-dev'nullInputOption::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
EOT
            )
        ;
    }

    
/**
     * @return int
     */
    
protected function execute(InputInterface $inputOutputInterface $output)
    {
        
$composer $this->getComposer();

        
$args $input->getArguments();

        return 
$composer->getEventDispatcher()->dispatchScript($this->script$input->getOption('dev') || !$input->getOption('no-dev'), $args['args']);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use 
Composer\Util\Filesystem;
use 
Symfony\Component\Console\Input\InputInterface;
use 
Symfony\Component\Console\Input\InputArgument;
use 
Symfony\Component\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\Util\Silencer;

/**
 * @author Jérémy Romey <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 */
class RequireCommand extends InitCommand
{
    
/** @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')
            ->
setDescription('Adds required packages to your composer.json and installs them.')
            ->
setDefinition(array(
                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"'),
                new 
InputOption('dev'nullInputOption::VALUE_NONE'Add requirement to require-dev.'),
                new 
InputOption('dry-run'nullInputOption::VALUE_NONE'Outputs the operations but will not execute anything (implicitly enables --verbose).'),
                new 
InputOption('prefer-source'nullInputOption::VALUE_NONE'Forces installation from package sources when possible, including VCS information.'),
                new 
InputOption('prefer-dist'nullInputOption::VALUE_NONE'Forces installation from package dist (default behavior).'),
                new 
InputOption('prefer-install'nullInputOption::VALUE_REQUIRED'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).'),
                new 
InputOption('fixed'nullInputOption::VALUE_NONE'Write fixed version to the composer.json.'),
                new 
InputOption('no-suggest'nullInputOption::VALUE_NONE'DEPRECATED: This flag does not exist anymore.'),
                new 
InputOption('no-progress'nullInputOption::VALUE_NONE'Do not output download progress.'),
                new 
InputOption('no-update'nullInputOption::VALUE_NONE'Disables the automatic update of the dependencies (implies --no-install).'),
                new 
InputOption('no-install'nullInputOption::VALUE_NONE'Skip the install step after updating the composer.lock file.'),
                new 
InputOption('update-no-dev'nullInputOption::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.'),
                new 
InputOption('update-with-all-dependencies''W'InputOption::VALUE_NONE'Allows all inherited dependencies to be updated, including those that are root requirements.'),
                new 
InputOption('with-dependencies'nullInputOption::VALUE_NONE'Alias for --update-with-dependencies'),
                new 
InputOption('with-all-dependencies'nullInputOption::VALUE_NONE'Alias for --update-with-all-dependencies'),
                new 
InputOption('ignore-platform-req'nullInputOption::VALUE_REQUIRED InputOption::VALUE_IS_ARRAY'Ignore a specific platform requirement (php & ext- packages).'),
                new 
InputOption('ignore-platform-reqs'nullInputOption::VALUE_NONE'Ignore all platform requirements (php & ext- packages).'),
                new 
InputOption('prefer-stable'nullInputOption::VALUE_NONE'Prefer stable versions of dependencies.'),
                new 
InputOption('prefer-lowest'nullInputOption::VALUE_NONE'Prefer lowest versions of dependencies.'),
                new 
InputOption('sort-packages'nullInputOption::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'nullInputOption::VALUE_NONE'Use APCu to cache found/not-found classes.'),
                new 
InputOption('apcu-autoloader-prefix'nullInputOption::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
EOT
            )
        ;
    }

    
/**
     * @return int
     * @throws \Seld\JsonLint\ParsingException
     */
    
protected function execute(InputInterface $inputOutputInterface $output)
    {
        if (
function_exists('pcntl_async_signals') && function_exists('pcntl_signal')) {
            
pcntl_async_signals(true);
            
pcntl_signal(SIGINT, array($this'revertComposerFile'));
            
pcntl_signal(SIGTERM, array($this'revertComposerFile'));
            
pcntl_signal(SIGHUP, array($this'revertComposerFile'));
        }

        
$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;

        
// 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) && !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') {
                
$io->writeError('<error>"--fixed" option is allowed for "project" package types only to prevent possible misuses.</error>');

                if (empty(
$config['type'])) {
                    
$io->writeError('<error>If your package is not library, you should explicitly specify "type" parameter in composer.json.</error>');
                }

                return 
1;
            }
        }

        
$composer $this->getComposer(true$input->getOption('no-plugins'));
        
$repos $composer->getRepositoryManager()->getRepositories();

        
$platformOverrides $composer->getConfig()->get('platform') ?: array();
        
// initialize $this->repos as it is used by the parent InitCommand
        
$this->repos = new CompositeRepository(array_merge(
            array(
$platformRepo = new PlatformRepository(array(), $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'),
                
$input->getOption('fixed')
            );
        } catch (
\Exception $e) {
            if (
$this->newlyCreated) {
                
$this->revertComposerFile(false);

                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;
        }

        
$requireKey $input->getOption('dev') ? 'require-dev' 'require';
        
$removeKey $input->getOption('dev') ? 'require' 'require-dev';
        
$requirements $this->formatRequirements($requirements);

        
// 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;
            }
            
$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 would 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) > '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;
                    }

                    list(
$requireKey$removeKey) = array($removeKey$requireKey);
                }
            }
        }

        
$sortPackages $input->getOption('sort-packages') || $composer->getConfig()->get('sort-packages');

        
$this->firstRequire $this->newlyCreated;
        if (!
$this->firstRequire) {
            
$composerDefinition $this->json->read();
            if (empty(
$composerDefinition['require']) && empty($composerDefinition['require-dev'])) {
                
$this->firstRequire true;
            }
        }

        if (!
$input->getOption('dry-run') && !$this->updateFileCleanly($this->json$requirements$requireKey$removeKey$sortPackages)) {
            
$composerDefinition $this->json->read();
            foreach (
$requirements 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);
        }

        
$io->writeError('<info>'.$this->file.' has been '.($this->newlyCreated 'created' 'updated').'</info>');

        if (
$input->getOption('no-update')) {
            return 
0;
        }

        
$composer->getPluginManager()->deactivateInstalledPlugins();

        try {
            return 
$this->doUpdate($input$output$io$requirements$requireKey$removeKey);
        } catch (
\Exception $e) {
            if (!
$this->dependencyResolutionCompleted) {
                
$this->revertComposerFile(false);
            }
            throw 
$e;
        }
    }

    
/**
     * @param array<string, string> $newRequirements
     * @param string $requireKey
     * @return string[]
     */
    
private function getInconsistentRequireKeys(array $newRequirements$requireKey)
    {
        
$requireKeys $this->getPackagesByRequireKey();
        
$inconsistentRequirements = array();
        foreach (
$requireKeys as $package => $packageRequireKey) {
            if (!isset(
$newRequirements[$package])) {
                continue;
            }
            if (
$requireKey !== $packageRequireKey) {
                
$inconsistentRequirements[] = $package;
            }
        }

        return 
$inconsistentRequirements;
    }

    
/**
     * @return array<string, string>
     */
    
private function getPackagesByRequireKey()
    {
        
$composerDefinition $this->json->read();
        
$require = array();
        
$requireDev = array();

        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')
        );
    }

    
/**
     * @private
     * @return void
     */
    
public function markSolverComplete()
    {
        
$this->dependencyResolutionCompleted true;
    }

    
/**
     * @param array<string, string> $requirements
     * @param string $requireKey
     * @param string $removeKey
     * @return int
     * @throws \Exception
     */
    
private function doUpdate(InputInterface $inputOutputInterface $outputIOInterface $io, array $requirements$requireKey$removeKey)
    {
        
// Update packages
        
$this->resetComposer();
        
$composer $this->getComposer(true$input->getOption('no-plugins'), $input->getOption('no-scripts'));

        
$this->dependencyResolutionCompleted false;
        
$composer->getEventDispatcher()->addListener(InstallerEvents::PRE_OPERATIONS_EXEC, array($this'markSolverComplete'), 10000);

        if (
$input->getOption('dry-run')) {
            
$rootPackage $composer->getPackage();
            
$links = array(
                
'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']);
        }

        
$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);

        
$ignorePlatformReqs $input->getOption('ignore-platform-reqs') ?: ($input->getOption('ignore-platform-req') ?: false);
        list(
$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(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs))
            ->
setPreferStable($input->getOption('prefer-stable'))
            ->
setPreferLowest($input->getOption('prefer-lowest'))
        ;

        
// 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) {
            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(false);
        }

        return 
$status;
    }

    
/**
     * @param array<string, string> $new
     * @param string $requireKey
     * @param string $removeKey
     * @param bool $sortPackages
     * @return bool
     */
    
private function updateFileCleanly(JsonFile $json, array $new$requireKey$removeKey$sortPackages)
    {
        
$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 $inputOutputInterface $output)
    {
        return;
    }

    
/**
     * @param bool $hardExit
     * @return void
     */
    
public function revertComposerFile($hardExit true)
    {
        
$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);
            }
        }

        if (
$hardExit) {
            exit(
1);
        }
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 
Symfony\Component\Console\Input\InputArgument;
use 
Symfony\Component\Console\Input\InputOption;

/**
 * @author Niels Keurentjes <[email protected]>
 */
class DependsCommand extends BaseDependencyCommand
{
    
/**
     * Configure command metadata.
     *
     * @return void
     */
    
protected function configure()
    {
        
$this
            
->setName('depends')
            ->
setAliases(array('why'))
            ->
setDescription('Shows which packages cause the given package to be installed.')
            ->
setDefinition(array(
                new 
InputArgument(self::ARGUMENT_PACKAGEInputArgument::REQUIRED'Package to inspect'),
                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'),
            ))
            ->
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
            )
        ;
    }

    
/**
     * Execute the function.
     *
     * @return int
     */
    
protected function execute(InputInterface $inputOutputInterface $output)
    {
        return 
parent::doExecute($input$output);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\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 
Symfony\Component\Console\Input\InputArgument;
use 
Symfony\Component\Console\Input\InputInterface;
use 
Symfony\Component\Console\Input\InputOption;
use 
Symfony\Component\Console\Output\OutputInterface;
use 
Symfony\Component\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;

/**
 * Install a package as new project into new directory.
 *
 * @author Benjamin Eberlei <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 * @author Tobias Munk <[email protected]>
 * @author Nils Adermann <[email protected]>
 */
class CreateProjectCommand extends BaseCommand
{
    
/**
     * @var SuggestedPackagesReporter
     */
    
protected $suggestedPackagesReporter;

    
/**
     * @return void
     */
    
protected function configure()
    {
        
$this
            
->setName('create-project')
            ->
setDescription('Creates new project from a package into given directory.')
            ->
setDefinition(array(
                new 
InputArgument('package'InputArgument::OPTIONAL'Package name to be installed'),
                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'nullInputOption::VALUE_NONE'Forces installation from package sources when possible, including VCS information.'),
                new 
InputOption('prefer-dist'nullInputOption::VALUE_NONE'Forces installation from package dist (default behavior).'),
                new 
InputOption('prefer-install'nullInputOption::VALUE_REQUIRED'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).'),
                new 
InputOption('repository'nullInputOption::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'nullInputOption::VALUE_REQUIRED'DEPRECATED: Use --repository instead.'),
                new 
InputOption('add-repository'nullInputOption::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'nullInputOption::VALUE_NONE'Enables installation of require-dev packages (enabled by default, only present for BC).'),
                new 
InputOption('no-dev'nullInputOption::VALUE_NONE'Disables installation of require-dev packages.'),
                new 
InputOption('no-custom-installers'nullInputOption::VALUE_NONE'DEPRECATED: Use no-plugins instead.'),
                new 
InputOption('no-scripts'nullInputOption::VALUE_NONE'Whether to prevent execution of all defined scripts in the root package.'),
                new 
InputOption('no-progress'nullInputOption::VALUE_NONE'Do not output download progress.'),
                new 
InputOption('no-secure-http'nullInputOption::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'nullInputOption::VALUE_NONE'Whether to prevent deleting the vcs folder.'),
                new 
InputOption('remove-vcs'nullInputOption::VALUE_NONE'Whether to force deletion of the vcs folder without prompting.'),
                new 
InputOption('no-install'nullInputOption::VALUE_NONE'Whether to skip installation of the package dependencies.'),
                new 
InputOption('ignore-platform-req'nullInputOption::VALUE_REQUIRED InputOption::VALUE_IS_ARRAY'Ignore a specific platform requirement (php & ext- packages).'),
                new 
InputOption('ignore-platform-reqs'nullInputOption::VALUE_NONE'Ignore all platform requirements (php & ext- packages).'),
                new 
InputOption('ask'nullInputOption::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
            )
        ;
    }

    
/**
     * @return int
     */
    
protected function execute(InputInterface $inputOutputInterface $output)
    {
        
$config Factory::createConfig();
        
$io $this->getIO();

        list(
$preferSource$preferDist) = $this->getPreferredInstallOptions($config$inputtrue);

        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')) {
            
$parts explode("/"strtolower($input->getArgument('package')), 2);
            
$input->setArgument('directory'$io->ask('New project directory [<comment>'.array_pop($parts).'</comment>]: '));
        }

        
$ignorePlatformReqs $input->getOption('ignore-platform-reqs') ?: ($input->getOption('ignore-platform-req') ?: false);

        return 
$this->installProject(
            
$io,
            
$config,
            
$input,
            
$input->getArgument('package'),
            
$input->getArgument('directory'),
            
$input->getArgument('version'),
            
$input->getOption('stability'),
            
$preferSource,
            
$preferDist,
            !
$input->getOption('no-dev'),
            
$input->getOption('repository') ?: $input->getOption('repository-url'),
            
$input->getOption('no-plugins'),
            
$input->getOption('no-scripts'),
            
$input->getOption('no-progress'),
            
$input->getOption('no-install'),
            
PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs),
            !
$input->getOption('no-secure-http'),
            
$input->getOption('add-repository')
        );
    }

    
/**
     * @param string|null               $packageName
     * @param string|null               $directory
     * @param string|null               $packageVersion
     * @param string                    $stability
     * @param bool                      $preferSource
     * @param bool                      $preferDist
     * @param bool                      $installDevPackages
     * @param string|array<string>|null $repositories
     * @param bool                      $disablePlugins
     * @param bool                      $disableScripts
     * @param bool                      $noProgress
     * @param bool                      $noInstall
     * @param bool                      $secureHttp
     * @param bool                      $addRepository
     *
     * @return int
     * @throws \Exception
     */
    
public function installProject(IOInterface $ioConfig $configInputInterface $input$packageName null$directory null$packageVersion null$stability 'stable'$preferSource false$preferDist false$installDevPackages false$repositories null$disablePlugins false$disableScripts false$noProgress false$noInstall falsePlatformRequirementFilterInterface $platformRequirementFilter null$secureHttp true$addRepository false)
    {
        
$oldCwd 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($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 Factory::create($ionull$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(), $repotrue);
                
$composerJsonRepositoriesConfig $composer->getConfig()->getRepositories();
                
$name RepositoryFactory::generateRepositoryName($index$repoConfig$composerJsonRepositoriesConfig);
                
$configSource = new JsonConfigSource(new JsonFile('composer.json'));

                if (
                    (isset(
$repoConfig['packagist']) && $repoConfig === array('packagist' => false))
                    || (isset(
$repoConfig['packagist.org']) && $repoConfig === array('packagist.org' => false))
                ) {
                    
$configSource->addRepository('packagist.org'false);
                } else {
                    
$configSource->addRepository($name$repoConfigfalse);
                }

                
$composer Factory::create($ionull$disablePlugins);
            }
        }

        
$process = new ProcessExecutor($io);
        
$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();
        list(
$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'));

            if (!
$composer->getLocker()->isLocked()) {
                
$installer->setUpdate(true);
            }

            if (
$disablePlugins) {
                
$installer->disablePlugins();
            }

            
$status $installer->run();
            if (
!== $status) {
                return 
$status;
            }
        }

        
$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(getcwd())->ignoreVCS(false)->ignoreDotFiles(false);
            foreach (array(
'.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($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);
        
$vendorComposerDir $config->get('vendor-dir').'/composer';
        if (
is_dir($vendorComposerDir) && $fs->isDirEmpty($vendorComposerDir)) {
            
Silencer::call('rmdir'$vendorComposerDir);
            
$vendorDir $config->get('vendor-dir');
            if (
is_dir($vendorDir) && $fs->isDirEmpty($vendorDir)) {
                
Silencer::call('rmdir'$vendorDir);
            }
        }

        return 
0;
    }

    
/**
     * @param string             $packageName
     * @param string|null        $directory
     * @param string|null        $packageVersion
     * @param string|null        $stability
     * @param bool               $preferSource
     * @param bool               $preferDist
     * @param bool               $installDevPackages
     * @param array<string>|null $repositories
     * @param bool               $disablePlugins
     * @param bool               $disableScripts
     * @param bool               $noProgress
     * @param bool               $secureHttp
     *
     * @return bool
     * @throws \Exception
     */
    
protected function installRootPackage(IOInterface $ioConfig $config$packageNamePlatformRequirementFilterInterface $platformRequirementFilter$directory null$packageVersion null$stability 'stable'$preferSource false$preferDist false$installDevPackages false, array $repositories null$disablePlugins false$disableScripts false$noProgress false$secureHttp true)
    {
        if (!
$secureHttp) {
            
$config->merge(array('config' => array('secure-http' => false)), Config::SOURCE_COMMAND);
        }

        
$parser = new VersionParser();
        
$requirements $parser->parseNameVersionPairs(array($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("/"$name2);
            
$directory getcwd() . DIRECTORY_SEPARATOR array_pop($parts);
        }

        
$process = new ProcessExecutor($io);
        
$fs = new Filesystem($process);
        if (!
$fs->isAbsolutePath($directory)) {
            
$directory getcwd() . DIRECTORY_SEPARATOR $directory;
        }

        
$io->writeError('<info>Creating a "' $packageName '" project at "' $fs->findShortestPath(getcwd(), $directorytrue) . '"</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::isMatch('{^[^,\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 Factory::create($io$config->all(), $disablePlugins);
        
$config $composer->getConfig();
        
$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$repotrue);
                if (
                    (isset(
$repoConfig['packagist']) && $repoConfig === array('packagist' => false))
                    || (isset(
$repoConfig['packagist.org']) && $repoConfig === array('packagist.org' => false))
                ) {
                    continue;
                }
                
$repositorySet->addRepository(RepositoryFactory::createRepo($io$config$repoConfig$rm));
            }
        }

        
$platformOverrides $config->get('platform') ?: array();
        
$platformRepo = new PlatformRepository(array(), $platformOverrides);

        
// find the latest version if there are multiple
        
$versionSelector = new VersionSelector($repositorySet$platformRepo);
        
$package $versionSelector->findBestCandidate($name$packageVersion$stability$platformRequirementFilter);

        if (!
$package) {
            
$errorMessage "Could not find package $name with " . ($packageVersion "version $packageVersion"stability $stability");
            if (!(
$platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter) && $versionSelector->findBestCandidate($name$packageVersion$stabilityPlatformRequirementFilterFactory::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 for unix-like systems
        
if (function_exists('pcntl_async_signals') && function_exists('pcntl_signal')) {
            @
mkdir($directory0777true);
            if (
$realDir realpath($directory)) {
                
pcntl_async_signals(true);
                
pcntl_signal(SIGINT, function () use ($realDir) {
                    
$fs = new Filesystem();
                    
$fs->removeDirectory($realDir);
                    exit(
130);
                });
            }
        }
        
// handler Ctrl+C for Windows on PHP 7.4+
        
if (function_exists('sapi_windows_set_ctrl_handler') && PHP_SAPI === 'cli') {
            @
mkdir($directory0777true);
            if (
$realDir realpath($directory)) {
                
sapi_windows_set_ctrl_handler(function () use ($realDir) {
                    
$fs = new Filesystem();
                    
$fs->removeDirectory($realDir);
                    exit(
130);
                });
            }
        }

        
// 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(), array(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);

        
Platform::putEnv('COMPOSER_ROOT_VERSION'$package->getPrettyVersion());

        return 
$installedFromVcs;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use 
Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
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 <[email protected]>
 */
class DumpAutoloadCommand extends BaseCommand
{
    
/**
     * @return void
     */
    
protected function configure()
    {
        
$this
            
->setName('dump-autoload')
            ->
setAliases(array('dumpautoload'))
            ->
setDescription('Dumps the autoloader.')
            ->
setDefinition(array(
                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'nullInputOption::VALUE_NONE'Use APCu to cache found/not-found classes.'),
                new 
InputOption('apcu-prefix'nullInputOption::VALUE_REQUIRED'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu'),
                new 
InputOption('dev'nullInputOption::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'nullInputOption::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'nullInputOption::VALUE_REQUIRED InputOption::VALUE_IS_ARRAY'Ignore a specific platform requirement (php & ext- packages).'),
                new 
InputOption('ignore-platform-reqs'nullInputOption::VALUE_NONE'Ignore all platform requirements (php & ext- packages).'),
            ))
            ->
setHelp(
                <<<EOT
<info>php composer.phar dump-autoload</info>

Read more at https://getcomposer.org/doc/03-cli.md#dump-autoload-dumpautoload-
EOT
            )
        ;
    }

    
/**
     * @return int
     */
    
protected function execute(InputInterface $inputOutputInterface $output)
    {
        
$composer $this->getComposer();

        
$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();

        
$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 (
$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>');
        }

        
$ignorePlatformReqs $input->getOption('ignore-platform-reqs') ?: ($input->getOption('ignore-platform-req') ?: false);

        
$generator $composer->getAutoloadGenerator();
        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(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs));
        
$numberOfClasses $generator->dump($config$localRepo$package$installationManager'composer'$optimize);

        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>');
        }

        return 
0;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use 
Composer\Package\AliasPackage;
use 
Composer\Package\BasePackage;
use 
Composer\Pcre\Preg;
use 
Composer\Plugin\CommandEvent;
use 
Composer\Plugin\PluginEvents;
use 
Symfony\Component\Console\Input\InputInterface;
use 
Symfony\Component\Console\Input\InputOption;
use 
Symfony\Component\Console\Input\InputArgument;
use 
Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <[email protected]>
 */
class ReinstallCommand extends BaseCommand
{
    
/**
     * @return void
     */
    
protected function configure()
    {
        
$this
            
->setName('reinstall')
            ->
setDescription('Uninstalls and reinstalls the given package names')
            ->
setDefinition(array(
                new 
InputOption('prefer-source'nullInputOption::VALUE_NONE'Forces installation from package sources when possible, including VCS information.'),
                new 
InputOption('prefer-dist'nullInputOption::VALUE_NONE'Forces installation from package dist (default behavior).'),
                new 
InputOption('prefer-install'nullInputOption::VALUE_REQUIRED'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).'),
                new 
InputOption('no-autoloader'nullInputOption::VALUE_NONE'Skips autoloader generation'),
                new 
InputOption('no-progress'nullInputOption::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'nullInputOption::VALUE_NONE'Use APCu to cache found/not-found classes.'),
                new 
InputOption('apcu-autoloader-prefix'nullInputOption::VALUE_REQUIRED'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'),
                new 
InputOption('ignore-platform-req'nullInputOption::VALUE_REQUIRED InputOption::VALUE_IS_ARRAY'Ignore a specific platform requirement (php & ext- packages).'),
                new 
InputOption('ignore-platform-reqs'nullInputOption::VALUE_NONE'Ignore all platform requirements (php & ext- packages).'),
                new 
InputArgument('packages'InputArgument::IS_ARRAY InputArgument::REQUIRED'List of package names to reinstall, can include a wildcard (*) to match any substring.'),
            ))
            ->
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 $inputOutputInterface $output)
    {
        
$io $this->getIO();

        
$composer $this->getComposer(true$input->getOption('no-plugins'), $input->getOption('no-scripts'));

        
$localRepo $composer->getRepositoryManager()->getLocalRepository();
        
$packagesToReinstall = array();
        
$packageNamesToReinstall = array();
        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 (!
$packagesToReinstall) {
            
$io->writeError('<warning>Found no packages to reinstall, aborting.</warning>');

            return 
1;
        }

        
$uninstallOperations = array();
        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(), $packageNamesToReinstalltrue)) {
                unset(
$presentPackages[$index]);
            }
        }
        
$transaction = new Transaction($presentPackages$resultPackages);
        
$installOperations $transaction->getOperations();

        
// reverse-sort the uninstalls based on the install order
        
$installOrder = array();
        foreach (
$installOperations as $index => $op) {
            if (
$op instanceof InstallOperation && !$op->getPackage() instanceof AliasPackage) {
                
$installOrder[$op->getPackage()->getName()] = $index;
            }
        }
        
usort($uninstallOperations, function ($a$b) use ($installOrder) {
            return 
$installOrder[$b->getPackage()->getName()] - $installOrder[$a->getPackage()->getName()];
        });

        
$commandEvent = new CommandEvent(PluginEvents::COMMAND'reinstall'$input$output);
        
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        
$config $composer->getConfig();
        list(
$preferSource$preferDist) = $this->getPreferredInstallOptions($config$input);

        
$installationManager $composer->getInstallationManager();
        
$downloadManager $composer->getDownloadManager();
        
$package $composer->getPackage();

        
$ignorePlatformReqs $input->getOption('ignore-platform-reqs') ?: ($input->getOption('ignore-platform-req') ?: false);

        
$installationManager->setOutputProgress(!$input->getOption('no-progress'));
        if (
$input->getOption('no-plugins')) {
            
$installationManager->disablePlugins();
        }

        
$downloadManager->setPreferSource($preferSource);
        
$downloadManager->setPreferDist($preferDist);

        
$installationManager->execute($localRepo$uninstallOperationstrue);
        
$installationManager->execute($localRepo$installOperationstrue);

        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(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs));
            
$generator->dump($config$localRepo$package$installationManager'composer'$optimize);
        }

        return 
0;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\Output\OutputInterface;

/**
 * @author David Neilsen <[email protected]>
 */
class ClearCacheCommand extends BaseCommand
{
    
/**
     * @return void
     */
    
protected function configure()
    {
        
$this
            
->setName('clear-cache')
            ->
setAliases(array('clearcache''cc'))
            ->
setDescription('Clears composer\'s internal package cache.')
            ->
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
            )
        ;
    }

    
/**
     * @return int
     */
    
protected function execute(InputInterface $inputOutputInterface $output)
    {
        
$config Factory::createConfig();
        
$io $this->getIO();

        
$cachePaths = array(
            
'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) {
            
$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;
            }

            
$io->writeError("<info>Clearing cache ($key): $cachePath</info>");
            
$cache->clear();
        }

        
$io->writeError('<info>All caches cleared.</info>');

        return 
0;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
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\Input\InputInterface;
use 
Symfony\Component\Console\Input\InputOption;
use 
Symfony\Component\Console\Input\InputArgument;
use 
Symfony\Component\Console\Output\OutputInterface;
use 
Composer\Package\BasePackage;

/**
 * @author Pierre du Plessis <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 */
class RemoveCommand extends BaseCommand
{
    
/**
     * @return void
     */
    
protected function configure()
    {
        
$this
            
->setName('remove')
            ->
setDescription('Removes a package from the require or require-dev.')
            ->
setDefinition(array(
                new 
InputArgument('packages'InputArgument::IS_ARRAY InputArgument::REQUIRED'Packages that should be removed.'),
                new 
InputOption('dev'nullInputOption::VALUE_NONE'Removes a package from the require-dev section.'),
                new 
InputOption('dry-run'nullInputOption::VALUE_NONE'Outputs the operations but will not execute anything (implicitly enables --verbose).'),
                new 
InputOption('no-progress'nullInputOption::VALUE_NONE'Do not output download progress.'),
                new 
InputOption('no-update'nullInputOption::VALUE_NONE'Disables the automatic update of the dependencies (implies --no-install).'),
                new 
InputOption('no-install'nullInputOption::VALUE_NONE'Skip the install step after updating the composer.lock file.'),
                new 
InputOption('update-no-dev'nullInputOption::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. (Deprecrated, 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.'),
                new 
InputOption('with-all-dependencies'nullInputOption::VALUE_NONE'Alias for --update-with-all-dependencies'),
                new 
InputOption('no-update-with-dependencies'nullInputOption::VALUE_NONE'Does not allow inherited dependencies to be updated with explicit dependencies.'),
                new 
InputOption('unused'nullInputOption::VALUE_NONE'Remove all packages which are locked but not required by any other package.'),
                new 
InputOption('ignore-platform-req'nullInputOption::VALUE_REQUIRED InputOption::VALUE_IS_ARRAY'Ignore a specific platform requirement (php & ext- packages).'),
                new 
InputOption('ignore-platform-reqs'nullInputOption::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'nullInputOption::VALUE_NONE'Use APCu to cache found/not-found classes.'),
                new 
InputOption('apcu-autoloader-prefix'nullInputOption::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
EOT
            )
        ;
    }

    
/**
     * @return void
     */
    
protected function interact(InputInterface $inputOutputInterface $output)
    {
        if (
$input->getOption('unused')) {
            
$composer $this->getComposer();
            
$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 = array();
            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 = array();
            foreach (
$lockedPackages as $package) {
                
$unused[] = $package->getName();
            }
            
$input->setArgument('packages'array_merge($input->getArgument('packages'), $unused));

            if (!
$input->getArgument('packages')) {
                
$this->getIO()->writeError('<info>No unused packages to remove</info>');
                
$this->setCode(function () {
                    return 
0;
                });
            }
        }
    }

    
/**
     * @return int
     * @throws \Seld\JsonLint\ParsingException
     */
    
protected function execute(InputInterface $inputOutputInterface $output)
    {
        
$packages $input->getArgument('packages');
        
$packages array_map('strtolower'$packages);

        
$file Factory::getComposerFile();

        
$jsonFile = new JsonFile($file);
        
$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 (array('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 = array();
        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]) && $matches Preg::grep(BasePackage::packageNameToRegexp($package), array_keys($composer[$type]))) {
                foreach (
$matches as $matchedPackage) {
                    if (
$dryRun) {
                        
$toRemove[$type][] = $matchedPackage;
                    } else {
                        
$json->removeLink($type$matchedPackage);
                    }
                }
            } elseif (isset(
$composer[$altType]) && $matches Preg::grep(BasePackage::packageNameToRegexp($package), array_keys($composer[$altType]))) {
                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->getComposer(false)) {
            
$composer->getPluginManager()->deactivateInstalledPlugins();
        }

        
// Update packages
        
$this->resetComposer();
        
$composer $this->getComposer(true$input->getOption('no-plugins'), $input->getOption('no-scripts'));

        if (
$dryRun) {
            
$rootPackage $composer->getPackage();
            
$links = array(
                
'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);

        
$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>');

        
$ignorePlatformReqs $input->getOption('ignore-platform-reqs') ?: ($input->getOption('ignore-platform-req') ?: false);

        
$install
            
->setVerbose($input->getOption('verbose'))
            ->
setDevMode($updateDevMode)
            ->
setOptimizeAutoloader($optimize)
            ->
setClassMapAuthoritative($authoritative)
            ->
setApcuAutoloader($apcu$apcuPrefix)
            ->
setUpdate(true)
            ->
setInstall(!$input->getOption('no-install'))
            ->
setUpdateAllowTransitiveDependencies($updateAllowTransitiveDependencies)
            ->
setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs))
            ->
setDryRun($dryRun)
        ;

        
// 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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use 
Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use 
Composer\Installer;
use 
Composer\Plugin\CommandEvent;
use 
Composer\Plugin\PluginEvents;
use 
Composer\Util\HttpDownloader;
use 
Symfony\Component\Console\Input\InputInterface;
use 
Symfony\Component\Console\Input\InputOption;
use 
Symfony\Component\Console\Input\InputArgument;
use 
Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <[email protected]>
 * @author Ryan Weaver <[email protected]>
 * @author Konstantin Kudryashov <[email protected]>
 * @author Nils Adermann <[email protected]>
 */
class InstallCommand extends BaseCommand
{
    
/**
     * @return void
     */
    
protected function configure()
    {
        
$this
            
->setName('install')
            ->
setAliases(array('i'))
            ->
setDescription('Installs the project dependencies from the composer.lock file if present, or falls back on the composer.json.')
            ->
setDefinition(array(
                new 
InputOption('prefer-source'nullInputOption::VALUE_NONE'Forces installation from package sources when possible, including VCS information.'),
                new 
InputOption('prefer-dist'nullInputOption::VALUE_NONE'Forces installation from package dist (default behavior).'),
                new 
InputOption('prefer-install'nullInputOption::VALUE_REQUIRED'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).'),
                new 
InputOption('dry-run'nullInputOption::VALUE_NONE'Outputs the operations but will not execute anything (implicitly enables --verbose).'),
                new 
InputOption('dev'nullInputOption::VALUE_NONE'DEPRECATED: Enables installation of require-dev packages (enabled by default, only present for BC).'),
                new 
InputOption('no-suggest'nullInputOption::VALUE_NONE'DEPRECATED: This flag does not exist anymore.'),
                new 
InputOption('no-dev'nullInputOption::VALUE_NONE'Disables installation of require-dev packages.'),
                new 
InputOption('no-autoloader'nullInputOption::VALUE_NONE'Skips autoloader generation'),
                new 
InputOption('no-progress'nullInputOption::VALUE_NONE'Do not output download progress.'),
                new 
InputOption('no-install'nullInputOption::VALUE_NONE'Do not use, only defined here to catch misuse of the install command.'),
                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'nullInputOption::VALUE_NONE'Use APCu to cache found/not-found classes.'),
                new 
InputOption('apcu-autoloader-prefix'nullInputOption::VALUE_REQUIRED'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'),
                new 
InputOption('ignore-platform-req'nullInputOption::VALUE_REQUIRED InputOption::VALUE_IS_ARRAY'Ignore a specific platform requirement (php & ext- packages).'),
                new 
InputOption('ignore-platform-reqs'nullInputOption::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 $inputOutputInterface $output)
    {
        
$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>');
        }

        if (
$args $input->getArgument('packages')) {
            
$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->getComposer(true$input->getOption('no-plugins'), $input->getOption('no-scripts'));

        if ((!
$composer->getLocker() || !$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();
        list(
$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');

        
$ignorePlatformReqs $input->getOption('ignore-platform-reqs') ?: ($input->getOption('ignore-platform-req') ?: false);

        
$composer->getInstallationManager()->setOutputProgress(!$input->getOption('no-progress'));

        
$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)
            ->
setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs))
        ;

        if (
$input->getOption('no-plugins')) {
            
$install->disablePlugins();
        }

        return 
$install->run();
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\Input\InputArgument;
use 
Symfony\Component\Console\Input\ArrayInput;
use 
Symfony\Component\Console\Input\InputOption;
use 
Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <[email protected]>
 */
class OutdatedCommand extends ShowCommand
{
    
/**
     * @return void
     */
    
protected function configure()
    {
        
$this
            
->setName('outdated')
            ->
setDescription('Shows a list of installed packages that have updates available, including their latest version.')
            ->
setDefinition(array(
                new 
InputArgument('package'InputArgument::OPTIONAL'Package to inspect. Or a name including a wildcard (*) to filter lists of packages instead.'),
                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'nullInputOption::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'nullInputOption::VALUE_NONE'Return a non-zero exit code when there are outdated packages'),
                new 
InputOption('minor-only''m'InputOption::VALUE_NONE'Show only packages that have minor SemVer-compatible updates. Use with the --outdated option.'),
                new 
InputOption('format''f'InputOption::VALUE_REQUIRED'Format of the output: text or json''text'),
                new 
InputOption('ignore'nullInputOption::VALUE_REQUIRED InputOption::VALUE_IS_ARRAY'Ignore specified package(s). Use it with the --outdated option if you don\'t want to be informed about new versions of some packages.'),
                new 
InputOption('no-dev'nullInputOption::VALUE_NONE'Disables search in require-dev packages.'),
                new 
InputOption('ignore-platform-req'nullInputOption::VALUE_REQUIRED InputOption::VALUE_IS_ARRAY'Ignore a specific platform requirement (php & ext- packages). Use with the --outdated option'),
                new 
InputOption('ignore-platform-reqs'nullInputOption::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 $inputOutputInterface $output)
    {
        
$args = array(
            
'command' => 'show',
            
'--latest' => true,
        );
        if (!
$input->getOption('all')) {
            
$args['--outdated'] = true;
        }
        if (
$input->getOption('direct')) {
            
$args['--direct'] = true;
        }
        if (
$input->getArgument('package')) {
            
$args['package'] = $input->getArgument('package');
        }
        if (
$input->getOption('strict')) {
            
$args['--strict'] = true;
        }
        if (
$input->getOption('minor-only')) {
            
$args['--minor-only'] = true;
        }
        if (
$input->getOption('locked')) {
            
$args['--locked'] = true;
        }
        if (
$input->getOption('no-dev')) {
            
$args['--no-dev'] = true;
        }
        if (
$input->getOption('ignore-platform-req')) {
            
$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()
    {
        return 
true;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\Downloader\TransportException;
use 
Composer\Pcre\Preg;
use 
Composer\Repository\PlatformRepository;
use 
Composer\Plugin\CommandEvent;
use 
Composer\Plugin\PluginEvents;
use 
Composer\Util\ConfigValidator;
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;

/**
 * @author Jordi Boggiano <[email protected]>
 */
class DiagnoseCommand extends BaseCommand
{
    
/** @var HttpDownloader */
    
protected $httpDownloader;

    
/** @var ProcessExecutor */
    
protected $process;

    
/** @var int */
    
protected $exitCode 0;

    
/**
     * @return void
     */
    
protected function configure()
    {
        
$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
            )
        ;
    }

    
/**
     * @return int
     */
    
protected function execute(InputInterface $inputOutputInterface $output)
    {
        
$composer $this->getComposer(false);
        
$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) {
            
$config $composer->getConfig();
        } else {
            
$config Factory::createConfig();
        }

        
$config->merge(array('config' => array('secure-http' => false)), Config::SOURCE_COMMAND);
        
$config->prohibitUrlByConfig('http://repo.packagist.org', new NullIO);

        
$this->httpDownloader Factory::createHttpDownloader($io$config);
        
$this->process = new ProcessExecutor($io);

        
$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));

        
$opts stream_context_get_options(StreamContextFactory::getContext('http://example.org'));
        if (!empty(
$opts['http']['proxy'])) {
            
$io->write('Checking HTTP proxy: 'false);
            
$this->outputResult($this->checkHttpProxy());
        }

        if (
$oauth $config->get('github-oauth')) {
            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(sprintf('Composer version: <comment>%s</comment>'Composer::getVersion()));

        
$platformOverrides $config->get('platform') ?: array();
        
$platformRepo = new PlatformRepository(array(), $platformOverrides);
        
$phpPkg $platformRepo->findPackage('php''*');
        
$phpVersion $phpPkg->getPrettyVersion();
        if (
$phpPkg instanceof CompletePackageInterface && false !== strpos($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, array('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());
        list(
$errors, , $warnings) = $validator->validate(Factory::getComposerFile());

        if (
$errors || $warnings) {
            
$messages = array(
                
'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 checkGit()
    {
        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>';
        }

        return 
true;
    }

    
/**
     * @param string $proto
     *
     * @return string|string[]|true
     */
    
private function checkHttp($protoConfig $config)
    {
        
$result $this->checkConnectivity();
        if (
$result !== true) {
            return 
$result;
        }

        
$result = array();
        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) {
            if (
$hints HttpDownloader::getExceptionHints($e)) {
                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|true|\Exception
     */
    
private function checkHttpProxy()
    {
        
$result $this->checkConnectivity();
        if (
$result !== true) {
            return 
$result;
        }

        
$protocol extension_loaded('openssl') ? 'https' 'http';
        try {
            
$json $this->httpDownloader->get($protocol '://repo.packagist.org/packages.json')->decodeJson();
            
$hash reset($json['provider-includes']);
            
$hash $hash['sha256'];
            
$path str_replace('%hash%'$hashkey($json['provider-includes']));
            
$provider $this->httpDownloader->get($protocol '://repo.packagist.org/'.$path)->getBody();

            if (
hash('sha256'$provider) !== $hash) {
                return 
'It seems that your proxy is modifying http traffic on the fly';
            }
        } catch (
\Exception $e) {
            return 
$e;
        }

        return 
true;
    }

    
/**
     * @param string $domain
     * @param string $token
     *
     * @return string|true|\Exception
     */
    
private function checkGithubOauth($domain$token)
    {
        
$result $this->checkConnectivity();
        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/';

            
$this->httpDownloader->get($url, array(
                
'retry-auth-failure' => false,
            ));

            return 
true;
        } 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             $domain
     * @param  string             $token
     * @throws TransportException
     * @return mixed|string
     */
    
private function getGithubRateLimit($domain$token null)
    {
        
$result $this->checkConnectivity();
        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, array('retry-auth-failure' => false))->decodeJson();

        return 
$data['resources']['core'];
    }

    
/**
     * @return string|true
     */
    
private function checkDiskSpace(Config $config)
    {
        
$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 = array();
        
$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->checkConnectivity();
        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
     */
    
private function getCurlVersion()
    {
        if (
extension_loaded('curl')) {
            if (!
HttpDownloader::isCurlEnabled()) {
                return 
'<error>disabled via disable_functions, using php streams fallback, which reduces performance</error>';
            }

            
$version curl_version();

            return 
'<comment>'.$version['version'].'</comment> '.
                
'libz <comment>'.(!empty($version['libz_version']) ? $version['libz_version'] : 'missing').'</comment> '.
                
'ssl <comment>'.(isset($version['ssl_version']) ? $version['ssl_version'] : 'missing').'</comment>';
        }

        return 
'<error>missing, using php streams fallback, which reduces performance</error>';
    }

    
/**
     * @param bool|string|string[]|\Exception $result
     *
     * @return void
     */
    
private function outputResult($result)
    {
        
$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 = array($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->exitCode2);
        } elseif (
$hadWarning) {
            
$io->write('<warning>WARNING</warning>');
            
$this->exitCode max($this->exitCode1);
        }

        if (
$result) {
            foreach (
$result as $message) {
                
$io->write($message);
            }
        }
    }

    
/**
     * @return string|true
     */
    
private function checkPlatform()
    {
        
$output '';
        
$out = function ($msg$style) use (&$output) {
            
$output .= '<'.$style.'>'.$msg.'</'.$style.'>'.PHP_EOL;
        };

        
// code below taken from getcomposer.org/installer, any changes should be made there and replicated here
        
$errors = array();
        
$warnings = array();
        
$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 50302) {
            
$errors['php'] = PHP_VERSION;
        }

        if (!isset(
$errors['php']) && PHP_VERSION_ID 50304) {
            
$warnings['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 (
Preg::isMatch('{Configure Command(?: *</td><td class="v">| *=> *)(.*?)(?:</td>|$)}m'$phpinfo$match)) {
            
$configure $match[1];

            if (
false !== strpos($configure'--enable-sigchild')) {
                
$warnings['sigchild'] = true;
            }

            if (
false !== strpos($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 (!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 5.3.2 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 
'php':
                        
$text "Your PHP ({$current}) is quite old, upgrading to PHP 5.3.4 or higher is recommended.".PHP_EOL;
                        
$text .= " Composer works with 5.3.2+ for most people, but there might be edge case issues.";
                        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;

                    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');
        }

        return !
$warnings && !$errors true $output;
    }

    
/**
     * Check if allow_url_fopen is ON
     *
     * @return string|true
     */
    
private function checkConnectivity()
    {
        if (!
ini_get('allow_url_fopen')) {
            return 
'<info>Skipped because allow_url_fopen is missing.</info>';
        }

        return 
true;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 
Symfony\Component\Console\Input\InputArgument;
use 
Symfony\Component\Console\Input\InputOption;

/**
 * @author Niels Keurentjes <[email protected]>
 */
class ProhibitsCommand extends BaseDependencyCommand
{
    
/**
     * Configure command metadata.
     *
     * @return void
     */
    
protected function configure()
    {
        
$this
            
->setName('prohibits')
            ->
setAliases(array('why-not'))
            ->
setDescription('Shows which packages prevent the given package from being installed.')
            ->
setDefinition(array(
                new 
InputArgument(self::ARGUMENT_PACKAGEInputArgument::REQUIRED'Package to inspect'),
                new 
InputArgument(self::ARGUMENT_CONSTRAINTInputArgument::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'),
            ))
            ->
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
            )
        ;
    }

    
/**
     * Execute the function.
     *
     * @return int
     */
    
protected function execute(InputInterface $inputOutputInterface $output)
    {
        return 
parent::doExecute($input$outputtrue);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\Input\InputInterface;
use 
Symfony\Component\Console\Input\InputArgument;
use 
Symfony\Component\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 <[email protected]>
 */
class SearchCommand extends BaseCommand
{
    
/**
     * @return void
     */
    
protected function configure()
    {
        
$this
            
->setName('search')
            ->
setDescription('Searches for packages.')
            ->
setDefinition(array(
                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'),
                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 $inputOutputInterface $output)
    {
        
// init repos
        
$platformRepo = new PlatformRepository;
        
$io $this->getIO();

        
$format $input->getOption('format');
        if (!
in_array($format, array('text''json'))) {
            
$io->writeError(sprintf('Unsupported format "%s". See help for supported formats.'$format));

            return 
1;
        }

        if (!(
$composer $this->getComposer(false))) {
            
$composer Factory::create($this->getIO(), array(), $input->hasParameterOption('--no-plugins'));
        }
        
$localRepo $composer->getRepositoryManager()->getLocalRepository();
        
$installedRepo = new CompositeRepository(array($localRepo$platformRepo));
        
$repos = new CompositeRepository(array_merge(array($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 (
$results && $format === 'text') {
            
$width $this->getTerminalWidth();

            
$nameLength 0;
            foreach (
$results as $result) {
                
$nameLength max(strlen($result['name']), $nameLength);
            }
            
$nameLength += 1;
            foreach (
$results as $result) {
                
$description = isset($result['description']) ? $result['description'] : '';
                
$warning = !empty($result['abandoned']) ? '<warning>! Abandoned !</warning> ' '';
                
$remaining $width $nameLength strlen($warning) - 2;
                if (
strlen($description) > $remaining) {
                    
$description substr($description0$remaining 3) . '...';
                }

                
$io->write(str_pad($result['name'], $nameLength' ') . $warning $description);
            }
        } elseif (
$format === 'json') {
            
$io->write(JsonFile::encode($results));
        }

        return 
0;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\Filesystem;
use 
Composer\Util\Platform;
use 
Composer\Util\Silencer;
use 
Symfony\Component\Console\Input\InputInterface;
use 
Symfony\Component\Console\Input\InputArgument;
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 <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 */
class ConfigCommand extends BaseCommand
{
    
/**
     * @var Config
     */
    
protected $config;

    
/**
     * @var JsonFile
     */
    
protected $configFile;

    
/**
     * @var JsonConfigSource
     */
    
protected $configSource;

    
/**
     * @var JsonFile
     */
    
protected $authConfigFile;

    
/**
     * @var JsonConfigSource
     */
    
protected $authConfigSource;

    
/**
     * @return void
     */
    
protected function configure()
    {
        
$this
            
->setName('config')
            ->
setDescription('Sets config options.')
            ->
setDefinition(array(
                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'nullInputOption::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'nullInputOption::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'nullInputOption::VALUE_NONE'When adding a repository, append it (lowest priority) to the existing ones instead of prepending it (highest priority)'),
                new 
InputOption('source'nullInputOption::VALUE_NONE'Display where the config value is loaded from'),
                new 
InputArgument('setting-key'null'Setting key'),
                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
            )
        ;
    }

    
/**
     * @return void
     * @throws \Exception
     */
    
protected function initialize(InputInterface $inputOutputInterface $output)
    {
        
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);

        
// Get the local composer.json, global config.json, or if the user
        // passed in a file to use
        
$configFile $input->getOption('global')
            ? (
$this->config->get('home') . '/config.json')
            : (
$input->getOption('file') ?: Factory::getComposerFile());

        
// Create global composer.json if this was invoked using `composer global config`
        
if (
            (
$configFile === 'composer.json' || $configFile === './composer.json')
            && !
file_exists($configFile)
            && 
realpath(getcwd()) === realpath($this->config->get('home'))
        ) {
            
file_put_contents($configFile"{\n}\n");
        }

        
$this->configFile = new JsonFile($configFilenull$io);
        
$this->configSource = new JsonConfigSource($this->configFile);

        
$authConfigFile $input->getOption('global')
            ? (
$this->config->get('home') . '/auth.json')
            : 
dirname(realpath($configFile)) . '/auth.json';

        
$this->authConfigFile = new JsonFile($authConfigFilenull$io);
        
$this->authConfigSource = new JsonConfigSource($this->authConfigFiletrue);

        
// 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(array('config' => new \ArrayObject));
            
Silencer::call('chmod'$this->configFile->getPath(), 0600);
        }
        if (
$input->getOption('global') && !$this->authConfigFile->exists()) {
            
touch($this->authConfigFile->getPath());
            
$this->authConfigFile->write(array('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));
        }
    }

    
/**
     * @return int
     * @throws \Seld\JsonLint\ParsingException
     */
    
protected function execute(InputInterface $inputOutputInterface $output)
    {
        
// Open file in editor
        
if (true === $input->getOption('editor')) {
            
$editor escapeshellcmd(Platform::getEnv('EDITOR'));
            if (!
$editor) {
                if (
Platform::isWindows()) {
                    
$editor 'notepad';
                } else {
                    foreach (array(
'editor''vim''vi''nano''pico''ed') as $candidate) {
                        if (
exec('which '.$candidate)) {
                            
$editor $candidate;
                            break;
                        }
                    }
                }
            }

            
$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(array('config' => $this->authConfigFile->exists() ? $this->authConfigFile->read() : array()), $this->authConfigFile->getPath());
        }

        
// List the configuration of the file settings
        
if (true === $input->getOption('list')) {
            
$this->listConfiguration($this->config->all(), $this->config->raw(), $outputnull, (bool) $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 (array() !== $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 (array() === $input->getArgument('setting-value') && !$input->getOption('unset')) {
            
$properties = array('name''type''description''homepage''version''minimum-stability''prefer-stable''keywords''license''extra');
            
$rawData $this->configFile->read();
            
$data $this->config->all();
            if (
Preg::isMatch('/^repos?(?:itories)?(?:\.(.+))?/'$settingKey$matches)) {
                if (!isset(
$matches[1]) || $matches[1] === '') {
                    
$value = isset($data['repositories']) ? $data['repositories'] : array();
                } 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') {
                    
$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') ? Config::RELATIVE_PATHS);
            } elseif (isset(
$rawData[$settingKey]) && in_array($settingKey$propertiestrue)) {
                
$value $rawData[$settingKey];
            } else {
                throw new 
\RuntimeException($settingKey.' is not defined');
            }

            if (
is_array($value)) {
                
$value json_encode($value);
            }

            
$sourceOfConfigValue '';
            if (
$input->getOption('source')) {
                
$sourceOfConfigValue ' (' $this->config->getSourceOfValue($settingKey) . ')';
            }

            
$this->getIO()->write($value $sourceOfConfigValuetrueIOInterface::QUIET);

            return 
0;
        }

        
$values $input->getArgument('setting-value'); // what the user is trying to add/change

        
$booleanValidator = function ($val) {
            return 
in_array($val, array('true''false''1''0'), true);
        };
        
$booleanNormalizer = function ($val) {
            return 
$val !== 'false' && (bool) $val;
        };

        
// handle config values
        
$uniqueConfigValues = array(
            
'process-timeout' => array('is_numeric''intval'),
            
'use-include-path' => array($booleanValidator$booleanNormalizer),
            
'use-github-api' => array($booleanValidator$booleanNormalizer),
            
'preferred-install' => array(
                function (
$val) {
                    return 
in_array($val, array('auto''source''dist'), true);
                },
                function (
$val) {
                    return 
$val;
                },
            ),
            
'gitlab-protocol' => array(
                function (
$val) {
                    return 
in_array($val, array('git''http''https'), true);
                },
                function (
$val) {
                    return 
$val;
                },
            ),
            
'store-auths' => array(
                function (
$val) {
                    return 
in_array($val, array('true''false''prompt'), true);
                },
                function (
$val) {
                    if (
'prompt' === $val) {
                        return 
'prompt';
                    }

                    return 
$val !== 'false' && (bool) $val;
                },
            ),
            
'notify-on-install' => array($booleanValidator$booleanNormalizer),
            
'vendor-dir' => array('is_string', function ($val) {
                return 
$val;
            }),
            
'bin-dir' => array('is_string', function ($val) {
                return 
$val;
            }),
            
'archive-dir' => array('is_string', function ($val) {
                return 
$val;
            }),
            
'archive-format' => array('is_string', function ($val) {
                return 
$val;
            }),
            
'data-dir' => array('is_string', function ($val) {
                return 
$val;
            }),
            
'cache-dir' => array('is_string', function ($val) {
                return 
$val;
            }),
            
'cache-files-dir' => array('is_string', function ($val) {
                return 
$val;
            }),
            
'cache-repo-dir' => array('is_string', function ($val) {
                return 
$val;
            }),
            
'cache-vcs-dir' => array('is_string', function ($val) {
                return 
$val;
            }),
            
'cache-ttl' => array('is_numeric''intval'),
            
'cache-files-ttl' => array('is_numeric''intval'),
            
'cache-files-maxsize' => array(
                function (
$val) {
                    return 
Preg::isMatch('/^\s*([0-9.]+)\s*(?:([kmg])(?:i?b)?)?\s*$/i'$val);
                },
                function (
$val) {
                    return 
$val;
                },
            ),
            
'bin-compat' => array(
                function (
$val) {
                    return 
in_array($val, array('auto''full''symlink'));
                },
                function (
$val) {
                    return 
$val;
                },
            ),
            
'discard-changes' => array(
                function (
$val) {
                    return 
in_array($val, array('stash''true''false''1''0'), true);
                },
                function (
$val) {
                    if (
'stash' === $val) {
                        return 
'stash';
                    }

                    return 
$val !== 'false' && (bool) $val;
                },
            ),
            
'autoloader-suffix' => array('is_string', function ($val) {
                return 
$val === 'null' null $val;
            }),
            
'sort-packages' => array($booleanValidator$booleanNormalizer),
            
'optimize-autoloader' => array($booleanValidator$booleanNormalizer),
            
'classmap-authoritative' => array($booleanValidator$booleanNormalizer),
            
'apcu-autoloader' => array($booleanValidator$booleanNormalizer),
            
'prepend-autoloader' => array($booleanValidator$booleanNormalizer),
            
'disable-tls' => array($booleanValidator$booleanNormalizer),
            
'secure-http' => array($booleanValidator$booleanNormalizer),
            
'cafile' => array(
                function (
$val) {
                    return 
file_exists($val) && Filesystem::isReadable($val);
                },
                function (
$val) {
                    return 
$val === 'null' null $val;
                },
            ),
            
'capath' => array(
                function (
$val) {
                    return 
is_dir($val) && Filesystem::isReadable($val);
                },
                function (
$val) {
                    return 
$val === 'null' null $val;
                },
            ),
            
'github-expose-hostname' => array($booleanValidator$booleanNormalizer),
            
'htaccess-protect' => array($booleanValidator$booleanNormalizer),
            
'lock' => array($booleanValidator$booleanNormalizer),
            
'allow-plugins' => array($booleanValidator$booleanNormalizer),
            
'platform-check' => array(
                function (
$val) {
                    return 
in_array($val, array('php-only''true''false''1''0'), true);
                },
                function (
$val) {
                    if (
'php-only' === $val) {
                        return 
'php-only';
                    }

                    return 
$val !== 'false' && (bool)$val;
                },
            ),
            
'use-parent-dir' => array(
                function (
$val) {
                    return 
in_array($val, array('true''false''prompt'), true);
                },
                function (
$val) {
                    if (
'prompt' === $val) {
                        return 
'prompt';
                    }

                    return 
$val !== 'false' && (bool) $val;
                },
            ),
        );
        
$multiConfigValues = array(
            
'github-protocols' => array(
                function (
$vals) {
                    if (!
is_array($vals)) {
                        return 
'array expected';
                    }

                    foreach (
$vals as $val) {
                        if (!
in_array($val, array('git''https''ssh'))) {
                            return 
'valid protocols include: git, https, ssh';
                        }
                    }

                    return 
true;
                },
                function (
$vals) {
                    return 
$vals;
                },
            ),
            
'github-domains' => array(
                function (
$vals) {
                    if (!
is_array($vals)) {
                        return 
'array expected';
                    }

                    return 
true;
                },
                function (
$vals) {
                    return 
$vals;
                },
            ),
            
'gitlab-domains' => array(
                function (
$vals) {
                    if (!
is_array($vals)) {
                        return 
'array expected';
                    }

                    return 
true;
                },
                function (
$vals) {
                    return 
$vals;
                },
            ),
        );

        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;
            }

            list(
$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 = array(
            
'name' => array('is_string', function ($val) {
                return 
$val;
            }),
            
'type' => array('is_string', function ($val) {
                return 
$val;
            }),
            
'description' => array('is_string', function ($val) {
                return 
$val;
            }),
            
'homepage' => array('is_string', function ($val) {
                return 
$val;
            }),
            
'version' => array('is_string', function ($val) {
                return 
$val;
            }),
            
'minimum-stability' => array(
                function (
$val) {
                    return isset(
BasePackage::$stabilities[VersionParser::normalizeStability($val)]);
                },
                function (
$val) {
                    return 
VersionParser::normalizeStability($val);
                },
            ),
            
'prefer-stable' => array($booleanValidator$booleanNormalizer),
        );
        
$multiProps = array(
            
'keywords' => array(
                function (
$vals) {
                    if (!
is_array($vals)) {
                        return 
'array expected';
                    }

                    return 
true;
                },
                function (
$vals) {
                    return 
$vals;
                },
            ),
            
'license' => array(
                function (
$vals) {
                    if (!
is_array($vals)) {
                        return 
'array expected';
                    }

                    return 
true;
                },
                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::isMatch('/^repos?(?:itories)?\.(.+)/'$settingKey$matches)) {
            if (
$input->getOption('unset')) {
                
$this->configSource->removeRepository($matches[1]);

                return 
0;
            }

            if (
=== count($values)) {
                
$this->configSource->addRepository($matches[1], array(
                    
'type' => $values[0],
                    
'url' => $values[1],
                ), 
$input->getOption('append'));

                return 
0;
            }

            if (
=== 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 = isset($currentValue[$bit]) ? $currentValue[$bit] : null;
                    }
                    if (
is_array($currentValue)) {
                        
$value array_merge($currentValue$value);
                    }
                }
            }
            
$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($settingKeyimplode(' '$values));

            return 
0;
        }

        
// handle unsetting extra/suggest
        
if (in_array($settingKey, array('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 (
!== 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], array('consumer-key' => $values[0], 'consumer-secret' => $values[1]));
            } elseif (
$matches[1] === 'gitlab-token' && === count($values)) {
                
$this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
                
$this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], array('username' => $values[0], 'token' => $values[1]));
            } elseif (
in_array($matches[1], array('github-oauth''gitlab-oauth''gitlab-token''bearer'), true)) {
                if (
!== 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 (
!== 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], array('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($settingKeycount($values) > $values $values[0]);

            return 
0;
        }

        throw new 
\InvalidArgumentException('Setting '.$settingKey.' does not exist or is not supported by this command');
    }

    
/**
     * @param string $key
     * @param array{callable, callable} $callbacks Validator and normalizer callbacks
     * @param array<string> $values
     * @param string $method
     *
     * @return void
     */
    
protected function handleSingleValue($key, array $callbacks, array $values$method)
    {
        list(
$validator$normalizer) = $callbacks;
        if (
!== 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(array($this->configSource$method), $key$normalizedValue);
    }

    
/**
     * @param string $key
     * @param array{callable, callable} $callbacks Validator and normalizer callbacks
     * @param array<string> $values
     * @param string $method
     *
     * @return void
     */
    
protected function handleMultiValue($key, array $callbacks, array $values$method)
    {
        list(
$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(array($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
     * @param string|null                $k
     * @param bool                       $showSource
     *
     * @return void
     */
    
protected function listConfiguration(array $contents, array $rawContentsOutputInterface $output$k null$showSource false)
    {
        
$origK $k;
        
$io $this->getIO();
        foreach (
$contents as $key => $value) {
            if (
$k === null && !in_array($key, array('config''repositories'))) {
                continue;
            }

            
$rawVal = isset($rawContents[$key]) ? $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(function ($val) {
                    return 
is_array($val) ? json_encode($val) : $val;
                }, 
$value);

                
$value '['.implode(', '$value).']';
            }

            if (
is_bool($value)) {
                
$value var_export($valuetrue);
            }

            
$source '';
            if (
$showSource) {
                
$source ' (' $this->config->getSourceOfValue($k $key) . ')';
            }
            if (
is_string($rawVal) && $rawVal != $value) {
                
$io->write('[<comment>' $k $key '</comment>] <info>' $rawVal ' (' $value ')</info>' $sourcetrueIOInterface::QUIET);
            } else {
                
$io->write('[<comment>' $k $key '</comment>] <info>' $value '</info>' $sourcetrueIOInterface::QUIET);
            }
        }
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\Factory;
use 
Composer\IO\IOInterface;
use 
Composer\IO\NullIO;
use 
Composer\Plugin\PreCommandRunEvent;
use 
Composer\Package\Version\VersionParser;
use 
Composer\Plugin\PluginEvents;
use 
Composer\Util\Platform;
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
 *
 * @method Application getApplication()
 *
 * @author Ryan Weaver <[email protected]>
 * @author Konstantin Kudryashov <[email protected]>
 */
abstract class BaseCommand extends Command
{
    
/**
     * @var Composer|null
     */
    
private $composer;

    
/**
     * @var IOInterface
     */
    
private $io;

    
/**
     * @param  bool              $required
     * @param  bool|null         $disablePlugins
     * @param  bool|null         $disableScripts
     * @throws \RuntimeException
     * @return Composer|null
     */
    
public function getComposer($required true$disablePlugins null$disableScripts null)
    {
        if (
null === $this->composer) {
            
$application $this->getApplication();
            if (
$application instanceof Application) {
                
/* @var $application    Application */
                
$this->composer $application->getComposer($required$disablePlugins$disableScripts);
            
/** @phpstan-ignore-next-line */
            
} elseif ($required) {
                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;
    }

    
/**
     * @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 $this->getApplication();
            if (
$application instanceof Application) {
                
$this->io $application->getIO();
            
/** @phpstan-ignore-next-line */
            
} else {
                
$this->io = new NullIO();
            }
        }

        return 
$this->io;
    }

    
/**
     * @return void
     */
    
public function setIO(IOInterface $io)
    {
        
$this->io $io;
    }

    
/**
     * @inheritDoc
     *
     * @return void
     */
    
protected function initialize(InputInterface $inputOutputInterface $output)
    {
        
// initialize a plugin-enabled Composer instance, either local or global
        
$disablePlugins $input->hasParameterOption('--no-plugins');
        
$disableScripts $input->hasParameterOption('--no-scripts');
        if (
$this instanceof SelfUpdateCommand) {
            
$disablePlugins true;
            
$disableScripts true;
        }

        
$composer $this->getComposer(false$disablePlugins$disableScripts);
        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(array('--no-ansi')) && $input->hasOption('no-progress')) {
            
$input->setOption('no-progress'true);
        }

        if (
true == $input->hasOption('no-dev')) {
            if (!
$input->getOption('no-dev') && true == Platform::getEnv('COMPOSER_NO_DEV')) {
                
$input->setOption('no-dev'true);
            }
        }

        
parent::initialize($input$output);
    }

    
/**
     * Returns preferSource and preferDist values based on the configuration.
     *
     * @param bool           $keepVcsRequiresPreferSource
     *
     * @return bool[] An array composed of the preferSource and preferDist values
     */
    
protected function getPreferredInstallOptions(Config $configInputInterface $input$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-install') && $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 = (bool) $input->getOption('prefer-dist');
        }

        return array(
$preferSource$preferDist);
    }

    
/**
     * @param array<string, string> $requirements
     *
     * @return array<string, string>
     */
    
protected function formatRequirements(array $requirements)
    {
        
$requires = array();
        
$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 $tableOutputInterface $output)
    {
        
$renderer = new Table($output);
        
$renderer->setStyle('compact');
        
$rendererStyle $renderer->getStyle();
        if (
method_exists($rendererStyle'setVerticalBorderChars')) {
            
$rendererStyle->setVerticalBorderChars('');
        } else {
            
// TODO remove in composer 2.2
            // @phpstan-ignore-next-line
            
$rendererStyle->setVerticalBorderChar('');
        }
        
$rendererStyle->setCellRowContentFormat('%s  ');
        
$renderer->setRows($table)->render();
    }

    
/**
     * @return int
     */
    
protected function getTerminalWidth()
    {
        if (
class_exists('Symfony\Component\Console\Terminal')) {
            
$terminal = new Terminal();
            
$width $terminal->getWidth();
        } else {
            
// For versions of Symfony console before 3.2
            // TODO remove in composer 2.2
            // @phpstan-ignore-next-line
            
list($width) = $this->getApplication()->getTerminalDimensions();
        }
        if (
null === $width) {
            
// In case the width is not detected, we're probably running the command
            // outside of a real terminal, use space without a limit
            
$width PHP_INT_MAX;
        }
        if (
Platform::isWindows()) {
            
$width--;
        } else {
            
$width max(80$width);
        }

        return 
$width;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\Input\InputOption;
use 
Symfony\Component\Console\Output\OutputInterface;
use 
Symfony\Component\Console\Input\InputArgument;

/**
 * @author Davey Shafik <[email protected]>
 */
class ExecCommand extends BaseCommand
{
    
/**
     * @return void
     */
    
protected function configure()
    {
        
$this
            
->setName('exec')
            ->
setDescription('Executes a vendored binary/script.')
            ->
setDefinition(array(
                new 
InputOption('list''l'InputOption::VALUE_NONE),
                new 
InputArgument('binary'InputArgument::OPTIONAL'The binary to run, e.g. phpunit'),
                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
            )
        ;
    }

    
/**
     * @return int
     */
    
protected function execute(InputInterface $inputOutputInterface $output)
    {
        
$composer $this->getComposer();
        
$binDir $composer->getConfig()->get('bin-dir');
        if (
$input->getOption('list') || !$input->getArgument('binary')) {
            
$bins glob($binDir '/*');
            
$bins array_merge($binsarray_map(function ($e) {
                return 
"$e (local)";
            }, 
$composer->getPackage()->getBinaries()));

            if (!
$bins) {
                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) {
                
// skip .bat copies
                
if (isset($previousBin) && $bin === $previousBin.'.bat') {
                    continue;
                }

                
$previousBin $bin;
                
$bin basename($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()) {
            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'));
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 
Symfony\Component\Console\Input\InputArgument;
use 
Symfony\Component\Console\Input\InputOption;
use 
Symfony\Component\Console\Input\InputInterface;
use 
Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Robert Schönthal <[email protected]>
 */
class HomeCommand extends BaseCommand
{
    
/**
     * @inheritDoc
     *
     * @return void
     */
    
protected function configure()
    {
        
$this
            
->setName('browse')
            ->
setAliases(array('home'))
            ->
setDescription('Opens the package\'s repository URL or homepage in your browser.')
            ->
setDefinition(array(
                new 
InputArgument('packages'InputArgument::IS_ARRAY'Package(s) to browse to.'),
                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
            );
    }

    
/**
     * @inheritDoc
     */
    
protected function execute(InputInterface $inputOutputInterface $output)
    {
        
$repos $this->initializeRepos();
        
$io $this->getIO();
        
$return 0;

        
$packages $input->getArgument('packages');
        if (!
$packages) {
            
$io->writeError('No package specified, opening homepage for the root package');
            
$packages = array($this->getComposer()->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;
    }

    
/**
     * @param bool $showHomepage
     * @param bool $showOnly
     * @return bool
     */
    
private function handlePackage(CompletePackageInterface $package$showHomepage$showOnly)
    {
        
$support $package->getSupport();
        
$url = isset($support['source']) ? $support['source'] : $package->getSourceUrl();
        if (!
$url || $showHomepage) {
            
$url $package->getHomepage();
        }

        if (!
$url || !filter_var($urlFILTER_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
     *
     * @param string $url
     * @return void
     */
    
private function openBrowser($url)
    {
        
$url ProcessExecutor::escape($url);

        
$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 (
=== $linux) {
            
$process->execute('xdg-open ' $url$output);
        } elseif (
=== $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()
    {
        
$composer $this->getComposer(false);

        if (
$composer) {
            return 
array_merge(
                array(new 
RootPackageRepository($composer->getPackage())), // root package
                
array($composer->getRepositoryManager()->getLocalRepository()), // installed packages
                
$composer->getRepositoryManager()->getRepositories() // remotes
            
);
        }

        return 
RepositoryFactory::defaultRepos($this->getIO());
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\Filter\PlatformRequirementFilter\PlatformRequirementFilterFactory;
use 
Composer\Installer;
use 
Composer\IO\IOInterface;
use 
Composer\Package\Loader\RootPackageLoader;
use 
Composer\Pcre\Preg;
use 
Composer\Plugin\CommandEvent;
use 
Composer\Plugin\PluginEvents;
use 
Composer\Package\Version\VersionParser;
use 
Composer\Util\HttpDownloader;
use 
Composer\Semver\Constraint\MultiConstraint;
use 
Composer\Package\Link;
use 
Symfony\Component\Console\Helper\Table;
use 
Symfony\Component\Console\Input\InputInterface;
use 
Symfony\Component\Console\Input\InputOption;
use 
Symfony\Component\Console\Input\InputArgument;
use 
Symfony\Component\Console\Output\OutputInterface;
use 
Symfony\Component\Console\Question\Question;

/**
 * @author Jordi Boggiano <[email protected]>
 * @author Nils Adermann <[email protected]>
 */
class UpdateCommand extends BaseCommand
{
    
/**
     * @return void
     */
    
protected function configure()
    {
        
$this
            
->setName('update')
            ->
setAliases(array('u''upgrade'))
            ->
setDescription('Upgrades your dependencies to the latest version according to composer.json, and updates the composer.lock file.')
            ->
setDefinition(array(
                new 
InputArgument('packages'InputArgument::IS_ARRAY InputArgument::OPTIONAL'Packages that should be updated, if not provided all packages are.'),
                new 
InputOption('with'nullInputOption::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'nullInputOption::VALUE_NONE'Forces installation from package sources when possible, including VCS information.'),
                new 
InputOption('prefer-dist'nullInputOption::VALUE_NONE'Forces installation from package dist (default behavior).'),
                new 
InputOption('prefer-install'nullInputOption::VALUE_REQUIRED'Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).'),
                new 
InputOption('dry-run'nullInputOption::VALUE_NONE'Outputs the operations but will not execute anything (implicitly enables --verbose).'),
                new 
InputOption('dev'nullInputOption::VALUE_NONE'DEPRECATED: Enables installation of require-dev packages (enabled by default, only present for BC).'),
                new 
InputOption('no-dev'nullInputOption::VALUE_NONE'Disables installation of require-dev packages.'),
                new 
InputOption('lock'nullInputOption::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'nullInputOption::VALUE_NONE'Skip the install step after updating the composer.lock file.'),
                new 
InputOption('no-autoloader'nullInputOption::VALUE_NONE'Skips autoloader generation'),
                new 
InputOption('no-suggest'nullInputOption::VALUE_NONE'DEPRECATED: This flag does not exist anymore.'),
                new 
InputOption('no-progress'nullInputOption::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.'),
                new 
InputOption('with-all-dependencies''W'InputOption::VALUE_NONE'Update also dependencies of packages in the argument list, including those which are root requirements.'),
                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'nullInputOption::VALUE_NONE'Use APCu to cache found/not-found classes.'),
                new 
InputOption('apcu-autoloader-prefix'nullInputOption::VALUE_REQUIRED'Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader'),
                new 
InputOption('ignore-platform-req'nullInputOption::VALUE_REQUIRED InputOption::VALUE_IS_ARRAY'Ignore a specific platform requirement (php & ext- packages).'),
                new 
InputOption('ignore-platform-reqs'nullInputOption::VALUE_NONE'Ignore all platform requirements (php & ext- packages).'),
                new 
InputOption('prefer-stable'nullInputOption::VALUE_NONE'Prefer stable versions of dependencies.'),
                new 
InputOption('prefer-lowest'nullInputOption::VALUE_NONE'Prefer lowest versions of dependencies.'),
                new 
InputOption('interactive''i'InputOption::VALUE_NONE'Interactive interface with autocompletion to select the packages to update.'),
                new 
InputOption('root-reqs'nullInputOption::VALUE_NONE'Restricts the update to your first degree dependencies.'),
            ))
            ->
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
EOT
            )
        ;
    }

    
/**
     * @return int
     * @throws \Exception
     */
    
protected function execute(InputInterface $inputOutputInterface $output)
    {
        
$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->getComposer(true$input->getOption('no-plugins'), $input->getOption('no-scripts'));

        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 ($packages) {
            
$allowlistPackagesWithRequirements array_filter($packages, function ($pkg) {
                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();
        
$rootRequires $rootPackage->getRequires();
        
$rootDevRequires $rootPackage->getDevRequires();
        foreach (
$reqs as $package => $constraint) {
            if (isset(
$rootRequires[$package])) {
                
$rootRequires[$package] = $this->appendConstraintToLink($rootRequires[$package], $constraint);
            } elseif (isset(
$rootDevRequires[$package])) {
                
$rootDevRequires[$package] = $this->appendConstraintToLink($rootDevRequires[$package], $constraint);
            } else {
                throw new 
\UnexpectedValueException('Only root package requirements can receive temporary constraints and '.$package.' is not one');
            }
        }
        
$rootPackage->setRequires($rootRequires);
        
$rootPackage->setDevRequires($rootDevRequires);
        
$rootPackage->setReferences(RootPackageLoader::extractReferences($reqs$rootPackage->getReferences()));
        
$rootPackage->setStabilityFlags(RootPackageLoader::extractStabilityFlags($reqs$rootPackage->getMinimumStability(), $rootPackage->getStabilityFlags()));

        if (
$input->getOption('interactive')) {
            
$packages $this->getPackagesInteractively($io$input$output$composer$packages);
        }

        if (
$input->getOption('root-reqs')) {
            
$requires array_keys($rootRequires);
            if (!
$input->getOption('no-dev')) {
                
$requires array_merge($requiresarray_keys($rootDevRequires));
            }

            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, function ($package) {
            return !
in_array($package, array('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();
        list(
$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;
        }

        
$ignorePlatformReqs $input->getOption('ignore-platform-reqs') ?: ($input->getOption('ignore-platform-req') ?: false);

        
$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(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs))
            ->
setPreferStable($input->getOption('prefer-stable'))
            ->
setPreferLowest($input->getOption('prefer-lowest'))
        ;

        if (
$input->getOption('no-plugins')) {
            
$install->disablePlugins();
        }

        return 
$install->run();
    }

    
/**
     * @param array<string> $packages
     * @return array<string>
     */
    
private function getPackagesInteractively(IOInterface $ioInputInterface $inputOutputInterface $outputComposer $composer, array $packages)
    {
        if (!
$input->isInteractive()) {
            throw new 
\InvalidArgumentException('--interactive cannot be used in non-interactive terminals.');
        }

        
$requires array_merge(
            
$composer->getPackage()->getRequires(),
            
$composer->getPackage()->getDevRequires()
        );
        
$autocompleterValues = array();
        foreach (
$requires as $require) {
            
$target $require->getTarget();
            
$autocompleterValues[strtolower($target)] = $target;
        }

        
$installedPackages $composer->getRepositoryManager()->getLocalRepository()->getPackages();
        foreach (
$installedPackages as $package) {
            
$autocompleterValues[$package->getName()] = $package->getPrettyName();
        }

        
$helper $this->getHelper('question');
        
$question = new Question('<comment>Enter package name: </comment>'null);

        
$io->writeError('<info>Press enter without value to end submission</info>');

        do {
            
$autocompleterValues array_diff($autocompleterValues$packages);
            
$question->setAutocompleterValues($autocompleterValues);
            
$addedPackage $helper->ask($input$output$question);

            if (!
is_string($addedPackage) || empty($addedPackage)) {
                break;
            }

            
$addedPackage strtolower($addedPackage);
            if (!
in_array($addedPackage$packages)) {
                
$packages[] = $addedPackage;
            }
        } while (
true);

        
$packages array_filter($packages);
        if (!
$packages) {
            throw new 
\InvalidArgumentException('You must enter minimum one package.');
        }

        
$table = new Table($output);
        
$table->setHeaders(array('Selected packages'));
        foreach (
$packages as $package) {
            
$table->addRow(array($package));
        }
        
$table->render();

        if (
$io->askConfirmation(sprintf(
            
'Would you like to continue and update the above package%s [<comment>yes</comment>]? ',
            
=== count($packages) ? '' 's'
        
))) {
            return 
$packages;
        }

        throw new 
\RuntimeException('Installation aborted.');
    }

    
/**
     * @param string $constraint
     * @return Link
     */
    
private function appendConstraintToLink(Link $link$constraint)
    {
        
$parser = new VersionParser;
        
$oldPrettyString $link->getConstraint()->getPrettyString();
        
$newConstraint MultiConstraint::create(array($link->getConstraint(), $parser->parseConstraints($constraint)));
        
$newConstraint->setPrettyString($oldPrettyString.', '.$constraint);

        return new 
Link(
            
$link->getSource(),
            
$link->getTarget(),
            
$newConstraint,
            
/** @phpstan-ignore-next-line */
            
$link->getDescription(),
            
$link->getPrettyConstraint() . ', ' $constraint
        
);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\PlatformRequirementFilterFactory;
use 
Composer\Json\JsonFile;
use 
Composer\Package\BasePackage;
use 
Composer\Package\CompletePackageInterface;
use 
Composer\Package\Link;
use 
Composer\Package\AliasPackage;
use 
Composer\Package\Package;
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\RootPackageRepository;
use 
Composer\Semver\Constraint\ConstraintInterface;
use 
Composer\Semver\Semver;
use 
Composer\Spdx\SpdxLicenses;
use 
Symfony\Component\Console\Formatter\OutputFormatterStyle;
use 
Symfony\Component\Console\Input\InputArgument;
use 
Symfony\Component\Console\Input\InputInterface;
use 
Symfony\Component\Console\Input\InputOption;
use 
Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Robert Schönthal <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 * @author Jérémy Romey <jeremyFreeAgent>
 * @author Mihai Plasoianu <[email protected]>
 */
class ShowCommand extends BaseCommand
{
    
/** @var VersionParser */
    
protected $versionParser;
    
/** @var string[] */
    
protected $colors;

    
/** @var ?RepositorySet */
    
private $repositorySet;

    
/**
     * @return void
     */
    
protected function configure()
    {
        
$this
            
->setName('show')
            ->
setAliases(array('info'))
            ->
setDescription('Shows information about packages.')
            ->
setDefinition(array(
                new 
InputArgument('package'InputArgument::OPTIONAL'Package to inspect. Or a name including a wildcard (*) to filter lists of packages instead.'),
                new 
InputArgument('version'InputArgument::OPTIONAL'Version or version constraint to inspect'),
                new 
InputOption('all'nullInputOption::VALUE_NONE'List all packages'),
                new 
InputOption('locked'nullInputOption::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'nullInputOption::VALUE_REQUIRED InputOption::VALUE_IS_ARRAY'Ignore specified package(s). Use it with the --outdated option if you don\'t want to be informed about new versions of some packages.'),
                new 
InputOption('minor-only''m'InputOption::VALUE_NONE'Show only packages that have minor SemVer-compatible updates. Use with the --outdated option.'),
                new 
InputOption('direct''D'InputOption::VALUE_NONE'Shows only packages that are directly required by the root package'),
                new 
InputOption('strict'nullInputOption::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'),
                new 
InputOption('no-dev'nullInputOption::VALUE_NONE'Disables search in require-dev packages.'),
                new 
InputOption('ignore-platform-req'nullInputOption::VALUE_REQUIRED InputOption::VALUE_IS_ARRAY'Ignore a specific platform requirement (php & ext- packages). Use with the --outdated option'),
                new 
InputOption('ignore-platform-reqs'nullInputOption::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
EOT
            )
        ;
    }

    protected function 
execute(InputInterface $inputOutputInterface $output)
    {
        
$this->versionParser = new VersionParser;
        if (
$input->getOption('tree')) {
            
$this->initStyles($output);
        }

        
$composer $this->getComposer(false);
        
$io $this->getIO();

        if (
$input->getOption('installed')) {
            
$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 (
$input->getOption('ignore')) {
            
$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 (
$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, array('text''json'))) {
            
$io->writeError(sprintf('Unsupported format "%s". See help for supported formats.'$format));

            return 
1;
        }

        
$ignorePlatformReqs $input->getOption('ignore-platform-reqs') ?: ($input->getOption('ignore-platform-req') ?: false);

        
// init repos
        
$platformOverrides = array();
        if (
$composer) {
            
$platformOverrides $composer->getConfig()->get('platform') ?: array();
        }
        
$platformRepo = new PlatformRepository(array(), $platformOverrides);
        
$lockedRepo null;

        if (
$input->getOption('self')) {
            
$package $this->getComposer()->getPackage();
            if (
$input->getOption('name-only')) {
                
$io->write($package->getName());

                return 
0;
            }
            
$repos $installedRepo = new InstalledRepository(array(new RootPackageRepository($package)));
        } elseif (
$input->getOption('platform')) {
            
$repos $installedRepo = new InstalledRepository(array($platformRepo));
        } elseif (
$input->getOption('available')) {
            
$installedRepo = new InstalledRepository(array($platformRepo));
            if (
$composer) {
                
$repos = new CompositeRepository($composer->getRepositoryManager()->getRepositories());
                
$installedRepo->addRepository($composer->getRepositoryManager()->getLocalRepository());
            } else {
                
$defaultRepos RepositoryFactory::defaultRepos($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(array($lockedRepo$localRepo$platformRepo));
            } else {
                
$installedRepo = new InstalledRepository(array($localRepo$platformRepo));
            }
            
$repos = new CompositeRepository(array_merge(array(new FilterRepository($installedRepo, array('canonical' => false))), $composer->getRepositoryManager()->getRepositories()));
        } elseif (
$input->getOption('all')) {
            
$defaultRepos RepositoryFactory::defaultRepos($io);
            
$io->writeError('No composer.json found in the current directory, showing available packages from ' implode(', 'array_keys($defaultRepos)));
            
$installedRepo = new InstalledRepository(array($platformRepo));
            
$repos = new CompositeRepository(array_merge(array($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'));
            
$repos $installedRepo = new InstalledRepository(array($lockedRepo));
        } else {
            
// --installed / default case
            
if (!$composer) {
                
$composer $this->getComposer();
            }
            
$rootPkg $composer->getPackage();
            
$repos $installedRepo = new InstalledRepository(array($composer->getRepositoryManager()->getLocalRepository()));

            if (
$input->getOption('no-dev')) {
                
$packages $this->filterRequiredPackages($installedRepo$rootPkg);
                
$repos $installedRepo = new InstalledRepository(array(new InstalledArrayRepository(array_map(function ($pkg) {
                    return clone 
$pkg;
                }, 
$packages))));
            }

            if (!
$installedRepo->getPackages() && ($rootPkg->getRequires() || $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 (($packageFilter && false === strpos($packageFilter'*')) || !empty($package)) {
            if (empty(
$package)) {
                list(
$package$versions) = $this->getPackage($installedRepo$repos$input->getArgument('package'), $input->getArgument('version'));

                if (empty(
$package)) {
                    
$options $input->getOptions();
                    if (!isset(
$options['working-dir']) || !file_exists('composer.json')) {
                        if (
PlatformRepository::isPlatformPackage($input->getArgument('package')) && !$input->getOption('platform')) {
                            throw new 
\InvalidArgumentException('Package ' $packageFilter ' not found, try using --platform (-p) to show platform packages.');
                        }
                        throw new 
\InvalidArgumentException('Package ' $packageFilter ' not found');
                    }

                    
$io->writeError('Package ' $packageFilter ' not found in ' $options['working-dir'] . '/composer.json');

                    return 
1;
                }
            } else {
                
$versions = array($package->getPrettyVersion() => $package->getVersion());
            }

            
$exitCode 0;
            if (
$input->getOption('tree')) {
                
$arrayTree $this->generatePackageTree($package$installedRepo$repos);

                if (
'json' === $format) {
                    
$io->write(JsonFile::encode(array('installed' => array($arrayTree))));
                } else {
                    
$this->displayPackageTree(array($arrayTree));
                }
            } else {
                
$latestPackage null;
                if (
$input->getOption('latest')) {
                    
$latestPackage $this->findLatestPackage($package$composer$platformRepo$input->getOption('minor-only'), $ignorePlatformReqs);
                }
                if (
                    
$input->getOption('outdated')
                    && 
$input->getOption('strict')
                    && 
$latestPackage
                    
&& $latestPackage->getFullPrettyVersion() !== $package->getFullPrettyVersion()
                    && (!
$latestPackage instanceof CompletePackageInterface || !$latestPackage->isAbandoned())
                ) {
                    
$exitCode 1;
                }
                if (
$input->getOption('path')) {
                    
$io->write($package->getName(), false);
                    
$io->write(' ' strtok(realpath($composer->getInstallationManager()->getInstallPath($package)), "\r\n"));

                    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, function (BasePackage $aBasePackage $b) {
                return 
strcmp((string) $a, (string) $b);
            });
            
$arrayTree = array();
            foreach (
$packages as $package) {
                if (
in_array($package->getName(), $rootRequirestrue)) {
                    
$arrayTree[] = $this->generatePackageTree($package$installedRepo$repos);
                }
            }

            if (
'json' === $format) {
                
$io->write(JsonFile::encode(array('installed' => $arrayTree)));
            } else {
                
$this->displayPackageTree($arrayTree);
            }

            return 
0;
        }

        
// list packages
        
$packages = array();
        
$packageFilterRegex null;
        if (
null !== $packageFilter) {
            
$packageFilterRegex '{^'.str_replace('\\*''.*?'preg_quote($packageFilter)).'$}i';
        }

        
$packageListFilter = array();
        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 (
$repos->getRepositories() 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 (!
$packageListFilter || in_array($package->getName(), $packageListFiltertrue)) {
                                
$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');
        
$showMinorOnly $input->getOption('minor-only');
        
$ignoredPackages array_map('strtolower'$input->getOption('ignore'));
        
$indent $showAllTypes '  ' '';
        
/** @var PackageInterface[] $latestPackages */
        
$latestPackages = array();
        
$exitCode 0;
        
$viewData = array();
        
$viewMetaData = array();
        foreach (array(
'platform' => true'locked' => true'available' => false'installed' => true) as $type => $showVersion) {
            if (isset(
$packages[$type])) {
                
ksort($packages[$type]);

                
$nameLength $versionLength $latestLength 0;

                if (
$showLatest && $showVersion) {
                    foreach (
$packages[$type] as $package) {
                        if (
is_object($package)) {
                            
$latestPackage $this->findLatestPackage($package$composer$platformRepo$showMinorOnly$ignorePlatformReqs);
                            if (
$latestPackage === false) {
                                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');

                
$hasOutdatedPackages false;

                
$viewData[$type] = array();
                foreach (
$packages[$type] as $package) {
                    
$packageViewData = array();
                    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());
                        
$packageIsIgnored \in_array($package->getPrettyName(), $ignoredPackagestrue);
                        if (
$input->getOption('outdated') && ($packageIsUpToDate || $packageIsIgnored)) {
                            continue;
                        }

                        if (
$input->getOption('outdated') || $input->getOption('strict')) {
                            
$hasOutdatedPackages true;
                        }

                        
$packageViewData['name'] = $package->getPrettyName();
                        
$nameLength max($nameLengthstrlen($package->getPrettyName()));
                        if (
$writeVersion) {
                            
$packageViewData['version'] = $package->getFullPrettyVersion();
                            
$versionLength max($versionLengthstrlen($package->getFullPrettyVersion()));
                        }
                        if (
$writeLatest && $latestPackage) {
                            
$packageViewData['latest'] = $latestPackage->getFullPrettyVersion();
                            
$packageViewData['latest-status'] = $this->getUpdateStatus($latestPackage$package);
                            
$latestLength max($latestLengthstrlen($latestPackage->getFullPrettyVersion()));
                        }
                        if (
$writeDescription && $package instanceof CompletePackageInterface) {
                            
$packageViewData['description'] = $package->getDescription();
                        }
                        if (
$writePath) {
                            
$packageViewData['path'] = strtok(realpath($composer->getInstallationManager()->getInstallPath($package)), "\r\n");
                        }

                        if (
$latestPackage instanceof CompletePackageInterface && $latestPackage->isAbandoned()) {
                            
$replacement is_string($latestPackage->getReplacementPackage())
                                ? 
'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;
                        }
                    } else {
                        
$packageViewData['name'] = $package;
                        
$nameLength max($nameLengthstrlen($package));
                    }
                    
$viewData[$type][] = $packageViewData;
                }
                
$viewMetaData[$type] = array(
                    
'nameLength' => $nameLength,
                    
'versionLength' => $versionLength,
                    
'latestLength' => $latestLength,
                );
                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'];

                
$writeVersion $nameLength $versionLength <= $width;
                
$writeLatest $nameLength $versionLength $latestLength <= $width;
                
$writeDescription $nameLength $versionLength $latestLength 24 <= $width;

                if (
$writeLatest && !$io->isDecorated()) {
                    
$latestLength += 2;
                }

                if (
$showAllTypes) {
                    if (
'available' === $type) {
                        
$io->write('<comment>' $type '</comment>:');
                    } else {
                        
$io->write('<info>' $type '</info>:');
                    }
                }

                foreach (
$packages as $package) {
                    
$io->write($indent str_pad($package['name'], $nameLength' '), false);
                    if (isset(
$package['version']) && $writeVersion) {
                        
$io->write(' ' str_pad($package['version'], $versionLength' '), false);
                    }
                    if (isset(
$package['latest']) && $writeLatest) {
                        
$latestVersion $package['latest'];
                        
$updateStatus $package['latest-status'];
                        
$style $this->updateStatusToVersionStyle($updateStatus);
                        if (!
$io->isDecorated()) {
                            
$latestVersion str_replace(array('up-to-date''semver-safe-update''update-possible'), array('=''!''~'), $updateStatus) . ' ' $latestVersion;
                        }
                        
$io->write(' <' $style '>' str_pad($latestVersion$latestLength' ') . '</' $style '>'false);
                    }
                    if (isset(
$package['description']) && $writeDescription) {
                        
$description strtok($package['description'], "\r\n");
                        
$remaining $width $nameLength $versionLength 4;
                        if (
$writeLatest) {
                            
$remaining -= $latestLength;
                        }
                        if (
strlen($description) > $remaining) {
                            
$description substr($description0$remaining 3) . '...';
                        }
                        
$io->write(' ' $descriptionfalse);
                    }
                    if (isset(
$package['path'])) {
                        
$io->write(' ' $package['path'], false);
                    }
                    
$io->write('');
                    if (isset(
$package['warning'])) {
                        
$io->write('<warning>' $package['warning'] . '</warning>');
                    }
                }

                if (
$showAllTypes) {
                    
$io->write('');
                }
            }
        }

        return 
$exitCode;
    }

    
/**
     * @return string[]
     */
    
protected function getRootRequires()
    {
        
$rootPackage $this->getComposer()->getPackage();

        return 
array_map(
            
'strtolower',
            
array_keys(array_merge($rootPackage->getRequires(), $rootPackage->getDevRequires()))
        );
    }

    
/**
     * @return array|string|string[]
     */
    
protected function getVersionStyle(PackageInterface $latestPackagePackageInterface $package)
    {
        return 
$this->updateStatusToVersionStyle($this->getUpdateStatus($latestPackage$package));
    }

    
/**
     * finds a package by name and version if provided
     *
     * @param  string                     $name
     * @param  ConstraintInterface|string $version
     * @throws \InvalidArgumentException
     * @return array{CompletePackageInterface|null, array<string, string>}
     */
    
protected function getPackage(InstalledRepository $installedRepoRepositoryInterface $repos$name$version null)
    {
        
$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 = array();
        if (
PlatformRepository::isPlatformPackage($name)) {
            
$pool $repositorySet->createPoolWithAllPackages();
        } else {
            
$pool $repositorySet->createPoolForPackage($name);
        }
        
$matches $pool->whatProvides($name$constraint);
        foreach (
$matches as $index => $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();
            
$matches[$index] = $package->getId();
        }

        
// select preferred package according to policy rules
        
if (!$matchedPackage && $matches && $preferred $policy->selectPreferredPackages($pool$matches)) {
            
$matchedPackage $pool->literalToPackage($preferred[0]);
        }

        return array(
$matchedPackage$versions);
    }

    
/**
     * Prints package info.
     *
     * @param array<string, string>    $versions
     * @param PackageInterface|null    $latestPackage
     *
     * @return void
     */
    
protected function printPackageInfo(CompletePackageInterface $package, array $versionsInstalledRepository $installedRepoPackageInterface $latestPackage null)
    {
        
$io $this->getIO();

        
$this->printMeta($package$versions$installedRepo$latestPackage ?: null);
        
$this->printLinks($packageLink::TYPE_REQUIRE);
        
$this->printLinks($packageLink::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($packageLink::TYPE_PROVIDE);
        
$this->printLinks($packageLink::TYPE_CONFLICT);
        
$this->printLinks($packageLink::TYPE_REPLACE);
    }

    
/**
     * Prints package metadata.
     *
     * @param array<string, string>    $versions
     * @param PackageInterface|null    $latestPackage
     *
     * @return void
     */
    
protected function printMeta(CompletePackageInterface $package, array $versionsInstalledRepository $installedRepoPackageInterface $latestPackage null)
    {
        
$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() ?: array()));
        
$this->printVersions($package$versions$installedRepo);
        if (
$latestPackage) {
            
$style $this->getVersionStyle($latestPackage$package);
            
$io->write('<info>latest</info>   : <'.$style.'>' $latestPackage->getPrettyVersion() . '</'.$style.'>');
        } 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 (
$installedRepo->hasPackage($package)) {
            
$io->write('<info>path</info>     : ' sprintf('%s'realpath($this->getComposer()->getInstallationManager()->getInstallPath($package))));
        }
        
$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 (
$package->getAutoload()) {
            
$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
     *
     * @return void
     */
    
protected function printVersions(CompletePackageInterface $package, array $versionsInstalledRepository $installedRepo)
    {
        
$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                   $linkType
     * @param string                   $title
     *
     * @return void
     */
    
protected function printLinks(CompletePackageInterface $package$linkType$title null)
    {
        
$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
     *
     * @return void
     */
    
protected function printLicenses(CompletePackageInterface $package)
    {
        
$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
     *
     * @return void
     */
    
protected function printPackageInfoAsJson(CompletePackageInterface $package, array $versionsInstalledRepository $installedRepoPackageInterface $latestPackage null)
    {
        
$json = array(
            
'name' => $package->getPrettyName(),
            
'description' => $package->getDescription(),
            
'keywords' => $package->getKeywords() ?: array(),
            
'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 (
$package->getSourceType()) {
            
$json['source'] = array(
                
'type' => $package->getSourceType(),
                
'url' => $package->getSourceUrl(),
                
'reference' => $package->getSourceReference(),
            );
        }

        if (
$package->getDistType()) {
            
$json['dist'] = array(
                
'type' => $package->getDistType(),
                
'url' => $package->getDistUrl(),
                
'reference' => $package->getDistReference(),
            );
        }

        if (
$installedRepo->hasPackage($package)) {
            
$json['path'] = realpath($this->getComposer()->getInstallationManager()->getInstallPath($package));
            if (
$json['path'] === false) {
                unset(
$json['path']);
            }
        }

        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 array<string, string|string[]|null> $json
     * @param array<string, string> $versions
     * @return array<string, string|string[]|null>
     */
    
private function appendVersions($json, array $versions)
    {
        
uasort($versions'version_compare');
        
$versions array_keys(array_reverse($versions));
        
$json['versions'] = $versions;

        return 
$json;
    }

    
/**
     * @param array<string, string|string[]|null> $json
     * @return array<string, string|string[]|null>
     */
    
private function appendLicenses($jsonCompletePackageInterface $package)
    {
        if (
$licenses $package->getLicense()) {
            
$spdxLicenses = new SpdxLicenses();

            
$json['licenses'] = array_map(function ($licenseId) use ($spdxLicenses) {
                
$license $spdxLicenses->getLicenseByIdentifier($licenseId); // keys: 0 fullname, 1 osi, 2 url

                
if (!$license) {
                    return 
$licenseId;
                }

                return array(
                    
'name' => $license[0],
                    
'osi' => $licenseId,
                    
'url' => $license[2],
                );
            }, 
$licenses);
        }

        return 
$json;
    }

    
/**
     * @param array<string, string|string[]|null> $json
     * @return array<string, string|string[]|null>
     */
    
private function appendAutoload($jsonCompletePackageInterface $package)
    {
        if (
$package->getAutoload()) {
            
$autoload = array();

            foreach (
$package->getAutoload() as $type => $autoloads) {
                if (
$type === 'psr-0' || $type === 'psr-4') {
                    
$psr = array();

                    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 array<string, string|string[]|null> $json
     * @return array<string, string|string[]|null>
     */
    
private function appendLinks($jsonCompletePackageInterface $package)
    {
        foreach (
Link::$TYPES as $linkType) {
            
$json $this->appendLink($json$package$linkType);
        }

        return 
$json;
    }

    
/**
     * @param array<string, string|string[]|null> $json
     * @param string $linkType
     * @return array<string, string|string[]|null>
     */
    
private function appendLink($jsonCompletePackageInterface $package$linkType)
    {
        
$links $package->{'get' ucfirst($linkType)}();

        if (
$links) {
            
$json[$linkType] = array();

            foreach (
$links as $link) {
                
$json[$linkType][$link->getTarget()] = $link->getPrettyConstraint();
            }
        }

        return 
$json;
    }

    
/**
     * Init styles for tree
     *
     * @return void
     */
    
protected function initStyles(OutputInterface $output)
    {
        
$this->colors = array(
            
'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
     * @return void
     */
    
protected function displayPackageTree(array $arrayTree)
    {
        
$io $this->getIO();
        foreach (
$arrayTree as $package) {
            
$io->write(sprintf('<info>%s</info>'$package['name']), false);
            
$io->write(' ' $package['version'], false);
            
$io->write(' ' strtok($package['description'], "\r\n"));

            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 = array($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
    
) {
        
$requires $package->getRequires();
        
ksort($requires);
        
$children = array();
        foreach (
$requires as $requireName => $require) {
            
$packagesInTree = array($package->getName(), $requireName);

            
$treeChildDesc = array(
                
'name' => $requireName,
                
'version' => $require->getPrettyConstraint(),
            );

            
$deepChildren $this->addTree($requireName$require$installedRepo$remoteRepos$packagesInTree);

            if (
$deepChildren) {
                
$treeChildDesc['requires'] = $deepChildren;
            }

            
$children[] = $treeChildDesc;
        }
        
$tree = array(
            
'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
     * @param string $previousTreeBar
     * @param int $level
     *
     * @return void
     */
    
protected function displayTree(
        
$package,
        array 
$packagesInTree,
        
$previousTreeBar '├',
        
$level 1
    
) {
        
$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];

                
$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   $name
     * @param  string[] $packagesInTree
     * @return array<int, array<string, array<int, array<string, string>>|string>>
     */
    
protected function addTree(
        
$name,
        
Link $link,
        
InstalledRepository $installedRepo,
        
RepositoryInterface $remoteRepos,
        array 
$packagesInTree
    
) {
        
$children = array();
        list(
$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 = array(
                    
'name' => $requireName,
                    
'version' => $require->getPrettyConstraint(),
                );

                if (!
in_array($requireName$currentTreetrue)) {
                    
$currentTree[] = $requireName;
                    
$deepChildren $this->addTree($requireName$require$installedRepo$remoteRepos$currentTree);
                    if (
$deepChildren) {
                        
$treeChildDesc['requires'] = $deepChildren;
                    }
                }

                
$children[] = $treeChildDesc;
            }
        }

        return 
$children;
    }

    
/**
     * @param string $updateStatus
     * @return string
     */
    
private function updateStatusToVersionStyle($updateStatus)
    {
        
// 'up-to-date' is printed green
        // 'semver-safe-update' is printed red
        // 'update-possible' is printed yellow
        
return str_replace(array('up-to-date''semver-safe-update''update-possible'), array('info''highlight''comment'), $updateStatus);
    }

    
/**
     * @return string
     */
    
private function getUpdateStatus(PackageInterface $latestPackagePackageInterface $package)
    {
        if (
$latestPackage->getFullPrettyVersion() === $package->getFullPrettyVersion()) {
            return 
'up-to-date';
        }

        
$constraint $package->getVersion();
        if (
!== 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';
    }

    
/**
     * @param string $line
     *
     * @return void
     */
    
private function writeTreeLine($line)
    {
        
$io $this->getIO();
        if (!
$io->isDecorated()) {
            
$line str_replace(array('└''├''──''│'), array('`-''|-''-''|'), $line);
        }

        
$io->write($line);
    }

    
/**
     * Given a package, this finds the latest package matching it
     *
     * @param bool $minorOnly
     * @param bool|string $ignorePlatformReqs
     *
     * @return PackageInterface|false
     */
    
private function findLatestPackage(PackageInterface $packageComposer $composerPlatformRepository $platformRepo$minorOnly false$ignorePlatformReqs false)
    {
        
// 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::$stabilitiestrue);
        }

        
$bestStability $stability;
        if (
$composer->getPackage()->getPreferStable()) {
            
$bestStability $package->getStability();
        }

        
$targetVersion null;
        if (
=== strpos($package->getVersion(), 'dev-')) {
            
$targetVersion $package->getVersion();
        }

        if (
$targetVersion === null && $minorOnly) {
            
$targetVersion '^' $package->getVersion();
        }

        
$candidate $versionSelector->findBestCandidate($name$targetVersion$bestStabilityPlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs));
        while (
$candidate instanceof AliasPackage) {
            
$candidate $candidate->getAliasOf();
        }

        return 
$candidate;
    }

    
/**
     * @return RepositorySet
     */
    
private function getRepositorySet(Composer $composer)
    {
        if (!
$this->repositorySet) {
            
$this->repositorySet = new RepositorySet($composer->getPackage()->getMinimumStability(), $composer->getPackage()->getStabilityFlags());
            
$this->repositorySet->addRepository(new CompositeRepository($composer->getRepositoryManager()->getRepositories()));
        }

        return 
$this->repositorySet;
    }

    
/**
     * Find package requires and child requires
     *
     * @param  array<PackageInterface> $bucket
     * @return array<PackageInterface>
     */
    
private function filterRequiredPackages(RepositoryInterface $repoPackageInterface $package$bucket = array())
    {
        
$requires $package->getRequires();

        foreach (
$repo->getPackages() as $candidate) {
            foreach (
$candidate->getNames() as $name) {
                if (isset(
$requires[$name])) {
                    if (!
in_array($candidate$buckettrue)) {
                        
$bucket[] = $candidate;
                        
$bucket $this->filterRequiredPackages($repo$candidate$bucket);
                    }
                    break;
                }
            }
        }

        return 
$bucket;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\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 <[email protected]>
 * @author Rui Marinho <[email protected]>
 */
class StatusCommand extends BaseCommand
{
    const 
EXIT_CODE_ERRORS 1;
    const 
EXIT_CODE_UNPUSHED_CHANGES 2;
    const 
EXIT_CODE_VERSION_CHANGES 4;

    
/**
     * @return void
     * @throws \Symfony\Component\Console\Exception\InvalidArgumentException
     */
    
protected function configure()
    {
        
$this
            
->setName('status')
            ->
setDescription('Shows a list of locally modified packages.')
            ->
setDefinition(array(
                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
            )
        ;
    }

    
/**
     * @return int
     */
    
protected function execute(InputInterface $inputOutputInterface $output)
    {
        
$composer $this->getComposer();

        
$commandEvent = new CommandEvent(PluginEvents::COMMAND'status'$input$output);
        
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        
// Dispatch pre-status-command
        
$composer->getEventDispatcher()->dispatchScript(ScriptEvents::PRE_STATUS_CMDtrue);

        
$exitCode $this->doExecute($input);

        
// Dispatch post-status-command
        
$composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_STATUS_CMDtrue);

        return 
$exitCode;
    }

    
/**
     * @return int
     */
    
private function doExecute(InputInterface $input)
    {
        
// init repos
        
$composer $this->getComposer();

        
$installedRepo $composer->getRepositoryManager()->getLocalRepository();

        
$dm $composer->getDownloadManager();
        
$im $composer->getInstallationManager();

        
$errors = array();
        
$io $this->getIO();
        
$unpushedChanges = array();
        
$vcsVersionChanges = array();

        
$parser = new VersionParser;
        
$guesser = new VersionGuesser($composer->getConfig(), new ProcessExecutor($io), $parser);
        
$dumper = new ArrayDumper;

        
// list packages
        
foreach ($installedRepo->getCanonicalPackages() as $package) {
            
$downloader $dm->getDownloaderForPackage($package);
            
$targetDir $im->getInstallPath($package);

            if (
$downloader instanceof ChangeReportInterface) {
                if (
is_link($targetDir)) {
                    
$errors[$targetDir] = $targetDir ' is a symbolic link.';
                }

                if (
$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) {
                        
$vcsVersionChanges[$targetDir] = array(
                            
'previous' => array(
                                
'version' => $package->getPrettyVersion(),
                                
'ref' => $previousRef,
                            ),
                            
'current' => array(
                                
'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(function ($line) {
                        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(function ($line) {
                        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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\CompletePackageInterface;
use 
Composer\Plugin\CommandEvent;
use 
Composer\Plugin\PluginEvents;
use 
Composer\Package\PackageInterface;
use 
Composer\Repository\RepositoryInterface;
use 
Symfony\Component\Console\Helper\Table;
use 
Symfony\Component\Console\Input\InputInterface;
use 
Symfony\Component\Console\Input\InputOption;
use 
Symfony\Component\Console\Output\OutputInterface;
use 
Symfony\Component\Console\Style\SymfonyStyle;

/**
 * @author Benoît Merlet <[email protected]>
 */
class LicensesCommand extends BaseCommand
{
    
/**
     * @return void
     */
    
protected function configure()
    {
        
$this
            
->setName('licenses')
            ->
setDescription('Shows information about licenses of dependencies.')
            ->
setDefinition(array(
                new 
InputOption('format''f'InputOption::VALUE_REQUIRED'Format of the output: text, json or summary''text'),
                new 
InputOption('no-dev'nullInputOption::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
            )
        ;
    }

    
/**
     * @return int
     */
    
protected function execute(InputInterface $inputOutputInterface $output)
    {
        
$composer $this->getComposer();

        
$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 $this->filterRequiredPackages($repo$root);
        } else {
            
$packages $this->appendPackages($repo->getPackages(), array());
        }

        
ksort($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');
                
$tableStyle $table->getStyle();
                if (
method_exists($tableStyle'setVerticalBorderChars')) {
                    
$tableStyle->setVerticalBorderChars('');
                } else {
                    
// TODO remove in composer 2.2
                    // @phpstan-ignore-next-line
                    
$tableStyle->setVerticalBorderChar('');
                }
                
$tableStyle->setCellRowContentFormat('%s  ');
                
$table->setHeaders(array('Name''Version''License'));
                foreach (
$packages as $package) {
                    
$table->addRow(array(
                        
$package->getPrettyName(),
                        
$package->getFullPrettyVersion(),
                        
implode(', '$package instanceof CompletePackageInterface $package->getLicense() : array()) ?: 'none',
                    ));
                }
                
$table->render();
                break;

            case 
'json':
                
$dependencies = array();
                foreach (
$packages as $package) {
                    
$dependencies[$package->getPrettyName()] = array(
                        
'version' => $package->getFullPrettyVersion(),
                        
'license' => $package instanceof CompletePackageInterface $package->getLicense() : array(),
                    );
                }

                
$io->write(JsonFile::encode(array(
                    
'name' => $root->getPrettyName(),
                    
'version' => $root->getFullPrettyVersion(),
                    
'license' => $root->getLicense(),
                    
'dependencies' => $dependencies,
                )));
                break;

            case 
'summary':
                
$usedLicenses = array();
                foreach (
$packages as $package) {
                    
$license $package instanceof CompletePackageInterface $package->getLicense() : array();
                    
$licenseName $license[0];
                    if (!isset(
$usedLicenses[$licenseName])) {
                        
$usedLicenses[$licenseName] = 0;
                    }
                    
$usedLicenses[$licenseName]++;
                }

                
// Sort licenses so that the most used license will appear first
                
arsort($usedLicensesSORT_NUMERIC);

                
$rows = array();
                foreach (
$usedLicenses as $usedLicense => $numberOfDependencies) {
                    
$rows[] = array($usedLicense$numberOfDependencies);
                }

                
$symfonyIo = new SymfonyStyle($input$output);
                
$symfonyIo->table(
                    array(
'License''Number of dependencies'),
                    
$rows
                
);
                break;
            default:
                throw new 
\RuntimeException(sprintf('Unsupported format "%s".  See help for supported formats.'$format));
        }

        return 
0;
    }

    
/**
     * Find package requires and child requires
     *
     * @param  array<string, PackageInterface> $bucket
     * @return array<string, PackageInterface>
     */
    
private function filterRequiredPackages(RepositoryInterface $repoPackageInterface $package$bucket = array())
    {
        
$requires array_keys($package->getRequires());

        
$packageListNames array_keys($bucket);
        
$packages array_filter(
            
$repo->getPackages(),
            function (
$package) use ($requires$packageListNames) {
                return 
in_array($package->getName(), $requires) && !in_array($package->getName(), $packageListNames);
            }
        );

        
$bucket $this->appendPackages($packages$bucket);

        foreach (
$packages as $package) {
            
$bucket $this->filterRequiredPackages($repo$package$bucket);
        }

        return 
$bucket;
    }

    
/**
     * Adds packages to the package list
     *
     * @param  PackageInterface[]              $packages the list of packages to add
     * @param  array<string, PackageInterface> $bucket   the list to add packages to
     * @return array<string, PackageInterface>
     */
    
public function appendPackages(array $packages, array $bucket)
    {
        foreach (
$packages as $package) {
            
$bucket[$package->getName()] = $package;
        }

        return 
$bucket;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\Input\InputInterface;
use 
Symfony\Component\Console\Input\InputOption;
use 
Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Nicolas Grekas <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 */
class FundCommand extends BaseCommand
{
    
/**
     * @return void
     */
    
protected function configure()
    {
        
$this->setName('fund')
            ->
setDescription('Discover how to help fund the maintenance of your dependencies.')
            ->
setDefinition(array(
                new 
InputOption('format''f'InputOption::VALUE_REQUIRED'Format of the output: text or json''text'),
            ))
        ;
    }

    
/**
     * @return int
     */
    
protected function execute(InputInterface $inputOutputInterface $output)
    {
        
$composer $this->getComposer();

        
$repo $composer->getRepositoryManager()->getLocalRepository();
        
$remoteRepos = new CompositeRepository($composer->getRepositoryManager()->getRepositories());
        
$fundings = array();

        
$packagesToLoad = array();
        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, array('dev' => BasePackage::STABILITY_DEV), array());

        
// 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, array('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('    %s'$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 $fundingsCompletePackageInterface $package)
    {
        foreach (
$package->getFunding() as $fundingOption) {
            list(
$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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 
Symfony\Component\Console\Input\InputOption;
use 
Symfony\Component\Console\Output\OutputInterface;
use 
Composer\Repository\PlatformRepository;
use 
Composer\Repository\RootPackageRepository;
use 
Composer\Repository\InstalledRepository;

class 
CheckPlatformReqsCommand extends BaseCommand
{
    
/**
     * @return void
     */
    
protected function configure()
    {
        
$this->setName('check-platform-reqs')
            ->
setDescription('Check that platform requirements are satisfied.')
            ->
setDefinition(array(
                new 
InputOption('no-dev'nullInputOption::VALUE_NONE'Disables checking of require-dev packages requirements.'),
                new 
InputOption('lock'nullInputOption::VALUE_NONE'Checks requirements only from the lock file, not from installed packages.'),
            ))
            ->
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
            );
    }

    
/**
     * @return int
     */
    
protected function execute(InputInterface $inputOutputInterface $output)
    {
        
$composer $this->getComposer();

        
$requires = array();
        
$removePackages = array();
        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')) {
            
$requires += $composer->getPackage()->getDevRequires();
        }

        foreach (
$requires as $require => $link) {
            
$requires[$require] = array($link);
        }

        
$installedRepo = new InstalledRepository(array($installedRepo, new RootPackageRepository($composer->getPackage())));
        foreach (
$installedRepo->getPackages() as $package) {
            if (
in_array($package->getName(), $removePackagestrue)) {
                continue;
            }
            foreach (
$package->getRequires() as $require => $link) {
                
$requires[$require][] = $link;
            }
        }

        
ksort($requires);

        
$installedRepo->addRepository(new PlatformRepository(array(), array()));

        
$results = array();
        
$exitCode 0;

        
/**
         * @var Link[] $links
         */
        
foreach ($requires as $require => $links) {
            if (
PlatformRepository::isPlatformPackage($require)) {
                
$candidates $installedRepo->findPackagesWithReplacersAndProviders($require);
                if (
$candidates) {
                    
$reqResults = array();
                    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[] = array(
                                    
$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[] = array(
                            
$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($exitCode1);

                    continue;
                }

                
$results[] = array(
                    
$require,
                    
'n/a',
                    
$links[0],
                    
'<error>missing</error>',
                );

                
$exitCode max($exitCode2);
            }
        }

        
$this->printTable($output$results);

        return 
$exitCode;
    }

    
/**
     * @param mixed[] $results
     *
     * @return void
     */
    
protected function printTable(OutputInterface $output$results)
    {
        
$rows = array();
        foreach (
$results as $result) {
            
/**
             * @var Link|null $link
             */
            
list($platformPackage$version$link$status) = $result;
            
$rows[] = array(
                
$platformPackage,
                
$version,
                
$link sprintf('%s %s %s (%s)'$link->getSource(), $link->getDescription(), $link->getTarget(), $link->getPrettyConstraint()) : '',
                
$status,
            );
        }

        
$this->renderTable($rows$output);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\Input\InputInterface;
use 
Symfony\Component\Console\Input\InputArgument;
use 
Symfony\Component\Console\Input\StringInput;
use 
Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <[email protected]>
 */
class GlobalCommand extends BaseCommand
{
    
/**
     * @return void
     */
    
protected function configure()
    {
        
$this
            
->setName('global')
            ->
setDescription('Allows running commands in the global composer dir ($COMPOSER_HOME).')
            ->
setDefinition(array(
                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
            )
        ;
    }

    
/**
     * @return int|void
     * @throws \Symfony\Component\Console\Exception\ExceptionInterface
     */
    
public function run(InputInterface $inputOutputInterface $output)
    {
        if (!
method_exists($input'__toString')) {
            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 = array();
        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);
        }

        
// 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);
        }
        
$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 
$this->getApplication()->run($input$output);
    }

    
/**
     * @inheritDoc
     */
    
public function isProxyCommand()
    {
        return 
true;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\Repository\CompositeRepository;
use 
Composer\Repository\RepositoryFactory;
use 
Composer\Script\ScriptEvents;
use 
Composer\Plugin\CommandEvent;
use 
Composer\Plugin\PluginEvents;
use 
Composer\Util\Filesystem;
use 
Composer\Util\Loop;
use 
Composer\Util\ProcessExecutor;
use 
Symfony\Component\Console\Input\InputArgument;
use 
Symfony\Component\Console\Input\InputInterface;
use 
Symfony\Component\Console\Input\InputOption;
use 
Symfony\Component\Console\Output\OutputInterface;

/**
 * Creates an archive of a package for distribution.
 *
 * @author Nils Adermann <[email protected]>
 */
class ArchiveCommand extends BaseCommand
{
    
/**
     * @return void
     */
    
protected function configure()
    {
        
$this
            
->setName('archive')
            ->
setDescription('Creates an archive of this composer package.')
            ->
setDefinition(array(
                new 
InputArgument('package'InputArgument::OPTIONAL'The package to archive instead of the current project'),
                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)'),
                new 
InputOption('dir'nullInputOption::VALUE_REQUIRED'Write the archive to this directory'),
                new 
InputOption('file'nullInputOption::VALUE_REQUIRED'Write the archive with the given file name.'
                    
.' Note that the format will be appended.'),
                new 
InputOption('ignore-filters'nullInputOption::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] [package [version]]</info>

Read more at https://getcomposer.org/doc/03-cli.md#archive
EOT
            )
        ;
    }

    protected function 
execute(InputInterface $inputOutputInterface $output)
    {
        
$composer $this->getComposer(false);
        
$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();
        }

        if (
null === $input->getOption('format')) {
            
$input->setOption('format'$config->get('archive-format'));
        }
        if (
null === $input->getOption('dir')) {
            
$input->setOption('dir'$config->get('archive-dir'));
        }

        
$returnCode $this->archive(
            
$this->getIO(),
            
$config,
            
$input->getArgument('package'),
            
$input->getArgument('version'),
            
$input->getOption('format'),
            
$input->getOption('dir'),
            
$input->getOption('file'),
            
$input->getOption('ignore-filters'),
            
$composer
        
);

        if (
=== $returnCode && $composer) {
            
$composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_ARCHIVE_CMD);
        }

        return 
$returnCode;
    }

    
/**
     * @param string|null $packageName
     * @param string|null $version
     * @param string $format
     * @param string $dest
     * @param string|null $fileName
     * @param bool $ignoreFilters
     *
     * @return int
     * @throws \Exception
     */
    
protected function archive(IOInterface $ioConfig $config$packageName null$version null$format 'tar'$dest '.'$fileName null$ignoreFilters falseComposer $composer null)
    {
        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->getComposer()->getPackage();
        }

        
$io->writeError('<info>Creating the archive into "'.$dest.'".</info>');
        
$packagePath $archiveManager->archive($package$format$dest$fileName$ignoreFilters);
        
$fs = new Filesystem;
        
$shortPath $fs->findShortestPath(getcwd(), $packagePathtrue);

        
$io->writeError('Created: 'false);
        
$io->write(strlen($shortPath) < strlen($packagePath) ? $shortPath $packagePath);

        return 
0;
    }

    
/**
     * @param string      $packageName
     * @param string|null $version
     *
     * @return (BasePackage&CompletePackageInterface)|false
     */
    
protected function selectPackage(IOInterface $io$packageName$version null)
    {
        
$io->writeError('<info>Searching for the specified package.</info>');

        if (
$composer $this->getComposer(false)) {
            
$localRepo $composer->getRepositoryManager()->getLocalRepository();
            
$repo = new CompositeRepository(array_merge(array($localRepo), $composer->getRepositoryManager()->getRepositories()));
        } else {
            
$defaultRepos RepositoryFactory::defaultRepos($this->getIO());
            
$io->writeError('No composer.json found in the current directory, searching packages from ' implode(', 'array_keys($defaultRepos)));
            
$repo = new CompositeRepository($defaultRepos);
        }

        
$packages $repo->findPackages($packageName$version);

        if (
count($packages) > 1) {
            
$package reset($packages);
            
$io->writeError('<info>Found multiple matches, selected '.$package->getPrettyString().'.</info>');
            
$io->writeError('Alternatives were '.implode(', 'array_map(function ($p) {
                return 
$p->getPrettyString();
            }, 
$packages)).'.');
            
$io->writeError('<comment>Please use a more specific constraint to pick a different package.</comment>');
        } elseif (
$packages) {
            
$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));
        }

        return 
$package;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class AboutCommand extends BaseCommand
{
    
/**
     * @return void
     */
    
protected function configure()
    {
        
$this
            
->setName('about')
            ->
setDescription('Shows a short information about Composer.')
            ->
setHelp(
                <<<EOT
<info>php composer.phar about</info>
EOT
            )
        ;
    }

    protected function 
execute(InputInterface $inputOutputInterface $output)
    {
        
$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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 
Symfony\Component\Console\Input\InputInterface;
use 
Symfony\Component\Console\Input\InputOption;
use 
Symfony\Component\Console\Input\InputArgument;
use 
Symfony\Component\Console\Output\OutputInterface;
use 
Symfony\Component\Finder\Finder;

/**
 * @author Igor Wiedler <[email protected]>
 * @author Kevin Ran <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 */
class SelfUpdateCommand extends BaseCommand
{
    const 
HOMEPAGE 'getcomposer.org';
    const 
OLD_INSTALL_EXT '-old.phar';

    
/**
     * @return void
     */
    
protected function configure()
    {
        
$this
            
->setName('self-update')
            ->
setAliases(array('selfupdate'))
            ->
setDescription('Updates composer.phar to the latest version.')
            ->
setDefinition(array(
                new 
InputOption('rollback''r'InputOption::VALUE_NONE'Revert to an older installation of composer'),
                new 
InputOption('clean-backups'nullInputOption::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'nullInputOption::VALUE_NONE'Do not output download progress.'),
                new 
InputOption('update-keys'nullInputOption::VALUE_NONE'Prompt user for a key update'),
                new 
InputOption('stable'nullInputOption::VALUE_NONE'Force an update to the stable channel'),
                new 
InputOption('preview'nullInputOption::VALUE_NONE'Force an update to the preview channel'),
                new 
InputOption('snapshot'nullInputOption::VALUE_NONE'Force an update to the snapshot channel'),
                new 
InputOption('1'nullInputOption::VALUE_NONE'Force an update to the stable channel, but only use 1.x versions'),
                new 
InputOption('2'nullInputOption::VALUE_NONE'Force an update to the stable channel, but only use 2.x versions'),
                new 
InputOption('set-channel-only'nullInputOption::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
            )
        ;
    }

    
/**
     * @return int
     * @throws FilesystemException
     */
    
protected function execute(InputInterface $inputOutputInterface $output)
    {
        
// 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);
                break;
            }
        }

        if (
$input->getOption('set-channel-only')) {
            return 
0;
        }

        
$cacheDir $config->get('cache-dir');
        
$rollbackDir $config->get('data-dir');
        
$home $config->get('home');
        
$localFilename realpath($_SERVER['argv'][0]) ?: $_SERVER['argv'][0];

        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')) {
            
$composeUser posix_getpwuid(posix_geteuid());
            
$homeOwner posix_getpwuid(fileowner($home));
            if (isset(
$composeUser['name'], $homeOwner['name']) && $composeUser['name'] !== $homeOwner['name']) {
                
$io->writeError('<warning>You are running Composer as "'.$composeUser['name'].'", while "'.$home.'" is owned by "'.$homeOwner['name'].'"</warning>');
            }
        }

        if (
$input->getOption('rollback')) {
            return 
$this->rollback($output$rollbackDir$localFilename);
        }

        
$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' && !$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>');
            }
        }

        if (
$requestedChannel && is_numeric($requestedChannel) && strpos($latestStable['version'], $requestedChannel) !== 0) {
            
$io->writeError('<warning>Warning: You forced the install of '.$latestVersion.' via --'.$requestedChannel.', but '.$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'.rand(010000000).'.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) || !$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);
            
$algo defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 'SHA384';
            if (!
in_array('sha384'array_map('strtolower'openssl_get_md_methods()))) {
                throw new 
\RuntimeException('SHA384 is not supported by your openssl extension, could not verify the phar file integrity');
            }
            
$signature json_decode($signaturetrue);
            
$signature base64_decode($signature['sha384']);
            
$verified === openssl_verify(file_get_contents($tempFilename), $signature$pubkeyid$algo);

            
// PHP 8 automatically frees the key instance and deprecates the function
            
if (PHP_VERSION_ID 80000) {
                
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;
    }

    
/**
     * @return void
     * @throws \Exception
     */
    
protected function fetchKeys(IOInterface $ioConfig $config)
    {
        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 = function ($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'));
    }

    
/**
     * @param string $rollbackDir
     * @param string $localFilename
     * @return int
     * @throws FilesystemException
     */
    
protected function rollback(OutputInterface $output$rollbackDir$localFilename)
    {
        
$rollbackVersion $this->getLastBackupVersion($rollbackDir);
        if (!
$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($localFilename$newFilename$backupTarget null)
    {
        
$io $this->getIO();
        @
chmod($newFilenamefileperms($localFilename));

        
// check phar validity
        
if (!$this->validatePhar($newFilename$error)) {
            
$io->writeError('<error>The '.($backupTarget 'update' 'backup').' file is corrupted ('.$error.')</error>');

            if (
$backupTarget) {
                
$io->writeError('<error>Please re-run the self-update command to try again.</error>');
            }

            return 
false;
        }

        
// copy current file into backups dir
        
if ($backupTarget) {
            @
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 'update' 'rollback');
            throw new 
FilesystemException($action.' failed: "'.$localFilename.'" could not be written.'.PHP_EOL.$e->getMessage());
        }
    }

    
/**
     * @param string $rollbackDir
     * @param string|null $except
     *
     * @return void
     */
    
protected function cleanBackups($rollbackDir$except null)
    {
        
$finder $this->getOldInstallationFinder($rollbackDir);
        
$io $this->getIO();
        
$fs = new Filesystem;

        foreach (
$finder as $file) {
            if (
$except && $file->getBasename(self::OLD_INSTALL_EXT) === $except) {
                continue;
            }
            
$file = (string) $file;
            
$io->writeError('<info>Removing: '.$file.'</info>');
            
$fs->remove($file);
        }
    }

    
/**
     * @param string $rollbackDir
     * @return string|false
     */
    
protected function getLastBackupVersion($rollbackDir)
    {
        
$finder $this->getOldInstallationFinder($rollbackDir);
        
$finder->sortByName();
        
$files iterator_to_array($finder);

        if (
count($files)) {
            return 
basename(end($files), self::OLD_INSTALL_EXT);
        }

        return 
false;
    }

    
/**
     * @param string $rollbackDir
     * @return Finder
     */
    
protected function getOldInstallationFinder($rollbackDir)
    {
        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($pharFile, &$error)
    {
        if (
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
     *
     * @return bool
     */
    
protected function isWindowsNonAdminUser()
    {
        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($localFilename$newFilename)
    {
        
$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($questionfalse)) {
            
$io->writeError('<warning>Operation cancelled. '.$helpMessage.'</warning>');

            return 
false;
        }

        
$tmpFile tempnam(sys_get_temp_dir(), '');
        
$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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\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 
Symfony\Component\Console\Formatter\OutputFormatterStyle;
use 
Composer\Package\Version\VersionParser;
use 
Symfony\Component\Console\Input\InputInterface;
use 
Symfony\Component\Console\Output\OutputInterface;

/**
 * Base implementation for commands mapping dependency relationships.
 *
 * @author Niels Keurentjes <[email protected]>
 */
class BaseDependencyCommand extends BaseCommand
{
    const 
ARGUMENT_PACKAGE 'package';
    const 
ARGUMENT_CONSTRAINT 'version';
    const 
OPTION_RECURSIVE 'recursive';
    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 $inputOutputInterface $output$inverted false)
    {
        
// Emit command event on startup
        
$composer $this->getComposer();
        
$commandEvent = new CommandEvent(PluginEvents::COMMAND$this->getName(), $input$output);
        
$composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        
$platformOverrides $composer->getConfig()->get('platform') ?: array();
        
$installedRepo = new InstalledRepository(array(
            new 
RootPackageRepository($composer->getPackage()),
            
$composer->getRepositoryManager()->getLocalRepository(),
            new 
PlatformRepository(array(), $platformOverrides),
        ));

        
// Parse package name and constraint
        
list($needle$textConstraint) = array_pad(
            
explode(':'$input->getArgument(self::ARGUMENT_PACKAGE)),
            
2,
            
$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.
        
if (!$installedRepo->findPackage($needle$textConstraint)) {
            
$defaultRepos = new CompositeRepository(RepositoryFactory::defaultRepos($this->getIO()));
            if (
$match $defaultRepos->findPackage($needle$textConstraint)) {
                
$installedRepo->addRepository(new InstalledArrayRepository(array(clone $match)));
            }
        }

        
// Include replaced packages for inverted lookups as they are then the actual starting point to consider
        
$needles = array($needle);
        if (
$inverted) {
            foreach (
$packages as $package) {
                
$needles array_merge($needlesarray_map(function (Link $link) {
                    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);

        
// 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
            
));
        } 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);
        }

        return 
0;
    }

    
/**
     * Assembles and prints a bottom-up table of the dependencies.
     *
     * @param array{PackageInterface, Link, mixed}[] $results
     *
     * @return void
     */
    
protected function printTable(OutputInterface $output$results)
    {
        
$table = array();
        
$doubles = array();
        do {
            
$queue = array();
            
$rows = array();
            foreach (
$results as $result) {
                
/**
                 * @var PackageInterface $package
                 * @var Link             $link
                 */
                
list($package$link$children) = $result;
                
$unique = (string) $link;
                if (isset(
$doubles[$unique])) {
                    continue;
                }
                
$doubles[$unique] = true;
                
$version $package->getPrettyVersion() === RootPackage::DEFAULT_PRETTY_VERSION '-' $package->getPrettyVersion();
                
$rows[] = array($package->getPrettyName(), $version$link->getDescription(), sprintf('%s (%s)'$link->getTarget(), $link->getPrettyConstraint()));
                if (
$children) {
                    
$queue array_merge($queue$children);
                }
            }
            
$results $queue;
            
$table array_merge($rows$table);
        } while (!empty(
$results));

        
$this->renderTable($table$output);
    }

    
/**
     * Init styles for tree
     *
     * @return void
     */
    
protected function initStyles(OutputInterface $output)
    {
        
$this->colors = array(
            
'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, mixed[]|bool}[] $results Results to be printed at this level.
     * @param string  $prefix  Prefix of the current tree level.
     * @param int     $level   Current level of recursion.
     *
     * @return void
     */
    
protected function printTree($results$prefix ''$level 1)
    {
        
$count count($results);
        
$idx 0;
        foreach (
$results as $result) {
            list(
$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();
            
$packageText rtrim(sprintf('<%s>%s</%1$s> %s'$color$package->getPrettyName(), $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 (
$children) {
                
$this->printTree($children$prefix . ($isLast '   ' '│  '), $level 1);
            }
        }
    }

    
/**
     * @param string $line
     *
     * @return void
     */
    
private function writeTreeLine($line)
    {
        
$io $this->getIO();
        if (!
$io->isDecorated()) {
            
$line str_replace(array('└''├''──''│'), array('`-''|-''-''|'), $line);
        }

        
$io->write($line);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 
Symfony\Component\Console\Input\InputArgument;
use 
Symfony\Component\Console\Input\InputInterface;
use 
Symfony\Component\Console\Input\InputOption;
use 
Symfony\Component\Console\Output\OutputInterface;

class 
SuggestsCommand extends BaseCommand
{
    
/**
     * @return void
     */
    
protected function configure()
    {
        
$this
            
->setName('suggests')
            ->
setDescription('Shows package suggestions.')
            ->
setDefinition(array(
                new 
InputOption('by-package'nullInputOption::VALUE_NONE'Groups output by suggesting package (default)'),
                new 
InputOption('by-suggestion'nullInputOption::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'nullInputOption::VALUE_NONE'Show only list of suggested package names'),
                new 
InputOption('no-dev'nullInputOption::VALUE_NONE'Exclude suggestions from require-dev packages'),
                new 
InputArgument('packages'InputArgument::IS_ARRAY InputArgument::OPTIONAL'Packages that you want to list suggestions from.'),
            ))
            ->
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
            )
        ;
    }

    
/**
     * @inheritDoc
     */
    
protected function execute(InputInterface $inputOutputInterface $output)
    {
        
$composer $this->getComposer();

        
$installedRepos = array(
            new 
RootPackageRepository(clone $composer->getPackage()),
        );

        
$locker $composer->getLocker();
        if (
$locker->isLocked()) {
            
$installedRepos[] = new PlatformRepository(array(), $locker->getPlatformOverrides());
            
$installedRepos[] = $locker->getLockedRepository(!$input->getOption('no-dev'));
        } else {
            
$installedRepos[] = new PlatformRepository(array(), $composer->getConfig()->get('platform') ?: array());
            
$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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 */
class ValidateCommand extends BaseCommand
{
    
/**
     * configure
     * @return void
     */
    
protected function configure()
    {
        
$this
            
->setName('validate')
            ->
setDescription('Validates a composer.json and composer.lock.')
            ->
setDefinition(array(
                new 
InputOption('no-check-all'nullInputOption::VALUE_NONE'Do not validate requires for overly strict/loose constraints'),
                new 
InputOption('no-check-lock'nullInputOption::VALUE_NONE'Do not check if lock file is up to date'),
                new 
InputOption('no-check-publish'nullInputOption::VALUE_NONE'Do not check for publish errors'),
                new 
InputOption('no-check-version'nullInputOption::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'nullInputOption::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
            );
    }

    
/**
     * @return int
     */
    
protected function execute(InputInterface $inputOutputInterface $output)
    {
        
$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') ? ValidatingArrayLoader::CHECK_ALL;
        
$checkPublish = !$input->getOption('no-check-publish');
        
$checkLock = !$input->getOption('no-check-lock');
        
$checkVersion $input->getOption('no-check-version') ? ConfigValidator::CHECK_VERSION;
        
$isStrict $input->getOption('strict');
        list(
$errors$publishErrors$warnings) = $validator->validate($file$checkAll$checkVersion);

        
$lockErrors = array();
        
$composer Factory::create($io$file$input->hasParameterOption('--no-plugins'));
        
$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()) {
            
$missingRequirements false;
            
$sets = array(
                array(
'repo' => $locker->getLockedRepository(false), 'method' => 'getRequires''description' => 'Required'),
                array(
'repo' => $locker->getLockedRepository(true), 'method' => 'getDevRequires''description' => 'Required (in require-dev)'),
            );
            foreach (
$sets as $set) {
                
$installedRepo = new InstalledRepository(array($set['repo']));

                foreach (
call_user_func(array($composer->getPackage(), $set['method'])) as $link) {
                    if (
PlatformRepository::isPlatformPackage($link->getTarget())) {
                        continue;
                    }
                    if (!
$installedRepo->findPackagesWithReplacersAndProviders($link->getTarget(), $link->getConstraint())) {
                        if (
$results $installedRepo->findPackagesWithReplacersAndProviders($link->getTarget())) {
                            
$provider reset($results);
                            
$lockErrors[] = '- ' $set['description'].' package "' $link->getTarget() . '" is in the lock file as "'.$provider->getPrettyVersion().'" but that does not satisfy your constraint "'.$link->getPrettyConstraint().'".';
                        } else {
                            
$lockErrors[] = '- ' $set['description'].' package "' $link->getTarget() . '" is not present in the lock file.';
                        }
                        
$missingRequirements true;
                    }
                }
            }

            if (
$missingRequirements) {
                
$lockErrors[] = 'This usually happens when composer files are incorrectly merged or the composer.json file is manually edited.';
                
$lockErrors[] = 'Read more about correctly resolving merge conflicts https://getcomposer.org/doc/articles/resolving-merge-conflicts.md';
                
$lockErrors[] = 'and prefer using the "require" command over editing the composer.json file directly https://getcomposer.org/doc/03-cli.md#require';
            }
        }

        
$this->outputResult($io$file$errors$warnings$checkPublish$publishErrors$checkLock$lockErrorstrue);

        
// $errors include publish and lock errors when exists
        
$exitCode $errors : ($isStrict && $warnings 0);

        if (
$input->getOption('with-dependencies')) {
            
$localRepo $composer->getRepositoryManager()->getLocalRepository();
            foreach (
$localRepo->getPackages() as $package) {
                
$path $composer->getInstallationManager()->getInstallPath($package);
                
$file $path '/composer.json';
                if (
is_dir($path) && file_exists($file)) {
                    list(
$errors$publishErrors$warnings) = $validator->validate($file$checkAll$checkVersion);

                    
$this->outputResult($io$package->getPrettyName(), $errors$warnings$checkPublish$publishErrors);

                    
// $errors include publish errors when exists
                    
$depCode $errors : ($isStrict && $warnings 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 $name
     * @param string[] $errors
     * @param string[] $warnings
     * @param bool $checkPublish
     * @param string[] $publishErrors
     * @param bool $checkLock
     * @param string[] $lockErrors
     * @param bool $printSchemaUrl
     *
     * @return void
     */
    
private function outputResult(IOInterface $io$name, &$errors, &$warnings$checkPublish false$publishErrors = array(), $checkLock false$lockErrors = array(), $printSchemaUrl false)
    {
        
$doPrintSchemaUrl false;

        if (
$errors) {
            
$io->writeError('<error>' $name ' is invalid, the following errors/warnings were found:</error>');
        } elseif (
$publishErrors) {
            
$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 (
$warnings) {
            
$io->writeError('<info>' $name ' is valid, but with a few warnings</info>');
            
$doPrintSchemaUrl $printSchemaUrl;
        } elseif (
$lockErrors) {
            
$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 (
$errors) {
            
$errors array_map(function ($err) {
                return 
'- ' $err;
            }, 
$errors);
            
array_unshift($errors'# General errors');
        }
        if (
$warnings) {
            
$warnings array_map(function ($err) {
                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 = array();

        
// If checking publish errors, display them as errors, otherwise just show them as warnings
        
if ($publishErrors) {
            
$publishErrors array_map(function ($err) {
                return 
'- ' $err;
            }, 
$publishErrors);

            if (
$checkPublish) {
                
array_unshift($publishErrors'# Publish errors');
                
$errors array_merge($errors$publishErrors);
            } else {
                
array_unshift($publishErrors'# Publish warnings');
                
$extraWarnings array_merge($extraWarnings$publishErrors);
            }
        }

        
// If checking lock errors, display them as errors, otherwise just show them as warnings
        
if ($lockErrors) {
            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 = array(
            
'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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\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 
Seld\JsonLint\DuplicateKeyException;
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 
Seld\JsonLint\JsonParser;

/**
 * Creates a configured instance of composer.
 *
 * @author Ryan Weaver <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 * @author Igor Wiedler <[email protected]>
 * @author Nils Adermann <[email protected]>
 */
class Factory
{
    
/**
     * @throws \RuntimeException
     * @return string
     */
    
protected static function getHomeDir()
    {
        
$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 = array();

        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];
    }

    
/**
     * @param  string $home
     * @return string
     */
    
protected static function getCacheDir($home)
    {
        
$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';
    }

    
/**
     * @param  string $home
     * @return string
     */
    
protected static function getDataDir($home)
    {
        
$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;
    }

    
/**
     * @param string|null $cwd
     *
     * @return Config
     */
    
public static function createConfig(IOInterface $io null$cwd null)
    {
        
$cwd $cwd ?: (string) getcwd();

        
$config = new Config(true$cwd);

        
// determine and add main dirs to the config
        
$home self::getHomeDir();
        
$config->merge(array('config' => array(
            
'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 && $io->isDebug()) {
                
$io->writeError('Loading config file ' $file->getPath());
            }
            
$config->merge($file->read(), $file->getPath());
        }
        
$config->setConfigSource(new JsonConfigSource($file));

        
$htaccessProtect = (bool) $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 = array($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'$dir0777true);
                    }
                    
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 && $io->isDebug()) {
                
$io->writeError('Loading config file ' $file->getPath());
            }
            
$config->merge(array('config' => $file->read()), $file->getPath());
        }
        
$config->setAuthConfigSource(new JsonConfigSource($filetrue));

        
// load COMPOSER_AUTH environment variable if set
        
if ($composerAuthEnv Platform::getEnv('COMPOSER_AUTH')) {
            
$authData json_decode($composerAuthEnvtrue);

            if (
null === $authData) {
                if (
$io) {
                    
$io->writeError('<error>COMPOSER_AUTH environment variable is malformed, should be a valid JSON object</error>');
                }
            } else {
                if (
$io && $io->isDebug()) {
                    
$io->writeError('Loading auth config from COMPOSER_AUTH');
                }
                
$config->merge(array('config' => $authData), 'COMPOSER_AUTH');
            }
        }

        return 
$config;
    }

    
/**
     * @return string
     */
    
public static function getComposerFile()
    {
        return 
trim(Platform::getEnv('COMPOSER')) ?: './composer.json';
    }

    
/**
     * @param string $composerFile
     *
     * @return string
     */
    
public static function getLockFile($composerFile)
    {
        return 
"json" === pathinfo($composerFilePATHINFO_EXTENSION)
                ? 
substr($composerFile0, -4).'lock'
                
$composerFile '.lock';
    }

    
/**
     * @return array{highlight: OutputFormatterStyle, warning: OutputFormatterStyle}
     */
    
public static function createAdditionalStyles()
    {
        return array(
            
'highlight' => new OutputFormatterStyle('red'),
            
'warning' => new OutputFormatterStyle('black''yellow'),
        );
    }

    
/**
     * Creates a ConsoleOutput instance
     *
     * @return ConsoleOutput
     */
    
public static function createOutput()
    {
        
$styles self::createAdditionalStyles();
        
$formatter = new OutputFormatter(false$styles);

        return new 
ConsoleOutput(ConsoleOutput::VERBOSITY_NORMALnull$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                              $disablePlugins Whether plugins should not be loaded
     * @param  bool                              $disableScripts Whether scripts should not be run
     * @param  string|null                       $cwd
     * @param  bool                              $fullLoad       Whether to initialize everything or only main project stuff (used when loading the global composer)
     * @throws \InvalidArgumentException
     * @throws \UnexpectedValueException
     * @return Composer
     */
    
public function createComposer(IOInterface $io$localConfig null$disablePlugins false$cwd null$fullLoad true$disableScripts false)
    {
        
$cwd $cwd ?: (string) getcwd();

        
// load Composer configuration
        
if (null === $localConfig) {
            
$localConfig = static::getComposerFile();
        }

        
$localConfigSource Config::SOURCE_UNKNOWN;
        if (
is_string($localConfig)) {
            
$composerFile $localConfig;

            
$file = new JsonFile($localConfignull$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);
            }

            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);
            }
            
$jsonParser = new JsonParser;
            try {
                
$jsonParser->parse(file_get_contents($localConfig), JsonParser::DETECT_KEY_CONFLICTS);
            } catch (
DuplicateKeyException $e) {
                
$details $e->getDetails();
                
$io->writeError('<warning>Key '.$details['key'].' is a duplicate in '.$localConfig.' at line '.$details['line'].'</warning>');
            }

            
$localConfig $file->read();
            
$localConfigSource $file->getPath();
        }

        
// Load config and override with local config/auth config
        
$config = static::createConfig($io$cwd);
        
$config->merge($localConfig$localConfigSource);
        if (isset(
$composerFile)) {
            
$io->writeError('Loading config file ' $composerFile .' ('.realpath($composerFile).')'trueIOInterface::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(), trueIOInterface::DEBUG);
                
$config->merge(array('config' => $localAuthFile->read()), $localAuthFile->getPath());
                
$config->setAuthConfigSource(new JsonConfigSource($localAuthFiletrue));
            }
        }

        
$vendorDir $config->get('vendor-dir');

        
// initialize composer
        
$composer = new Composer();
        
$composer->setConfig($config);

        if (
$fullLoad) {
            
// load auth configs into the IO instance
            
$io->loadConfiguration($config);

            
// load existing Composer\InstalledVersions instance if available
            
if (!class_exists('Composer\InstalledVersions'false) && file_exists($installedVersionsPath $config->get('vendor-dir').'/composer/InstalledVersions.php')) {
                include 
$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);
        
$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 (
$fullLoad) {
            
// 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);

        if (
$fullLoad) {
            
$globalComposer null;
            if (
realpath($config->get('home')) !== $cwd) {
                
$globalComposer $this->createGlobalComposer($io$config$disablePlugins$disableScripts);
            }

            
$pm $this->createPluginManager($io$composer$globalComposer$disablePlugins);
            
$composer->setPluginManager($pm);

            
$pm->loadInstalledPlugins();
        }

        
// init locker if possible
        
if ($fullLoad && isset($composerFile)) {
            
$lockFile self::getLockFile($composerFile);

            
$locker = new Package\Locker($io, new JsonFile($lockFilenull$io), $imfile_get_contents($composerFile), $process);
            
$composer->setLocker($locker);
        }

        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  IOInterface   $io             IO instance
     * @param  bool          $disablePlugins Whether plugins should not be loaded
     * @param  bool          $disableScripts Whether scripts should not be executed
     * @return Composer|null
     */
    
public static function createGlobal(IOInterface $io$disablePlugins false$disableScripts false)
    {
        
$factory = new static();

        return 
$factory->createGlobalComposer($io, static::createConfig($io), $disablePlugins$disableScriptstrue);
    }

    
/**
     * @param Repository\RepositoryManager $rm
     * @param string                       $vendorDir
     *
     * @return void
     */
    
protected function addLocalRepository(IOInterface $ioRepositoryManager $rm$vendorDirRootPackageInterface $rootPackageProcessExecutor $process null)
    {
        
$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 $disablePlugins
     * @param bool $disableScripts
     * @param bool $fullLoad
     *
     * @return Composer|null
     */
    
protected function createGlobalComposer(IOInterface $ioConfig $config$disablePlugins$disableScripts$fullLoad false)
    {
        
$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(), trueIOInterface::DEBUG);
        }

        return 
$composer;
    }

    
/**
     * @param  IO\IOInterface             $io
     * @param  Config                     $config
     * @param  EventDispatcher            $eventDispatcher
     * @return Downloader\DownloadManager
     */
    
public function createDownloadManager(IOInterface $ioConfig $configHttpDownloader $httpDownloaderProcessExecutor $processEventDispatcher $eventDispatcher null)
    {
        
$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($iofalse$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 $configDownloader\DownloadManager $dmLoop $loop)
    {
        
$am = new Archiver\ArchiveManager($dm$loop);
        
$am->addArchiver(new Archiver\ZipArchiver);
        
$am->addArchiver(new Archiver\PharArchiver);

        return 
$am;
    }

    
/**
     * @param  IOInterface          $io
     * @param  Composer             $composer
     * @param  Composer             $globalComposer
     * @param  bool                 $disablePlugins
     * @return Plugin\PluginManager
     */
    
protected function createPluginManager(IOInterface $ioComposer $composerComposer $globalComposer null$disablePlugins false)
    {
        return new 
Plugin\PluginManager($io$composer$globalComposer$disablePlugins);
    }

    
/**
     * @return Installer\InstallationManager
     */
    
public function createInstallationManager(Loop $loopIOInterface $ioEventDispatcher $eventDispatcher null)
    {
        return new 
Installer\InstallationManager($loop$io$eventDispatcher);
    }

    
/**
     * @return void
     */
    
protected function createDefaultInstallers(Installer\InstallationManager $imComposer $composerIOInterface $ioProcessExecutor $process null)
    {
        
$fs = new Filesystem($process);
        
$binaryInstaller = new Installer\BinaryInstaller($iortrim($composer->getConfig()->get('bin-dir'), '/'), $composer->getConfig()->get('bin-compat'), $fsrtrim($composer->getConfig()->get('vendor-dir'), '/'));

        
$im->addInstaller(new Installer\LibraryInstaller($io$composernull$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
     *
     * @return void
     */
    
protected function purgePackages(InstalledRepositoryInterface $repoInstaller\InstallationManager $im)
    {
        foreach (
$repo->getPackages() as $package) {
            if (!
$im->isPackageInstalled($repo$package)) {
                
$repo->removePackage($package);
            }
        }
    }

    
/**
     * @return Package\Loader\RootPackageLoader
     */
    
protected function loadRootPackage(RepositoryManager $rmConfig $configVersionParser $parserVersionGuesser $guesserIOInterface $io)
    {
        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        $disablePlugins Whether plugins should not be loaded
     * @param  bool        $disableScripts Whether scripts should not be run
     * @return Composer
     */
    
public static function create(IOInterface $io$config null$disablePlugins false$disableScripts false)
    {
        
$factory = new static();

        return 
$factory->createComposer($io$config$disablePluginsnulltrue$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
     * @return HttpDownloader
     */
    
public static function createHttpDownloader(IOInterface $ioConfig $config$options = array())
    {
        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 = array();
        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>');
                if (
PHP_VERSION_ID 50600) {
                    
$io->write('<error>It is recommended you upgrade to PHP 5.6+ which can detect your system CA file automatically.</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;
    }

    
/**
     * @return bool
     */
    
private static function useXdg()
    {
        foreach (
array_keys($_SERVER) as $key) {
            if (
strpos($key'XDG_') === 0) {
                return 
true;
            }
        }

        if (
Silencer::call('is_dir''/etc/xdg')) {
            return 
true;
        }

        return 
false;
    }

    
/**
     * @throws \RuntimeException
     * @return string
     */
    
private static function getUserDir()
    {
        
$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'\\''/'), '/');
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\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\Util\Platform;

/**
 * @author Jordi Boggiano <[email protected]>
 * @author Beau Simensen <[email protected]>
 * @author Konstantin Kudryashov <[email protected]>
 * @author Nils Adermann <[email protected]>
 */
class Installer
{
    const 
ERROR_NONE 0// no error/success state
    
const ERROR_GENERIC_FAILURE 1;
    const 
ERROR_NO_LOCK_FILE_FOR_PARTIAL_UPDATE 3;
    const 
ERROR_LOCK_FILE_INVALID 4;
    
// used/declared in SolverProblemsException, carried over here for completeness
    
const ERROR_DEPENDENCY_RESOLUTION_FAILED 2;

    
/**
     * @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 $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 $writeLock;
    
/** @var bool */
    
protected $executeOperations true;

    
/** @var bool */
    
protected $updateMirrors false;
    
/**
     * Array of package names/globs flagged for update
     *
     * @var 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;

    
/**
     * Constructor
     *
     * @param IOInterface          $io
     * @param Config               $config
     * @param RootPackageInterface&BasePackage $package
     * @param DownloadManager      $downloadManager
     * @param RepositoryManager    $repositoryManager
     * @param Locker               $locker
     * @param InstallationManager  $installationManager
     * @param EventDispatcher      $eventDispatcher
     * @param AutoloadGenerator    $autoloadGenerator
     */
    
public function __construct(IOInterface $ioConfig $configRootPackageInterface $packageDownloadManager $downloadManagerRepositoryManager $repositoryManagerLocker $lockerInstallationManager $installationManagerEventDispatcher $eventDispatcherAutoloadGenerator $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()
    {
        
// 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 && $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->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(array(
                
$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);
        }

        if (
$this->install && $this->executeOperations) {
            
// force binaries re-generation in case they are missing
            
foreach ($localRepo->getPackages() as $package) {
                
$this->installationManager->ensureBinariesPresence($package);
            }
        }

        
$fundingCount 0;
        foreach (
$localRepo->getPackages() as $package) {
            if (
$package instanceof CompletePackageInterface && !$package instanceof AliasPackage && $package->getFunding()) {
                
$fundingCount++;
            }
        }
        if (
$fundingCount 0) {
            
$this->io->writeError(array(
                
sprintf(
                    
"<info>%d package%s you are using %s looking for funding.</info>",
                    
$fundingCount,
                    
=== $fundingCount '' 's',
                    
=== $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();
        }

        return 
0;
    }

    
/**
     * @param bool $doInstall
     *
     * @return int
     * @phpstan-return self::ERROR_*
     */
    
protected function doUpdate(InstalledRepositoryInterface $localRepo$doInstall)
    {
        
$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 || $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 || $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>'trueIOInterface::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);
        
$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$lockedRepositorytrue);

        
// pass the allow list into the request, so the pool builder can apply it
        
if ($this->updateAllowList) {
            
$request->setUpdateAllowList($this->updateAllowList$this->updateAllowTransitiveDependencies);
        }

        
$pool $repositorySet->createPool($request$this->io$this->eventDispatcher$this->createPoolOptimizer($policy));

        
$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>'trueIOInterface::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>'trueIOInterface::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"trueIOInterface::VERBOSE);
        
$this->io->writeError("Analyzed ".$ruleSetSize." rules to resolve dependencies"trueIOInterface::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;
        }

        
// write lock
        
$platformReqs $this->extractPlatformRequirements($this->package->getRequires());
        
$platformDevReqs $this->extractPlatformRequirements($this->package->getDevRequires());

        
$installsUpdates $uninstalls = array();
        if (
$lockTransaction->getOperations()) {
            
$installNames $updateNames $uninstallNames = array();
            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();
                }
            }

            
$this->io->writeError(sprintf(
                
"<info>Lock file operations: %d install%s, %d update%s, %d removal%s</info>",
                
count($installNames),
                
=== count($installNames) ? '' 's',
                
count($updateNames),
                
=== count($updateNames) ? '' 's',
                
count($uninstalls),
                
=== count($uninstalls) ? '' 's'
            
));
            if (
$installNames) {
                
$this->io->writeError("Installs: ".implode(', '$installNames), trueIOInterface::VERBOSE);
            }
            if (
$updateNames) {
                
$this->io->writeError("Updates: ".implode(', '$updateNames), trueIOInterface::VERBOSE);
            }
            if (
$uninstalls) {
                
$this->io->writeError("Removals: ".implode(', '$uninstallNames), trueIOInterface::VERBOSE);
            }
        }

        
$sortByName = function ($a$b) {
            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, but alias op only in debug verbosity
            
if (false === strpos($operation->getOperationType(), 'Alias') || $this->io->isDebug()) {
                
$this->io->writeError('  - ' $operation->show(true));
            }
        }

        
$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') ?: array(),
            
$this->writeLock && $this->executeOperations
        
);
        if (
$updatedLock && $this->writeLock && $this->executeOperations) {
            
$this->io->writeError('<info>Writing lock file</info>');
        }

        
// see https://github.com/composer/composer/issues/2764
        
if ($this->executeOperations && count($lockTransaction->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);
            }
        }

        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($localRepotrue);
        }

        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
     *
     * @return int
     *
     * @phpstan-param list<array{package: string, version: string, alias: string, alias_normalized: string}> $aliases
     * @phpstan-return self::ERROR_*
     */
    
protected function extractDevPackages(LockTransaction $lockTransactionPlatformRepository $platformRepo, array $aliasesPolicyInterface $policyLockArrayRepository $lockedRepository null)
    {
        if (!
$this->package->getDevRequires()) {
            return 
0;
        }

        
$resultRepo = new ArrayRepository(array());
        
$loader = new ArrayLoader(nulltrue);
        
$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$lockedRepositoryfalse);

        
$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>'trueIOInterface::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  InstalledRepositoryInterface $localRepo
     * @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$alreadySolved false)
    {
        
$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, array(), $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>'trueIOInterface::QUIET);
            }

            foreach (
$lockedRepository->getPackages() as $package) {
                
$request->fixLockedPackage($package);
            }

            foreach (
$this->locker->getPlatformRequirements($this->devMode) as $link) {
                
$request->requireName($link->getTarget(), $link->getConstraint());
            }

            
$pool $repositorySet->createPool($request$this->io$this->eventDispatcher);

            
// 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 (!== count($lockTransaction->getOperations())) {
                    
$this->io->writeError('<error>Your lock file cannot be installed on this system without changes. Please run composer update.</error>'trueIOInterface::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>'trueIOInterface::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);

        if (!
$localRepoTransaction->getOperations()) {
            
$this->io->writeError('Nothing to install, update or remove');
        }

        if (
$localRepoTransaction->getOperations()) {
            
$installs $updates $uninstalls = array();
            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();
                }
            }

            
$this->io->writeError(sprintf(
                
"<info>Package operations: %d install%s, %d update%s, %d removal%s</info>",
                
count($installs),
                
=== count($installs) ? '' 's',
                
count($updates),
                
=== count($updates) ? '' 's',
                
count($uninstalls),
                
=== count($uninstalls) ? '' 's'
            
));
            if (
$installs) {
                
$this->io->writeError("Installs: ".implode(', '$installs), trueIOInterface::VERBOSE);
            }
            if (
$updates) {
                
$this->io->writeError("Updates: ".implode(', '$updates), trueIOInterface::VERBOSE);
            }
            if (
$uninstalls) {
                
$this->io->writeError("Removals: ".implode(', '$uninstalls), trueIOInterface::VERBOSE);
            }
        }

        if (
$this->executeOperations) {
            
$localRepo->setDevPackageNames($this->locker->getDevPackageNames());
            
$this->installationManager->execute($localRepo$localRepoTransaction->getOperations(), $this->devMode$this->runScripts);
        } 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;
    }

    
/**
     * @param bool $forUpdate
     *
     * @return PlatformRepository
     */
    
protected function createPlatformRepo($forUpdate)
    {
        if (
$forUpdate) {
            
$platformOverrides $this->config->get('platform') ?: array();
        } else {
            
$platformOverrides $this->locker->getPlatformOverrides();
        }

        return new 
PlatformRepository(array(), $platformOverrides);
    }

    
/**
     * @param  bool                              $forUpdate
     * @param  array<int, array<string, string>> $rootAliases
     * @param  RepositoryInterface|null          $lockedRepository
     *
     * @return RepositorySet
     *
     * @phpstan-param list<array{package: string, version: string, alias: string, alias_normalized: string}> $rootAliases
     */
    
private function createRepositorySet($forUpdatePlatformRepository $platformRepo, array $rootAliases = array(), $lockedRepository null)
    {
        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 = array();
            foreach (
$lockedRepository->getPackages() as $package) {
                
$constraint = new Constraint('='$package->getVersion());
                
$constraint->setPrettyString($package->getPrettyVersion());
                
$requires[$package->getName()] = $constraint;
            }
        }

        
$rootRequires = array();
        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(array());
        
$this->fixedRootPackage->setDevRequires(array());

        
$stabilityFlags[$this->package->getName()] = BasePackage::$stabilities[VersionParser::parseStability($this->package->getVersion())];

        
$repositorySet = new RepositorySet($minimumStability$stabilityFlags$rootAliases$this->package->getReferences(), $rootRequires);
        
$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 = array($additionalFixedRepositories);
            }
            foreach (
$additionalFixedRepositories as $additionalFixedRepository) {
                if (
$additionalFixedRepository instanceof InstalledRepository || $additionalFixedRepository instanceof InstalledRepositoryInterface) {
                    
$repositorySet->allowInstalledRepositories();
                    break;
                }
            }

            
$repositorySet->addRepository($this->additionalFixedRepository);
        }

        return 
$repositorySet;
    }

    
/**
     * @param bool $forUpdate
     *
     * @return DefaultPolicy
     */
    
private function createPolicy($forUpdate)
    {
        
$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;
        }

        return new 
DefaultPolicy($preferStable$preferLowest);
    }

    
/**
     * @param RootPackageInterface&BasePackage $rootPackage
     * @return Request
     */
    
private function createRequest(RootPackageInterface $rootPackagePlatformRepository $platformRepoLockArrayRepository $lockedRepository null)
    {
        
$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;
    }

    
/**
     * @param LockArrayRepository|null $lockedRepository
     * @param bool                     $includeDevRequires
     *
     * @return void
     */
    
private function requirePackagesForUpdate(Request $requestLockArrayRepository $lockedRepository null$includeDevRequires true)
    {
        
// 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 = array();
            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());
            }
        }
    }

    
/**
     * @param bool $forUpdate
     *
     * @return array<int, array<string, string>>
     *
     * @phpstan-return list<array{package: string, version: string, alias: string, alias_normalized: string}>
     */
    
private function getRootAliases($forUpdate)
    {
        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)
    {
        
$platformReqs = array();
        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
     *
     * @return void
     */
    
private function mockLocalRepositories(RepositoryManager $rm)
    {
        
$packages = array();
        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)
        );
    }

    
/**
     * @return PoolOptimizer|null
     */
    
private function createPoolOptimizer(PolicyInterface $policy)
    {
        
// 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.'trueIOInterface::DEBUG);

            return 
null;
        }

        return new 
PoolOptimizer($policy);
    }

    
/**
     * Create Installer
     *
     * @param  IOInterface $io
     * @param  Composer    $composer
     * @return Installer
     */
    
public static function create(IOInterface $ioComposer $composer)
    {
        return new static(
            
$io,
            
$composer->getConfig(),
            
$composer->getPackage(),
            
$composer->getDownloadManager(),
            
$composer->getRepositoryManager(),
            
$composer->getLocker(),
            
$composer->getInstallationManager(),
            
$composer->getEventDispatcher(),
            
$composer->getAutoloadGenerator()
        );
    }

    
/**
     * @param  RepositoryInterface $additionalFixedRepository
     * @return $this
     */
    
public function setAdditionalFixedRepository(RepositoryInterface $additionalFixedRepository)
    {
        
$this->additionalFixedRepository $additionalFixedRepository;

        return 
$this;
    }

    
/**
     * Whether to run in drymode or not
     *
     * @param  bool      $dryRun
     * @return Installer
     */
    
public function setDryRun($dryRun true)
    {
        
$this->dryRun = (bool) $dryRun;

        return 
$this;
    }

    
/**
     * Checks, if this is a dry run (simulation mode).
     *
     * @return bool
     */
    
public function isDryRun()
    {
        return 
$this->dryRun;
    }

    
/**
     * prefer source installation
     *
     * @param  bool      $preferSource
     * @return Installer
     */
    
public function setPreferSource($preferSource true)
    {
        
$this->preferSource = (bool) $preferSource;

        return 
$this;
    }

    
/**
     * prefer dist installation
     *
     * @param  bool      $preferDist
     * @return Installer
     */
    
public function setPreferDist($preferDist true)
    {
        
$this->preferDist = (bool) $preferDist;

        return 
$this;
    }

    
/**
     * Whether or not generated autoloader are optimized
     *
     * @param  bool      $optimizeAutoloader
     * @return Installer
     */
    
public function setOptimizeAutoloader($optimizeAutoloader)
    {
        
$this->optimizeAutoloader = (bool) $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.
     *
     * @param  bool      $classMapAuthoritative
     * @return Installer
     */
    
public function setClassMapAuthoritative($classMapAuthoritative)
    {
        
$this->classMapAuthoritative = (bool) $classMapAuthoritative;
        if (
$this->classMapAuthoritative) {
            
// Force optimizeAutoloader when classmap is authoritative
            
$this->setOptimizeAutoloader(true);
        }

        return 
$this;
    }

    
/**
     * Whether or not generated autoloader considers APCu caching.
     *
     * @param  bool        $apcuAutoloader
     * @param  string|null $apcuAutoloaderPrefix
     * @return Installer
     */
    
public function setApcuAutoloader($apcuAutoloader$apcuAutoloaderPrefix null)
    {
        
$this->apcuAutoloader $apcuAutoloader;
        
$this->apcuAutoloaderPrefix $apcuAutoloaderPrefix;

        return 
$this;
    }

    
/**
     * update packages
     *
     * @param  bool      $update
     * @return Installer
     */
    
public function setUpdate($update)
    {
        
$this->update = (bool) $update;

        return 
$this;
    }

    
/**
     * Allows disabling the install step after an update
     *
     * @param  bool      $install
     * @return Installer
     */
    
public function setInstall($install)
    {
        
$this->install = (bool) $install;

        return 
$this;
    }

    
/**
     * enables dev packages
     *
     * @param  bool      $devMode
     * @return Installer
     */
    
public function setDevMode($devMode true)
    {
        
$this->devMode = (bool) $devMode;

        return 
$this;
    }

    
/**
     * set whether to run autoloader or not
     *
     * This is disabled implicitly when enabling dryRun
     *
     * @param  bool      $dumpAutoloader
     * @return Installer
     */
    
public function setDumpAutoloader($dumpAutoloader true)
    {
        
$this->dumpAutoloader = (bool) $dumpAutoloader;

        return 
$this;
    }

    
/**
     * set whether to run scripts or not
     *
     * This is disabled implicitly when enabling dryRun
     *
     * @param  bool      $runScripts
     * @return Installer
     * @deprecated Use setRunScripts(false) on the EventDispatcher instance being injected instead
     */
    
public function setRunScripts($runScripts true)
    {
        
$this->runScripts = (bool) $runScripts;

        return 
$this;
    }

    
/**
     * set the config instance
     *
     * @param  Config    $config
     * @return Installer
     */
    
public function setConfig(Config $config)
    {
        
$this->config $config;

        return 
$this;
    }

    
/**
     * run in verbose mode
     *
     * @param  bool      $verbose
     * @return Installer
     */
    
public function setVerbose($verbose true)
    {
        
$this->verbose = (bool) $verbose;

        return 
$this;
    }

    
/**
     * Checks, if running in verbose mode.
     *
     * @return bool
     */
    
public function isVerbose()
    {
        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)
    {
        
trigger_error('Installer::setIgnorePlatformRequirements is deprecated since Composer 2.2, use setPlatformRequirementFilter instead.'E_USER_DEPRECATED);

        return 
$this->setPlatformRequirementFilter(PlatformRequirementFilterFactory::fromBoolOrList($ignorePlatformReqs));
    }

    
/**
     * @param PlatformRequirementFilterInterface $platformRequirementFilter
     * @return Installer
     */
    
public function setPlatformRequirementFilter(PlatformRequirementFilterInterface $platformRequirementFilter)
    {
        
$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
     *
     * @param  bool      $updateMirrors
     * @return Installer
     */
    
public function setUpdateMirrors($updateMirrors)
    {
        
$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)
    {
        
$this->updateAllowList array_flip(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($updateAllowTransitiveDependencies)
    {
        if (!
in_array($updateAllowTransitiveDependencies, array(Request::UPDATE_ONLY_LISTEDRequest::UPDATE_LISTED_WITH_TRANSITIVE_DEPS_NO_ROOT_REQUIRERequest::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?
     *
     * @param  bool      $preferStable
     * @return Installer
     */
    
public function setPreferStable($preferStable true)
    {
        
$this->preferStable = (bool) $preferStable;

        return 
$this;
    }

    
/**
     * Should packages be preferred in a lowest version when updating?
     *
     * @param  bool      $preferLowest
     * @return Installer
     */
    
public function setPreferLowest($preferLowest true)
    {
        
$this->preferLowest = (bool) $preferLowest;

        return 
$this;
    }

    
/**
     * Should the lock file be updated when updating?
     *
     * This is disabled implicitly when enabling dryRun
     *
     * @param  bool      $writeLock
     * @return Installer
     */
    
public function setWriteLock($writeLock true)
    {
        
$this->writeLock = (bool) $writeLock;

        return 
$this;
    }

    
/**
     * Should the operations (package install, update and removal) be executed on disk?
     *
     * This is disabled implicitly when enabling dryRun
     *
     * @param  bool      $executeOperations
     * @return Installer
     */
    
public function setExecuteOperations($executeOperations true)
    {
        
$this->executeOperations = (bool) $executeOperations;

        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()
    {
        
$this->installationManager->disablePlugins();

        return 
$this;
    }

    
/**
     * @param  SuggestedPackagesReporter $suggestedPackagesReporter
     * @return Installer
     */
    
public function setSuggestedPackagesReporter(SuggestedPackagesReporter $suggestedPackagesReporter)
    {
        
$this->suggestedPackagesReporter $suggestedPackagesReporter;

        return 
$this;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\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 <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 */
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<int, int> */
    
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 $inputOutputInterface $outputHelperSet $helperSet)
    {
        
$this->input $input;
        
$this->output $output;
        
$this->helperSet $helperSet;
        
$this->verbosityMap = array(
            
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,
        );
    }

    
/**
     * @param float $startTime
     *
     * @return void
     */
    
public function enableDebugging($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->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE;
    }

    
/**
     * @inheritDoc
     */
    
public function isVeryVerbose()
    {
        return 
$this->output->getVerbosity() >= OutputInterface::VERBOSITY_VERY_VERBOSE;
    }

    
/**
     * @inheritDoc
     */
    
public function isDebug()
    {
        return 
$this->output->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG;
    }

    
/**
     * @inheritDoc
     */
    
public function write($messages$newline true$verbosity self::NORMAL)
    {
        
$this->doWrite($messages$newlinefalse$verbosity);
    }

    
/**
     * @inheritDoc
     */
    
public function writeError($messages$newline true$verbosity self::NORMAL)
    {
        
$this->doWrite($messages$newlinetrue$verbosity);
    }

    
/**
     * @inheritDoc
     */
    
public function writeRaw($messages$newline true$verbosity self::NORMAL)
    {
        
$this->doWrite($messages$newlinefalse$verbositytrue);
    }

    
/**
     * @inheritDoc
     */
    
public function writeErrorRaw($messages$newline true$verbosity self::NORMAL)
    {
        
$this->doWrite($messages$newlinetrue$verbositytrue);
    }

    
/**
     * @param string[]|string $messages
     * @param bool                 $newline
     * @param bool                 $stderr
     * @param int                  $verbosity
     * @param bool                 $raw
     *
     * @return void
     */
    
private function doWrite($messages$newline$stderr$verbosity$raw false)
    {
        
$sfVerbosity $this->verbosityMap[$verbosity];
        if (
$sfVerbosity $this->output->getVerbosity()) {
            return;
        }

        if (
$raw) {
            if (
$sfVerbosity === OutputInterface::OUTPUT_NORMAL) {
                
$sfVerbosity OutputInterface::OUTPUT_RAW;
            } else {
                
$sfVerbosity |= OutputInterface::OUTPUT_RAW;
            }
        }

        if (
null !== $this->startTime) {
            
$memoryUsage memory_get_usage() / 1024 1024;
            
$timeSpent microtime(true) - $this->startTime;
            
$messages array_map(function ($message) use ($memoryUsage$timeSpent) {
                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$newline true$size null$verbosity self::NORMAL)
    {
        
$this->doOverwrite($messages$newline$sizefalse$verbosity);
    }

    
/**
     * @inheritDoc
     */
    
public function overwriteError($messages$newline true$size null$verbosity self::NORMAL)
    {
        
$this->doOverwrite($messages$newline$sizetrue$verbosity);
    }

    
/**
     * @param string[]|string $messages
     * @param bool         $newline
     * @param int|null     $size
     * @param bool         $stderr
     * @param int          $verbosity
     *
     * @return void
     */
    
private function doOverwrite($messages$newline$size$stderr$verbosity)
    {
        
// 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($messagesfalse$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;
        }
    }

    
/**
     * @param  int         $max
     * @return ProgressBar
     */
    
public function getProgressBar($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);

        if (!
is_array($result)) {
            return (string) 
array_search($result$choicestrue);
        }

        
$results = array();
        foreach (
$choices as $index => $choice) {
            if (
in_array($choice$resulttrue)) {
                
$results[] = (string) $index;
            }
        }

        return 
$results;
    }

    
/**
     * @return OutputInterface
     */
    
private function getErrorOutput()
    {
        if (
$this->output instanceof ConsoleOutputInterface) {
            return 
$this->output->getErrorOutput();
        }

        return 
$this->output;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class NullIO extends BaseIO
{
    
/**
     * @inheritDoc
     */
    
public function isInteractive()
    {
        return 
false;
    }

    
/**
     * @inheritDoc
     */
    
public function isVerbose()
    {
        return 
false;
    }

    
/**
     * @inheritDoc
     */
    
public function isVeryVerbose()
    {
        return 
false;
    }

    
/**
     * @inheritDoc
     */
    
public function isDebug()
    {
        return 
false;
    }

    
/**
     * @inheritDoc
     */
    
public function isDecorated()
    {
        return 
false;
    }

    
/**
     * @inheritDoc
     */
    
public function write($messages$newline true$verbosity self::NORMAL)
    {
    }

    
/**
     * @inheritDoc
     */
    
public function writeError($messages$newline true$verbosity self::NORMAL)
    {
    }

    
/**
     * @inheritDoc
     */
    
public function overwrite($messages$newline true$size 80$verbosity self::NORMAL)
    {
    }

    
/**
     * @inheritDoc
     */
    
public function overwriteError($messages$newline true$size 80$verbosity self::NORMAL)
    {
    }

    
/**
     * @inheritDoc
     */
    
public function ask($question$default null)
    {
        return 
$default;
    }

    
/**
     * @inheritDoc
     */
    
public function askConfirmation($question$default true)
    {
        return 
$default;
    }

    
/**
     * @inheritDoc
     */
    
public function askAndValidate($question$validator$attempts null$default null)
    {
        return 
$default;
    }

    
/**
     * @inheritDoc
     */
    
public function askAndHideAnswer($question)
    {
        return 
null;
    }

    
/**
     * @inheritDoc
     */
    
public function select($question$choices$default$attempts false$errorMessage 'Value "%s" is invalid'$multiselect false)
    {
        return 
$default;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 
Psr\Log\LogLevel;

abstract class 
BaseIO implements IOInterface
{
    
/** @var array<string, array{username: string|null, password: string|null}> */
    
protected $authentications = array();

    
/**
     * @inheritDoc
     */
    
public function getAuthentications()
    {
        return 
$this->authentications;
    }

    
/**
     * @return void
     */
    
public function resetAuthentications()
    {
        
$this->authentications = array();
    }

    
/**
     * @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 array(
'username' => null'password' => null);
    }

    
/**
     * @inheritDoc
     */
    
public function setAuthentication($repositoryName$username$password null)
    {
        
$this->authentications[$repositoryName] = array('username' => $username'password' => $password);
    }

    
/**
     * @inheritDoc
     */
    
public function writeRaw($messages$newline true$verbosity self::NORMAL)
    {
        
$this->write($messages$newline$verbosity);
    }

    
/**
     * @inheritDoc
     */
    
public function writeErrorRaw($messages$newline true$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($repositoryName$username$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') ?: array();
        
$githubOauth $config->get('github-oauth') ?: array();
        
$gitlabOauth $config->get('gitlab-oauth') ?: array();
        
$gitlabToken $config->get('gitlab-token') ?: array();
        
$httpBasic $config->get('http-basic') ?: array();
        
$bearerToken $config->get('bearer') ?: array();

        
// 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) {
            
// 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) {
            
$this->checkAndSetAuthentication($domain$token'oauth2');
        }

        foreach (
$gitlabToken as $domain => $token) {
            
$username is_array($token) && array_key_exists("username"$token) ? $token["username"] : $token;
            
$password is_array($token) && array_key_exists("token"$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((int) $config->get('process-timeout'));
    }

    
/**
     * @return void
     */
    
public function emergency($message, array $context = array())
    {
        
$this->log(LogLevel::EMERGENCY$message$context);
    }

    
/**
     * @return void
     */
    
public function alert($message, array $context = array())
    {
        
$this->log(LogLevel::ALERT$message$context);
    }

    
/**
     * @return void
     */
    
public function critical($message, array $context = array())
    {
        
$this->log(LogLevel::CRITICAL$message$context);
    }

    
/**
     * @return void
     */
    
public function error($message, array $context = array())
    {
        
$this->log(LogLevel::ERROR$message$context);
    }

    
/**
     * @return void
     */
    
public function warning($message, array $context = array())
    {
        
$this->log(LogLevel::WARNING$message$context);
    }

    
/**
     * @return void
     */
    
public function notice($message, array $context = array())
    {
        
$this->log(LogLevel::NOTICE$message$context);
    }

    
/**
     * @return void
     */
    
public function info($message, array $context = array())
    {
        
$this->log(LogLevel::INFO$message$context);
    }

    
/**
     * @return void
     */
    
public function debug($message, array $context = array())
    {
        
$this->log(LogLevel::DEBUG$message$context);
    }

    
/**
     * @return void
     */
    
public function log($level$message, array $context = array())
    {
        if (
in_array($level, array(LogLevel::EMERGENCYLogLevel::ALERTLogLevel::CRITICALLogLevel::ERROR))) {
            
$this->writeError('<error>'.$message.'</error>');
        } elseif (
$level === LogLevel::WARNING) {
            
$this->writeError('<warning>'.$message.'</warning>');
        } elseif (
$level === LogLevel::NOTICE) {
            
$this->writeError('<info>'.$message.'</info>'trueself::VERBOSE);
        } elseif (
$level === LogLevel::INFO) {
            
$this->writeError('<info>'.$message.'</info>'trueself::VERY_VERBOSE);
        } else {
            
$this->writeError($messagetrueself::DEBUG);
        }
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
interface IOInterface extends LoggerInterface
{
    const 
QUIET 1;
    const 
NORMAL 2;
    const 
VERBOSE 4;
    const 
VERY_VERBOSE 8;
    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$newline true$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$newline true$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$newline true$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$newline true$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$newline true$size null$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$newline true$size null$verbosity self::NORMAL);

    
/**
     * Asks a question to the user.
     *
     * @param string $question The question to ask
     * @param string $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 string|null       The user answer
     */
    
public function ask($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($question$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($question$validator$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($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|string[]|bool     The selected value or values (the key of the choices array)
     */
    
public function select($question$choices$default$attempts false$errorMessage 'Value "%s" is invalid'$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($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($repositoryName);

    
/**
     * 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
     */
    
public function setAuthentication($repositoryName$username$password null);

    
/**
     * Loads authentications from a config instance
     *
     * @param Config $config
     *
     * @return void
     */
    
public function loadConfiguration(Config $config);
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class BufferIO extends ConsoleIO
{
    
/** @var StringInput */
    
protected $input;
    
/** @var StreamOutput */
    
protected $output;

    
/**
     * @param string                        $input
     * @param int                           $verbosity
     * @param OutputFormatterInterface|null $formatter
     */
    
public function __construct($input ''$verbosity StreamOutput::VERBOSITY_NORMALOutputFormatterInterface $formatter null)
    {
        
$input = new StringInput($input);
        
$input->setInteractive(false);

        
$output = new StreamOutput(fopen('php://memory''rw'), $verbosity$formatter $formatter->isDecorated() : false$formatter);

        
parent::__construct($input$output, new HelperSet(array(
            new 
QuestionHelper(),
        )));
    }

    
/**
     * @return string output
     */
    
public function getOutput()
    {
        
fseek($this->output->getStream(), 0);

        
$output stream_get_contents($this->output->getStream());

        
$output Preg::replaceCallback("{(?<=^|\n|\x08)(.+?)(\x08+)}", function ($matches) {
            
$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
     *
     * @return void
     */
    
public function setUserInputs(array $inputs)
    {
        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 false|resource stream
     */
    
private function createStream(array $inputs)
    {
        
$stream fopen('php://memory''r+');

        foreach (
$inputs as $input) {
            
fwrite($stream$input.PHP_EOL);
        }

        
rewind($stream);

        return 
$stream;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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
 *
 * @author Jordi Boggiano <[email protected]>
 */
interface PackageInterface
{
    const 
DISPLAY_SOURCE_REF_IF_DEV 0;
    const 
DISPLAY_SOURCE_REF 1;
    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();

    
/**
     * Returns the package's pretty (i.e. with proper case) name
     *
     * @return string package name
     */
    
public function getPrettyName();

    
/**
     * 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($provides true);

    
/**
     * Allows the solver to set an id for this package to refer to it.
     *
     * @param int $id
     *
     * @return void
     */
    
public function setId($id);

    
/**
     * Retrieves the package's id set through setId
     *
     * @return int The previously set package id
     */
    
public function getId();

    
/**
     * Returns whether the package is a development virtual package or a concrete one
     *
     * @return bool
     */
    
public function isDev();

    
/**
     * Returns the package type, e.g. library
     *
     * @return string The package type
     */
    
public function getType();

    
/**
     * Returns the package targetDir property
     *
     * @return ?string The package targetDir
     */
    
public function getTargetDir();

    
/**
     * Returns the package extra data
     *
     * @return mixed[] The package extra data
     */
    
public function getExtra();

    
/**
     * Sets source from which this package was installed (source/dist).
     *
     * @param string $type source/dist
     * @phpstan-param 'source'|'dist'|null $type
     *
     * @return void
     */
    
public function setInstallationSource($type);

    
/**
     * Returns source from which this package was installed (source/dist).
     *
     * @return ?string source/dist
     * @phpstan-return 'source'|'dist'|null
     */
    
public function getInstallationSource();

    
/**
     * Returns the repository type of this package, e.g. git, svn
     *
     * @return ?string The repository type
     */
    
public function getSourceType();

    
/**
     * Returns the repository url of this package, e.g. git://github.com/naderman/composer.git
     *
     * @return ?string The repository url
     */
    
public function getSourceUrl();

    
/**
     * Returns the repository urls of this package including mirrors, e.g. git://github.com/naderman/composer.git
     *
     * @return string[]
     */
    
public function getSourceUrls();

    
/**
     * 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();

    
/**
     * Returns the source mirrors of this package
     *
     * @return ?array<int, array{url: string, preferred: bool}>
     */
    
public function getSourceMirrors();

    
/**
     * @param  ?array<int, array{url: string, preferred: bool}> $mirrors
     * @return void
     */
    
public function setSourceMirrors($mirrors);

    
/**
     * Returns the type of the distribution archive of this version, e.g. zip, tarball
     *
     * @return ?string The repository type
     */
    
public function getDistType();

    
/**
     * Returns the url of the distribution archive of this version
     *
     * @return ?string
     */
    
public function getDistUrl();

    
/**
     * Returns the urls of the distribution archive of this version, including mirrors
     *
     * @return string[]
     */
    
public function getDistUrls();

    
/**
     * 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();

    
/**
     * Returns the sha1 checksum for the distribution archive of this version
     *
     * @return ?string
     */
    
public function getDistSha1Checksum();

    
/**
     * Returns the dist mirrors of this package
     *
     * @return ?array<int, array{url: string, preferred: bool}>
     */
    
public function getDistMirrors();

    
/**
     * @param  ?array<int, array{url: string, preferred: bool}> $mirrors
     * @return void
     */
    
public function setDistMirrors($mirrors);

    
/**
     * Returns the version of this package
     *
     * @return string version
     */
    
public function getVersion();

    
/**
     * Returns the pretty (i.e. non-normalized) version string of this package
     *
     * @return string version
     */
    
public function getPrettyVersion();

    
/**
     * 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($truncate true$displayMode self::DISPLAY_SOURCE_REF_IF_DEV);

    
/**
     * Returns the release date of the package
     *
     * @return ?\DateTime
     */
    
public function getReleaseDate();

    
/**
     * Returns the stability of this package: one of (dev, alpha, beta, RC, stable)
     *
     * @return string
     *
     * @phpstan-return 'stable'|'RC'|'beta'|'alpha'|'dev'
     */
    
public function getStability();

    
/**
     * 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();

    
/**
     * 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();

    
/**
     * 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();

    
/**
     * 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();

    
/**
     * 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();

    
/**
     * 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();

    
/**
     * 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 array{psr-0?: array<string, string|string[]>, psr-4?: array<string, string|string[]>, classmap?: list<string>, files?: list<string>}
     */
    
public function getAutoload();

    
/**
     * 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 array{psr-0?: array<string, string|string[]>, psr-4?: array<string, string|string[]>, classmap?: list<string>, files?: list<string>}
     */
    
public function getDevAutoload();

    
/**
     * Returns a list of directories which should get added to PHP's
     * include path.
     *
     * @return string[]
     */
    
public function getIncludePaths();

    
/**
     * Stores a reference to the repository that owns the package
     *
     * @param RepositoryInterface $repository
     *
     * @return void
     */
    
public function setRepository(RepositoryInterface $repository);

    
/**
     * Returns a reference to the repository that owns the package
     *
     * @return ?RepositoryInterface
     */
    
public function getRepository();

    
/**
     * Returns the package binaries
     *
     * @return string[]
     */
    
public function getBinaries();

    
/**
     * Returns package unique name, constructed from name and version.
     *
     * @return string
     */
    
public function getUniqueName();

    
/**
     * Returns the package notification url
     *
     * @return ?string
     */
    
public function getNotificationUrl();

    
/**
     * Converts the package into a readable and unique string
     *
     * @return string
     */
    
public function __toString();

    
/**
     * Converts the package into a pretty readable string
     *
     * @return string
     */
    
public function getPrettyString();

    
/**
     * @return bool
     */
    
public function isDefaultBranch();

    
/**
     * Returns a list of options to download package dist files
     *
     * @return mixed[]
     */
    
public function getTransportOptions();

    
/**
     * Configures the list of options to download package dist files
     *
     * @param mixed[] $options
     *
     * @return void
     */
    
public function setTransportOptions(array $options);

    
/**
     * @param string $reference
     *
     * @return void
     */
    
public function setSourceReference($reference);

    
/**
     * @param string $url
     *
     * @return void
     */
    
public function setDistUrl($url);

    
/**
     * @param string $type
     *
     * @return void
     */
    
public function setDistType($type);

    
/**
     * @param string $reference
     *
     * @return void
     */
    
public function setDistReference($reference);

    
/**
     * Set dist and source references and update dist URL for ones that contain a reference
     *
     * @param string $reference
     *
     * @return void
     */
    
public function setSourceDistReferences($reference);
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class CompletePackage extends Package implements CompletePackageInterface
{
    
/** @var mixed[] */
    
protected $repositories = array();
    
/** @var string[] */
    
protected $license = array();
    
/** @var string[] */
    
protected $keywords = array();
    
/** @var array<array{name?: string, homepage?: string, email?: string, role?: string}> */
    
protected $authors = array();
    
/** @var ?string */
    
protected $description null;
    
/** @var ?string */
    
protected $homepage null;
    
/** @var array<string, string[]> Map of script name to array of handlers */
    
protected $scripts = array();
    
/** @var array{issues?: string, forum?: string, wiki?: string, source?: string, email?: string, irc?: string, docs?: string, rss?: string, chat?: string} */
    
protected $support = array();
    
/** @var array<array{url?: string, type?: string}> */
    
protected $funding = array();
    
/** @var bool|string */
    
protected $abandoned false;
    
/** @var ?string */
    
protected $archiveName null;
    
/** @var string[] */
    
protected $archiveExcludes = array();

    
/**
     * @inheritDoc
     */
    
public function setScripts(array $scripts)
    {
        
$this->scripts $scripts;
    }

    
/**
     * @inheritDoc
     */
    
public function getScripts()
    {
        return 
$this->scripts;
    }

    
/**
     * @inheritDoc
     */
    
public function setRepositories(array $repositories)
    {
        
$this->repositories $repositories;
    }

    
/**
     * @inheritDoc
     */
    
public function getRepositories()
    {
        return 
$this->repositories;
    }

    
/**
     * @inheritDoc
     */
    
public function setLicense(array $license)
    {
        
$this->license $license;
    }

    
/**
     * @inheritDoc
     */
    
public function getLicense()
    {
        return 
$this->license;
    }

    
/**
     * @inheritDoc
     */
    
public function setKeywords(array $keywords)
    {
        
$this->keywords $keywords;
    }

    
/**
     * @inheritDoc
     */
    
public function getKeywords()
    {
        return 
$this->keywords;
    }

    
/**
     * @inheritDoc
     */
    
public function setAuthors(array $authors)
    {
        
$this->authors $authors;
    }

    
/**
     * @inheritDoc
     */
    
public function getAuthors()
    {
        return 
$this->authors;
    }

    
/**
     * @inheritDoc
     */
    
public function setDescription($description)
    {
        
$this->description $description;
    }

    
/**
     * @inheritDoc
     */
    
public function getDescription()
    {
        return 
$this->description;
    }

    
/**
     * @inheritDoc
     */
    
public function setHomepage($homepage)
    {
        
$this->homepage $homepage;
    }

    
/**
     * @inheritDoc
     */
    
public function getHomepage()
    {
        return 
$this->homepage;
    }

    
/**
     * @inheritDoc
     */
    
public function setSupport(array $support)
    {
        
$this->support $support;
    }

    
/**
     * @inheritDoc
     */
    
public function getSupport()
    {
        return 
$this->support;
    }

    
/**
     * @inheritDoc
     */
    
public function setFunding(array $funding)
    {
        
$this->funding $funding;
    }

    
/**
     * @inheritDoc
     */
    
public function getFunding()
    {
        return 
$this->funding;
    }

    
/**
     * @inheritDoc
     */
    
public function isAbandoned()
    {
        return (bool) 
$this->abandoned;
    }

    
/**
     * @inheritDoc
     */
    
public function setAbandoned($abandoned)
    {
        
$this->abandoned $abandoned;
    }

    
/**
     * @inheritDoc
     */
    
public function getReplacementPackage()
    {
        return 
\is_string($this->abandoned) ? $this->abandoned null;
    }

    
/**
     * @inheritDoc
     */
    
public function setArchiveName($name)
    {
        
$this->archiveName $name;
    }

    
/**
     * @inheritDoc
     */
    
public function getArchiveName()
    {
        return 
$this->archiveName;
    }

    
/**
     * @inheritDoc
     */
    
public function setArchiveExcludes(array $excludes)
    {
        
$this->archiveExcludes $excludes;
    }

    
/**
     * @inheritDoc
     */
    
public function getArchiveExcludes()
    {
        return 
$this->archiveExcludes;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\CompletePackageInterface;
use 
Composer\Package\CompletePackage;
use 
Composer\Package\CompleteAliasPackage;
use 
Composer\Package\RootAliasPackage;
use 
Composer\Package\RootPackage;

/**
 * Defines a loader that takes an array to create package instances
 *
 * @author Jordi Boggiano <[email protected]>
 */
interface LoaderInterface
{
    
/**
     * Converts a package from an array to a real instance
     *
     * @template PackageClass of CompletePackageInterface
     *
     * @param  mixed[] $config package data
     * @param  string  $class  FQCN to be instantiated
     *
     * @return CompletePackage|CompleteAliasPackage|RootPackage|RootAliasPackage
     *
     * @phpstan-param class-string<PackageClass> $class
     */
    
public function load(array $config$class 'Composer\Package\CompletePackage');
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class InvalidPackageException extends \Exception
{
    
/** @var string[] */
    
private $errors;
    
/** @var string[] */
    
private $warnings;
    
/** @var mixed[] package config */
    
private $data;

    
/**
     * @param string[] $errors
     * @param 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()
    {
        return 
$this->data;
    }

    
/**
     * @return string[]
     */
    
public function getErrors()
    {
        return 
$this->errors;
    }

    
/**
     * @return string[]
     */
    
public function getWarnings()
    {
        return 
$this->warnings;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\CompletePackage;
use 
Composer\Package\CompleteAliasPackage;

/**
 * @author Konstantin Kudryashiv <[email protected]>
 */
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
     */
    
public function load($json)
    {
        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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\Spdx\SpdxLicenses;

/**
 * @author Jordi Boggiano <[email protected]>
 */
class ValidatingArrayLoader implements LoaderInterface
{
    const 
CHECK_ALL 3;
    const 
CHECK_UNBOUND_CONSTRAINTS 1;
    const 
CHECK_STRICT_CONSTRAINTS 2;

    
/** @var LoaderInterface */
    
private $loader;
    
/** @var VersionParser */
    
private $versionParser;
    
/** @var string[] */
    
private $errors;
    
/** @var string[] */
    
private $warnings;
    
/** @var mixed[] */
    
private $config;
    
/** @var int One or more of self::CHECK_* constants */
    
private $flags;

    
/**
     * @param true $strictName
     * @param int  $flags
     */
    
public function __construct(LoaderInterface $loader$strictName trueVersionParser $parser null$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$class 'Composer\Package\CompletePackage')
    {
        
$this->errors = array();
        
$this->warnings = array();
        
$this->config $config;

        
$this->validateString('name'true);
        if (
$err self::hasPackageNamingError($config['name'])) {
            
$this->errors[] = 'name : '.$err;
        }

        if (!empty(
$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 (!empty(
$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($platformtrue).'): 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 (!empty(
$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']);
            }
        }

        
// check for license validity on newly updated branches
        
if (isset($this->config['license']) && (!$releaseDate || $releaseDate->getTimestamp() >= strtotime('-8days'))) {
            if (
is_array($this->config['license']) || is_string($this->config['license'])) {
                
$licenses = (array) $this->config['license'];

                
$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)
                            );
                        }
                    }
                }
            }
        }

        if (
$this->validateArray('authors') && !empty($this->config['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 (array(
'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']) && !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 (empty(
$this->config['authors'][$key])) {
                    unset(
$this->config['authors'][$key]);
                }
            }
            if (empty(
$this->config['authors'])) {
                unset(
$this->config['authors']);
            }
        }

        if (
$this->validateArray('support') && !empty($this->config['support'])) {
            foreach (array(
'issues''forum''wiki''source''email''irc''docs''rss''chat') 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'], array('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 (array(
'issues''forum''wiki''source''docs''chat') 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 (array(
'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']);
            }
        }

        
$unboundConstraint = new Constraint('=''10000000-dev');
        
$stableConstraint = new Constraint('=''1.0.0');

        foreach (
array_keys(BasePackage::$supportedLinkTypes) as $linkType) {
            if (
$this->validateArray($linkType) && isset($this->config[$linkType])) {
                foreach (
$this->config[$linkType] as $package => $constraint) {
                    if (
=== 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($packagetrue)) {
                        
$this->errors[] = $linkType.'.'.$err;
                    } elseif (!
Preg::isMatch('{^[A-Za-z0-9_./-]+$}'$package)) {
                        
$this->warnings[] = $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
                            
&& strpos($linkConstraint'=') === 0
                            
&& $stableConstraint->versionCompare($stableConstraint$linkConstraint'<=')
                        ) {
                            
$this->warnings[] = $linkType.'.'.$package.' : exact version constraints ('.$constraint.') should be avoided if the package follows semantic versioning';
                        }
                    }

                    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') && !empty($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') && !empty($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') && !empty($this->config['autoload'])) {
            
$types = array('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($namespace, -1)) {
                            
$this->errors[] = 'autoload.psr-4 : invalid value ('.$namespace.'), namespaces must end with a namespace separator, should be '.$namespace.'\\\\';
                        }
                    }
                }
            }
        }

        if (!empty(
$this->config['autoload']['psr-4']) && !empty($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 (array(
'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 (!
is_string($this->config[$srcType]['type'])) {
                    
$this->errors[] = $srcType '.type : should be a string, '.gettype($this->config[$srcType]['type']).' given';
                }
                if (!
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 (
Preg::isMatch('{^\s*-}'$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($targetBranch0, -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 = array();

        return 
$package;
    }

    
/**
     * @return string[]
     */
    
public function getWarnings()
    {
        return 
$this->warnings;
    }

    
/**
     * @return string[]
     */
    
public function getErrors()
    {
        return 
$this->errors;
    }

    
/**
     * @param string $name
     * @param bool   $isLink
     *
     * @return string|null
     */
    
public static function hasPackageNamingError($name$isLink false)
    {
        if (
PlatformRepository::isPlatformPackage($name)) {
            return 
null;
        }

        if (!
Preg::isMatch('{^[a-z0-9](?:[_.-]?[a-z0-9]+)*/[a-z0-9](?:(?:[_.]?|-{0,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 = array('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], $reservedNamestrue) || in_array($bits[1], $reservedNamestrue)) {
            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;
    }

    
/**
     * @param string $property
     * @param string $regex
     * @param bool   $mandatory
     *
     * @return bool
     *
     * @phpstan-param non-empty-string $property
     * @phpstan-param non-empty-string $regex
     */
    
private function validateRegex($property$regex$mandatory false)
    {
        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;
    }

    
/**
     * @param string $property
     * @param bool   $mandatory
     *
     * @return bool
     *
     * @phpstan-param non-empty-string $property
     */
    
private function validateString($property$mandatory false)
    {
        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;
    }

    
/**
     * @param string $property
     * @param bool   $mandatory
     *
     * @return bool
     *
     * @phpstan-param non-empty-string $property
     */
    
private function validateArray($property$mandatory false)
    {
        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;
    }

    
/**
     * @param string      $property
     * @param string|null $regex
     * @param bool        $mandatory
     *
     * @return bool
     *
     * @phpstan-param non-empty-string      $property
     * @phpstan-param non-empty-string|null $regex
     */
    
private function validateFlatArray($property$regex null$mandatory false)
    {
        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'$value)) {
                
$this->warnings[] = $property.'.'.$key.' : invalid value ('.$value.'), must match '.$regex;
                unset(
$this->config[$property][$key]);
                
$pass false;
            }
        }

        return 
$pass;
    }

    
/**
     * @param string $property
     * @param bool $mandatory
     *
     * @return bool
     *
     * @phpstan-param non-empty-string $property
     */
    
private function validateUrl($property$mandatory false)
    {
        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
     *
     * @return bool
     */
    
private function filterUrl($value, array $schemes = array('http''https'))
    {
        if (
$value === '') {
            return 
true;
        }

        
$bits parse_url($value);
        if (empty(
$bits['scheme']) || empty($bits['host'])) {
            return 
false;
        }

        if (!
in_array($bits['scheme'], $schemestrue)) {
            return 
false;
        }

        return 
true;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 */
class ArrayLoader implements LoaderInterface
{
    
/** @var VersionParser */
    
protected $versionParser;
    
/** @var bool */
    
protected $loadOptions;

    
/**
     * @param bool $loadOptions
     */
    
public function __construct(VersionParser $parser null$loadOptions false)
    {
        if (!
$parser) {
            
$parser = new VersionParser;
        }
        
$this->versionParser $parser;
        
$this->loadOptions $loadOptions;
    }

    
/**
     * @inheritDoc
     */
    
public function load(array $config$class 'Composer\Package\CompletePackage')
    {
        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])) {
                
$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 list<array<mixed>> $versions
     *
     * @return list<CompletePackage|CompleteAliasPackage>
     */
    
public function loadPackages(array $versions)
    {
        
$packages = array();
        
$linkCache = array();

        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 CompletePackageInterface
     *
     * @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$class)
    {
        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)
    {
        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'] = array($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']) ? $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']) ? $config['dist']['reference'] : null);
            
$package->setDistSha1Checksum(isset($config['dist']['shasum']) ? $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 (!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 (array(
'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($config['keywords']);
            }

            if (!empty(
$config['license'])) {
                
$package->setLicense(\is_array($config['license']) ? $config['license'] : array($config['license']));
            }

            if (!empty(
$config['authors']) && \is_array($config['authors'])) {
                
$package->setAuthors($config['authors']);
            }

            if (isset(
$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<string, array<string, array{string, Link}>>>> $linkCache
     * @param PackageInterface                                                                $package
     * @param mixed[]                                                                         $config
     *
     * @return void
     */
    
private function configureCachedLinks(&$linkCache$package, array $config)
    {
        
$name $package->getName();
        
$prettyVersion $package->getPrettyVersion();

        foreach (
BasePackage::$supportedLinkTypes as $type => $opts) {
            if (isset(
$config[$type])) {
                
$method 'set'.ucfirst($opts['method']);

                
$links = array();
                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] = array($target$this->createLink($name$prettyVersion$opts['method'], $target$constraint));
                        }

                        list(
$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, string> $links         array of package name => constraint mappings
     *
     * @return Link[]
     *
     * @phpstan-param Link::TYPE_* $description
     */
    
public function parseLinks($source$sourceVersion$description$links)
    {
        
$res = array();
        foreach (
$links as $target => $constraint) {
            
$target strtolower($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
     * @return Link
     */
    
private function createLink($source$sourceVersion$description$target$prettyConstraint)
    {
        if (!
\is_string($prettyConstraint)) {
            throw new 
\UnexpectedValueException('Link constraint in '.$source.' '.$description.' > '.$target.' should be a string, got '.\gettype($prettyConstraint) . ' (' var_export($prettyConstrainttrue) . ')');
        }
        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)
    {
        if (
strpos($config['version'], 'dev-') !== && '-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) {
                
// 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($targetBranch0, -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($config['version'])
        ) {
            return 
VersionParser::DEFAULT_BRANCH_ALIAS;
        }

        return 
null;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class RootPackageLoader extends ArrayLoader
{
    
/**
     * @var RepositoryManager
     */
    
private $manager;

    
/**
     * @var Config
     */
    
private $config;

    
/**
     * @var VersionGuesser
     */
    
private $versionGuesser;

    public function 
__construct(RepositoryManager $managerConfig $configVersionParser $parser nullVersionGuesser $versionGuesser nullIOInterface $io null)
    {
        
parent::__construct($parser);

        
$this->manager $manager;
        
$this->config $config;
        
$this->versionGuesser $versionGuesser ?: new VersionGuesser($config, new ProcessExecutor($io), $this->versionParser);
    }

    
/**
     * @inheritDoc
     *
     * @template PackageClass of RootPackage
     *
     * @param string|null $cwd
     *
     * @return RootPackage|RootAliasPackage
     *
     * @phpstan-param class-string<PackageClass> $class
     */
    
public function load(array $config$class 'Composer\Package\RootPackage'$cwd null)
    {
        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'] = Platform::getEnv('COMPOSER_ROOT_VERSION');
            } else {
                
$versionData $this->versionGuesser->guessVersion($config$cwd ?: getcwd());
                if (
$versionData) {
                    
$config['version'] = $versionData['pretty_version'];
                    
$config['version_normalized'] = $versionData['version'];
                    
$commit $versionData['commit'];
                }
            }

            if (!isset(
$config['version'])) {
                
$config['version'] = '1.0.0';
                
$autoVersioned true;
            }

            if (
$commit) {
                
$config['source'] = array(
                    
'type' => '',
                    
'url' => '',
                    
'reference' => $commit,
                );
                
$config['dist'] = array(
                    
'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 = array();
        
$stabilityFlags = array();
        
$references = array();
        foreach (array(
'require''require-dev') as $linkType) {
            if (isset(
$config[$linkType])) {
                
$linkInfo BasePackage::$supportedLinkTypes[$linkType];
                
$method 'get'.ucfirst($linkInfo['method']);
                
$links = array();
                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($linkNametrue)) {
                        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)
    {
        foreach (
$requires as $reqName => $reqVersion) {
            if (
Preg::isMatch('{^([^,\s#]+)(?:#[^ ]+)? +as +([^,\s]+)$}'$reqVersion$match)) {
                
$aliases[] = array(
                    
'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 string                $minimumStability
     * @param array<string, int>    $stabilityFlags
     *
     * @return array<string, int>
     *
     * @phpstan-param array<string, BasePackage::STABILITY_*> $stabilityFlags
     * @phpstan-return array<string, BasePackage::STABILITY_*>
     */
    
public static function extractStabilityFlags(array $requires$minimumStability, array $stabilityFlags)
    {
        
$stabilities BasePackage::$stabilities;
        
/** @var int $minimumStability */
        
$minimumStability $stabilities[$minimumStability];
        foreach (
$requires as $reqName => $reqVersion) {
            
$constraints = array();

            
// 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
            
$match false;
            foreach (
$constraints as $constraint) {
                if (
Preg::isMatch('{^[^@]*?@('.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;
                    
$match true;
                }
            }

            if (
$match) {
                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)
    {
        foreach (
$requires as $reqName => $reqVersion) {
            
$reqVersion Preg::replace('{^([^,\s@]+) as .+$}''$1'$reqVersion);
            if (
Preg::isMatch('{^[^,\s@]+?#([a-f0-9]+)$}'$reqVersion$match) && 'dev' === VersionParser::parseStability($reqVersion)) {
                
$name strtolower($reqName);
                
$references[$name] = $match[1];
            }
        }

        return 
$references;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
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$version$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()
    {
        return 
$this->version;
    }

    
/**
     * @inheritDoc
     */
    
public function getStability()
    {
        return 
$this->stability;
    }

    
/**
     * @inheritDoc
     */
    
public function getPrettyVersion()
    {
        return 
$this->prettyVersion;
    }

    
/**
     * @inheritDoc
     */
    
public function isDev()
    {
        return 
$this->dev;
    }

    
/**
     * @inheritDoc
     */
    
public function getRequires()
    {
        return 
$this->requires;
    }

    
/**
     * @inheritDoc
     * @return array<string|int, Link>
     */
    
public function getConflicts()
    {
        return 
$this->conflicts;
    }

    
/**
     * @inheritDoc
     * @return array<string|int, Link>
     */
    
public function getProvides()
    {
        return 
$this->provides;
    }

    
/**
     * @inheritDoc
     * @return array<string|int, Link>
     */
    
public function getReplaces()
    {
        return 
$this->replaces;
    }

    
/**
     * @inheritDoc
     */
    
public function getDevRequires()
    {
        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
     *
     * @param bool $value
     *
     * @return mixed
     */
    
public function setRootPackageAlias($value)
    {
        return 
$this->rootPackageAlias $value;
    }

    
/**
     * @see setRootPackageAlias
     * @return bool
     */
    
public function isRootPackageAlias()
    {
        return 
$this->rootPackageAlias;
    }

    
/**
     * @param Link[]       $links
     * @param Link::TYPE_* $linkType
     *
     * @return Link[]
     */
    
protected function replaceSelfVersionDependencies(array $links$linkType)
    {
        
// 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, array(Link::TYPE_CONFLICTLink::TYPE_PROVIDELink::TYPE_REPLACE), true)) {
            
$newLinks = array();
            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;
    }

    
/**
     * @return bool
     */
    
public function hasSelfVersionRequires()
    {
        return 
$this->hasSelfVersionRequires;
    }

    public function 
__toString()
    {
        return 
parent::__toString().' ('.($this->rootPackageAlias 'root ' ''). 'alias of '.$this->aliasOf->getVersion().')';
    }

    
/***************************************
     * Wrappers around the aliased package *
     ***************************************/

    
public function getType()
    {
        return 
$this->aliasOf->getType();
    }

    public function 
getTargetDir()
    {
        return 
$this->aliasOf->getTargetDir();
    }

    public function 
getExtra()
    {
        return 
$this->aliasOf->getExtra();
    }

    public function 
setInstallationSource($type)
    {
        
$this->aliasOf->setInstallationSource($type);
    }

    public function 
getInstallationSource()
    {
        return 
$this->aliasOf->getInstallationSource();
    }

    public function 
getSourceType()
    {
        return 
$this->aliasOf->getSourceType();
    }

    public function 
getSourceUrl()
    {
        return 
$this->aliasOf->getSourceUrl();
    }

    public function 
getSourceUrls()
    {
        return 
$this->aliasOf->getSourceUrls();
    }

    public function 
getSourceReference()
    {
        return 
$this->aliasOf->getSourceReference();
    }

    public function 
setSourceReference($reference)
    {
        
$this->aliasOf->setSourceReference($reference);
    }

    public function 
setSourceMirrors($mirrors)
    {
        
$this->aliasOf->setSourceMirrors($mirrors);
    }

    public function 
getSourceMirrors()
    {
        return 
$this->aliasOf->getSourceMirrors();
    }

    public function 
getDistType()
    {
        return 
$this->aliasOf->getDistType();
    }

    public function 
getDistUrl()
    {
        return 
$this->aliasOf->getDistUrl();
    }

    public function 
getDistUrls()
    {
        return 
$this->aliasOf->getDistUrls();
    }

    public function 
getDistReference()
    {
        return 
$this->aliasOf->getDistReference();
    }

    public function 
setDistReference($reference)
    {
        
$this->aliasOf->setDistReference($reference);
    }

    public function 
getDistSha1Checksum()
    {
        return 
$this->aliasOf->getDistSha1Checksum();
    }

    public function 
setTransportOptions(array $options)
    {
        
$this->aliasOf->setTransportOptions($options);
    }

    public function 
getTransportOptions()
    {
        return 
$this->aliasOf->getTransportOptions();
    }

    public function 
setDistMirrors($mirrors)
    {
        
$this->aliasOf->setDistMirrors($mirrors);
    }

    public function 
getDistMirrors()
    {
        return 
$this->aliasOf->getDistMirrors();
    }

    public function 
getAutoload()
    {
        return 
$this->aliasOf->getAutoload();
    }

    public function 
getDevAutoload()
    {
        return 
$this->aliasOf->getDevAutoload();
    }

    public function 
getIncludePaths()
    {
        return 
$this->aliasOf->getIncludePaths();
    }

    public function 
getReleaseDate()
    {
        return 
$this->aliasOf->getReleaseDate();
    }

    public function 
getBinaries()
    {
        return 
$this->aliasOf->getBinaries();
    }

    public function 
getSuggests()
    {
        return 
$this->aliasOf->getSuggests();
    }

    public function 
getNotificationUrl()
    {
        return 
$this->aliasOf->getNotificationUrl();
    }

    public function 
isDefaultBranch()
    {
        return 
$this->aliasOf->isDefaultBranch();
    }

    public function 
setDistUrl($url)
    {
        
$this->aliasOf->setDistUrl($url);
    }

    public function 
setDistType($type)
    {
        
$this->aliasOf->setDistType($type);
    }

    public function 
setSourceDistReferences($reference)
    {
        
$this->aliasOf->setSourceDistReferences($reference);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\LockArrayRepository;
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 <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 */
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 IOInterface         $io
     * @param JsonFile            $lockFile             lockfile loader
     * @param InstallationManager $installationManager  installation manager instance
     * @param string              $composerFileContents The contents of the composer file
     */
    
public function __construct(IOInterface $ioJsonFile $lockFileInstallationManager $installationManager$composerFileContentsProcessExecutor $process null)
    {
        
$this->lockFile $lockFile;
        
$this->installationManager $installationManager;
        
$this->hash md5($composerFileContents);
        
$this->contentHash self::getContentHash($composerFileContents);
        
$this->loader = new ArrayLoader(nulltrue);
        
$this->dumper = new ArrayDumper();
        
$this->process $process ?: new ProcessExecutor($io);
    }

    
/**
     * Returns the md5 hash of the sorted content of the composer file.
     *
     * @param string $composerFileContents The contents of the composer file.
     *
     * @return string
     */
    
public static function getContentHash($composerFileContents)
    {
        
$content json_decode($composerFileContentstrue);

        
$relevantKeys = array(
            
'name',
            
'version',
            
'require',
            
'require-dev',
            
'conflict',
            
'replace',
            
'provide',
            
'minimum-stability',
            
'prefer-stable',
            
'repositories',
            
'extra',
        );

        
$relevantContent = array();

        foreach (
array_intersect($relevantKeysarray_keys($content)) as $key) {
            
$relevantContent[$key] = $content[$key];
        }
        if (isset(
$content['config']['platform'])) {
            
$relevantContent['config']['platform'] = $content['config']['platform'];
        }

        
ksort($relevantContent);

        return 
md5(json_encode($relevantContent));
    }

    
/**
     * Checks whether locker has been locked (lockfile found).
     *
     * @return bool
     */
    
public function isLocked()
    {
        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
     *
     * @return bool
     */
    
public function isFresh()
    {
        
$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
     * @return \Composer\Repository\LockArrayRepository
     */
    
public function getLockedRepository($withDevReqs false)
    {
        
$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 = array();
            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()
    {
        
$names = array();
        
$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($withDevReqs false)
    {
        
$lockData $this->getLockData();
        
$requirements = array();

        if (!empty(
$lockData['platform'])) {
            
$requirements $this->loader->parseLinks(
                
'__root__',
                
'1.0.0',
                
Link::TYPE_REQUIRE,
                isset(
$lockData['platform']) ? $lockData['platform'] : array()
            );
        }

        if (
$withDevReqs && !empty($lockData['platform-dev'])) {
            
$devRequirements $this->loader->parseLinks(
                
'__root__',
                
'1.0.0',
                
Link::TYPE_REQUIRE,
                isset(
$lockData['platform-dev']) ? $lockData['platform-dev'] : array()
            );

            
$requirements array_merge($requirements$devRequirements);
        }

        return 
$requirements;
    }

    
/**
     * @return string
     */
    
public function getMinimumStability()
    {
        
$lockData $this->getLockData();

        return isset(
$lockData['minimum-stability']) ? $lockData['minimum-stability'] : 'stable';
    }

    
/**
     * @return array<string, string>
     */
    
public function getStabilityFlags()
    {
        
$lockData $this->getLockData();

        return isset(
$lockData['stability-flags']) ? $lockData['stability-flags'] : array();
    }

    
/**
     * @return bool|null
     */
    
public function getPreferStable()
    {
        
$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 isset($lockData['prefer-stable']) ? $lockData['prefer-stable'] : null;
    }

    
/**
     * @return bool|null
     */
    
public function getPreferLowest()
    {
        
$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 isset($lockData['prefer-lowest']) ? $lockData['prefer-lowest'] : null;
    }

    
/**
     * @return array<string, string>
     */
    
public function getPlatformOverrides()
    {
        
$lockData $this->getLockData();

        return isset(
$lockData['platform-overrides']) ? $lockData['platform-overrides'] : array();
    }

    
/**
     * @return string[][]
     *
     * @phpstan-return list<array{package: string, version: string, alias: string, alias_normalized: string}>
     */
    
public function getAliases()
    {
        
$lockData $this->getLockData();

        return isset(
$lockData['aliases']) ? $lockData['aliases'] : array();
    }

    
/**
     * @return array<string, mixed>
     */
    
public function getLockData()
    {
        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 string                  $minimumStability
     * @param array<string, int>      $stabilityFlags
     * @param bool                    $preferStable
     * @param bool                    $preferLowest
     * @param array<string, string>   $platformOverrides
     * @param bool                    $write             Whether to actually write data to disk, useful in tests and for --dry-run
     *
     * @return bool
     *
     * @phpstan-param list<array{package: string, version: string, alias: string, alias_normalized: string}> $aliases
     */
    
public function setLockData(array $packages$devPackages, array $platformReqs$platformDevReqs, array $aliases$minimumStability, array $stabilityFlags$preferStable$preferLowest, array $platformOverrides$write true)
    {
        
// 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(function ($alias) {
            if (
in_array($alias['version'], array('dev-master''dev-trunk''dev-default'), true)) {
                
$alias['version'] = VersionParser::DEFAULT_BRANCH_ALIAS;
            }

            return 
$alias;
        }, 
$aliases);

        
$lock = array(
            
'_readme' => array('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' => null,
            
'packages-dev' => null,
            
'aliases' => $aliases,
            
'minimum-stability' => $minimumStability,
            
'stability-flags' => $stabilityFlags,
            
'prefer-stable' => $preferStable,
            
'prefer-lowest' => $preferLowest,
        );

        
$lock['packages'] = $this->lockPackages($packages);
        if (
null !== $devPackages) {
            
$lock['packages-dev'] = $this->lockPackages($devPackages);
        }

        
$lock['platform'] = $platformReqs;
        
$lock['platform-dev'] = $platformDevReqs;
        if (
$platformOverrides) {
            
$lock['platform-overrides'] = $platformOverrides;
        }
        
$lock['plugin-api-version'] = PluginInterface::PLUGIN_API_VERSION;

        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($lock448 JsonFile::JSON_PRETTY_PRINT));
            }

            return 
true;
        }

        return 
false;
    }

    
/**
     * @param PackageInterface[] $packages
     *
     * @return mixed[][]
     *
     * @phpstan-return list<array<string, mixed>>
     */
    
private function lockPackages(array $packages)
    {
        
$locked = array();

        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 = isset($spec['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, function ($a$b) {
            
$comparison strcmp($a['name'], $b['name']);

            if (
!== $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)
    {
        if (!
function_exists('proc_open')) {
            return 
null;
        }

        
$path realpath($this->installationManager->getInstallPath($package));
        
$sourceType $package->getSourceType();
        
$datetime null;

        if (
$path && in_array($sourceType, array('git''hg'))) {
            
$sourceRef $package->getSourceReference() ?: $package->getDistReference();
            switch (
$sourceType) {
                case 
'git':
                    
GitUtil::cleanEnv();

                    if (
=== $this->process->execute('git log -n1 --pretty=%ct '.ProcessExecutor::escape($sourceRef).GitUtil::getNoShowSignatureFlag($this->process), $output$path) && Preg::isMatch('{^\s*\d+\s*$}'$output)) {
                        
$datetime = new \DateTime('@'.trim($output), new \DateTimeZone('UTC'));
                    }
                    break;

                case 
'hg':
                    if (
=== $this->process->execute('hg log --template "{date|hgdate}" -r '.ProcessExecutor::escape($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;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
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 ?array<int, array{url: string, preferred: bool}> */
    
protected $sourceMirrors;
    
/** @var ?string */
    
protected $distType;
    
/** @var ?string */
    
protected $distUrl;
    
/** @var ?string */
    
protected $distReference;
    
/** @var ?string */
    
protected $distSha1Checksum;
    
/** @var ?array<int, array{url: string, preferred: bool}> */
    
protected $distMirrors;
    
/** @var string */
    
protected $version;
    
/** @var string */
    
protected $prettyVersion;
    
/** @var ?\DateTime */
    
protected $releaseDate;
    
/** @var mixed[] */
    
protected $extra = array();
    
/** @var string[] */
    
protected $binaries = array();
    
/** @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 = array();
    
/** @var array<string, Link> */
    
protected $conflicts = array();
    
/** @var array<string, Link> */
    
protected $provides = array();
    
/** @var array<string, Link> */
    
protected $replaces = array();
    
/** @var array<string, Link> */
    
protected $devRequires = array();
    
/** @var array<string, string> */
    
protected $suggests = array();
    
/** @var array{psr-0?: array<string, string|string[]>, psr-4?: array<string, string|string[]>, classmap?: list<string>, files?: list<string>} */
    
protected $autoload = array();
    
/** @var array{psr-0?: array<string, string|string[]>, psr-4?: array<string, string|string[]>, classmap?: list<string>, files?: list<string>} */
    
protected $devAutoload = array();
    
/** @var string[] */
    
protected $includePaths = array();
    
/** @var bool */
    
protected $isDefaultBranch false;
    
/** @var mixed[] */
    
protected $transportOptions = array();

    
/**
     * 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($name$version$prettyVersion)
    {
        
parent::__construct($name);

        
$this->version $version;
        
$this->prettyVersion $prettyVersion;

        
$this->stability VersionParser::parseStability($version);
        
$this->dev $this->stability === 'dev';
    }

    
/**
     * @inheritDoc
     */
    
public function isDev()
    {
        return 
$this->dev;
    }

    
/**
     * @param string $type
     *
     * @return void
     */
    
public function setType($type)
    {
        
$this->type $type;
    }

    
/**
     * @inheritDoc
     */
    
public function getType()
    {
        return 
$this->type ?: 'library';
    }

    
/**
     * @inheritDoc
     */
    
public function getStability()
    {
        return 
$this->stability;
    }

    
/**
     * @param string $targetDir
     *
     * @return void
     */
    
public function setTargetDir($targetDir)
    {
        
$this->targetDir $targetDir;
    }

    
/**
     * @inheritDoc
     */
    
public function getTargetDir()
    {
        if (
null === $this->targetDir) {
            return 
null;
        }

        return 
ltrim(Preg::replace('{ (?:^|[\\\\/]+) \.\.? (?:[\\\\/]+|$) (?:\.\.? (?:[\\\\/]+|$) )*}x''/'$this->targetDir), '/');
    }

    
/**
     * @param mixed[] $extra
     *
     * @return void
     */
    
public function setExtra(array $extra)
    {
        
$this->extra $extra;
    }

    
/**
     * @inheritDoc
     */
    
public function getExtra()
    {
        return 
$this->extra;
    }

    
/**
     * @param string[] $binaries
     *
     * @return void
     */
    
public function setBinaries(array $binaries)
    {
        
$this->binaries $binaries;
    }

    
/**
     * @inheritDoc
     */
    
public function getBinaries()
    {
        return 
$this->binaries;
    }

    
/**
     * @inheritDoc
     *
     * @return void
     */
    
public function setInstallationSource($type)
    {
        
$this->installationSource $type;
    }

    
/**
     * @inheritDoc
     */
    
public function getInstallationSource()
    {
        return 
$this->installationSource;
    }

    
/**
     * @param string $type
     *
     * @return void
     */
    
public function setSourceType($type)
    {
        
$this->sourceType $type;
    }

    
/**
     * @inheritDoc
     */
    
public function getSourceType()
    {
        return 
$this->sourceType;
    }

    
/**
     * @param string $url
     *
     * @return void
     */
    
public function setSourceUrl($url)
    {
        
$this->sourceUrl $url;
    }

    
/**
     * @inheritDoc
     */
    
public function getSourceUrl()
    {
        return 
$this->sourceUrl;
    }

    
/**
     * @param string $reference
     *
     * @return void
     */
    
public function setSourceReference($reference)
    {
        
$this->sourceReference $reference;
    }

    
/**
     * @inheritDoc
     */
    
public function getSourceReference()
    {
        return 
$this->sourceReference;
    }

    
/**
     * @inheritDoc
     *
     * @return void
     */
    
public function setSourceMirrors($mirrors)
    {
        
$this->sourceMirrors $mirrors;
    }

    
/**
     * @inheritDoc
     */
    
public function getSourceMirrors()
    {
        return 
$this->sourceMirrors;
    }

    
/**
     * @inheritDoc
     */
    
public function getSourceUrls()
    {
        return 
$this->getUrls($this->sourceUrl$this->sourceMirrors$this->sourceReference$this->sourceType'source');
    }

    
/**
     * @param string $type
     *
     * @return void
     */
    
public function setDistType($type)
    {
        
$this->distType $type;
    }

    
/**
     * @inheritDoc
     */
    
public function getDistType()
    {
        return 
$this->distType;
    }

    
/**
     * @param string $url
     *
     * @return void
     */
    
public function setDistUrl($url)
    {
        
$this->distUrl $url;
    }

    
/**
     * @inheritDoc
     */
    
public function getDistUrl()
    {
        return 
$this->distUrl;
    }

    
/**
     * @param string $reference
     *
     * @return void
     */
    
public function setDistReference($reference)
    {
        
$this->distReference $reference;
    }

    
/**
     * @inheritDoc
     */
    
public function getDistReference()
    {
        return 
$this->distReference;
    }

    
/**
     * @param string $sha1checksum
     *
     * @return void
     */
    
public function setDistSha1Checksum($sha1checksum)
    {
        
$this->distSha1Checksum $sha1checksum;
    }

    
/**
     * @inheritDoc
     */
    
public function getDistSha1Checksum()
    {
        return 
$this->distSha1Checksum;
    }

    
/**
     * @inheritDoc
     *
     * @return void
     */
    
public function setDistMirrors($mirrors)
    {
        
$this->distMirrors $mirrors;
    }

    
/**
     * @inheritDoc
     */
    
public function getDistMirrors()
    {
        return 
$this->distMirrors;
    }

    
/**
     * @inheritDoc
     */
    
public function getDistUrls()
    {
        return 
$this->getUrls($this->distUrl$this->distMirrors$this->distReference$this->distType'dist');
    }

    
/**
     * @inheritDoc
     */
    
public function getTransportOptions()
    {
        return 
$this->transportOptions;
    }

    
/**
     * @inheritDoc
     */
    
public function setTransportOptions(array $options)
    {
        
$this->transportOptions $options;
    }

    
/**
     * @inheritDoc
     */
    
public function getVersion()
    {
        return 
$this->version;
    }

    
/**
     * @inheritDoc
     */
    
public function getPrettyVersion()
    {
        return 
$this->prettyVersion;
    }

    
/**
     * Set the releaseDate
     *
     * @param \DateTime $releaseDate
     *
     * @return void
     */
    
public function setReleaseDate(\DateTime $releaseDate)
    {
        
$this->releaseDate $releaseDate;
    }

    
/**
     * @inheritDoc
     */
    
public function getReleaseDate()
    {
        return 
$this->releaseDate;
    }

    
/**
     * Set the required packages
     *
     * @param array<string, Link> $requires A set of package links
     *
     * @return void
     */
    
public function setRequires(array $requires)
    {
        if (isset(
$requires[0])) { // @phpstan-ignore-line
            
$requires $this->convertLinksToMap($requires'setRequires');
        }

        
$this->requires $requires;
    }

    
/**
     * @inheritDoc
     */
    
public function getRequires()
    {
        return 
$this->requires;
    }

    
/**
     * Set the conflicting packages
     *
     * @param array<string, Link> $conflicts A set of package links
     *
     * @return void
     */
    
public function setConflicts(array $conflicts)
    {
        if (isset(
$conflicts[0])) { // @phpstan-ignore-line
            
$conflicts $this->convertLinksToMap($conflicts'setConflicts');
        }

        
$this->conflicts $conflicts;
    }

    
/**
     * @inheritDoc
     * @return array<string, Link>
     */
    
public function getConflicts()
    {
        return 
$this->conflicts;
    }

    
/**
     * Set the provided virtual packages
     *
     * @param array<string, Link> $provides A set of package links
     *
     * @return void
     */
    
public function setProvides(array $provides)
    {
        if (isset(
$provides[0])) { // @phpstan-ignore-line
            
$provides $this->convertLinksToMap($provides'setProvides');
        }

        
$this->provides $provides;
    }

    
/**
     * @inheritDoc
     * @return array<string, Link>
     */
    
public function getProvides()
    {
        return 
$this->provides;
    }

    
/**
     * Set the packages this one replaces
     *
     * @param array<string, Link> $replaces A set of package links
     *
     * @return void
     */
    
public function setReplaces(array $replaces)
    {
        if (isset(
$replaces[0])) { // @phpstan-ignore-line
            
$replaces $this->convertLinksToMap($replaces'setReplaces');
        }

        
$this->replaces $replaces;
    }

    
/**
     * @inheritDoc
     * @return array<string, Link>
     */
    
public function getReplaces()
    {
        return 
$this->replaces;
    }

    
/**
     * Set the recommended packages
     *
     * @param array<string, Link> $devRequires A set of package links
     *
     * @return void
     */
    
public function setDevRequires(array $devRequires)
    {
        if (isset(
$devRequires[0])) { // @phpstan-ignore-line
            
$devRequires $this->convertLinksToMap($devRequires'setDevRequires');
        }

        
$this->devRequires $devRequires;
    }

    
/**
     * @inheritDoc
     */
    
public function getDevRequires()
    {
        return 
$this->devRequires;
    }

    
/**
     * Set the suggested packages
     *
     * @param array<string, string> $suggests A set of package names/comments
     *
     * @return void
     */
    
public function setSuggests(array $suggests)
    {
        
$this->suggests $suggests;
    }

    
/**
     * @inheritDoc
     */
    
public function getSuggests()
    {
        return 
$this->suggests;
    }

    
/**
     * Set the autoload mapping
     *
     * @param array $autoload Mapping of autoloading rules
     *
     * @return void
     *
     * @phpstan-param array{psr-0?: array<string, string|string[]>, psr-4?: array<string, string|string[]>, classmap?: list<string>, files?: list<string>} $autoload
     */
    
public function setAutoload(array $autoload)
    {
        
$this->autoload $autoload;
    }

    
/**
     * @inheritDoc
     */
    
public function getAutoload()
    {
        return 
$this->autoload;
    }

    
/**
     * Set the dev autoload mapping
     *
     * @param array $devAutoload Mapping of dev autoloading rules
     *
     * @return void
     *
     * @phpstan-param array{psr-0?: array<string, string|string[]>, psr-4?: array<string, string|string[]>, classmap?: list<string>, files?: list<string>} $devAutoload
     */
    
public function setDevAutoload(array $devAutoload)
    {
        
$this->devAutoload $devAutoload;
    }

    
/**
     * @inheritDoc
     */
    
public function getDevAutoload()
    {
        return 
$this->devAutoload;
    }

    
/**
     * Sets the list of paths added to PHP's include path.
     *
     * @param string[] $includePaths List of directories.
     *
     * @return void
     */
    
public function setIncludePaths(array $includePaths)
    {
        
$this->includePaths $includePaths;
    }

    
/**
     * @inheritDoc
     */
    
public function getIncludePaths()
    {
        return 
$this->includePaths;
    }

    
/**
     * Sets the notification URL
     *
     * @param string $notificationUrl
     *
     * @return void
     */
    
public function setNotificationUrl($notificationUrl)
    {
        
$this->notificationUrl $notificationUrl;
    }

    
/**
     * @inheritDoc
     */
    
public function getNotificationUrl()
    {
        return 
$this->notificationUrl;
    }

    
/**
     * @param bool $defaultBranch
     *
     * @return void
     */
    
public function setIsDefaultBranch($defaultBranch)
    {
        
$this->isDefaultBranch $defaultBranch;
    }

    
/**
     * @inheritDoc
     */
    
public function isDefaultBranch()
    {
        return 
$this->isDefaultBranch;
    }

    
/**
     * @inheritDoc
     */
    
public function setSourceDistReferences($reference)
    {
        
$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
     *
     * @return void
     */
    
public function replaceVersion($version$prettyVersion)
    {
        
$this->version $version;
        
$this->prettyVersion $prettyVersion;

        
$this->stability VersionParser::parseStability($version);
        
$this->dev $this->stability === 'dev';
    }

    
/**
     * @param string|null  $url
     * @param mixed[]|null $mirrors
     * @param string|null  $ref
     * @param string|null  $type
     * @param string       $urlType
     *
     * @return string[]
     *
     * @phpstan-param list<array{url: string, preferred: bool}>|null $mirrors
     */
    
protected function getUrls($url$mirrors$ref$type$urlType)
    {
        if (!
$url) {
            return array();
        }

        if (
$urlType === 'dist' && false !== strpos($url'%')) {
            
$url ComposerMirror::processUrl($url$this->name$this->version$ref$type$this->prettyVersion);
        }

        
$urls = array($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
     * @param  string $source
     * @return array<string, Link>
     */
    
private function convertLinksToMap(array $links$source)
    {
        
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 = array();
        foreach (
$links as $link) {
            
$newLinks[$link->getTarget()] = $link;
        }

        return 
$newLinks;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
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$version$prettyVersion)
    {
        
parent::__construct($aliasOf$version$prettyVersion);
    }

    
/**
     * @return CompletePackage
     */
    
public function getAliasOf()
    {
        return 
$this->aliasOf;
    }

    public function 
getScripts()
    {
        return 
$this->aliasOf->getScripts();
    }

    public function 
setScripts(array $scripts)
    {
        
$this->aliasOf->setScripts($scripts);
    }

    public function 
getRepositories()
    {
        return 
$this->aliasOf->getRepositories();
    }

    public function 
setRepositories(array $repositories)
    {
        
$this->aliasOf->setRepositories($repositories);
    }

    public function 
getLicense()
    {
        return 
$this->aliasOf->getLicense();
    }

    public function 
setLicense(array $license)
    {
        
$this->aliasOf->setLicense($license);
    }

    public function 
getKeywords()
    {
        return 
$this->aliasOf->getKeywords();
    }

    public function 
setKeywords(array $keywords)
    {
        
$this->aliasOf->setKeywords($keywords);
    }

    public function 
getDescription()
    {
        return 
$this->aliasOf->getDescription();
    }

    public function 
setDescription($description)
    {
        
$this->aliasOf->setDescription($description);
    }

    public function 
getHomepage()
    {
        return 
$this->aliasOf->getHomepage();
    }

    public function 
setHomepage($homepage)
    {
        
$this->aliasOf->setHomepage($homepage);
    }

    public function 
getAuthors()
    {
        return 
$this->aliasOf->getAuthors();
    }

    public function 
setAuthors(array $authors)
    {
        
$this->aliasOf->setAuthors($authors);
    }

    public function 
getSupport()
    {
        return 
$this->aliasOf->getSupport();
    }

    public function 
setSupport(array $support)
    {
        
$this->aliasOf->setSupport($support);
    }

    public function 
getFunding()
    {
        return 
$this->aliasOf->getFunding();
    }

    public function 
setFunding(array $funding)
    {
        
$this->aliasOf->setFunding($funding);
    }

    public function 
isAbandoned()
    {
        return 
$this->aliasOf->isAbandoned();
    }

    public function 
getReplacementPackage()
    {
        return 
$this->aliasOf->getReplacementPackage();
    }

    public function 
setAbandoned($abandoned)
    {
        
$this->aliasOf->setAbandoned($abandoned);
    }

    public function 
getArchiveName()
    {
        return 
$this->aliasOf->getArchiveName();
    }

    public function 
setArchiveName($name)
    {
        
$this->aliasOf->setArchiveName($name);
    }

    public function 
getArchiveExcludes()
    {
        return 
$this->aliasOf->getArchiveExcludes();
    }

    public function 
setArchiveExcludes(array $excludes)
    {
        
$this->aliasOf->setArchiveExcludes($excludes);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Comparer;

/**
 * class Comparer
 *
 * @author Hector Prats <[email protected]>
 */
class Comparer
{
    
/** @var string Source directory */
    
private $source;
    
/** @var string Target directory */
    
private $update;
    
/** @var array{changed?: string[], removed?: string[], added?: string[]} */
    
private $changed;

    
/**
     * @param string $source
     *
     * @return void
     */
    
public function setSource($source)
    {
        
$this->source $source;
    }

    
/**
     * @param string $update
     *
     * @return void
     */
    
public function setUpdate($update)
    {
        
$this->update $update;
    }

    
/**
     * @param bool $toString
     * @param bool $explicated
     *
     * @return array{changed?: string[], removed?: string[], added?: string[]}|string|false false if no change, string only if $toString is true
     */
    
public function getChanged($toString false$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.')';
                }
            }
        }

        if (
$toString) {
            
$strings = array();
            foreach (
$changed as $sectionKey => $itemSection) {
                foreach (
$itemSection as $itemKey => $item) {
                    
$strings[] = $item."\r\n";
                }
            }
            
$changed implode("\r\n"$strings);
        }

        return 
$changed;
    }

    
/**
     * @return void
     */
    
public function doCompare()
    {
        
$source = array();
        
$destination = array();
        
$this->changed = array();
        
$currentDirectory 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 string $dir
     * @param mixed $array
     *
     * @return array<string, array<string, string|false>>|false
     */
    
private function doTree($dir, &$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] = md5_file($dir.'/'.$file);
                    }
                }
            }
            if (
count($array) > && isset($array['0'])) {
                unset(
$array['0']);
            }

            return 
$array;
        }

        return 
false;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class RootPackage extends CompletePackage implements RootPackageInterface
{
    const 
DEFAULT_PRETTY_VERSION '1.0.0+no-version-set';

    
/** @var string */
    
protected $minimumStability 'stable';
    
/** @var bool */
    
protected $preferStable false;
    
/** @var array<string, BasePackage::STABILITY_*> Map of package name to stability constant */
    
protected $stabilityFlags = array();
    
/** @var mixed[] */
    
protected $config = array();
    
/** @var array<string, string> Map of package name to reference/commit hash */
    
protected $references = array();
    
/** @var array<array{package: string, version: string, alias: string, alias_normalized: string}> */
    
protected $aliases = array();

    
/**
     * {@inerhitDoc}
     */
    
public function setMinimumStability($minimumStability)
    {
        
$this->minimumStability $minimumStability;
    }

    
/**
     * @inheritDoc
     */
    
public function getMinimumStability()
    {
        return 
$this->minimumStability;
    }

    
/**
     * @inheritDoc
     */
    
public function setStabilityFlags(array $stabilityFlags)
    {
        
$this->stabilityFlags $stabilityFlags;
    }

    
/**
     * @inheritDoc
     */
    
public function getStabilityFlags()
    {
        return 
$this->stabilityFlags;
    }

    
/**
     * {@inerhitDoc}
     */
    
public function setPreferStable($preferStable)
    {
        
$this->preferStable $preferStable;
    }

    
/**
     * @inheritDoc
     */
    
public function getPreferStable()
    {
        return 
$this->preferStable;
    }

    
/**
     * {@inerhitDoc}
     */
    
public function setConfig(array $config)
    {
        
$this->config $config;
    }

    
/**
     * @inheritDoc
     */
    
public function getConfig()
    {
        return 
$this->config;
    }

    
/**
     * {@inerhitDoc}
     */
    
public function setReferences(array $references)
    {
        
$this->references $references;
    }

    
/**
     * @inheritDoc
     */
    
public function getReferences()
    {
        return 
$this->references;
    }

    
/**
     * {@inerhitDoc}
     */
    
public function setAliases(array $aliases)
    {
        
$this->aliases $aliases;
    }

    
/**
     * @inheritDoc
     */
    
public function getAliases()
    {
        return 
$this->aliases;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
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$version$prettyVersion)
    {
        
parent::__construct($aliasOf$version$prettyVersion);
    }

    
/**
     * @return RootPackage
     */
    
public function getAliasOf()
    {
        return 
$this->aliasOf;
    }

    
/**
     * @inheritDoc
     */
    
public function getAliases()
    {
        return 
$this->aliasOf->getAliases();
    }

    
/**
     * @inheritDoc
     */
    
public function getMinimumStability()
    {
        return 
$this->aliasOf->getMinimumStability();
    }

    
/**
     * @inheritDoc
     */
    
public function getStabilityFlags()
    {
        return 
$this->aliasOf->getStabilityFlags();
    }

    
/**
     * @inheritDoc
     */
    
public function getReferences()
    {
        return 
$this->aliasOf->getReferences();
    }

    
/**
     * @inheritDoc
     */
    
public function getPreferStable()
    {
        return 
$this->aliasOf->getPreferStable();
    }

    
/**
     * @inheritDoc
     */
    
public function getConfig()
    {
        return 
$this->aliasOf->getConfig();
    }

    
/**
     * @inheritDoc
     */
    
public function setRequires(array $require)
    {
        
$this->requires $this->replaceSelfVersionDependencies($requireLink::TYPE_REQUIRE);

        
$this->aliasOf->setRequires($require);
    }

    
/**
     * @inheritDoc
     */
    
public function setDevRequires(array $devRequire)
    {
        
$this->devRequires $this->replaceSelfVersionDependencies($devRequireLink::TYPE_DEV_REQUIRE);

        
$this->aliasOf->setDevRequires($devRequire);
    }

    
/**
     * @inheritDoc
     */
    
public function setConflicts(array $conflicts)
    {
        
$this->conflicts $this->replaceSelfVersionDependencies($conflictsLink::TYPE_CONFLICT);
        
$this->aliasOf->setConflicts($conflicts);
    }

    
/**
     * @inheritDoc
     */
    
public function setProvides(array $provides)
    {
        
$this->provides $this->replaceSelfVersionDependencies($providesLink::TYPE_PROVIDE);
        
$this->aliasOf->setProvides($provides);
    }

    
/**
     * @inheritDoc
     */
    
public function setReplaces(array $replaces)
    {
        
$this->replaces $this->replaceSelfVersionDependencies($replacesLink::TYPE_REPLACE);
        
$this->aliasOf->setReplaces($replaces);
    }

    
/**
     * @inheritDoc
     */
    
public function setAutoload(array $autoload)
    {
        
$this->aliasOf->setAutoload($autoload);
    }

    
/**
     * @inheritDoc
     */
    
public function setDevAutoload(array $devAutoload)
    {
        
$this->aliasOf->setDevAutoload($devAutoload);
    }

    
/**
     * @inheritDoc
     */
    
public function setStabilityFlags(array $stabilityFlags)
    {
        
$this->aliasOf->setStabilityFlags($stabilityFlags);
    }

    
/**
     * @inheritDoc
     */
    
public function setMinimumStability($minimumStability)
    {
        
$this->aliasOf->setMinimumStability($minimumStability);
    }

    
/**
     * @inheritDoc
     */
    
public function setPreferStable($preferStable)
    {
        
$this->aliasOf->setPreferStable($preferStable);
    }

    
/**
     * @inheritDoc
     */
    
public function setConfig(array $config)
    {
        
$this->aliasOf->setConfig($config);
    }

    
/**
     * @inheritDoc
     */
    
public function setReferences(array $references)
    {
        
$this->aliasOf->setReferences($references);
    }

    
/**
     * @inheritDoc
     */
    
public function setAliases(array $aliases)
    {
        
$this->aliasOf->setAliases($aliases);
    }

    
/**
     * @inheritDoc
     */
    
public function setSuggests(array $suggests)
    {
        
$this->aliasOf->setSuggests($suggests);
    }

    
/**
     * @inheritDoc
     */
    
public function setExtra(array $extra)
    {
        
$this->aliasOf->setExtra($extra);
    }

    public function 
__clone()
    {
        
parent::__clone();
        
$this->aliasOf = clone $this->aliasOf;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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
 *
 * @author Jordi Boggiano <[email protected]>
 */
interface RootPackageInterface extends CompletePackageInterface
{
    
/**
     * Returns a set of package names and their aliases
     *
     * @return array<array{package: string, version: string, alias: string, alias_normalized: string}>
     */
    
public function getAliases();

    
/**
     * Returns the minimum stability of the package
     *
     * @return string
     */
    
public function getMinimumStability();

    
/**
     * Returns the stability flags to apply to dependencies
     *
     * array('foo/bar' => 'dev')
     *
     * @return array<string, BasePackage::STABILITY_*>
     */
    
public function getStabilityFlags();

    
/**
     * 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();

    
/**
     * Returns true if the root package prefers picking stable packages over unstable ones
     *
     * @return bool
     */
    
public function getPreferStable();

    
/**
     * Returns the root package's configuration
     *
     * @return mixed[]
     */
    
public function getConfig();

    
/**
     * Set the required packages
     *
     * @param Link[] $requires A set of package links
     *
     * @return void
     */
    
public function setRequires(array $requires);

    
/**
     * Set the recommended packages
     *
     * @param Link[] $devRequires A set of package links
     *
     * @return void
     */
    
public function setDevRequires(array $devRequires);

    
/**
     * Set the conflicting packages
     *
     * @param Link[] $conflicts A set of package links
     *
     * @return void
     */
    
public function setConflicts(array $conflicts);

    
/**
     * Set the provided virtual packages
     *
     * @param Link[] $provides A set of package links
     *
     * @return void
     */
    
public function setProvides(array $provides);

    
/**
     * Set the packages this one replaces
     *
     * @param Link[] $replaces A set of package links
     *
     * @return void
     */
    
public function setReplaces(array $replaces);

    
/**
     * Set the repositories
     *
     * @param mixed[] $repositories
     *
     * @return void
     */
    
public function setRepositories(array $repositories);

    
/**
     * Set the autoload mapping
     *
     * @param array{psr-0?: array<string, string|string[]>, psr-4?: array<string, string|string[]>, classmap?: list<string>, files?: list<string>} $autoload Mapping of autoloading rules
     *
     * @return void
     */
    
public function setAutoload(array $autoload);

    
/**
     * Set the dev autoload mapping
     *
     * @param array{psr-0?: array<string, string|string[]>, psr-4?: array<string, string|string[]>, classmap?: list<string>, files?: list<string>} $devAutoload Mapping of dev autoloading rules
     *
     * @return void
     */
    
public function setDevAutoload(array $devAutoload);

    
/**
     * Set the stabilityFlags
     *
     * @param array<string, BasePackage::STABILITY_*> $stabilityFlags
     *
     * @return void
     */
    
public function setStabilityFlags(array $stabilityFlags);

    
/**
     * Set the minimumStability
     *
     * @param string $minimumStability
     *
     * @return void
     */
    
public function setMinimumStability($minimumStability);

    
/**
     * Set the preferStable
     *
     * @param bool $preferStable
     *
     * @return void
     */
    
public function setPreferStable($preferStable);

    
/**
     * Set the config
     *
     * @param mixed[] $config
     *
     * @return void
     */
    
public function setConfig(array $config);

    
/**
     * Set the references
     *
     * @param array<string, string> $references
     *
     * @return void
     */
    
public function setReferences(array $references);

    
/**
     * Set the aliases
     *
     * @param array<array{package: string, version: string, alias: string, alias_normalized: string}> $aliases
     *
     * @return void
     */
    
public function setAliases(array $aliases);

    
/**
     * Set the suggested packages
     *
     * @param array<string, string> $suggests A set of package names/comments
     *
     * @return void
     */
    
public function setSuggests(array $suggests);

    
/**
     * @param mixed[] $extra
     *
     * @return void
     */
    
public function setExtra(array $extra);
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @author Nils Adermann <[email protected]>
 * @author Matthieu Moquet <[email protected]>
 */
class PharArchiver implements ArchiverInterface
{
    
/** @var array<string, int> */
    
protected static $formats = array(
        
'zip' => \Phar::ZIP,
        
'tar' => \Phar::TAR,
        
'tar.gz' => \Phar::TAR,
        
'tar.bz2' => \Phar::TAR,
    );

    
/** @var array<string, int> */
    
protected static $compressFormats = array(
        
'tar.gz' => \Phar::GZ,
        
'tar.bz2' => \Phar::BZ2,
    );

    
/**
     * @inheritDoc
     */
    
public function archive($sources$target$format, array $excludes = array(), $ignoreFilters false)
    {
        
$sources realpath($sources);

        
// Phar would otherwise load the file which we don't want
        
if (file_exists($target)) {
            
unlink($target);
        }

        try {
            
$filename substr($target0strrpos($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 (isset(static::
$compressFormats[$format])) {
                
// Check can be compressed?
                
if (!$phar->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($format$sourceType)
    {
        return isset(static::
$formats[$format]);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 
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 <[email protected]>
 */
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($sources, array $excludes$ignoreFilters false)
    {
        
$fs = new Filesystem();

        
$sources $fs->normalizePath(realpath($sources));

        if (
$ignoreFilters) {
            
$filters = array();
        } else {
            
$filters = array(
                new 
GitExcludeFilter($sources),
                new 
ComposerExcludeFilter($sources$excludes),
            );
        }

        
$this->finder = new Finder();

        
$filter = function (\SplFileInfo $file) use ($sources$filters$fs) {
            if (
$file->isLink() && strpos($file->getRealPath(), $sources) !== 0) {
                return 
false;
            }

            
$relativePath Preg::replace(
                
'#^'.preg_quote($sources'#').'#',
                
'',
                
$fs->normalizePath($file->getRealPath())
            );

            
$exclude false;
            foreach (
$filters as $filter) {
                
$exclude $filter->filter($relativePath$exclude);
            }

            return !
$exclude;
        };

        if (
method_exists($filter'bindTo')) {
            
$filter $filter->bindTo(null);
        }

        
$this->finder
            
->in($sources)
            ->
filter($filter)
            ->
ignoreVCS(true)
            ->
ignoreDotFiles(false)
            ->
sortByName();

        
parent::__construct($this->finder->getIterator());
    }

    #[
\ReturnTypeWillChange]
    public function 
accept()
    {
        
/** @var SplFileInfo $current */
        
$current $this->getInnerIterator()->current();

        if (!
$current->isDir()) {
            return 
true;
        }

        
$iterator = new FilesystemIterator($currentFilesystemIterator::SKIP_DOTS);

        return !
$iterator->valid();
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
abstract class BaseExcludeFilter
{
    
/**
     * @var string
     */
    
protected $sourcePath;

    
/**
     * @var array<array{0: 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($sourcePath)
    {
        
$this->sourcePath $sourcePath;
        
$this->excludePatterns = array();
    }

    
/**
     * 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($relativePath$exclude)
    {
        foreach (
$this->excludePatterns as $patternData) {
            list(
$pattern$negate$stripLeadingSlash) = $patternData;

            if (
$stripLeadingSlash) {
                
$path substr($relativePath1);
            } 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: string, 1: bool, 2: bool}> Exclude patterns to be used in filter()
     */
    
protected function parseLines(array $lines$lineParser)
    {
        return 
array_filter(
            
array_map(
                function (
$line) use ($lineParser) {
                    
$line trim($line);

                    if (!
$line || === strpos($line'#')) {
                        return 
null;
                    }

                    return 
call_user_func($lineParser$line);
                },
                
$lines
            
),
            function (
$pattern) {
                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: string, 1: bool, 2: bool}> Exclude patterns
     */
    
protected function generatePatterns($rules)
    {
        
$patterns = array();
        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: string, 1: bool, 2: bool} An exclude pattern
     */
    
protected function generatePattern($rule)
    {
        
$negate false;
        
$pattern '';

        if (
$rule !== '' && $rule[0] === '!') {
            
$negate true;
            
$rule ltrim($rule'!');
        }

        
$firstSlashPosition strpos($rule'/');
        if (
=== $firstSlashPosition) {
            
$pattern '^/';
        } elseif (
false === $firstSlashPosition || strlen($rule) - === $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 array(
'{'.$pattern.$rule.'(?=$|/)}'$negatefalse);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Archiver;

use 
ZipArchive;
use 
Composer\Util\Filesystem;

/**
 * @author Jan Prieser <[email protected]>
 */
class ZipArchiver implements ArchiverInterface
{
    
/** @var array<string, bool> */
    
protected static $formats = array(
        
'zip' => true,
    );

    
/**
     * @inheritDoc
     */
    
public function archive($sources$target$format, array $excludes = array(), $ignoreFilters false)
    {
        
$fs = new Filesystem();
        
$sources $fs->normalizePath($sources);

        
$zip = new ZipArchive();
        
$res $zip->open($targetZipArchive::CREATE);
        if (
$res === true) {
            
$files = new ArchivableFilesFinder($sources$excludes$ignoreFilters);
            foreach (
$files as $file) {
                
/** @var \SplFileInfo $file */
                
$filepath strtr($file->getPath()."/".$file->getFilename(), '\\''/');
                
$localname $filepath;
                if (
strpos($localname$sources '/') === 0) {
                    
$localname substr($localnamestrlen($sources '/'));
                }
                if (
$file->isDir()) {
                    
$zip->addEmptyDir($localname);
                } else {
                    
$zip->addFile($filepath$localname);
                }

                
/**
                 * ZipArchive::setExternalAttributesName is available from >= PHP 5.6
                 * setExternalAttributesName() is only available with libzip 0.11.2 or above
                 */
                
if (PHP_VERSION_ID >= 50600 && method_exists($zip'setExternalAttributesName')) {
                    
$perms fileperms($filepath);

                    
/**
                     * Ensure to preserve the permission umasks for the filepath in the archive.
                     */
                    
$zip->setExternalAttributesName($localnameZipArchive::OPSYS_UNIX$perms << 16);
                }
            }
            if (
$zip->close()) {
                return 
$target;
            }
        }
        
$message sprintf(
            
"Could not create archive '%s' from '%s': %s",
            
$target,
            
$sources,
            
$zip->getStatusString()
        );
        throw new 
\RuntimeException($message);
    }

    
/**
     * @inheritDoc
     */
    
public function supports($format$sourceType)
    {
        return isset(static::
$formats[$format]) && $this->compressionAvailable();
    }

    
/**
     * @return bool
     */
    
private function compressionAvailable()
    {
        return 
class_exists('ZipArchive');
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @author Matthieu Moquet <[email protected]>
 * @author Nils Adermann <[email protected]>
 */
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($sources$target$format, array $excludes = array(), $ignoreFilters false);

    
/**
     * 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($format$sourceType);
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @author Till Klampaeckel <[email protected]>
 */
class ArchiveManager
{
    
/** @var DownloadManager */
    
protected $downloadManager;
    
/** @var Loop */
    
protected $loop;

    
/**
     * @var ArchiverInterface[]
     */
    
protected $archivers = array();

    
/**
     * @var bool
     */
    
protected $overwriteFiles true;

    
/**
     * @param DownloadManager $downloadManager A manager used to download package sources
     */
    
public function __construct(DownloadManager $downloadManagerLoop $loop)
    {
        
$this->downloadManager $downloadManager;
        
$this->loop $loop;
    }

    
/**
     * @param ArchiverInterface $archiver
     *
     * @return void
     */
    
public function addArchiver(ArchiverInterface $archiver)
    {
        
$this->archivers[] = $archiver;
    }

    
/**
     * Set whether existing archives should be overwritten
     *
     * @param bool $overwriteFiles New setting
     *
     * @return $this
     */
    
public function setOverwriteFiles($overwriteFiles)
    {
        
$this->overwriteFiles $overwriteFiles;

        return 
$this;
    }

    
/**
     * 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)
    {
        if (
$package->getArchiveName()) {
            
$baseName $package->getArchiveName();
        } else {
            
$baseName Preg::replace('#[^a-z0-9-_]#i''-'$package->getName());
        }
        
$nameParts = array($baseName);

        if (
null !== $package->getDistReference() && Preg::isMatch('{^[a-f0-9]{40}$}'$package->getDistReference())) {
            
array_push($nameParts$package->getDistReference(), $package->getDistType());
        } else {
            
array_push($nameParts$package->getPrettyVersion(), $package->getDistReference());
        }

        if (
$package->getSourceReference()) {
            
$nameParts[] = substr(sha1($package->getSourceReference()), 06);
        }

        
$name implode('-'array_filter($nameParts, function ($p) {
            return !empty(
$p);
        }));

        return 
str_replace('/''-'$name);
    }

    
/**
     * 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$format$targetDir$fileName null$ignoreFilters false)
    {
        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'.uniqid();
            
$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']);
                }
            }
        }

        if (
null === $fileName) {
            
$packageName $this->getPackageFilename($package);
        } else {
            
$packageName $fileName;
        }

        
// 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'.uniqid().'.'.$format;
        
$filesystem->ensureDirectoryExists(dirname($tempTarget));

        
$archivePath $usableArchiver->archive($sourcePath$tempTarget$format$package->getArchiveExcludes(), $ignoreFilters);
        
$filesystem->rename($archivePath$target);

        
// cleanup temporary download
        
if (!$package instanceof RootPackageInterface) {
            
$filesystem->removeDirectory($sourcePath);
        }
        
$filesystem->remove($tempTarget);

        return 
$target;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
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($sourcePath, array $excludeRules)
    {
        
parent::__construct($sourcePath);
        
$this->excludePatterns $this->generatePatterns($excludeRules);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 
PharData;

class 
ArchivableFilesFilter extends FilterIterator
{
    
/** @var string[] */
    
private $dirs = array();

    
/**
     * @return bool true if the current element is acceptable, otherwise false.
     */
    
#[\ReturnTypeWillChange]
    public function 
accept()
    {
        
$file $this->getInnerIterator()->current();
        if (
$file->isDir()) {
            
$this->dirs[] = (string) $file;

            return 
false;
        }

        return 
true;
    }

    
/**
     * @param string $sources
     *
     * @return void
     */
    
public function addEmptyDir(PharData $phar$sources)
    {
        foreach (
$this->dirs as $filepath) {
            
$localname str_replace($sources "/"''$filepath);
            
$phar->addEmptyDir($localname);
        }
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class GitExcludeFilter extends BaseExcludeFilter
{
    
/**
     * Parses .gitattributes if it exists
     *
     * @param string $sourcePath
     */
    
public function __construct($sourcePath)
    {
        
parent::__construct($sourcePath);

        if (
file_exists($sourcePath.'/.gitattributes')) {
            
$this->excludePatterns array_merge(
                
$this->excludePatterns,
                
$this->parseLines(
                    
file($sourcePath.'/.gitattributes'),
                    array(
$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($line)
    {
        
$parts Preg::split('#\s+#'$line);

        if (
count($parts) == && $parts[1] === 'export-ignore') {
            return 
$this->generatePattern($parts[0]);
        }

        if (
count($parts) == && $parts[1] === '-export-ignore') {
            return 
$this->generatePattern('!'.$parts[0]);
        }

        return 
null;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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
 *
 * @author Nils Adermann <[email protected]>
 */
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();

    
/**
     * @param  array<string, string[]> $scripts
     * @return void
     */
    
public function setScripts(array $scripts);

    
/**
     * Returns an array of repositories
     *
     * @return mixed[] Repositories
     */
    
public function getRepositories();

    
/**
     * Set the repositories
     *
     * @param  mixed[] $repositories
     * @return void
     */
    
public function setRepositories(array $repositories);

    
/**
     * Returns the package license, e.g. MIT, BSD, GPL
     *
     * @return string[] The package licenses
     */
    
public function getLicense();

    
/**
     * Set the license
     *
     * @param  string[] $license
     * @return void
     */
    
public function setLicense(array $license);

    
/**
     * Returns an array of keywords relating to the package
     *
     * @return string[]
     */
    
public function getKeywords();

    
/**
     * Set the keywords
     *
     * @param  string[] $keywords
     * @return void
     */
    
public function setKeywords(array $keywords);

    
/**
     * Returns the package description
     *
     * @return ?string
     */
    
public function getDescription();

    
/**
     * Set the description
     *
     * @param  string $description
     * @return void
     */
    
public function setDescription($description);

    
/**
     * Returns the package homepage
     *
     * @return ?string
     */
    
public function getHomepage();

    
/**
     * Set the homepage
     *
     * @param  string $homepage
     * @return void
     */
    
public function setHomepage($homepage);

    
/**
     * 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();

    
/**
     * Set the authors
     *
     * @param  array<array{name?: string, homepage?: string, email?: string, role?: string}> $authors
     * @return void
     */
    
public function setAuthors(array $authors);

    
/**
     * Returns the support information
     *
     * @return array{issues?: string, forum?: string, wiki?: string, source?: string, email?: string, irc?: string, docs?: string, rss?: string, chat?: string}
     */
    
public function getSupport();

    
/**
     * Set the support information
     *
     * @param  array{issues?: string, forum?: string, wiki?: string, source?: string, email?: string, irc?: string, docs?: string, rss?: string, chat?: string} $support
     * @return void
     */
    
public function setSupport(array $support);

    
/**
     * 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();

    
/**
     * Set the funding
     *
     * @param  array<array{type?: string, url?: string}> $funding
     * @return void
     */
    
public function setFunding(array $funding);

    
/**
     * Returns if the package is abandoned or not
     *
     * @return bool
     */
    
public function isAbandoned();

    
/**
     * If the package is abandoned and has a suggested replacement, this method returns it
     *
     * @return string|null
     */
    
public function getReplacementPackage();

    
/**
     * @param  bool|string $abandoned
     * @return void
     */
    
public function setAbandoned($abandoned);

    
/**
     * Returns default base filename for archive
     *
     * @return ?string
     */
    
public function getArchiveName();

    
/**
     * Sets default base filename for archive
     *
     * @param  string $name
     * @return void
     */
    
public function setArchiveName($name);

    
/**
     * Returns a list of patterns to exclude from package archives
     *
     * @return string[]
     */
    
public function getArchiveExcludes();

    
/**
     * Sets a list of patterns to be excluded from archives
     *
     * @param  string[] $excludes
     * @return void
     */
    
public function setArchiveExcludes(array $excludes);
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class Link
{
    const 
TYPE_REQUIRE 'requires';
    const 
TYPE_DEV_REQUIRE 'devRequires';
    const 
TYPE_PROVIDE 'provides';
    const 
TYPE_CONFLICT 'conflicts';
    const 
TYPE_REPLACE 'replaces';

    
/**
     * Special type
     * @internal
     */
    
const TYPE_DOES_NOT_REQUIRE 'does not require';
    
/**
     * TODO should be marked private once 5.3 is dropped
     * @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 = array(
        
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 string              $source
     * @param string              $target
     * @param ConstraintInterface $constraint       Constraint applying to the target of this link
     * @param self::TYPE_*        $description      Used to create a descriptive string representation
     * @param string|null         $prettyConstraint
     */
    
public function __construct(
        
$source,
        
$target,
        
ConstraintInterface $constraint,
        
$description self::TYPE_UNKNOWN,
        
$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;
    }

    
/**
     * @return string
     */
    
public function getDescription()
    {
        return 
$this->description;
    }

    
/**
     * @return string
     */
    
public function getSource()
    {
        return 
$this->source;
    }

    
/**
     * @return string
     */
    
public function getTarget()
    {
        return 
$this->target;
    }

    
/**
     * @return ConstraintInterface
     */
    
public function getConstraint()
    {
        return 
$this->constraint;
    }

    
/**
     * @throws \UnexpectedValueException If no pretty constraint was provided
     * @return string
     */
    
public function getPrettyConstraint()
    {
        if (
null === $this->prettyConstraint) {
            throw new 
\UnexpectedValueException(sprintf('Link %s has been misconfigured and had no prettyConstraint given.'$this));
        }

        return 
$this->prettyConstraint;
    }

    
/**
     * @return string
     */
    
public function __toString()
    {
        return 
$this->source.' '.$this->description.' '.$this->target.' ('.$this->constraint.')';
    }

    
/**
     * @param  PackageInterface $sourcePackage
     * @return string
     */
    
public function getPrettyString(PackageInterface $sourcePackage)
    {
        return 
$sourcePackage->getPrettyString().' '.$this->description.' '.$this->target.' '.$this->constraint->getPrettyString();
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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
{
    const 
DEFAULT_BRANCH_ALIAS '9999999-dev';

    
/** @var array<string, ConstraintInterface> Constraint parsing cache */
    
private static $constraints = array();

    
/**
     * @inheritDoc
     */
    
public function parseConstraints($constraints)
    {
        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)
    {
        
$pairs array_values($pairs);
        
$result = array();

        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' ')) {
                list(
$name$version) = explode(' '$pair2);
                
$result[] = array('name' => $name'version' => $version);
            } else {
                
$result[] = array('name' => $pair);
            }
        }

        return 
$result;
    }

    
/**
     * @param string $normalizedFrom
     * @param string $normalizedTo
     *
     * @return bool
     */
    
public static function isUpgrade($normalizedFrom$normalizedTo)
    {
        if (
$normalizedFrom === $normalizedTo) {
            return 
true;
        }

        if (
in_array($normalizedFrom, array('dev-master''dev-trunk''dev-default'), true)) {
            
$normalizedFrom VersionParser::DEFAULT_BRANCH_ALIAS;
        }
        if (
in_array($normalizedTo, array('dev-master''dev-trunk''dev-default'), true)) {
            
$normalizedTo VersionParser::DEFAULT_BRANCH_ALIAS;
        }

        if (
strpos($normalizedFrom'dev-') === || strpos($normalizedTo'dev-') === 0) {
            return 
true;
        }

        
$sorted Semver::sort(array($normalizedTo$normalizedFrom));

        return 
$sorted[0] === $normalizedFrom;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\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\ProcessExecutor;
use 
Composer\Util\Svn as SvnUtil;

/**
 * Try to guess the current version number based on different VCS configuration.
 *
 * @author Jordi Boggiano <[email protected]>
 * @author Samuel Roze <[email protected]>
 *
 * @phpstan-type Version 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;

    
/**
     * @param Config              $config
     * @param ProcessExecutor     $process
     * @param SemverVersionParser $versionParser
     */
    
public function __construct(Config $configProcessExecutor $processSemverVersionParser $versionParser)
    {
        
$this->config $config;
        
$this->process $process;
        
$this->versionParser $versionParser;
    }

    
/**
     * @param array<string, mixed> $packageConfig
     * @param string               $path Path to guess into
     *
     * @return array|null
     * @phpstan-return Version|null
     */
    
public function guessVersion(array $packageConfig$path)
    {
        if (!
function_exists('proc_open')) {
            return 
null;
        }

        
$versionData $this->guessGitVersion($packageConfig$path);
        if (
null !== $versionData && 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 && null !== $versionData['version']) {
            return 
$this->postprocess($versionData);
        }

        
$versionData $this->guessSvnVersion($packageConfig$path);
        if (
null !== $versionData && null !== $versionData['version']) {
            return 
$this->postprocess($versionData);
        }

        return 
null;
    }

    
/**
     * @param array $versionData
     *
     * @phpstan-param Version $versionData
     *
     * @return array
     * @phpstan-return Version
     */
    
private function postprocess(array $versionData)
    {
        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
     * @param string               $path
     *
     * @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$path)
    {
        
GitUtil::cleanEnv();
        
$commit null;
        
$version null;
        
$prettyVersion null;
        
$featureVersion null;
        
$featurePrettyVersion null;
        
$isDetached false;

        
// try to fetch current version from git branch
        
if (=== $this->process->execute('git branch -a --no-color --no-abbrev -v'$output$path)) {
            
$branches = array();
            
$isFeatureBranch false;

            
// find current branch and collect all branch names
            
foreach ($this->process->splitLines($output) as $branch) {
                if (
$branch && Preg::isMatch('{^(?:\* ) *(\(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]);
                    }

                    if (
$match[2]) {
                        
$commit $match[2];
                    }
                }

                if (
$branch && !Preg::isMatch('{^ *.+/HEAD }'$branch)) {
                    if (
Preg::isMatch('{^(?:\* )? *((?: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'];
            }
        }

        if (!
$version || $isDetached) {
            
$result $this->versionFromGitTags($path);
            if (
$result) {
                
$version $result['version'];
                
$prettyVersion $result['pretty_version'];
                
$featureVersion null;
                
$featurePrettyVersion null;
            }
        }

        if (!
$commit) {
            
$command 'git log --pretty="%H" -n1 HEAD'.GitUtil::getNoShowSignatureFlag($this->process);
            if (
=== $this->process->execute($command$output$path)) {
                
$commit trim($output) ?: null;
            }
        }

        if (
$featureVersion) {
            return array(
'version' => $version'commit' => $commit'pretty_version' => $prettyVersion'feature_version' => $featureVersion'feature_pretty_version' => $featurePrettyVersion);
        }

        return array(
'version' => $version'commit' => $commit'pretty_version' => $prettyVersion);
    }

    
/**
     * @param string $path
     *
     * @return array{version: string, pretty_version: string}|null
     */
    
private function versionFromGitTags($path)
    {
        
// try to fetch current version from git tags
        
if (=== $this->process->execute('git describe --exact-match --tags'$output$path)) {
            try {
                
$version $this->versionParser->normalize(trim($output));

                return array(
'version' => $version'pretty_version' => trim($output));
            } catch (
\Exception $e) {
            }
        }

        return 
null;
    }

    
/**
     * @param array<string, mixed> $packageConfig
     * @param string               $path
     *
     * @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$path)
    {
        
// try to fetch current version from hg branch
        
if (=== $this->process->execute('hg branch'$output$path)) {
            
$branch trim($output);
            
$version $this->versionParser->normalizeBranch($branch);
            
$isFeatureBranch === strpos($version'dev-');

            if (
VersionParser::DEFAULT_BRANCH_ALIAS === $version) {
                return array(
'version' => $version'commit' => null'pretty_version' => 'dev-'.$branch);
            }

            if (!
$isFeatureBranch) {
                return array(
'version' => $version'commit' => null'pretty_version' => $version);
            }

            
// re-use the HgDriver to fetch branches (this properly includes bookmarks)
            
$io = new NullIO();
            
$driver = new HgDriver(array('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 string|null              $version
     * @param string[]                 $branches
     * @param string                   $scmCmdline
     * @param string                   $path
     *
     * @phpstan-param non-empty-string $scmCmdline
     *
     * @return array{version: string|null, pretty_version: string|null}
     */
    
private function guessFeatureVersion(array $packageConfig$version, array $branches$scmCmdline$path)
    {
        
$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 array(
'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, main is picked
            // and sort using natural sort so that 1.10 will appear before 1.9
            
usort($branches, function ($a$b) {
                
$aRemote === strpos($a'remotes/');
                
$bRemote === strpos($b'remotes/');

                if (
$aRemote !== $bRemote) {
                    return 
$aRemote : -1;
                }

                return 
strnatcasecmp($b$a);
            });

            foreach (
$branches as $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 str_replace(array('%candidate%''%branch%'), array($candidate$branch), $scmCmdline);
                if (
!== $this->process->execute($cmdLine$output$path)) {
                    continue;
                }

                if (
strlen($output) < $length) {
                    
$length strlen($output);
                    
$version $this->versionParser->normalizeBranch($candidateVersion);
                    
$prettyVersion 'dev-' $candidateVersion;
                    if (
$length === 0) {
                        break;
                    }
                }
            }
        }

        return array(
'version' => $version'pretty_version' => $prettyVersion);
    }

    
/**
     * @param array<string, mixed> $packageConfig
     * @param string|null          $branchName
     *
     * @return bool
     */
    
private function isFeatureBranch(array $packageConfig$branchName)
    {
        
$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);
    }

    
/**
     * @param string $path
     *
     * @return array{version: string|null, commit: '', pretty_version: string|null}
     */
    
private function guessFossilVersion($path)
    {
        
$version null;
        
$prettyVersion null;

        
// try to fetch current version from fossil
        
if (=== $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 (=== $this->process->execute('fossil tag list'$output$path)) {
            try {
                
$version $this->versionParser->normalize(trim($output));
                
$prettyVersion trim($output);
            } catch (
\Exception $e) {
            }
        }

        return array(
'version' => $version'commit' => '''pretty_version' => $prettyVersion);
    }

    
/**
     * @param array<string, mixed> $packageConfig
     * @param string               $path
     *
     * @return array{version: string, commit: '', pretty_version: string}|null
     */
    
private function guessSvnVersion(array $packageConfig$path)
    {
        
SvnUtil::cleanEnv();

        
// try to fetch current version from svn
        
if (=== $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]) && ($branchesPath === $matches[2] || $tagsPath === $matches[2])) {
                    
// we are in a branches path
                    
$version $this->versionParser->normalizeBranch($matches[3]);
                    
$prettyVersion 'dev-' $matches[3];

                    return array(
'version' => $version'commit' => '''pretty_version' => $prettyVersion);
                }

                
$prettyVersion trim($matches[1]);
                if (
$prettyVersion === 'trunk') {
                    
$version 'dev-trunk';
                } else {
                    
$version $this->versionParser->normalize($prettyVersion);
                }

                return array(
'version' => $version'commit' => '''pretty_version' => $prettyVersion);
            }
        }

        return 
null;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\PlatformRequirementFilterFactory;
use 
Composer\Filter\PlatformRequirementFilter\PlatformRequirementFilterInterface;
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 <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 */
class VersionSelector
{
    
/** @var RepositorySet */
    
private $repositorySet;

    
/** @var array<string, ConstraintInterface[]> */
    
private $platformConstraints = array();

    
/** @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 $repositorySetPlatformRepository $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                                           $packageName
     * @param string                                           $targetPackageVersion
     * @param string                                           $preferredStability
     * @param PlatformRequirementFilterInterface|bool|string[] $platformRequirementFilter
     * @param int                                              $repoSetFlags*
     * @return PackageInterface|false
     */
    
public function findBestCandidate($packageName$targetPackageVersion null$preferredStability 'stable'$platformRequirementFilter null$repoSetFlags 0)
    {
        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);

        if (
$this->platformConstraints && !($platformRequirementFilter instanceof IgnoreAllPlatformRequirementFilter)) {
            
$platformConstraints $this->platformConstraints;
            
$candidates array_filter($candidates, function ($pkg) use ($platformConstraints$platformRequirementFilter) {
                
$reqs $pkg->getRequires();

                foreach (
$reqs as $name => $link) {
                    if (!
$platformRequirementFilter->isIgnored($name)) {
                        if (isset(
$platformConstraints[$name])) {
                            foreach (
$platformConstraints[$name] as $constraint) {
                                if (
$link->getConstraint()->matches($constraint)) {
                                    continue 
2;
                                }
                            }

                            return 
false;
                        } elseif (
PlatformRepository::isPlatformPackage($name)) {
                            
// 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.
                            
return false;
                        }
                    }
                }

                return 
true;
            });
        }

        if (!
$candidates) {
            return 
false;
        }

        
// select highest version if we have many
        
$package reset($candidates);
        
$minPriority BasePackage::$stabilities[$preferredStability];
        foreach (
$candidates as $candidate) {
            
$candidatePriority $candidate->getStabilityPriority();
            
$currentPriority $package->getStabilityPriority();

            
// candidate is less stable than our preferred stability,
            // and current package is more stable than candidate, skip it
            
if ($minPriority $candidatePriority && $currentPriority $candidatePriority) {
                continue;
            }

            
// candidate is less stable than our preferred stability,
            // and current package is less stable than candidate, select candidate
            
if ($minPriority $candidatePriority && $candidatePriority $currentPriority) {
                
$package $candidate;
                continue;
            }

            
// candidate is more stable than our preferred stability,
            // and current package is less stable than preferred stability, select candidate
            
if ($minPriority >= $candidatePriority && $minPriority $currentPriority) {
                
$package $candidate;
                continue;
            }

            
// select highest version of the two
            
if (version_compare($package->getVersion(), $candidate->getVersion(), '<')) {
                
$package $candidate;
            }
        }

        
// 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
     *  * 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)
     *
     * @param  PackageInterface $package
     * @return string
     */
    
public function findRecommendedRequireVersion(PackageInterface $package)
    {
        
// 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 (=== strpos($package->getName(), 'ext-')) {
            
$phpVersion PHP_MAJOR_VERSION '.' PHP_MINOR_VERSION '.' PHP_RELEASE_VERSION;
            
$extVersion implode('.'array_slice(explode('.'$package->getVersion()), 03));
            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) {
                
$extra str_replace('.9999999''.0'$extra);

                return 
$this->transformVersion($extra$extra'dev');
            }
        }

        return 
$package->getPrettyVersion();
    }

    
/**
     * @param string $version
     * @param string $prettyVersion
     * @param string $stability
     *
     * @return string
     */
    
private function transformVersion($version$prettyVersion$stability)
    {
        
// 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) == && Preg::isMatch('{^0\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;
    }

    
/**
     * @return VersionParser
     */
    
private function getParser()
    {
        if (
$this->parser === null) {
            
$this->parser = new VersionParser();
        }

        return 
$this->parser;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
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<string, 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  string   $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$stability)
    {
        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

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 */
class ArrayDumper
{
    
/**
     * @return array<string, mixed>
     */
    
public function dump(PackageInterface $package)
    {
        
$keys = array(
            
'binaries' => 'bin',
            
'type',
            
'extra',
            
'installationSource' => 'installation-source',
            
'autoload',
            
'devAutoload' => 'autoload-dev',
            
'notificationUrl' => 'notification-url',
            
'includePaths' => 'include-path',
        );

        
$data = array();
        
$data['name'] = $package->getPrettyName();
        
$data['version'] = $package->getPrettyVersion();
        
$data['version_normalized'] = $package->getVersion();

        if (
$package->getTargetDir()) {
            
$data['target-dir'] = $package->getTargetDir();
        }

        if (
$package->getSourceType()) {
            
$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()) {
            
$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) {
            if (
$links $package->{'get'.ucfirst($opts['method'])}()) {
                foreach (
$links as $link) {
                    
$data[$type][$link->getTarget()] = $link->getPrettyConstraint();
                }
                
ksort($data[$type]);
            }
        }

        if (
$packages $package->getSuggests()) {
            
ksort($packages);
            
$data['suggest'] = $packages;
        }

        if (
$package->getReleaseDate()) {
            
$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 = array(
                
'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)
    {
        foreach (
$keys as $method => $key) {
            if (
is_numeric($method)) {
                
$method $key;
            }

            
$getter 'get'.ucfirst($method);
            
$value $package->$getter();

            if (
null !== $value && !(\is_array($value) && === \count($value))) {
                
$data[$key] = $value;
            }
        }

        return 
$data;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
abstract class BasePackage implements PackageInterface
{
    
/**
     * @phpstan-var array<non-empty-string, array{description: string, method: Link::TYPE_*}>
     * @internal
     */
    
public static $supportedLinkTypes = array(
        
'require' => array('description' => 'requires''method' => Link::TYPE_REQUIRE),
        
'conflict' => array('description' => 'conflicts''method' => Link::TYPE_CONFLICT),
        
'provide' => array('description' => 'provides''method' => Link::TYPE_PROVIDE),
        
'replace' => array('description' => 'replaces''method' => Link::TYPE_REPLACE),
        
'require-dev' => array('description' => 'requires (for development)''method' => Link::TYPE_DEV_REQUIRE),
    );

    const 
STABILITY_STABLE 0;
    const 
STABILITY_RC 5;
    const 
STABILITY_BETA 10;
    const 
STABILITY_ALPHA 15;
    const 
STABILITY_DEV 20;

    
/** @var array<string, self::STABILITY_*> */
    
public static $stabilities = array(
        
'stable' => self::STABILITY_STABLE,
        
'RC' => self::STABILITY_RC,
        
'beta' => self::STABILITY_BETA,
        
'alpha' => self::STABILITY_ALPHA,
        
'dev' => self::STABILITY_DEV,
    );

    
/**
     * READ-ONLY: The package id, public for fast access in dependency solver
     * @var int
     * @internal
     * @readonly
     */
    
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($name)
    {
        
$this->prettyName $name;
        
$this->name strtolower($name);
        
$this->id = -1;
    }

    
/**
     * @inheritDoc
     */
    
public function getName()
    {
        return 
$this->name;
    }

    
/**
     * @inheritDoc
     */
    
public function getPrettyName()
    {
        return 
$this->prettyName;
    }

    
/**
     * @inheritDoc
     */
    
public function getNames($provides true)
    {
        
$names = array(
            
$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($id)
    {
        
$this->id $id;
    }

    
/**
     * @inheritDoc
     */
    
public function getId()
    {
        return 
$this->id;
    }

    
/**
     * @inheritDoc
     */
    
public function setRepository(RepositoryInterface $repository)
    {
        if (
$this->repository && $repository !== $this->repository) {
            throw new 
\LogicException('A package can only be added to one repository');
        }
        
$this->repository $repository;
    }

    
/**
     * @inheritDoc
     */
    
public function getRepository()
    {
        return 
$this->repository;
    }

    
/**
     * checks if this package is a platform package
     *
     * @return bool
     */
    
public function isPlatform()
    {
        return 
$this->getRepository() instanceof PlatformRepository;
    }

    
/**
     * Returns package unique name, constructed from name, version and release type.
     *
     * @return string
     */
    
public function getUniqueName()
    {
        return 
$this->getName().'-'.$this->getVersion();
    }

    
/**
     * @return bool
     */
    
public function equals(PackageInterface $package)
    {
        
$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
     *
     * @return string
     */
    
public function __toString()
    {
        return 
$this->getUniqueName();
    }

    public function 
getPrettyString()
    {
        return 
$this->getPrettyName().' '.$this->getPrettyVersion();
    }

    
/**
     * @inheritDoc
     */
    
public function getFullPrettyVersion($truncate true$displayMode PackageInterface::DISPLAY_SOURCE_REF_IF_DEV)
    {
        if (
$displayMode === PackageInterface::DISPLAY_SOURCE_REF_IF_DEV &&
            (!
$this->isDev() || !\in_array($this->getSourceType(), array('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($reference07);
        }

        return 
$this->getPrettyVersion() . ' ' $reference;
    }

    
/**
     * @return int
     *
     * @phpstan-return self::STABILITY_*
     */
    
public function getStabilityPriority()
    {
        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  string $allowPattern
     * @param  string $wrap         Wrap the cleaned string by the given string
     * @return string
     */
    
public static function packageNameToRegexp($allowPattern$wrap '{^%s$}i')
    {
        
$cleanedAllowPattern str_replace('\\*''.*'preg_quote($allowPattern));

        return 
sprintf($wrap$cleanedAllowPattern);
    }

    
/**
     * Build a regexp from package names, expanding * globs as required
     *
     * @param string[] $packageNames
     * @param string $wrap
     * @return string
     */
    
public static function packageNamesToRegexp(array $packageNames$wrap '{^(?:%s)$}iD')
    {
        
$packageNames array_map(
            function (
$packageName) {
                return 
BasePackage::packageNameToRegexp($packageName'%s');
            },
            
$packageNames
        
);

        return 
sprintf($wrapimplode('|'$packageNames));
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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\Package\Locker;
use 
Composer\Pcre\Preg;
use 
Composer\Util\Loop;
use 
Composer\Repository\RepositoryManager;
use 
Composer\Installer\InstallationManager;
use 
Composer\Plugin\PluginManager;
use 
Composer\Downloader\DownloadManager;
use 
Composer\EventDispatcher\EventDispatcher;
use 
Composer\Autoload\AutoloadGenerator;
use 
Composer\Package\Archiver\ArchiveManager;

/**
 * @author Jordi Boggiano <[email protected]>
 * @author Konstantin Kudryashiv <[email protected]>
 * @author Nils Adermann <[email protected]>
 */
class Composer
{
    
/*
     * Examples of the following constants in the various configurations they can be in
     *
     * 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';
     */
    
const VERSION '2.2.5';
    const 
BRANCH_ALIAS_VERSION '';
    const 
RELEASE_DATE '2022-01-21 17:25:52';
    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
     */
    
const RUNTIME_API_VERSION '2.2.2';

    
/**
     * @return string
     */
    
public static function getVersion()
    {
        
// 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 RootPackageInterface
     */
    
private $package;

    
/**
     * @var ?Locker
     */
    
private $locker null;

    
/**
     * @var Loop
     */
    
private $loop;

    
/**
     * @var Repository\RepositoryManager
     */
    
private $repositoryManager;

    
/**
     * @var Downloader\DownloadManager
     */
    
private $downloadManager;

    
/**
     * @var Installer\InstallationManager
     */
    
private $installationManager;

    
/**
     * @var Plugin\PluginManager
     */
    
private $pluginManager;

    
/**
     * @var Config
     */
    
private $config;

    
/**
     * @var EventDispatcher
     */
    
private $eventDispatcher;

    
/**
     * @var Autoload\AutoloadGenerator
     */
    
private $autoloadGenerator;

    
/**
     * @var ArchiveManager
     */
    
private $archiveManager;

    
/**
     * @return void
     */
    
public function setPackage(RootPackageInterface $package)
    {
        
$this->package $package;
    }

    
/**
     * @return RootPackageInterface
     */
    
public function getPackage()
    {
        return 
$this->package;
    }

    
/**
     * @return void
     */
    
public function setConfig(Config $config)
    {
        
$this->config $config;
    }

    
/**
     * @return Config
     */
    
public function getConfig()
    {
        return 
$this->config;
    }

    
/**
     * @return void
     */
    
public function setLocker(Locker $locker)
    {
        
$this->locker $locker;
    }

    
/**
     * @return ?Locker
     */
    
public function getLocker()
    {
        return 
$this->locker;
    }

    
/**
     * @return void
     */
    
public function setLoop(Loop $loop)
    {
        
$this->loop $loop;
    }

    
/**
     * @return Loop
     */
    
public function getLoop()
    {
        return 
$this->loop;
    }

    
/**
     * @return void
     */
    
public function setRepositoryManager(RepositoryManager $manager)
    {
        
$this->repositoryManager $manager;
    }

    
/**
     * @return RepositoryManager
     */
    
public function getRepositoryManager()
    {
        return 
$this->repositoryManager;
    }

    
/**
     * @return void
     */
    
public function setDownloadManager(DownloadManager $manager)
    {
        
$this->downloadManager $manager;
    }

    
/**
     * @return DownloadManager
     */
    
public function getDownloadManager()
    {
        return 
$this->downloadManager;
    }

    
/**
     * @return void
     */
    
public function setArchiveManager(ArchiveManager $manager)
    {
        
$this->archiveManager $manager;
    }

    
/**
     * @return ArchiveManager
     */
    
public function getArchiveManager()
    {
        return 
$this->archiveManager;
    }

    
/**
     * @return void
     */
    
public function setInstallationManager(InstallationManager $manager)
    {
        
$this->installationManager $manager;
    }

    
/**
     * @return InstallationManager
     */
    
public function getInstallationManager()
    {
        return 
$this->installationManager;
    }

    
/**
     * @return void
     */
    
public function setPluginManager(PluginManager $manager)
    {
        
$this->pluginManager $manager;
    }

    
/**
     * @return PluginManager
     */
    
public function getPluginManager()
    {
        return 
$this->pluginManager;
    }

    
/**
     * @return void
     */
    
public function setEventDispatcher(EventDispatcher $eventDispatcher)
    {
        
$this->eventDispatcher $eventDispatcher;
    }

    
/**
     * @return EventDispatcher
     */
    
public function getEventDispatcher()
    {
        return 
$this->eventDispatcher;
    }

    
/**
     * @return void
     */
    
public function setAutoloadGenerator(AutoloadGenerator $autoloadGenerator)
    {
        
$this->autoloadGenerator $autoloadGenerator;
    }

    
/**
     * @return AutoloadGenerator
     */
    
public function getAutoloadGenerator()
    {
        return 
$this->autoloadGenerator;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class JsonValidationException extends Exception
{
    
/**
     * @var string[]
     */
    
protected $errors;

    
/**
     * @param string   $message
     * @param string[] $errors
     */
    
public function __construct($message$errors = array(), Exception $previous null)
    {
        
$this->errors $errors;
        
parent::__construct((string) $message0$previous);
    }

    
/**
     * @return string[]
     */
    
public function getErrors()
    {
        return 
$this->errors;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class JsonManipulator
{
    
/** @var string */
    
private static $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;

    
/**
     * @param string $contents
     */
    
public function __construct($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();
    }

    
/**
     * @return string
     */
    
public function getContents()
    {
        return 
$this->contents $this->newline;
    }

    
/**
     * @param string $type
     * @param string $package
     * @param string $constraint
     * @param bool   $sortPackages
     * @return bool
     */
    
public function addLink($type$package$constraint$sortPackages false)
    {
        
$decoded JsonFile::parseJson($this->contents);

        
// no link of that type yet
        
if (!isset($decoded[$type])) {
            return 
$this->addMainKey($type, array($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;
        }

        
$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)) {
            
// update existing link
            
$existingPackage $packageMatches['package'];
            
$packageRegex str_replace('/''\\\\?/'preg_quote($existingPackage));
            
$links Preg::replaceCallback('{'.self::$DEFINES.'"'.$packageRegex.'"(?P<separator>\s*:\s*)(?&string)}ix', function ($m) use ($existingPackage$constraint) {
                return 
JsonFile::encode(str_replace('\\/''/'$existingPackage)) . $m['separator'] . '"' $constraint '"';
            }, 
$links);
        } else {
            if (
Preg::isMatch('#^\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($linkstrue);
            
$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
     * @return void
     */
    
private function sortPackages(array &$packages = array())
    {
        
$prefix = function ($requirement) {
            if (
PlatformRepository::isPlatformPackage($requirement)) {
                return 
Preg::replace(
                    array(
                        
'/^php/',
                        
'/^hhvm/',
                        
'/^ext/',
                        
'/^lib/',
                        
'/^\D/',
                    ),
                    array(
                        
'0-$0',
                        
'1-$0',
                        
'2-$0',
                        
'3-$0',
                        
'4-$0',
                    ),
                    
$requirement
                
);
            }

            return 
'5-'.$requirement;
        };

        
uksort($packages, function ($a$b) use ($prefix) {
            return 
strnatcmp($prefix($a), $prefix($b));
        });
    }

    
/**
     * @param string                $name
     * @param array<string, mixed>  $config
     * @param bool                  $append
     * @return bool
     */
    
public function addRepository($name$config$append true)
    {
        return 
$this->addSubNode('repositories'$name$config$append);
    }

    
/**
     * @param string $name
     * @return bool
     */
    
public function removeRepository($name)
    {
        return 
$this->removeSubNode('repositories'$name);
    }

    
/**
     * @param string $name
     * @param mixed  $value
     * @return bool
     */
    
public function addConfigSetting($name$value)
    {
        return 
$this->addSubNode('config'$name$value);
    }

    
/**
     * @param string $name
     * @return bool
     */
    
public function removeConfigSetting($name)
    {
        return 
$this->removeSubNode('config'$name);
    }

    
/**
     * @param string $name
     * @param mixed $value
     * @return bool
     */
    
public function addProperty($name$value)
    {
        if (
strpos($name'suggest.') === 0) {
            return 
$this->addSubNode('suggest'substr($name8), $value);
        }

        if (
strpos($name'extra.') === 0) {
            return 
$this->addSubNode('extra'substr($name6), $value);
        }

        if (
strpos($name'scripts.') === 0) {
            return 
$this->addSubNode('scripts'substr($name8), $value);
        }

        return 
$this->addMainKey($name$value);
    }

    
/**
     * @param string $name
     * @return bool
     */
    
public function removeProperty($name)
    {
        if (
strpos($name'suggest.') === 0) {
            return 
$this->removeSubNode('suggest'substr($name8));
        }

        if (
strpos($name'extra.') === 0) {
            return 
$this->removeSubNode('extra'substr($name6));
        }

        if (
strpos($name'scripts.') === 0) {
            return 
$this->removeSubNode('scripts'substr($name8));
        }

        return 
$this->removeMainKey($name);
    }

    
/**
     * @param string $mainNode
     * @param string $name
     * @param mixed  $value
     * @param bool   $append
     * @return bool
     */
    
public function addSubNode($mainNode$name$value$append true)
    {
        
$decoded JsonFile::parseJson($this->contents);

        
$subName null;
        if (
in_array($mainNode, array('config''extra''scripts')) && false !== strpos($name'.')) {
            list(
$name$subName) = explode('.'$name2);
        }

        
// no main node yet
        
if (!isset($decoded[$mainNode])) {
            if (
$subName !== null) {
                
$this->addMainKey($mainNode, array($name => array($subName => $value)));
            } else {
                
$this->addMainKey($mainNode, array($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;
        }

        
$children $match['content'];
        
// invalid match due to un-regexable content, abort
        
if (!@json_decode($children)) {
            return 
false;
        }

        
$that $this;

        
// 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$that) {
                if (
$subName !== null) {
                    
$curVal json_decode($matches['content'], true);
                    if (!
is_array($curVal)) {
                        
$curVal = array();
                    }
                    
$curVal[$subName] = $value;
                    
$value $curVal;
                }

                return 
$matches['start'] . $that->format($value1) . $matches['end'];
            }, 
$children);
        } else {
            
Preg::match('#^{ (?P<leadingspace>\s*?) (?P<content>\S+.*?)? (?P<trailingspace>\s*) }$#sx'$children$match);

            
$whitespace '';
            if (!empty(
$match['trailingspace'])) {
                
$whitespace $match['trailingspace'];
            }

            if (!empty(
$match['content'])) {
                if (
$subName !== null) {
                    
$value = array($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($value1) . $whitespace '}''\\$'),
                        
$children
                    
);
                } else {
                    
$whitespace '';
                    if (!empty(
$match['leadingspace'])) {
                        
$whitespace $match['leadingspace'];
                    }

                    
$children Preg::replace(
                        
'#^{'.$whitespace.'#',
                        
addcslashes('{' $whitespace JsonFile::encode($name).': '.$this->format($value1) . ',' $this->newline $this->indent $this->indent'\\$'),
                        
$children
                    
);
                }
            } else {
                if (
$subName !== null) {
                    
$value = array($subName => $value);
                }

                
// children present but empty
                
$children '{' $this->newline $this->indent $this->indent JsonFile::encode($name).': '.$this->format($value1) . $whitespace '}';
            }
        }

        
$this->contents Preg::replaceCallback($nodeRegex, function ($m) use ($children) {
            return 
$m['start'] . $children $m['end'];
        }, 
$this->contents);

        return 
true;
    }

    
/**
     * @param string $mainNode
     * @param string $name
     * @return bool
     */
    
public function removeSubNode($mainNode$name)
    {
        
$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;
        }

        
$children $match['content'];

        
// invalid match due to un-regexable content, abort
        
if (!@json_decode($childrentrue)) {
            return 
false;
        }

        
$subName null;
        if (
in_array($mainNode, array('config''extra''scripts')) && false !== strpos($name'.')) {
            list(
$name$subName) = explode('.'$name2);
        }

        
// 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) {
                    if (
strlen($bestMatch) < strlen($match)) {
                        
$bestMatch $match;
                    }
                }
                
$childrenClean Preg::replace('{,\s*'.preg_quote($bestMatch).'}i'''$children, -1$count);
                if (
!== $count) {
                    
$childrenClean Preg::replace('{'.preg_quote($bestMatch).'\s*,?\s*}i'''$childrenClean, -1$count);
                    if (
!== $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
        
Preg::match('#^{ \s*? (?P<content>\S+.*?)? (?P<trailingspace>\s*) }$#sx'$childrenClean$match);
        if (empty(
$match['content'])) {
            
$newline $this->newline;
            
$indent $this->indent;

            
$this->contents Preg::replaceCallback($nodeRegex, function ($matches) use ($indent$newline) {
                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($childrentrue);
                unset(
$curVal[$name][$subName]);
                
$this->addSubNode($mainNode$name$curVal[$name]);
            }

            return 
true;
        }

        
$that $this;
        
$this->contents Preg::replaceCallback($nodeRegex, function ($matches) use ($that$name$subName$childrenClean) {
            if (
$subName !== null) {
                
$curVal json_decode($matches['content'], true);
                unset(
$curVal[$name][$subName]);
                
$childrenClean $that->format($curVal);
            }

            return 
$matches['start'] . $childrenClean $matches['end'];
        }, 
$this->contents);

        return 
true;
    }

    
/**
     * @param string $key
     * @param mixed  $content
     * @return bool
     */
    
public function addMainKey($key$content)
    {
        
$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;
    }

    
/**
     * @param string $key
     * @return bool
     */
    
public function removeMainKey($key)
    {
        
$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)) {
            
// 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::isMatch('#,\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;
    }

    
/**
     * @param string $key
     * @return bool
     */
    
public function removeMainKeyIfEmpty($key)
    {
        
$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
     * @param int   $depth
     * @return string
     */
    
public function format($data$depth 0)
    {
        if (
is_array($data)) {
            
reset($data);

            if (
is_numeric(key($data))) {
                foreach (
$data as $key => $val) {
                    
$data[$key] = $this->format($val$depth 1);
                }

                return 
'['.implode(', '$data).']';
            }

            
$out '{' $this->newline;
            
$elems = array();
            foreach (
$data as $key => $val) {
                
$elems[] = str_repeat($this->indent$depth 2) . JsonFile::encode($key). ': '.$this->format($val$depth 1);
            }

            return 
$out implode(','.$this->newline$elems) . $this->newline str_repeat($this->indent$depth 1) . '}';
        }

        return 
JsonFile::encode($data);
    }

    
/**
     * @return void
     */
    
protected function detectIndenting()
    {
        if (
Preg::isMatch('{^([ \t]+)"}m'$this->contents$match)) {
            
$this->indent $match[1];
        } else {
            
$this->indent '    ';
        }
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 */
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 <[email protected]>
     *
     *
     * @param  string $json
     * @param  bool   $unescapeUnicode Un escape unicode
     * @param  bool   $unescapeSlashes Un escape slashes
     * @return string
     */
    
public static function format($json$unescapeUnicode$unescapeSlashes)
    {
        
$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$i1);

            
// 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', function ($match) {
                        
$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 11);

                if (
'{' !== $prevChar && '[' !== $prevChar) {
                    
// If this character is the end of an element,
                    // output a new line and indent the next line
                    
$result .= $newLine;
                    for (
$j 0$j $pos$j++) {
                        
$result .= $indentStr;
                    }
                } 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++;
                }

                for (
$j 0$j $pos$j++) {
                    
$result .= $indentStr;
                }
            }
        }

        return 
$result;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 
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 <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 */
class JsonFile
{
    const 
LAX_SCHEMA 1;
    const 
STRICT_SCHEMA 2;

    const 
JSON_UNESCAPED_SLASHES 64;
    const 
JSON_PRETTY_PRINT 128;
    const 
JSON_UNESCAPED_UNICODE 256;

    const 
COMPOSER_SCHEMA_PATH '/../../../res/composer-schema.json';

    
/** @var string */
    
private $path;
    
/** @var ?HttpDownloader */
    
private $httpDownloader;
    
/** @var ?IOInterface */
    
private $io;

    
/**
     * 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($pathHttpDownloader $httpDownloader nullIOInterface $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;
    }

    
/**
     * @return string
     */
    
public function getPath()
    {
        return 
$this->path;
    }

    
/**
     * Checks whether json file exists.
     *
     * @return bool
     */
    
public function exists()
    {
        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 (
$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());
        }

        return static::
parseJson($json$this->path);
    }

    
/**
     * Writes json file.
     *
     * @param  mixed[]                          $hash    writes hash into json file
     * @param  int                                  $options json_encode options (defaults to JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
     * @throws \UnexpectedValueException|\Exception
     * @return void
     */
    
public function write(array $hash$options 448)
    {
        if (
$this->path === 'php://memory') {
            
file_put_contents($this->path, static::encode($hash$options));

            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($dir0777true)) {
                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). ($options self::JSON_PRETTY_PRINT "\n" ''));
                break;
            } catch (
\Exception $e) {
                if (
$retries 0) {
                    
usleep(500000);
                    continue;
                }

                throw 
$e;
            }
        }
    }

    
/**
     * Modify file properties only if content modified
     *
     * @param string $path
     * @param string $content
     * @return int|false
     */
    
private function filePutContentsIfModified($path$content)
    {
        
$currentContent = @file_get_contents($path);
        if (!
$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 bool                    true on success
     */
    
public function validateSchema($schema self::STRICT_SCHEMA$schemaFile null)
    {
        
$content file_get_contents($this->path);
        
$data json_decode($content);

        if (
null === $data && 'null' !== $content) {
            
self::validateSyntax($content$this->path);
        }

        
$isComposerSchemaFile false;
        if (
null === $schemaFile) {
            
$isComposerSchemaFile true;
            
$schemaFile __DIR__ 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) array('$ref' => $schemaFile);

        if (
$schema === self::LAX_SCHEMA) {
            
$schemaData->additionalProperties true;
            
$schemaData->required = array();
        } elseif (
$schema === self::STRICT_SCHEMA && $isComposerSchemaFile) {
            
$schemaData->additionalProperties false;
            
$schemaData->required = array('name''description');
        }

        
$validator = new Validator();
        
$validator->check($data$schemaData);

        if (!
$validator->isValid()) {
            
$errors = array();
            foreach ((array) 
$validator->getErrors() as $error) {
                
$errors[] = ($error['property'] ? $error['property'].' : ' '').$error['message'];
            }
            throw new 
JsonValidationException('"'.$this->path.'" 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)
     * @return string Encoded json
     */
    
public static function encode($data$options 448)
    {
        if (
PHP_VERSION_ID >= 50400) {
            
$json json_encode($data$options);
            if (
false === $json) {
                
self::throwEncodeError(json_last_error());
            }

            
//  compact brackets to follow recent php versions
            
if (PHP_VERSION_ID 50428 || (PHP_VERSION_ID >= 50500 && PHP_VERSION_ID 50512) || (defined('JSON_C_VERSION') && version_compare(phpversion('json'), '1.3.6''<'))) {
                
$json Preg::replace('/\[\s+\]/''[]'$json);
                
$json Preg::replace('/\{\s+\}/''{}'$json);
            }

            return 
$json;
        }

        
$json json_encode($data);
        if (
false === $json) {
            
self::throwEncodeError(json_last_error());
        }

        
$prettyPrint = (bool) ($options self::JSON_PRETTY_PRINT);
        
$unescapeUnicode = (bool) ($options self::JSON_UNESCAPED_UNICODE);
        
$unescapeSlashes = (bool) ($options self::JSON_UNESCAPED_SLASHES);

        if (!
$prettyPrint && !$unescapeUnicode && !$unescapeSlashes) {
            return 
$json;
        }

        return 
JsonFormatter::format($json$unescapeUnicode$unescapeSlashes);
    }

    
/**
     * 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 void
     */
    
private static function throwEncodeError($code)
    {
        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 ?string $json json string
     * @param string $file the json file
     *
     * @throws ParsingException
     * @return mixed
     */
    
public static function parseJson($json$file null)
    {
        if (
null === $json) {
            return 
null;
        }
        
$data json_decode($jsontrue);
        if (
null === $data && JSON_ERROR_NONE !== json_last_error()) {
            
self::validateSyntax($json$file);
        }

        return 
$data;
    }

    
/**
     * Validates the syntax of a JSON string
     *
     * @param  string                    $json
     * @param  string                    $file
     * @throws \UnexpectedValueException
     * @throws ParsingException
     * @return bool                      true on success
     */
    
protected static function validateSyntax($json$file null)
    {
        
$parser = new JsonParser();
        
$result $parser->lint($json);
        if (
null === $result) {
            if (
defined('JSON_ERROR_UTF8') && JSON_ERROR_UTF8 === json_last_error()) {
                throw new 
\UnexpectedValueException('"'.$file.'" is not UTF-8, could not parse as JSON');
            }

            return 
true;
        }

        throw new 
ParsingException('"'.$file.'" does not contain valid JSON'."\n".$result->getMessage(), $result->getDetails());
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 nullProcessExecutor $processExecutor null)
    {
        
$this->executableFinder $executableFinder;
        
$this->processExecutor $processExecutor;
    }

    
/**
     * @return void
     */
    
public function reset()
    {
        
self::$hhvmVersion null;
    }

    
/**
     * @return string|null
     */
    
public function getVersion()
    {
        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(
                    
ProcessExecutor::escape($hhvmPath).
                    
' --php -d hhvm.jit=0 -r "echo HHVM_VERSION;" 2>/dev/null',
                    
self::$hhvmVersion
                
);
                if (
$exitCode !== 0) {
                    
self::$hhvmVersion false;
                }
            }
        }

        return 
self::$hhvmVersion ?: null;
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 */
class Version
{
    
/**
     * @param  string      $opensslVersion
     * @param  bool        $isFips
     * @return string|null
     */
    
public static function parseOpenssl($opensslVersion, &$isFips)
    {
        
$isFips false;

        if (!
Preg::isMatch('/^(?<version>[0-9.]+)(?<patch>[a-z]{0,2})?(?<suffix>(?:-?(?:dev|pre|alpha|beta|rc|fips)[\d]*)*)?(?<garbage>-\w+)?$/'$opensslVersion$matches)) {
            return 
null;
        }

        
$isFips strpos($matches['suffix'], 'fips') !== false;
        
$suffix strtr('-'.ltrim($matches['suffix'], '-'), array('-fips' => '''-pre' => '-alpha'));
        
$patch self::convertAlphaVersionToIntVersion($matches['patch']);

        return 
rtrim($matches['version'].'.'.$patch.$suffix'-');
    }

    
/**
     * @param  string      $libjpegVersion
     * @return string|null
     */
    
public static function parseLibjpeg($libjpegVersion)
    {
        if (!
Preg::isMatch('/^(?<major>\d+)(?<minor>[a-z]*)$/'$libjpegVersion$matches)) {
            return 
null;
        }

        return 
$matches['major'].'.'.self::convertAlphaVersionToIntVersion($matches['minor']);
    }

    
/**
     * @param  string      $zoneinfoVersion
     * @return string|null
     */
    
public static function parseZoneinfoVersion($zoneinfoVersion)
    {
        if (!
Preg::isMatch('/^(?<year>\d{4})(?<revision>[a-z]*)$/'$zoneinfoVersion$matches)) {
            return 
null;
        }

        return 
$matches['year'].'.'.self::convertAlphaVersionToIntVersion($matches['revision']);
    }

    
/**
     * "" => 0, "a" => 1, "zg" => 33
     *
     * @param  string $alpha
     * @return int
     */
    
private static function convertAlphaVersionToIntVersion($alpha)
    {
        return 
strlen($alpha) * (-ord('a') + 1) + array_sum(array_map('ord'str_split($alpha)));
    }

    
/**
     * @param  int    $versionId
     * @return string
     */
    
public static function convertLibxpmVersionId($versionId)
    {
        return 
self::convertVersionId($versionId100);
    }

    
/**
     * @param  int    $versionId
     * @return string
     */
    
public static function convertOpenldapVersionId($versionId)
    {
        return 
self::convertVersionId($versionId100);
    }

    
/**
     * @param int $versionId
     * @param int $base
     *
     * @return string
     */
    
private static function convertVersionId($versionId$base)
    {
        return 
sprintf(
            
'%d.%d.%d',
            
$versionId / ($base $base),
            (int) (
$versionId $base) % $base,
            
$versionId $base
        
);
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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 string $constant
     * @param class-string $class
     *
     * @return bool
     */
    
public function hasConstant($constant$class null)
    {
        return 
defined(ltrim($class.'::'.$constant':'));
    }

    
/**
     * @param string $constant
     * @param class-string $class
     *
     * @return mixed
     */
    
public function getConstant($constant$class null)
    {
        return 
constant(ltrim($class.'::'.$constant':'));
    }

    
/**
     * @param string $fn
     *
     * @return bool
     */
    
public function hasFunction($fn)
    {
        return 
function_exists($fn);
    }

    
/**
     * @param callable $callable
     * @param mixed[] $arguments
     *
     * @return mixed
     */
    
public function invoke($callable, array $arguments = array())
    {
        return 
call_user_func_array($callable$arguments);
    }

    
/**
     * @param class-string $class
     *
     * @return bool
     */
    
public function hasClass($class)
    {
        return 
class_exists($classfalse);
    }

    
/**
     * @param class-string $class
     * @param mixed[] $arguments
     *
     * @return object
     * @throws \ReflectionException
     */
    
public function construct($class, array $arguments = array())
    {
        if (empty(
$arguments)) {
            return new 
$class;
        }

        
$refl = new \ReflectionClass($class);

        return 
$refl->newInstanceArgs($arguments);
    }

    
/** @return string[] */
    
public function getExtensions()
    {
        return 
get_loaded_extensions();
    }

    
/**
     * @param string $extension
     *
     * @return string
     */
    
public function getExtensionVersion($extension)
    {
        return 
phpversion($extension);
    }

    
/**
     * @param string $extension
     *
     * @return string
     * @throws \ReflectionException
     */
    
public function getExtensionInfo($extension)
    {
        
$reflector = new \ReflectionExtension($extension);

        
ob_start();
        
$reflector->info();

        return 
ob_get_clean();
    }
}
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * @param string $file
 * @return ?\Composer\Autoload\ClassLoader
 */
function includeIfExists($file)
{
    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

// autoload_real.php @generated by Composer

class ComposerAutoloaderInit04114110af87f5624b3580eba43b8770
{
    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('ComposerAutoloaderInit04114110af87f5624b3580eba43b8770''loadClassLoader'), truetrue);
        
self::$loader $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
        
spl_autoload_unregister(array('ComposerAutoloaderInit04114110af87f5624b3580eba43b8770''loadClassLoader'));

        
$useStaticLoader PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
        if (
$useStaticLoader) {
            require 
__DIR__ '/autoload_static.php';

            
call_user_func(\Composer\Autoload\ComposerStaticInit04114110af87f5624b3580eba43b8770::getInitializer($loader));
        } else {
            
$map = require __DIR__ '/autoload_namespaces.php';
            foreach (
$map as $namespace => $path) {
                
$loader->set($namespace$path);
            }

            
$map = require __DIR__ '/autoload_psr4.php';
            foreach (
$map as $namespace => $path) {
                
$loader->setPsr4($namespace$path);
            }

            
$classMap = require __DIR__ '/autoload_classmap.php';
            if (
$classMap) {
                
$loader->addClassMap($classMap);
            }
        }

        
$loader->register(true);

        if (
$useStaticLoader) {
            
$includeFiles Composer\Autoload\ComposerStaticInit04114110af87f5624b3580eba43b8770::$files;
        } else {
            
$includeFiles = require __DIR__ '/autoload_files.php';
        }
        foreach (
$includeFiles as $fileIdentifier => $file) {
            
composerRequire04114110af87f5624b3580eba43b8770($fileIdentifier$file);
        }

        return 
$loader;
    }
}

/**
 * @param string $fileIdentifier
 * @param string $file
 * @return void
 */
function composerRequire04114110af87f5624b3580eba43b8770($fileIdentifier$file)
{
    if (empty(
$GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
        
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;

        require 
$file;
    }
}
<?
php

// autoload_classmap.php @generated by Composer

$vendorDir dirname(dirname(__FILE__));
$baseDir dirname($vendorDir);

return array(
    
'Composer\\InstalledVersions' => $vendorDir '/composer/InstalledVersions.php',
    
'WP_CLI' => $vendorDir '/wp-cli/wp-cli/php/class-wp-cli.php',
    
'WP_CLI_Command' => $vendorDir '/wp-cli/wp-cli/php/class-wp-cli-command.php',
);
<?
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($jsontrue) 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($jsontrue) 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 (
=== $match) {
            return 
false;
        }

        if (
false === $match) {
            throw new 
\RuntimeException('Regex failed to compile/run.');
        }

        return 
true;
    }
}
<?
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 <[email protected]>
 * @author Jordi Boggiano <[email protected]>
 */
class CaBundle
{
    
/** @var string|null */
    
private static $caPath;
    
/** @var array<string, bool> */
    
private static $caFileValidity = array();
    
/** @var bool|null */
    
private static $useOpensslParse;

    
/**
     * 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 <[email protected]>
     *
     * 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/tls/certs/ca-bundle.crt'// Fedora, RHEL, CentOS (ca-certificates package)
            
'/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/local/share/certs/ca-root-nss.crt'// FreeBSD (ca_root_nss_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/ssl/cert.pem'// FreeBSD 10.x
            
'/usr/local/etc/openssl/cert.pem'// OS X homebrew, openssl package
            
'/usr/local/etc/[email protected]/cert.pem'// OS X homebrew, [email protected] package
        
);

        foreach(
$otherLocations as $location) {
            
$otherLocations[] = dirname($location);
        }

        
$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 (=== 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($filenameLoggerInterface $logger null)
    {
        static 
$warned false;

        if (isset(
self::$caFileValidity[$filename])) {
            return 
self::$caFileValidity[$filename];
        }

        
$contents file_get_contents($filename);

        
// assume the CA is valid if php is vulnerable to
        // https://www.sektioneins.de/advisories/advisory-012013-php-openssl_x509_parse-memory-corruption-vulnerability.html
        
if (!static::isOpensslParseSafe()) {
            if (!
$warned && $logger) {
                
$logger->warning(sprintf(
                    
'Your version of PHP, %s, is affected by CVE-2013-6420 and cannot safely perform certificate validation, we strongly suggest you upgrade.',
                    
PHP_VERSION
                
));
                
$warned true;
            }

            
$isValid = !empty($contents);
        } elseif (
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()
    {
        if (
null !== self::$useOpensslParse) {
            return 
self::$useOpensslParse;
        }

        if (
PHP_VERSION_ID >= 50600) {
            return 
self::$useOpensslParse true;
        }

        
// Vulnerable:
        // PHP 5.3.0 - PHP 5.3.27
        // PHP 5.4.0 - PHP 5.4.22
        // PHP 5.5.0 - PHP 5.5.6
        
if (
               (
PHP_VERSION_ID 50400 && PHP_VERSION_ID >= 50328)
            || (
PHP_VERSION_ID 50500 && PHP_VERSION_ID >= 50423)
            || 
PHP_VERSION_ID >= 50507
        
) {
            
// This version of PHP has the fix for CVE-2013-6420 applied.
            
return self::$useOpensslParse true;
        }

        if (
defined('PHP_WINDOWS_VERSION_BUILD')) {
            
// Windows is probably insecure in this case.
            
return self::$useOpensslParse false;
        }

        
$compareDistroVersionPrefix = function ($prefix$fixedVersion) {
            
$regex '{^'.preg_quote($prefix).'([0-9]+)$}';

            if (
preg_match($regexPHP_VERSION$m)) {
                return ((int) 
$m[1]) >= $fixedVersion;
            }

            return 
false;
        };

        
// Hard coded list of PHP distributions with the fix backported.
        
if (
            
$compareDistroVersionPrefix('5.3.3-7+squeeze'18// Debian 6 (Squeeze)
            
|| $compareDistroVersionPrefix('5.4.4-14+deb7u'7// Debian 7 (Wheezy)
            
|| $compareDistroVersionPrefix('5.3.10-1ubuntu3.'9// Ubuntu 12.04 (Precise)
        
) {
            return 
self::$useOpensslParse true;
        }

        
// Symfony Process component is missing so we assume it is unsafe at this point
        
if (!class_exists('Symfony\Component\Process\PhpProcess')) {
            return 
self::$useOpensslParse false;
        }

        
// This is where things get crazy, because distros backport security
        // fixes the chances are on NIX systems the fix has been applied but
        // it's not possible to verify that from the PHP version.
        //
        // To verify exec a new PHP process and run the issue testcase with
        // known safe input that replicates the bug.

        // Based on testcase in https://github.com/php/php-src/commit/c1224573c773b6845e83505f717fbf820fc18415
        // changes in https://github.com/php/php-src/commit/76a7fd893b7d6101300cc656058704a73254d593
        
$cert 'LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVwRENDQTR5Z0F3SUJBZ0lKQUp6dThyNnU2ZUJjTUEwR0NTcUdTSWIzRFFFQkJRVUFNSUhETVFzd0NRWUQKVlFRR0V3SkVSVEVjTUJvR0ExVUVDQXdUVG05eVpISm9aV2x1TFZkbGMzUm1ZV3hsYmpFUU1BNEdBMVVFQnd3SApTOE9Ed3Jac2JqRVVNQklHQTFVRUNnd0xVMlZyZEdsdmJrVnBibk14SHpBZEJnTlZCQXNNRmsxaGJHbGphVzkxCmN5QkRaWEowSUZObFkzUnBiMjR4SVRBZkJnTlZCQU1NR0cxaGJHbGphVzkxY3k1elpXdDBhVzl1WldsdWN5NWsKWlRFcU1DZ0dDU3FHU0liM0RRRUpBUlliYzNSbFptRnVMbVZ6YzJWeVFITmxhM1JwYjI1bGFXNXpMbVJsTUhVWQpaREU1TnpBd01UQXhNREF3TURBd1dnQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBCkFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUEKQUFBQUFBQVhEVEUwTVRFeU9ERXhNemt6TlZvd2djTXhDekFKQmdOVkJBWVRBa1JGTVJ3d0dnWURWUVFJREJOTwpiM0prY21obGFXNHRWMlZ6ZEdaaGJHVnVNUkF3RGdZRFZRUUhEQWRMdzRQQ3RteHVNUlF3RWdZRFZRUUtEQXRUClpXdDBhVzl1UldsdWN6RWZNQjBHQTFVRUN3d1dUV0ZzYVdOcGIzVnpJRU5sY25RZ1UyVmpkR2x2YmpFaE1COEcKQTFVRUF3d1liV0ZzYVdOcGIzVnpMbk5sYTNScGIyNWxhVzV6TG1SbE1Tb3dLQVlKS29aSWh2Y05BUWtCRmh0egpkR1ZtWVc0dVpYTnpaWEpBYzJWcmRHbHZibVZwYm5NdVpHVXdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCCkR3QXdnZ0VLQW9JQkFRRERBZjNobDdKWTBYY0ZuaXlFSnBTU0RxbjBPcUJyNlFQNjV1c0pQUnQvOFBhRG9xQnUKd0VZVC9OYSs2ZnNnUGpDMHVLOURaZ1dnMnRIV1dvYW5TYmxBTW96NVBINlorUzRTSFJaN2UyZERJalBqZGhqaAowbUxnMlVNTzV5cDBWNzk3R2dzOWxOdDZKUmZIODFNTjJvYlhXczROdHp0TE11RDZlZ3FwcjhkRGJyMzRhT3M4CnBrZHVpNVVhd1Raa3N5NXBMUEhxNWNNaEZHbTA2djY1Q0xvMFYyUGQ5K0tBb2tQclBjTjVLTEtlYno3bUxwazYKU01lRVhPS1A0aWRFcXh5UTdPN2ZCdUhNZWRzUWh1K3ByWTNzaTNCVXlLZlF0UDVDWm5YMmJwMHdLSHhYMTJEWAoxbmZGSXQ5RGJHdkhUY3lPdU4rblpMUEJtM3ZXeG50eUlJdlZBZ01CQUFHalFqQkFNQWtHQTFVZEV3UUNNQUF3CkVRWUpZSVpJQVliNFFnRUJCQVFEQWdlQU1Bc0dBMVVkRHdRRUF3SUZvREFUQmdOVkhTVUVEREFLQmdnckJnRUYKQlFjREFqQU5CZ2txaGtpRzl3MEJBUVVGQUFPQ0FRRUFHMGZaWVlDVGJkajFYWWMrMVNub2FQUit2SThDOENhRAo4KzBVWWhkbnlVNGdnYTBCQWNEclk5ZTk0ZUVBdTZacXljRjZGakxxWFhkQWJvcHBXb2NyNlQ2R0QxeDMzQ2tsClZBcnpHL0t4UW9oR0QySmVxa2hJTWxEb214SE83a2EzOStPYThpMnZXTFZ5alU4QVp2V01BcnVIYTRFRU55RzcKbFcyQWFnYUZLRkNyOVRuWFRmcmR4R1ZFYnY3S1ZRNmJkaGc1cDVTanBXSDErTXEwM3VSM1pYUEJZZHlWODMxOQpvMGxWajFLRkkyRENML2xpV2lzSlJvb2YrMWNSMzVDdGQwd1lCY3BCNlRac2xNY09QbDc2ZHdLd0pnZUpvMlFnClpzZm1jMnZDMS9xT2xOdU5xLzBUenprVkd2OEVUVDNDZ2FVK1VYZTRYT1Z2a2NjZWJKbjJkZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K';
        
$script = <<<'EOT'

error_reporting(-1);
$info = openssl_x509_parse(base64_decode('%s'));
var_dump(PHP_VERSION, $info['issuer']['emailAddress'], $info['validFrom_time_t']);

EOT;
        
$script '<'."?php\n".sprintf($script$cert);

        try {
            
$process = new PhpProcess($script);
            
$process->mustRun();
        } catch (
\Exception $e) {
            
// In the case of any exceptions just accept it is not possible to
            // determine the safety of openssl_x509_parse and bail out.
            
return self::$useOpensslParse false;
        }

        
$output preg_split('{\r?\n}'trim($process->getOutput()));
        
$errorOutput trim($process->getErrorOutput());

        if (
            
is_array($output)
            && 
count($output) === 3
            
&& $output[0] === sprintf('string(%d) "%s"'strlen(PHP_VERSION), PHP_VERSION)
            && 
$output[1] === 'string(27) "[email protected]"'
            
&& $output[2] === 'int(-1)'
            
&& preg_match('{openssl_x509_parse\(\): illegal (?:ASN1 data type for|length in) timestamp in - on line \d+}'$errorOutput)
        ) {
            
// This PHP has the fix backported probably by a distro security team.
            
return self::$useOpensslParse true;
        }

        return 
self::$useOpensslParse false;
    }

    
/**
     * Resets the static caches
     * @return void
     */
    
public static function reset()
    {
        
self::$caFileValidity = array();
        
self::$caPath null;
        
self::$useOpensslParse 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($certFileLoggerInterface $logger null)
    {
        return 
$certFile
            
&& static::isFile($certFile$logger)
            && static::
isReadable($certFile$logger)
            && static::
validateCaFile($certFile$logger);
    }

    
/**
     * @param  string|false $certDir
     * @param  LoggerInterface|null $logger
     * @return bool
     */
    
private static function caDirUsable($certDirLoggerInterface $logger null)
    {
        return 
$certDir
            
&& static::isDir($certDir$logger)
            && static::
isReadable($certDir$logger)
            && static::
glob($certDir '/*'$logger);
    }

    
/**
     * @param  string $certFile
     * @param  LoggerInterface|null $logger
     * @return bool
     */
    
private static function isFile($certFileLoggerInterface $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($certDirLoggerInterface $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($certFileOrDirLoggerInterface $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($patternLoggerInterface $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;
    }
}
<?
php return array(
    
'root' => array(
        
'pretty_version' => 'dev-release-2-6-0',
        
'version' => 'dev-release-2-6-0',
        
'type' => 'library',
        
'install_path' => __DIR__ '/../../',
        
'aliases' => array(),
        
'reference' => '19712f1a5e20612a75512d2f418cde455adc5125',
        
'name' => 'wp-cli/wp-cli-bundle',
        
'dev' => false,
    ),
    
'versions' => array(
        
'composer/ca-bundle' => array(
            
'pretty_version' => '1.3.1',
            
'version' => '1.3.1.0',
            
'type' => 'library',
            
'install_path' => __DIR__ '/./ca-bundle',
            
'aliases' => array(),
            
'reference' => '4c679186f2aca4ab6a0f1b0b9cf9252decb44d0b',
            
'dev_requirement' => false,
        ),
        
'composer/composer' => array(
            
'pretty_version' => '2.2.5',
            
'version' => '2.2.5.0',
            
'type' => 'library',
            
'install_path' => __DIR__ '/./composer',
            
'aliases' => array(),
            
'reference' => '22c41ef275c7bb64fa28fb2c0871a39666832cb9',
            
'dev_requirement' => false,
        ),
        
'composer/metadata-minifier' => array(
            
'pretty_version' => '1.0.0',
            
'version' => '1.0.0.0',
            
'type' => 'library',
            
'install_path' => __DIR__ '/./metadata-minifier',
            
'aliases' => array(),
            
'reference' => 'c549d23829536f0d0e984aaabbf02af91f443207',
            
'dev_requirement' => false,
        ),
        
'composer/pcre' => array(
            
'pretty_version' => '1.0.1',
            
'version' => '1.0.1.0',
            
'type' => 'library',
            
'install_path' => __DIR__ '/./pcre',
            
'aliases' => array(),
            
'reference' => '67a32d7d6f9f560b726ab25a061b38ff3a80c560',
            
'dev_requirement' => false,
        ),
        
'composer/semver' => array(
            
'pretty_version' => '3.2.7',
            
'version' => '3.2.7.0',
            
'type' => 'library',
            
'install_path' => __DIR__ '/./semver',
            
'aliases' => array(),
            
'reference' => 'deac27056b57e46faf136fae7b449eeaa71661ee',
            
'dev_requirement' => false,
        ),
        
'composer/spdx-licenses' => array(
            
'pretty_version' => '1.5.6',
            
'version' => '1.5.6.0',
            
'type' => 'library',
            
'install_path' => __DIR__ '/./spdx-licenses',
            
'aliases' => array(),
            
'reference' => 'a30d487169d799745ca7280bc90fdfa693536901',
            
'dev_requirement' => false,
        ),
        
'composer/xdebug-handler' => array(
            
'pretty_version' => '2.0.4',
            
'version' => '2.0.4.0',
            
'type' => 'library',
            
'install_path' => __DIR__ '/./xdebug-handler',
            
'aliases' => array(),
            
'reference' => '0c1a3925ec58a4ec98e992b9c7d171e9e184be0a',
            
'dev_requirement' => false,
        ),
        
'gettext/gettext' => array(
            
'pretty_version' => 'v4.8.6',
            
'version' => '4.8.6.0',
            
'type' => 'library',
            
'install_path' => __DIR__ '/../gettext/gettext',
            
'aliases' => array(),
            
'reference' => 'bbeb8f4d3077663739aecb4551b22e720c0e9efe',
            
'dev_requirement' => false,
        ),
        
'gettext/languages' => array(
            
'pretty_version' => '2.9.0',
            
'version' => '2.9.0.0',
            
'type' => 'library',
            
'install_path' => __DIR__ '/../gettext/languages',
            
'aliases' => array(),
            
'reference' => 'ed56dd2c7f4024cc953ed180d25f02f2640e3ffa',
            
'dev_requirement' => false,
        ),
        
'justinrainbow/json-schema' => array(
            
'pretty_version' => '5.2.11',
            
'version' => '5.2.11.0',
            
'type' => 'library',
            
'install_path' => __DIR__ '/../justinrainbow/json-schema',
            
'aliases' => array(),
            
'reference' => '2ab6744b7296ded80f8cc4f9509abbff393399aa',
            
'dev_requirement' => false,
        ),
        
'mck89/peast' => array(
            
'pretty_version' => 'v1.13.11',
            
'version' => '1.13.11.0',
            
'type' => 'library',
            
'install_path' => __DIR__ '/../mck89/peast',
            
'aliases' => array(),
            
'reference' => '78c57966f3da5f223636ea0417d71ac6ff61e47f',
            
'dev_requirement' => false,
        ),
        
'mustache/mustache' => array(
            
'pretty_version' => 'v2.14.1',
            
'version' => '2.14.1.0',
            
'type' => 'library',
            
'install_path' => __DIR__ '/../mustache/mustache',
            
'aliases' => array(),
            
'reference' => '579ffa5c96e1d292c060b3dd62811ff01ad8c24e',
            
'dev_requirement' => false,
        ),
        
'nb/oxymel' => array(
            
'pretty_version' => 'v0.1.0',
            
'version' => '0.1.0.0',
            
'type' => 'library',
            
'install_path' => __DIR__ '/../nb/oxymel',
            
'aliases' => array(),
            
'reference' => 'cbe626ef55d5c4cc9b5e6e3904b395861ea76e3c',
            
'dev_requirement' => false,
        ),
        
'psr/log' => array(
            
'pretty_version' => '1.1.4',
            
'version' => '1.1.4.0',
            
'type' => 'library',
            
'install_path' => __DIR__ '/../psr/log',
            
'aliases' => array(),
            
'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11',
            
'dev_requirement' => false,
        ),
        
'psr/log-implementation' => array(
            
'dev_requirement' => false,
            
'provided' => array(
                
=> '1.0',
            ),
        ),
        
'react/promise' => array(
            
'pretty_version' => 'v2.8.0',
            
'version' => '2.8.0.0',
            
'type' => 'library',
            
'install_path' => __DIR__ '/../react/promise',
            
'aliases' => array(),
            
'reference' => 'f3cff96a19736714524ca0dd1d4130de73dbbbc4',
            
'dev_requirement' => false,
        ),
        
'rmccue/requests' => array(
            
'pretty_version' => 'v1.8.1',
            
'version' => '1.8.1.0',
            
'type' => 'library',
            
'install_path' => __DIR__ '/../rmccue/requests',
            
'aliases' => array(),
            
'reference' => '82e6936366eac3af4d836c18b9d8c31028fe4cd5',
            
'dev_requirement' => false,
        ),
        
'seld/jsonlint' => array(
            
'pretty_version' => '1.8.3',
            
'version' => '1.8.3.0',
            
'type' => 'library',
            
'install_path' => __DIR__ '/../seld/jsonlint',
            
'aliases' => array(),
            
'reference' => '9ad6ce79c342fbd44df10ea95511a1b24dee5b57',
            
'dev_requirement' => false,
        ),
        
'seld/phar-utils' => array(
            
'pretty_version' => '1.2.0',
            
'version' => '1.2.0.0',
            
'type' => 'library',
            
'install_path' => __DIR__ '/../seld/phar-utils',
            
'aliases' => array(),
            
'reference' => '9f3452c93ff423469c0d56450431562ca423dcee',
            
'dev_requirement' => false,
        ),
        
'symfony/console' => array(
            
'pretty_version' => 'v3.4.47',
            
'version' => '3.4.47.0',
            
'type' => 'library',
            
'install_path' => __DIR__ '/../symfony/console',
            
'aliases' => array(),
            
'reference' => 'a10b1da6fc93080c180bba7219b5ff5b7518fe81',
            
'dev_requirement' => false,
        ),
        
'symfony/debug' => array(
            
'pretty_version' => 'v3.4.47',
            
'version' => '3.4.47.0',
            
'type' => 'library',
            
'install_path' => __DIR__ '/../symfony/debug',
            
'aliases' => array(),
            
'reference' => 'ab42889de57fdfcfcc0759ab102e2fd4ea72dcae',
            
'dev_requirement' => false,
        ),
        
'symfony/filesystem' => array(
            
'pretty_version' => 'v3.4.47',
            
'version' => '3.4.47.0',
            
'type' => 'library',
            
'install_path' => __DIR__ '/../symfony/filesystem',
            
'aliases' => array(),
            
'reference' => 'e58d7841cddfed6e846829040dca2cca0ebbbbb3',
            
'dev_requirement' => false,
        ),
        
'symfony/finder' => array(
            
'pretty_version' => 'v3.4.47',
            
'version' => '3.4.47.0',
            
'type' => 'library',
            
'install_path' => __DIR__ '/../symfony/finder',
            
'aliases' => array(),
            
'reference' => 'b6b6ad3db3edb1b4b1c1896b1975fb684994de6e',
            
'dev_requirement' => false,
        ),
        
'symfony/polyfill-ctype' => array(
            
'pretty_version' => 'v1.19.0',
            
'version' => '1.19.0.0',
            
'type' => 'library',
            
'install_path' => __DIR__ '/../symfony/polyfill-ctype',
            
'aliases' => array(),
            
'reference' => 'aed596913b70fae57be53d86faa2e9ef85a2297b',
            
'dev_requirement' => false,
        ),
        
'symfony/polyfill-mbstring' => array(
            
'pretty_version' => 'v1.19.0',
            
'version' => '1.19.0.0',
            
'type' => 'library',
            
'install_path' => __DIR__ '/../symfony/polyfill-mbstring',
            
'aliases' => array(),
            
'reference' => 'b5f7b932ee6fa802fc792eabd77c4c88084517ce',
            
'dev_requirement' => false,
        ),
        
'symfony/process' => array(
            
'pretty_version' => 'v3.4.47',
            
'version' => '3.4.47.0',
            
'type' => 'library',
            
'install_path' => __DIR__ '/../symfony/process',
            
'aliases' => array(),
            
'reference' => 'b8648cf1d5af12a44a51d07ef9bf980921f15fca',
            
'dev_requirement' => false,
        ),
        
'wp-cli/cache-command' => array(
            
'pretty_version' => 'v2.0.9',
            
'version' => '2.0.9.0',
            
'type' => 'wp-cli-package',
            
'install_path' => __DIR__ '/../wp-cli/cache-command',
            
'aliases' => array(),
            
'reference' => '05378440d8c6d4d2a1a5e5cbc1ba92a5e4bf1c40',
            
'dev_requirement' => false,
        ),
        
'wp-cli/checksum-command' => array(
            
'pretty_version' => 'v2.1.2',
            
'version' => '2.1.2.0',
            
'type' => 'wp-cli-package',
            
'install_path' => __DIR__ '/../wp-cli/checksum-command',
            
'aliases' => array(),
            
'reference' => 'ec59a24af2ca97b770a4709b0a1c241eeb4b4cff',
            
'dev_requirement' => false,
        ),
        
'wp-cli/config-command' => array(
            
'pretty_version' => 'v2.1.3',
            
'version' => '2.1.3.0',
            
'type' => 'wp-cli-package',
            
'install_path' => __DIR__ '/../wp-cli/config-command',
            
'aliases' => array(),
            
'reference' => 'cdabbc47dae464a93b10361b9a18e84cf4e72fe2',
            
'dev_requirement' => false,
        ),
        
'wp-cli/core-command' => array(
            
'pretty_version' => 'v2.1.1',
            
'version' => '2.1.1.0',
            
'type' => 'wp-cli-package',
            
'install_path' => __DIR__ '/../wp-cli/core-command',
            
'aliases' => array(),
            
'reference' => 'a75d052ea000b4f0ec14106c110836b376e95a4f',
            
'dev_requirement' => false,
        ),
        
'wp-cli/cron-command' => array(
            
'pretty_version' => 'v2.1.0',
            
'version' => '2.1.0.0',
            
'type' => 'wp-cli-package',
            
'install_path' => __DIR__ '/../wp-cli/cron-command',
            
'aliases' => array(),
            
'reference' => 'bb9fd9645e9a5276d024a59affeda3e6aa8530be',
            
'dev_requirement' => false,
        ),
        
'wp-cli/db-command' => array(
            
'pretty_version' => 'v2.0.20',
            
'version' => '2.0.20.0',
            
'type' => 'wp-cli-package',
            
'install_path' => __DIR__ '/../wp-cli/db-command',
            
'aliases' => array(),
            
'reference' => '709f58c30d178afcdecaf56068ca9f5e985ed6b9',
            
'dev_requirement' => false,
        ),
        
'wp-cli/embed-command' => array(
            
'pretty_version' => 'v2.0.11',
            
'version' => '2.0.11.0',
            
'type' => 'wp-cli-package',
            
'install_path' => __DIR__ '/../wp-cli/embed-command',
            
'aliases' => array(),
            
'reference' => '00a901a66aecb4da94a8dace610eb1135fc82386',
            
'dev_requirement' => false,
        ),
        
'wp-cli/entity-command' => array(
            
'pretty_version' => 'v2.2.1',
            
'version' => '2.2.1.0',
            
'type' => 'wp-cli-package',
            
'install_path' => __DIR__ '/../wp-cli/entity-command',
            
'aliases' => array(),
            
'reference' => 'd7d08b05c67651abde5d570851e46498a164cb34',
            
'dev_requirement' => false,
        ),
        
'wp-cli/eval-command' => array(
            
'pretty_version' => 'v2.1.2',
            
'version' => '2.1.2.0',
            
'type' => 'wp-cli-package',
            
'install_path' => __DIR__ '/../wp-cli/eval-command',
            
'aliases' => array(),
            
'reference' => '5213040ec2167b2748f2689ff6fe24b92a064a90',
            
'dev_requirement' => false,
        ),
        
'wp-cli/export-command' => array(
            
'pretty_version' => 'v2.0.11',
            
'version' => '2.0.11.0',
            
'type' => 'wp-cli-package',
            
'install_path' => __DIR__ '/../wp-cli/export-command',
            
'aliases' => array(),
            
'reference' => '8dd137e0c739a59bb3d3de684a219dbb34473e11',
            
'dev_requirement' => false,
        ),
        
'wp-cli/extension-command' => array(
            
'pretty_version' => 'v2.1.4',
            
'version' => '2.1.4.0',
            
'type' => 'wp-cli-package',
            
'install_path' => __DIR__ '/../wp-cli/extension-command',
            
'aliases' => array(),
            
'reference' => '6401d7ea51084fac40010c2fb305be640675f385',
            
'dev_requirement' => false,
        ),
        
'wp-cli/i18n-command' => array(
            
'pretty_version' => 'v2.2.13',
            
'version' => '2.2.13.0',
            
'type' => 'wp-cli-package',
            
'install_path' => __DIR__ '/../wp-cli/i18n-command',
            
'aliases' => array(),
            
'reference' => '77ece9e2c914bb56ea72b9ee9f00556dece07b3f',
            
'dev_requirement' => false,
        ),
        
'wp-cli/import-command' => array(
            
'pretty_version' => 'v2.0.8',
            
'version' => '2.0.8.0',
            
'type' => 'wp-cli-package',
            
'install_path' => __DIR__ '/../wp-cli/import-command',
            
'aliases' => array(),
            
'reference' => 'a092e3abcca843f1fabf2e9b706a912ae075355f',
            
'dev_requirement' => false,
        ),
        
'wp-cli/language-command' => array(
            
'pretty_version' => 'v2.0.12',
            
'version' => '2.0.12.0',
            
'type' => 'wp-cli-package',
            
'install_path' => __DIR__ '/../wp-cli/language-command',
            
'aliases' => array(),
            
'reference' => 'bbdba69179fc8df597928587111500c8ade40a38',
            
'dev_requirement' => false,
        ),
        
'wp-cli/maintenance-mode-command' => array(
            
'pretty_version' => 'v2.0.8',
            
'version' => '2.0.8.0',
            
'type' => 'wp-cli-package',
            
'install_path' => __DIR__ '/../wp-cli/maintenance-mode-command',
            
'aliases' => array(),
            
'reference' => 'e65505c973ea9349257a4f33ac9edc78db0b189a',
            
'dev_requirement' => false,
        ),
        
'wp-cli/media-command' => array(
            
'pretty_version' => 'v2.0.12',
            
'version' => '2.0.12.0',
            
'type' => 'wp-cli-package',
            
'install_path' => __DIR__ '/../wp-cli/media-command',
            
'aliases' => array(),
            
'reference' => '7cbf32c7fa68631619f85a9bea0fb80fca119ba6',
            
'dev_requirement' => false,
        ),
        
'wp-cli/mustangostang-spyc' => array(
            
'pretty_version' => '0.6.3',
            
'version' => '0.6.3.0',
            
'type' => 'library',
            
'install_path' => __DIR__ '/../wp-cli/mustangostang-spyc',
            
'aliases' => array(),
            
'reference' => '6aa0b4da69ce9e9a2c8402dab8d43cf32c581cc7',
            
'dev_requirement' => false,
        ),
        
'wp-cli/package-command' => array(
            
'pretty_version' => 'v2.2.2',
            
'version' => '2.2.2.0',
            
'type' => 'wp-cli-package',
            
'install_path' => __DIR__ '/../wp-cli/package-command',
            
'aliases' => array(),
            
'reference' => '36afdee21d022e6270867aa0cbfef6f453041814',
            
'dev_requirement' => false,
        ),
        
'wp-cli/php-cli-tools' => array(
            
'pretty_version' => 'v0.11.13',
            
'version' => '0.11.13.0',
            
'type' => 'library',
            
'install_path' => __DIR__ '/../wp-cli/php-cli-tools',
            
'aliases' => array(),
            
'reference' => 'a2866855ac1abc53005c102e901553ad5772dc04',
            
'dev_requirement' => false,
        ),
        
'wp-cli/rewrite-command' => array(
            
'pretty_version' => 'v2.0.10',
            
'version' => '2.0.10.0',
            
'type' => 'wp-cli-package',
            
'install_path' => __DIR__ '/../wp-cli/rewrite-command',
            
'aliases' => array(),
            
'reference' => '562a0a5a0d51be000de87d7a8a870de13383ecd6',
            
'dev_requirement' => false,
        ),
        
'wp-cli/role-command' => array(
            
'pretty_version' => 'v2.0.9',
            
'version' => '2.0.9.0',
            
'type' => 'wp-cli-package',
            
'install_path' => __DIR__ '/../wp-cli/role-command',
            
'aliases' => array(),
            
'reference' => '9abd93952565935084160bc3be49dfb2483bb0b6',
            
'dev_requirement' => false,
        ),
        
'wp-cli/scaffold-command' => array(
            
'pretty_version' => 'v2.0.16',
            
'version' => '2.0.16.0',
            
'type' => 'wp-cli-package',
            
'install_path' => __DIR__ '/../wp-cli/scaffold-command',
            
'aliases' => array(),
            
'reference' => '6d92fb363b8ed7473af7f12cf342aaf9d2c96e81',
            
'dev_requirement' => false,
        ),
        
'wp-cli/search-replace-command' => array(
            
'pretty_version' => 'v2.0.16',
            
'version' => '2.0.16.0',
            
'type' => 'wp-cli-package',
            
'install_path' => __DIR__ '/../wp-cli/search-replace-command',
            
'aliases' => array(),
            
'reference' => 'dbf21560fd91710b2900f5631448657d28f2b380',
            
'dev_requirement' => false,
        ),
        
'wp-cli/server-command' => array(
            
'pretty_version' => 'v2.0.10',
            
'version' => '2.0.10.0',
            
'type' => 'wp-cli-package',
            
'install_path' => __DIR__ '/../wp-cli/server-command',
            
'aliases' => array(),
            
'reference' => '50c81f45f1cf09bc0a52e3582b3e56d27ca3c33c',
            
'dev_requirement' => false,
        ),
        
'wp-cli/shell-command' => array(
            
'pretty_version' => 'v2.0.11',
            
'version' => '2.0.11.0',
            
'type' => 'wp-cli-package',
            
'install_path' => __DIR__ '/../wp-cli/shell-command',
            
'aliases' => array(),
            
'reference' => '28a7de3134c9f059900d8fa4aea1d7d618481454',
            
'dev_requirement' => false,
        ),
        
'wp-cli/super-admin-command' => array(
            
'pretty_version' => 'v2.0.10',
            
'version' => '2.0.10.0',
            
'type' => 'wp-cli-package',
            
'install_path' => __DIR__ '/../wp-cli/super-admin-command',
            
'aliases' => array(),
            
'reference' => 'e6707f3acfff089d19c5c55bba0fd66cd7d6c2fa',
            
'dev_requirement' => false,
        ),
        
'wp-cli/widget-command' => array(
            
'pretty_version' => 'v2.1.7',
            
'version' => '2.1.7.0',
            
'type' => 'wp-cli-package',
            
'install_path' => __DIR__ '/../wp-cli/widget-command',
            
'aliases' => array(),
            
'reference' => '6aedab77f1cd2a0f511b62110c244c32b84dd429',
            
'dev_requirement' => false,
        ),
        
'wp-cli/wp-cli' => array(
            
'pretty_version' => 'v2.6.0',
            
'version' => '2.6.0.0',
            
'type' => 'library',
            
'install_path' => __DIR__ '/../wp-cli/wp-cli',
            
'aliases' => array(),
            
'reference' => 'dee13c2baf6bf972484a63f8b8dab48f7220f095',
            
'dev_requirement' => false,
        ),
        
'wp-cli/wp-cli-bundle' => array(
            
'pretty_version' => 'dev-release-2-6-0',
            
'version' => 'dev-release-2-6-0',
            
'type' => 'library',
            
'install_path' => __DIR__ '/../../',
            
'aliases' => array(),
            
'reference' => '19712f1a5e20612a75512d2f418cde455adc5125',
            
'dev_requirement' => false,
        ),
        
'wp-cli/wp-config-transformer' => array(
            
'pretty_version' => 'v1.3.0',
            
'version' => '1.3.0.0',
            
'type' => 'library',
            
'install_path' => __DIR__ '/../wp-cli/wp-config-transformer',
            
'aliases' => array(),
            
'reference' => '2e90eefc6b8f5166f53aa5414fd8f1a572164ef1',
            
'dev_requirement' => false,
        ),
    ),
);
<?
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.
 */

namespace Composer\XdebugHandler;

use 
Psr\Log\LoggerInterface;
use 
Psr\Log\LogLevel;

/**
 * @author John Stevenson <[email protected]>
 * @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;

    
/**
     * Constructor
     *
     * @param string $envAllowXdebug Prefixed _ALLOW_XDEBUG name
     * @param bool $debug Whether debug output is required
     */
    
public function __construct($envAllowXdebug$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;
    }

    
/**
     * @param LoggerInterface $logger
     *
     * @return void
     */
    
public function setLogger(LoggerInterface $logger)
    {
        
$this->logger $logger;
    }

    
/**
     * Calls a handler method to report a message
     *
     * @param string $op The handler constant
     * @param null|string $data Data required by the handler
     *
     * @return void
     * @throws \InvalidArgumentException If $op is not known
     */
    
public function report($op$data)
    {
        if (
$this->logger !== null || $this->debug) {
            
$callable = array($this'report'.$op);

            if (!
is_callable($callable)) {
                throw new 
\InvalidArgumentException('Unknown op handler: '.$op);
            }

            
$params $data !== null $data : array();
            
call_user_func_array($callable, array($params));
        }
    }

    
/**
     * Outputs a status message
     *
     * @param string $text
     * @param string $level
     *
     * @return void
     */
    
private function output($text$level null)
    {
        if (
$this->logger !== null) {
            
$this->logger->log($level !== null $levelLogLevel::DEBUG$text);
        }

        if (
$this->debug) {
            
fwrite(STDERRsprintf('xdebug-handler[%d] %s'getmypid(), $text.PHP_EOL));
        }
    }

    
/**
     * @param string $loaded
     *
     * @return void
     */
    
private function reportCheck($loaded)
    {
        list(
$version$mode) = explode('|'$loaded);

        if (
$version !== '') {
            
$this->loaded '('.$version.')'.($mode !== '' ' mode='.$mode '');
        }
        
$this->modeOff $mode === 'off';
        
$this->output('Checking '.$this->envAllowXdebug);
    }

    
/**
     * @param string $error
     *
     * @return void
     */
    
private function reportError($error)
    {
        
$this->output(sprintf('No restart (%s)'$error), LogLevel::WARNING);
    }

    
/**
     * @param string $info
     *
     * @return void
     */
    
private function reportInfo($info)
    {
        
$this->output($info);
    }

    
/**
     * @return void
     */
    
private function reportNoRestart()
    {
        
$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 'mode' 'application');
            }
            
$this->output($text);
        }
    }

    
/**
     * @return void
     */
    
private function reportRestart()
    {
        
$this->output($this->getLoadedMessage());
        
Process::setEnv(self::ENV_RESTART, (string) microtime(true));
    }

    
/**
     * @return void
     */
    
private function reportRestarted()
    {
        
$loaded $this->getLoadedMessage();
        
$text sprintf('Restarted (%d ms). %s'$this->time$loaded);
        
$level $this->loaded !== null LogLevel::WARNING null;
        
$this->output($text$level);
    }

    
/**
     * @param string $command
     *
     * @return void
     */
    
private function reportRestarting($command)
    {
        
$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
     *
     * @return string
     */
    
private function getEnvAllow()
    {
        return 
$this->envAllowXdebug.'='.getenv($this->envAllowXdebug);
    }

    
/**
     * Returns the Xdebug status and version
     *
     * @return string
     */
    
private function getLoadedMessage()
    {
        
$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.
 */

namespace Composer\XdebugHandler;

/**
 * @author John Stevenson <[email protected]>
 *
 * @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()
    {
        
$this->getDataAndReset();
        return array();
    }

    
/**
     * Use standard restart settings
     *
     * @return string[] PHP cli options
     */
    
public function useStandard()
    {
        
$data $this->getDataAndReset();
        if (
$data !== null) {
            return array(
'-n''-c'$data['tmpIni']);
        }

        return array();
    }

    
/**
     * Use environment variables to persist settings
     *
     * @return string[] Empty array of PHP cli options
     */
    
public function usePersistent()
    {
        
$data $this->getDataAndReset();
        if (
$data !== null) {
            
$this->updateEnv('PHPRC'$data['tmpIni']);
            
$this->updateEnv('PHP_INI_SCAN_DIR''');
        }

        return array();
    }

    
/**
     * Returns restart data if available and resets the environment
     *
     * @return array|null
     * @phpstan-return restartData|null
     */
    
private function getDataAndReset()
    {
        
$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
     *
     * @return void
     */
    
private function updateEnv($name$value)
    {
        
Process::setEnv($namefalse !== $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.
 */

namespace Composer\XdebugHandler;

use 
Composer\Pcre\Preg;
use 
Psr\Log\LoggerInterface;

/**
 * @author John Stevenson <[email protected]>
 *
 * @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($envPrefix)
    {
        if (!
is_string($envPrefix) || $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
     *
     * @param LoggerInterface $logger
     *
     * @return $this
     */
    
public function setLogger(LoggerInterface $logger)
    {
        
$this->statusWriter->setLogger($logger);
        return 
$this;
    }

    
/**
     * Sets the main script location if it cannot be called from argv
     *
     * @param string $script
     *
     * @return $this
     */
    
public function setMainScript($script)
    {
        
$this->script $script;
        return 
$this;
    }

    
/**
     * Persist the settings to keep Xdebug out of sub-processes
     *
     * @return $this
     */
    
public function setPersistent()
    {
        
$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.
     *
     * @return void
     */
    
public function check()
    {
        
$this->notify(Status::CHECKself::$xdebugVersion.'|'.self::$xdebugMode);
        
$envArgs explode('|', (string) getenv($this->envAllowXdebug));

        if (!((bool) 
$envArgs[0]) && $this->requiresRestart(self::$xdebugActive)) {
            
// Restart required
            
$this->notify(Status::RESTART);

            if (
$this->prepareRestart()) {
                
$command $this->getCommand();
                
$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 empty.
     *
     * @return string[]
     */
    
public static function getAllIniFiles()
    {
        if (
self::$name !== null) {
            
$env getenv(self::$name.self::SUFFIX_INIS);

            if (
false !== $env) {
                return 
explode(PATH_SEPARATOR$env);
            }
        }

        
$paths = array((string) php_ini_loaded_file());
        
$scanned php_ini_scanned_files();

        if (
$scanned !== false) {
            
$paths array_merge($pathsarray_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.
     *
     * @return array|null
     * @phpstan-return restartData|null
     */
    
public static function getRestartSettings()
    {
        
$envArgs explode('|', (string) getenv(self::RESTART_SETTINGS));

        if (
count($envArgs) !== 6
            
|| (!self::$inRestart && php_ini_loaded_file() !== $envArgs[0])) {
            return 
null;
        }

        return array(
            
'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
     *
     * @return string
     */
    
public static function getSkippedVersion()
    {
        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.
     *
     * @return bool
     */
    
public static function isXdebugActive()
    {
        
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".
     * Do not typehint for 1.x compatibility.
     *
     * @param bool $default The default behaviour
     *
     * @return bool Whether the process should restart
     */
    
protected function requiresRestart($default)
    {
        return 
$default;
    }

    
/**
     * Allows an extending class to access the tmpIni
     *
     * Do not typehint for 1.x compatibility
     *
     * @param string[] $command
     *
     * @return void
     */
    
protected function restart($command)
    {
        
$this->doRestart($command);
    }

    
/**
     * Executes the restarted command then deletes the tmp ini
     *
     * @param string[] $command
     *
     * @return void
     * @phpstan-return never
     */
    
private function doRestart(array $command)
    {
        
$this->tryEnableSignals();
        
$this->notify(Status::RESTARTINGimplode(' '$command));

        if (
PHP_VERSION_ID >= 70400) {
            
$cmd $command;
        } else {
            
$cmd Process::escapeShellCommand($command);
            if (
defined('PHP_WINDOWS_VERSION_BUILD')) {
                
// Outer quotes required on cmd string below PHP 8
                
$cmd '"'.$cmd.'"';
            }
        }

        
$process proc_open($cmd, array(), $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 true 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 bool
     */
    
private function prepareRestart()
    {
        
$error null;
        
$iniFiles self::getAllIniFiles();
        
$scannedInis count($iniFiles) > 1;
        
$tmpDir sys_get_temp_dir();

        if (!
$this->cli) {
            
$error 'Unsupported SAPI: '.PHP_SAPI;
        } elseif (!
defined('PHP_BINARY')) {
            
$error 'PHP version is too old: '.PHP_VERSION;
        } elseif (!
$this->checkConfiguration($info)) {
            
$error $info;
        } elseif (!
$this->checkScanDirConfig()) {
            
$error 'PHP version does not report scanned inis: '.PHP_VERSION;
        } elseif (!
$this->checkMainScript()) {
            
$error 'Unable to access main script: '.$this->script;
        } elseif (!
$this->writeTmpIni($iniFiles$tmpDir$error)) {
            
$error $error !== null $error 'Unable to create temp ini file at: '.$tmpDir;
        } elseif (!
$this->setEnvironment($scannedInis$iniFiles)) {
            
$error 'Unable to set environment variables';
        }

        if (
$error !== null) {
            
$this->notify(Status::ERROR$error);
        }

        return 
$error === null;
    }

    
/**
     * Returns true if the tmp ini file was written
     *
     * @param string[] $iniFiles All ini files used in the current process
     * @param string $tmpDir The system temporary directory
     * @param null|string $error Set by method if ini file cannot be read
     *
     * @return bool
     */
    
private function writeTmpIni(array $iniFiles$tmpDir, &$error)
    {
        if ((
$tmpfile = @tempnam($tmpDir'')) === false) {
            return 
false;
        }

        
$this->tmpIni $tmpfile;

        
// $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$matchesPREG_OFFSET_CAPTURE)) {
                
$data substr($data0$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(nullfalse);

        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($this->tmpIni$content);
    }

    
/**
     * Returns the command line arguments for the restart
     *
     * @return string[]
     */
    
private function getCommand()
    {
        
$php = array(PHP_BINARY);
        
$args array_slice($_SERVER['argv'], 1);

        if (!
$this->persistent) {
            
// Use command-line options
            
array_push($php'-n''-c'$this->tmpIni);
        }

        return 
array_merge($php, array($this->script), $args);
    }

    
/**
     * Returns true if the restart environment variables were set
     *
     * No need to update $_SERVER since this is set in the restarted process.
     *
     * @param bool $scannedInis Whether there were scanned ini files
     * @param string[] $iniFiles All ini files used in the current process
     *
     * @return bool
     */
    
private function setEnvironment($scannedInis, array $iniFiles)
    {
        
$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='.$this->tmpIni)) {
                return 
false;
            }
        }

        
// Flag restarted process and save values for it to use
        
$envArgs = array(
            
self::RESTART_ID,
            
self::$xdebugVersion,
            (int) 
$scannedInis,
            
false === $scanDir '*' $scanDir,
            
false === $phprc '*' $phprc,
        );

        return 
putenv($this->envAllowXdebug.'='.implode('|'$envArgs));
    }

    
/**
     * Logs status messages
     *
     * @param string $op Status handler constant
     * @param null|string $data Optional data
     *
     * @return void
     */
    
private function notify($op$data null)
    {
        
$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
     *
     * @return string
     */
    
private function mergeLoadedConfig(array $loadedConfig, array $iniConfig)
    {
        
$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
     *
     * @return bool
     */
    
private function checkMainScript()
    {
        if (
null !== $this->script) {
            
// Allow an application to set -- for standard input
            
return file_exists($this->script) || '--' === $this->script;
        }

        if (
file_exists($this->script $_SERVER['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($this->script $main['file']);
        }

        return 
false;
    }

    
/**
     * Adds restart settings to the environment
     *
     * @param string[] $envArgs
     *
     * @return void
     */
    
private function setEnvRestartSettings($envArgs)
    {
        
$settings = array(
            
php_ini_loaded_file(),
            
$envArgs[2],
            
$envArgs[3],
            
$envArgs[4],
            
getenv($this->envOriginalInis),
            
self::$skipped,
        );

        
Process::setEnv(self::RESTART_SETTINGSimplode('|'$settings));
    }

    
/**
     * Syncs settings and the environment if called with existing settings
     *
     * @param array $settings
     * @phpstan-param restartData $settings
     *
     * @return void
     */
    
private function syncSettings(array $settings)
    {
        if (
false === getenv($this->envOriginalInis)) {
            
// Called by another app, so make original inis available
            
Process::setEnv($this->envOriginalInisimplode(PATH_SEPARATOR$settings['inis']));
        }

        
self::$skipped $settings['skipped'];
        
$this->notify(Status::INFO'Process called with existing restart settings');
    }

    
/**
     * Returns true if there are scanned inis and PHP is able to report them
     *
     * php_ini_scanned_files will fail when PHP_CONFIG_FILE_SCAN_DIR is empty.
     * Fixed in 7.1.13 and 7.2.1
     *
     * @return bool
     */
    
private function checkScanDirConfig()
    {
        if (
PHP_VERSION_ID >= 70113 && PHP_VERSION_ID !== 70200) {
            return 
true;
        }

        return ((string) 
getenv('PHP_INI_SCAN_DIR') === '')
            || 
PHP_CONFIG_FILE_SCAN_DIR !== '';
    }

    
/**
     * Returns true if there are no known configuration issues
     *
     * @param string $info Set by method
     * @return bool
     */
    
private function checkConfiguration(&$info)
    {
        if (!
function_exists('proc_open')) {
            
$info 'proc_open function is disabled';
            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 (
=== 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+.
     *
     * @return void
     */
    
private function tryEnableSignals()
    {
        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(SIGINTSIG_IGN);
            } elseif (
is_int(pcntl_signal_get_handler(SIGINT))) {
                
// Restarted, no handler set so force default action
                
pcntl_signal(SIGINTSIG_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) {});
        }
    }

    
/**
     * Sets static properties $xdebugActive, $xdebugVersion and $xdebugMode
     *
     * @return void
     */
    
private static function setXdebugDetails()
    {
        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) === '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) {
            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.
 */

namespace Composer\XdebugHandler;

use 
Composer\Pcre\Preg;

/**
 * Process utility functions
 *
 * @author John Stevenson <[email protected]>
 */
class Process
{
    
/**
     * Escapes a string to be used as a shell argument.
     *
     * From https://github.com/johnstevenson/winbox-args
     * MIT Licensed (c) John Stevenson <[email protected]>
     *
     * @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
     *
     * @return string The escaped argument
     */
    
public static function escape($arg$meta true$module false)
    {
        if (!
defined('PHP_WINDOWS_VERSION_BUILD')) {
            return 
"'".str_replace("'""'\\''"$arg)."'";
        }

        
$quote strpbrk($arg" \t") !== false || $arg === '';

        
$arg Preg::replace('/(\\\\*)"/''$1$1\\"'$arg, -1$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
     *
     * @return string The escaped command line
     */
    
public static function escapeShellCommand(array $args)
    {
        
$command '';
        
$module array_shift($args);

        if (
$module !== null) {
            
$command self::escape($moduletruetrue);

            foreach (
$args as $arg) {
                
$command .= ' '.self::escape($arg);
            }
        }

        return 
$command;
    }

    
/**
     * Makes putenv environment changes available in $_SERVER and $_ENV
     *
     * @param string $name
     * @param string|null $value A null value unsets the variable
     *
     * @return bool Whether the environment variable was set
     */
    
public static function setEnv($name$value null)
    {
        
$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

// autoload_static.php @generated by Composer

namespace Composer\Autoload;

class 
ComposerStaticInit04114110af87f5624b3580eba43b8770
{
    public static 
$files = array (
        
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ '/..' '/symfony/polyfill-ctype/bootstrap.php',
        
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ '/..' '/symfony/polyfill-mbstring/bootstrap.php',
        
'ad155f8f1cf0d418fe49e248db8c661b' => __DIR__ '/..' '/react/promise/src/functions_include.php',
        
'be01b9b16925dcb22165c40b46681ac6' => __DIR__ '/..' '/wp-cli/php-cli-tools/lib/cli/cli.php',
        
'3937806105cc8e221b8fa8db5b70d2f2' => __DIR__ '/..' '/wp-cli/mustangostang-spyc/includes/functions.php',
        
'ba366f96f4fddbdef61ad7a862b44f61' => __DIR__ '/..' '/wp-cli/package-command/package-command.php',
        
'8a0ad02df6a5087f2c380f8fd52db273' => __DIR__ '/..' '/wp-cli/cache-command/cache-command.php',
        
'f3f0199a3ecd9f501d0a3b361bd2f61c' => __DIR__ '/..' '/wp-cli/entity-command/entity-command.php',
        
'3f201033d5aceb2293314273be88f7c6' => __DIR__ '/..' '/wp-cli/extension-command/extension-command.php',
        
'c65f753375faee349b7adc48c2ee7cc2' => __DIR__ '/..' '/wp-cli/db-command/db-command.php',
        
'5e099d3cac677dd2bec1003ea7707745' => __DIR__ '/..' '/wp-cli/media-command/media-command.php',
        
'd979c11fe80ba96ae3037b43429fe546' => __DIR__ '/..' '/wp-cli/scaffold-command/scaffold-command.php',
        
'5c6ec5cff8f9d625772c8ed147f6b894' => __DIR__ '/..' '/wp-cli/export-command/export-command.php',
        
'ac949ce40a981819ba132473518a9a31' => __DIR__ '/..' '/wp-cli/wp-config-transformer/src/WPConfigTransformer.php',
        
'b66d29757fcb2fb7a9608d068e3716b0' => __DIR__ '/..' '/wp-cli/checksum-command/checksum-command.php',
        
'30cbb6e4122dc988e494c6b9c0438233' => __DIR__ '/..' '/wp-cli/import-command/import-command.php',
        
'9f04dd0aa5d67ec75a75c88c345a079e' => __DIR__ '/..' '/wp-cli/server-command/server-command.php',
        
'8519779bbb65eeb842af2f629ce7b6f8' => __DIR__ '/..' '/wp-cli/super-admin-command/super-admin-command.php',
        
'5deaf6ce9c8bbdfb65104c7e938d5875' => __DIR__ '/..' '/wp-cli/config-command/config-command.php',
        
'68c39b88215b6cf7a0da164166670ef9' => __DIR__ '/..' '/wp-cli/core-command/core-command.php',
        
'7654e00bf0e632580764400bd8293a9c' => __DIR__ '/..' '/wp-cli/cron-command/cron-command.php',
        
'021d3a13471556f0b57038d679f7f8ea' => __DIR__ '/..' '/wp-cli/embed-command/embed-command.php',
        
'f958dca3f412fd7975da1700912a9321' => __DIR__ '/..' '/wp-cli/eval-command/eval-command.php',
        
'ffb465a494c3101218c4417180c2c9a2' => __DIR__ '/..' '/wp-cli/i18n-command/i18n-command.php',
        
'1c88c1eff05217a8cac80c64c9ac2080' => __DIR__ '/..' '/wp-cli/maintenance-mode-command/maintenance-mode-command.php',
        
'ace0d205db7f4135ec32132a0076d555' => __DIR__ '/..' '/wp-cli/language-command/language-command.php',
        
'f399c1c8d0c787d5c94c09884cdd9762' => __DIR__ '/..' '/wp-cli/rewrite-command/rewrite-command.php',
        
'080fadd667195d055c5a23386f270261' => __DIR__ '/..' '/wp-cli/role-command/role-command.php',
        
'8ecb13f8bbc22b1b34d12b14ec01077a' => __DIR__ '/..' '/wp-cli/search-replace-command/search-replace-command.php',
        
'129d58fa8151374aceb8571bcaa97504' => __DIR__ '/..' '/wp-cli/shell-command/shell-command.php',
        
'1f05372afcc7d0c51a305cef1d56dd01' => __DIR__ '/..' '/wp-cli/widget-command/widget-command.php',
    );

    public static 
$prefixLengthsPsr4 = array (
        
'W' => 
        array (
            
'WP_CLI\\MaintenanceMode\\' => 23,
            
'WP_CLI\\I18n\\' => 12,
            
'WP_CLI\\Embeds\\' => 14,
            
'WP_CLI\\' => 7,
        ),
        
'S' => 
        array (
            
'Symfony\\Polyfill\\Mbstring\\' => 26,
            
'Symfony\\Polyfill\\Ctype\\' => 23,
            
'Symfony\\Component\\Process\\' => 26,
            
'Symfony\\Component\\Finder\\' => 25,
            
'Symfony\\Component\\Filesystem\\' => 29,
            
'Symfony\\Component\\Debug\\' => 24,
            
'Symfony\\Component\\Console\\' => 26,
            
'Seld\\PharUtils\\' => 15,
            
'Seld\\JsonLint\\' => 14,
        ),
        
'R' => 
        array (
            
'React\\Promise\\' => 14,
        ),
        
'P' => 
        array (
            
'Psr\\Log\\' => 8,
            
'Peast\\test\\' => 11,
            
'Peast\\' => 6,
        ),
        
'M' => 
        array (
            
'Mustangostang\\' => 14,
        ),
        
'J' => 
        array (
            
'JsonSchema\\' => 11,
        ),
        
'G' => 
        array (
            
'Gettext\\Languages\\' => 18,
            
'Gettext\\' => 8,
        ),
        
'C' => 
        array (
            
'Composer\\XdebugHandler\\' => 23,
            
'Composer\\Spdx\\' => 14,
            
'Composer\\Semver\\' => 16,
            
'Composer\\Pcre\\' => 14,
            
'Composer\\MetadataMinifier\\' => 26,
            
'Composer\\CaBundle\\' => 18,
            
'Composer\\' => 9,
        ),
    );

    public static 
$prefixDirsPsr4 = array (
        
'WP_CLI\\MaintenanceMode\\' => 
        array (
            
=> __DIR__ '/..' '/wp-cli/maintenance-mode-command/src',
        ),
        
'WP_CLI\\I18n\\' => 
        array (
            
=> __DIR__ '/..' '/wp-cli/i18n-command/src',
        ),
        
'WP_CLI\\Embeds\\' => 
        array (
            
=> __DIR__ '/..' '/wp-cli/embed-command/src',
        ),
        
'WP_CLI\\' => 
        array (
            
=> __DIR__ '/..' '/wp-cli/entity-command/src/WP_CLI',
            
=> __DIR__ '/..' '/wp-cli/shell-command/src/WP_CLI',
        ),
        
'Symfony\\Polyfill\\Mbstring\\' => 
        array (
            
=> __DIR__ '/..' '/symfony/polyfill-mbstring',
        ),
        
'Symfony\\Polyfill\\Ctype\\' => 
        array (
            
=> __DIR__ '/..' '/symfony/polyfill-ctype',
        ),
        
'Symfony\\Component\\Process\\' => 
        array (
            
=> __DIR__ '/..' '/symfony/process',
        ),
        
'Symfony\\Component\\Finder\\' => 
        array (
            
=> __DIR__ '/..' '/symfony/finder',
        ),
        
'Symfony\\Component\\Filesystem\\' => 
        array (
            
=> __DIR__ '/..' '/symfony/filesystem',
        ),
        
'Symfony\\Component\\Debug\\' => 
        array (
        ),
        
'Symfony\\Component\\Console\\' => 
        array (
            
=> __DIR__ '/..' '/symfony/console',
        ),
        
'Seld\\PharUtils\\' => 
        array (
            
=> __DIR__ '/..' '/seld/phar-utils/src',
        ),
        
'Seld\\JsonLint\\' => 
        array (
            
=> __DIR__ '/..' '/seld/jsonlint/src/Seld/JsonLint',
        ),
        
'React\\Promise\\' => 
        array (
            
=> __DIR__ '/..' '/react/promise/src',
        ),
        
'Psr\\Log\\' => 
        array (
            
=> __DIR__ '/..' '/psr/log/Psr/Log',
        ),
        
'Peast\\test\\' => 
        array (
            
=> __DIR__ '/..' '/mck89/peast/test/Peast',
        ),
        
'Peast\\' => 
        array (
            
=> __DIR__ '/..' '/mck89/peast/lib/Peast',
        ),
        
'Mustangostang\\' => 
        array (
            
=> __DIR__ '/..' '/wp-cli/mustangostang-spyc/src',
        ),
        
'JsonSchema\\' => 
        array (
            
=> __DIR__ '/..' '/justinrainbow/json-schema/src/JsonSchema',
        ),
        
'Gettext\\Languages\\' => 
        array (
            
=> __DIR__ '/..' '/gettext/languages/src',
        ),
        
'Gettext\\' => 
        array (
            
=> __DIR__ '/..' '/gettext/gettext/src',
        ),
        
'Composer\\XdebugHandler\\' => 
        array (
            
=> __DIR__ '/..' '/composer/xdebug-handler/src',
        ),
        
'Composer\\Spdx\\' => 
        array (
        ),
        
'Composer\\Semver\\' => 
        array (
            
=> __DIR__ '/..' '/composer/semver/src',
        ),
        
'Composer\\Pcre\\' => 
        array (
            
=> __DIR__ '/..' '/composer/pcre/src',
        ),
        
'Composer\\MetadataMinifier\\' => 
        array (
            
=> __DIR__ '/..' '/composer/metadata-minifier/src',
        ),
        
'Composer\\CaBundle\\' => 
        array (
            
=> __DIR__ '/..' '/composer/ca-bundle/src',
        ),
        
'Composer\\' => 
        array (
            
=> __DIR__ '/..' '/composer/composer/src/Composer',
        ),
    );

    public static 
$fallbackDirsPsr4 = array (
        
=> __DIR__ '/..' '/wp-cli/cache-command/src',
        
=> __DIR__ '/..' '/wp-cli/config-command/src',
        
=> __DIR__ '/..' '/wp-cli/core-command/src',
        
=> __DIR__ '/..' '/wp-cli/checksum-command/src',
        
=> __DIR__ '/..' '/wp-cli/cron-command/src',
        
=> __DIR__ '/..' '/wp-cli/db-command/src',
        
=> __DIR__ '/..' '/wp-cli/eval-command/src',
        
=> __DIR__ '/..' '/wp-cli/export-command/src',
        
=> __DIR__ '/..' '/wp-cli/entity-command/src',
        
=> __DIR__ '/..' '/wp-cli/extension-command/src',
        
10 => __DIR__ '/..' '/wp-cli/import-command/src',
        
11 => __DIR__ '/..' '/wp-cli/language-command/src',
        
12 => __DIR__ '/..' '/wp-cli/media-command/src',
        
13 => __DIR__ '/..' '/wp-cli/package-command/src',
        
14 => __DIR__ '/..' '/wp-cli/rewrite-command/src',
        
15 => __DIR__ '/..' '/wp-cli/role-command/src',
        
16 => __DIR__ '/..' '/wp-cli/search-replace-command/src',
        
17 => __DIR__ '/..' '/wp-cli/server-command/src',
        
18 => __DIR__ '/..' '/wp-cli/scaffold-command/src',
        
19 => __DIR__ '/..' '/wp-cli/shell-command/src',
        
20 => __DIR__ '/..' '/wp-cli/super-admin-command/src',
        
21 => __DIR__ '/..' '/wp-cli/widget-command/src',
    );

    public static 
$prefixesPsr0 = array (
        
'c' => 
        array (
            
'cli' => 
            array (
                
=> __DIR__ '/..' '/wp-cli/php-cli-tools/lib',
            ),
        ),
        
'W' => 
        array (
            
'WP_CLI\\' => 
            array (
                
=> __DIR__ '/..' '/wp-cli/wp-cli/php',
            ),
        ),
        
'R' => 
        array (
            
'Requests' => 
            array (
                
=> __DIR__ '/..' '/rmccue/requests/library',
            ),
        ),
        
'O' => 
        array (
            
'Oxymel' => 
            array (
                
=> __DIR__ '/..' '/nb/oxymel',
            ),
        ),
        
'M' => 
        array (
            
'Mustache' => 
            array (
                
=> __DIR__ '/..' '/mustache/mustache/src',
            ),
        ),
    );

    public static 
$classMap = array (
        
'Composer\\InstalledVersions' => __DIR__ '/..' '/composer/InstalledVersions.php',
        
'WP_CLI' => __DIR__ '/..' '/wp-cli/wp-cli/php/class-wp-cli.php',
        
'WP_CLI_Command' => __DIR__ '/..' '/wp-cli/wp-cli/php/class-wp-cli-command.php',
    );

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

        }, 
nullClassLoader::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 
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 (
PHP_VERSION_ID >= 80000) {
            return 
preg_last_error_msg();
        }

        
// older php versions did not set the code properly in all cases
        
if (PHP_VERSION_ID 70201 && $code === 0) {
            return 
'UNDEFINED_ERROR';
        }

        
$constants get_defined_constants(true);
        if (!isset(
$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($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
     * @param string $subject
     * @param int    $offset
     * @return bool
     */
    
public static function isMatch($pattern$subject$offset 0)
    {
        return (bool) 
Preg::match($pattern$subject$matches0$offset);
    }

    
/**
     * @param non-empty-string $pattern
     * @param string $subject
     * @param int    $flags PREG_UNMATCHED_AS_NULL, only available on PHP 7.2+
     * @param int    $offset
     * @return MatchResult
     */
    
public static function match($pattern$subject$flags 0$offset 0)
    {
        if ((
$flags PREG_OFFSET_CAPTURE) !== 0) {
            throw new 
\InvalidArgumentException('PREG_OFFSET_CAPTURE is not supported as it changes the return type, use matchWithOffsets() instead');
        }

        
$count Preg::match($pattern$subject$matches$flags$offset);

        return new 
MatchResult($count$matches);
    }

    
/**
     * Runs preg_match with PREG_OFFSET_CAPTURE
     *
     * @param non-empty-string $pattern
     * @param string $subject
     * @param int    $flags PREG_UNMATCHED_AS_NULL, only available on PHP 7.2+
     * @param int    $offset
     * @return MatchWithOffsetsResult
     */
    
public static function matchWithOffsets($pattern$subject$flags 0$offset 0)
    {
        
$count Preg::matchWithOffsets($pattern$subject$matches$flags$offset);

        return new 
MatchWithOffsetsResult($count$matches);
    }

    
/**
     * @param non-empty-string $pattern
     * @param string $subject
     * @param int    $flags PREG_UNMATCHED_AS_NULL, only available on PHP 7.2+
     * @param int    $offset
     * @return MatchAllResult
     */
    
public static function matchAll($pattern$subject$flags 0$offset 0)
    {
        if ((
$flags PREG_OFFSET_CAPTURE) !== 0) {
            throw new 
\InvalidArgumentException('PREG_OFFSET_CAPTURE is not supported as it changes the return type, use matchAllWithOffsets() instead');
        }

        if ((
$flags PREG_SET_ORDER) !== 0) {
            throw new 
\InvalidArgumentException('PREG_SET_ORDER is not supported as it changes the return type');
        }

        
$count Preg::matchAll($pattern$subject$matches$flags$offset);

        return new 
MatchAllResult($count$matches);
    }

    
/**
     * Runs preg_match_all with PREG_OFFSET_CAPTURE
     *
     * @param non-empty-string $pattern
     * @param string $subject
     * @param int    $flags PREG_UNMATCHED_AS_NULL, only available on PHP 7.2+
     * @param int    $offset
     * @return MatchAllWithOffsetsResult
     */
    
public static function matchAllWithOffsets($pattern$subject$flags 0$offset 0)
    {
        
$count Preg::matchAllWithOffsets($pattern$subject$matches$flags$offset);

        return new 
MatchAllWithOffsetsResult($count$matches);
    }
    
/**
     * @param string|string[] $pattern
     * @param string|string[] $replacement
     * @param string          $subject
     * @param int             $limit
     * @return ReplaceResult
     */
    
public static function replace($pattern$replacement$subject$limit = -1)
    {
        
$result Preg::replace($pattern$replacement$subject$limit$count);

        return new 
ReplaceResult($count$result);
    }

    
/**
     * @param string|string[] $pattern
     * @param callable        $replacement
     * @param string          $subject
     * @param int             $limit
     * @param int             $flags PREG_OFFSET_CAPTURE or PREG_UNMATCHED_AS_NULL, only available on PHP 7.4+
     * @return ReplaceResult
     */
    
public static function replaceCallback($pattern$replacement$subject$limit = -1$flags 0)
    {
        
$result Preg::replaceCallback($pattern$replacement$subject$limit$count$flags);

        return new 
ReplaceResult($count$result);
    }

    
/**
     * Available from PHP 7.0
     *
     * @param array<string, callable> $pattern
     * @param string $subject
     * @param int    $limit
     * @param int    $flags PREG_OFFSET_CAPTURE or PREG_UNMATCHED_AS_NULL, only available on PHP 7.4+
     * @return ReplaceResult
     */
    
public static function replaceCallbackArray($pattern$subject$limit = -1$flags 0)
    {
        
$result Preg::replaceCallbackArray($pattern$subject$limit$count$flags);

        return new 
ReplaceResult($count$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;

class 
Preg
{
    const 
ARRAY_MSG '$subject as an array is not supported. You can use \'foreach\' instead.';

    
/**
     * @param non-empty-string   $pattern
     * @param string   $subject
     * @param array<string|null> $matches Set by method
     * @param int      $flags PREG_UNMATCHED_AS_NULL, only available on PHP 7.2+
     * @param int      $offset
     * @return 0|1
     */
    
public static function match($pattern$subject, &$matches null$flags 0$offset 0)
    {
        if ((
$flags PREG_OFFSET_CAPTURE) !== 0) {
            throw new 
\InvalidArgumentException('PREG_OFFSET_CAPTURE is not supported as it changes the type of $matches, use matchWithOffsets() instead');
        }

        
$result preg_match($pattern$subject$matches$flags$offset);
        if (
$result === false) {
            throw 
PcreException::fromFunction('preg_match'$pattern);
        }

        return 
$result;
    }

    
/**
     * Runs preg_match with PREG_OFFSET_CAPTURE
     *
     * @param non-empty-string   $pattern
     * @param string   $subject
     * @param array<int|string, array{string|null, int}> $matches Set by method
     * @param int      $flags PREG_UNMATCHED_AS_NULL, only available on PHP 7.2+
     * @param int      $offset
     * @return 0|1
     *
     * @phpstan-param array<int|string, array{string|null, int<-1, max>}> $matches
     */
    
public static function matchWithOffsets($pattern$subject, &$matches$flags 0$offset 0)
    {
        
$result preg_match($pattern$subject$matches$flags PREG_OFFSET_CAPTURE$offset);
        if (
$result === false) {
            throw 
PcreException::fromFunction('preg_match'$pattern);
        }

        return 
$result;
    }

    
/**
     * @param non-empty-string   $pattern
     * @param string   $subject
     * @param array<int|string, list<string|null>> $matches Set by method
     * @param int      $flags PREG_UNMATCHED_AS_NULL, only available on PHP 7.2+
     * @param int      $offset
     * @return 0|positive-int
     */
    
public static function matchAll($pattern$subject, &$matches null$flags 0$offset 0)
    {
        if ((
$flags PREG_OFFSET_CAPTURE) !== 0) {
            throw new 
\InvalidArgumentException('PREG_OFFSET_CAPTURE is not supported as it changes the type of $matches, use matchAllWithOffsets() instead');
        }

        if ((
$flags PREG_SET_ORDER) !== 0) {
            throw new 
\InvalidArgumentException('PREG_SET_ORDER is not supported as it changes the type of $matches');
        }

        
$result preg_match_all($pattern$subject$matches$flags$offset);
        if (
$result === false || $result === null) {
            throw 
PcreException::fromFunction('preg_match_all'$pattern);
        }

        return 
$result;
    }

    
/**
     * Runs preg_match_all with PREG_OFFSET_CAPTURE
     *
     * @param non-empty-string   $pattern
     * @param string   $subject
     * @param array<int|string, list<array{string|null, int}>> $matches Set by method
     * @param int      $flags PREG_UNMATCHED_AS_NULL, only available on PHP 7.2+
     * @param int      $offset
     * @return 0|positive-int
     *
     * @phpstan-param array<int|string, list<array{string|null, int<-1, max>}>> $matches
     */
    
public static function matchAllWithOffsets($pattern$subject, &$matches$flags 0$offset 0)
    {
        
$result preg_match_all($pattern$subject$matches$flags PREG_OFFSET_CAPTURE$offset);
        if (
$result === false || $result === null) {
            throw 
PcreException::fromFunction('preg_match_all'$pattern);
        }

        return 
$result;
    }

    
/**
     * @param string|string[] $pattern
     * @param string|string[] $replacement
     * @param string          $subject
     * @param int             $limit
     * @param int             $count Set by method
     * @return string
     */
    
public static function replace($pattern$replacement$subject$limit = -1, &$count null)
    {
        if (
is_array($subject)) { // @phpstan-ignore-line
            
throw new \InvalidArgumentException(static::ARRAY_MSG);
        }

        
$result preg_replace($pattern$replacement$subject$limit$count);
        if (
$result === null) {
            throw 
PcreException::fromFunction('preg_replace'$pattern);
        }

        return 
$result;
    }

    
/**
     * @param string|string[] $pattern
     * @param callable        $replacement
     * @param string          $subject
     * @param int             $limit
     * @param int             $count Set by method
     * @param int             $flags PREG_OFFSET_CAPTURE or PREG_UNMATCHED_AS_NULL, only available on PHP 7.4+
     * @return string
     */
    
public static function replaceCallback($pattern$replacement$subject$limit = -1, &$count null$flags 0)
    {
        if (
is_array($subject)) { // @phpstan-ignore-line
            
throw new \InvalidArgumentException(static::ARRAY_MSG);
        }

        if (
PHP_VERSION_ID >= 70400) {
            
$result preg_replace_callback($pattern$replacement$subject$limit$count$flags);
        } else {
            
$result preg_replace_callback($pattern$replacement$subject$limit$count);
        }
        if (
$result === null) {
            throw 
PcreException::fromFunction('preg_replace_callback'$pattern);
        }

        return 
$result;
    }

    
/**
     * Available from PHP 7.0
     *
     * @param array<string, callable> $pattern
     * @param string $subject
     * @param int    $limit
     * @param int    $count Set by method
     * @param int    $flags PREG_OFFSET_CAPTURE or PREG_UNMATCHED_AS_NULL, only available on PHP 7.4+
     * @return string
     */
    
public static function replaceCallbackArray(array $pattern$subject$limit = -1, &$count null$flags 0)
    {
        if (
is_array($subject)) { // @phpstan-ignore-line
            
throw new \InvalidArgumentException(static::ARRAY_MSG);
        }

        if (
PHP_VERSION_ID >= 70400) {
            
$result preg_replace_callback_array($pattern$subject$limit$count$flags);
        } else {
            
$result preg_replace_callback_array($pattern$subject$limit$count);
        }
        if (
$result === null) {
            
$pattern array_keys($pattern);
            throw 
PcreException::fromFunction('preg_replace_callback_array'$pattern);
        }

        return 
$result;
    }

    
/**
     * @param string $pattern
     * @param string $subject
     * @param int    $limit
     * @param int    $flags PREG_SPLIT_NO_EMPTY or PREG_SPLIT_DELIM_CAPTURE
     * @return list<string>
     */
    
public static function split($pattern$subject$limit = -1$flags 0)
    {
        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 string $pattern
     * @param string $subject
     * @param int    $limit
     * @param int    $flags PREG_SPLIT_NO_EMPTY or PREG_SPLIT_DELIM_CAPTURE
     * @return list<array{string, int}>
     * @phpstan-return list<array{string, int<0, max>}>
     */
    
public static function splitWithOffsets($pattern$subject$limit = -1$flags 0)
    {
        
$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      $flags PREG_GREP_INVERT
     * @return array<T>
     */
    
public static function grep($pattern, array $array$flags 0)
    {
        
$result preg_grep($pattern$array$flags);
        if (
$result === false) {
            throw 
PcreException::fromFunction('preg_grep'$pattern);
        }

        return 
$result;
    }

    
/**
     * @param non-empty-string   $pattern
     * @param string   $subject
     * @param array<string|null> $matches Set by method
     * @param int      $flags PREG_UNMATCHED_AS_NULL, only available on PHP 7.2+
     * @param int      $offset
     * @return bool
     */
    
public static function isMatch($pattern$subject, &$matches null$flags 0$offset 0)
    {
        return (bool) static::match(
$pattern$subject$matches$flags$offset);
    }

    
/**
     * @param non-empty-string   $pattern
     * @param string   $subject
     * @param array<int|string, list<string|null>> $matches Set by method
     * @param int      $flags PREG_UNMATCHED_AS_NULL, only available on PHP 7.2+
     * @param int      $offset
     * @return bool
     */
    
public static function isMatchAll($pattern$subject, &$matches null$flags 0$offset 0)
    {
        return (bool) static::
matchAll($pattern$subject$matches$flags$offset);
    }

    
/**
     * Runs preg_match with PREG_OFFSET_CAPTURE
     *
     * @param non-empty-string   $pattern
     * @param string   $subject
     * @param array<int|string, array{string|null, int}> $matches Set by method
     * @param int      $flags PREG_UNMATCHED_AS_NULL, only available on PHP 7.2+
     * @param int      $offset
     * @return bool
     *
     * @phpstan-param array<int|string, array{string|null, int<-1, max>}> $matches
     */
    
public static function isMatchWithOffsets($pattern$subject, &$matches$flags 0$offset 0)
    {
        return (bool) static::
matchWithOffsets($pattern$subject$matches$flags$offset);
    }

    
/**
     * Runs preg_match_all with PREG_OFFSET_CAPTURE
     *
     * @param non-empty-string   $pattern
     * @param string   $subject
     * @param array<int|string, list<array{string|null, int}>> $matches Set by method
     * @param int      $flags PREG_UNMATCHED_AS_NULL, only available on PHP 7.2+
     * @param int      $offset
     * @return bool
     *
     * @phpstan-param array<int|string, list<array{string|null, int<-1, max>}>> $matches
     */
    
public static function isMatchAllWithOffsets($pattern$subject, &$matches$flags 0$offset 0)
    {
        return (bool) static::
matchAllWithOffsets($pattern$subject$matches$flags$offset);
    }
}
<?
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($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 
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<array<string|null>> $matches
     */
    
public function __construct($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
     * @param string $result
     */
    
public function __construct($count$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 
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($count, array $matches)
    {
        
$this->matches $matches;
        
$this->matched = (bool) $count;
    }
}
<?
php

// autoload_namespaces.php @generated by Composer

$vendorDir dirname(dirname(__FILE__));
$baseDir dirname($vendorDir);

return array(
    
'cli' => array($vendorDir '/wp-cli/php-cli-tools/lib'),
    
'WP_CLI\\' => array($vendorDir '/wp-cli/wp-cli/php'),
    
'Requests' => array($vendorDir '/rmccue/requests/library'),
    
'Oxymel' => array($vendorDir '/nb/oxymel'),
    
'Mustache' => array($vendorDir '/mustache/mustache/src'),
);
<?
php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * 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`
 */
class InstalledVersions
{
    
/**
     * @var mixed[]|null
     * @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}|array{}|null
     */
    
private static $installed;

    
/**
     * @var bool|null
     */
    
private static $canGetVendors;

    
/**
     * @var array[]
     * @psalm-var array<string, array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: 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 (
=== \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 || empty($installed['versions'][$packageName]['dev_requirement']);
            }
        }

        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($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, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}
     */
    
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, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: 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__, -81) !== '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, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: 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, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>} $data
     */
    
public static function reload($data)
    {
        
self::$installed $data;
        
self::$installedByVendor = array();
    }

    
/**
     * @return array[]
     * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
     */
    
private static function getInstalled()
    {
        if (
null === self::$canGetVendors) {
            
self::$canGetVendors method_exists('Composer\Autoload\ClassLoader''getRegisteredLoaders');
        }

        
$installed = array();

        if (
self::$canGetVendors) {
            foreach (
ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
                if (isset(
self::$installedByVendor[$vendorDir])) {
                    
$installed[] = self::$installedByVendor[$vendorDir];
                } elseif (
is_file($vendorDir.'/composer/installed.php')) {
                    
$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
                    if (
null === self::$installed && strtr($vendorDir.'/composer''\\''/') === strtr(__DIR__'\\''/')) {
                        
self::$installed $installed[count($installed) - 1];
                    }
                }
            }
        }

        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__, -81) !== 'C') {
                
self::$installed = require __DIR__ '/installed.php';
            } else {
                
self::$installed = array();
            }
        }
        
$installed[] = self::$installed;

        return 
$installed;
    }
}
<?
php

// autoload_files.php @generated by Composer

$vendorDir dirname(dirname(__FILE__));
$baseDir dirname($vendorDir);

return array(
    
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir '/symfony/polyfill-ctype/bootstrap.php',
    
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir '/symfony/polyfill-mbstring/bootstrap.php',
    
'ad155f8f1cf0d418fe49e248db8c661b' => $vendorDir '/react/promise/src/functions_include.php',
    
'be01b9b16925dcb22165c40b46681ac6' => $vendorDir '/wp-cli/php-cli-tools/lib/cli/cli.php',
    
'3937806105cc8e221b8fa8db5b70d2f2' => $vendorDir '/wp-cli/mustangostang-spyc/includes/functions.php',
    
'ba366f96f4fddbdef61ad7a862b44f61' => $vendorDir '/wp-cli/package-command/package-command.php',
    
'8a0ad02df6a5087f2c380f8fd52db273' => $vendorDir '/wp-cli/cache-command/cache-command.php',
    
'f3f0199a3ecd9f501d0a3b361bd2f61c' => $vendorDir '/wp-cli/entity-command/entity-command.php',
    
'3f201033d5aceb2293314273be88f7c6' => $vendorDir '/wp-cli/extension-command/extension-command.php',
    
'c65f753375faee349b7adc48c2ee7cc2' => $vendorDir '/wp-cli/db-command/db-command.php',
    
'5e099d3cac677dd2bec1003ea7707745' => $vendorDir '/wp-cli/media-command/media-command.php',
    
'd979c11fe80ba96ae3037b43429fe546' => $vendorDir '/wp-cli/scaffold-command/scaffold-command.php',
    
'5c6ec5cff8f9d625772c8ed147f6b894' => $vendorDir '/wp-cli/export-command/export-command.php',
    
'ac949ce40a981819ba132473518a9a31' => $vendorDir '/wp-cli/wp-config-transformer/src/WPConfigTransformer.php',
    
'b66d29757fcb2fb7a9608d068e3716b0' => $vendorDir '/wp-cli/checksum-command/checksum-command.php',
    
'30cbb6e4122dc988e494c6b9c0438233' => $vendorDir '/wp-cli/import-command/import-command.php',
    
'9f04dd0aa5d67ec75a75c88c345a079e' => $vendorDir '/wp-cli/server-command/server-command.php',
    
'8519779bbb65eeb842af2f629ce7b6f8' => $vendorDir '/wp-cli/super-admin-command/super-admin-command.php',
    
'5deaf6ce9c8bbdfb65104c7e938d5875' => $vendorDir '/wp-cli/config-command/config-command.php',
    
'68c39b88215b6cf7a0da164166670ef9' => $vendorDir '/wp-cli/core-command/core-command.php',
    
'7654e00bf0e632580764400bd8293a9c' => $vendorDir '/wp-cli/cron-command/cron-command.php',
    
'021d3a13471556f0b57038d679f7f8ea' => $vendorDir '/wp-cli/embed-command/embed-command.php',
    
'f958dca3f412fd7975da1700912a9321' => $vendorDir '/wp-cli/eval-command/eval-command.php',
    
'ffb465a494c3101218c4417180c2c9a2' => $vendorDir '/wp-cli/i18n-command/i18n-command.php',
    
'1c88c1eff05217a8cac80c64c9ac2080' => $vendorDir '/wp-cli/maintenance-mode-command/maintenance-mode-command.php',
    
'ace0d205db7f4135ec32132a0076d555' => $vendorDir '/wp-cli/language-command/language-command.php',
    
'f399c1c8d0c787d5c94c09884cdd9762' => $vendorDir '/wp-cli/rewrite-command/rewrite-command.php',
    
'080fadd667195d055c5a23386f270261' => $vendorDir '/wp-cli/role-command/role-command.php',
    
'8ecb13f8bbc22b1b34d12b14ec01077a' => $vendorDir '/wp-cli/search-replace-command/search-replace-command.php',
    
'129d58fa8151374aceb8571bcaa97504' => $vendorDir '/wp-cli/shell-command/shell-command.php',
    
'1f05372afcc7d0c51a305cef1d56dd01' => $vendorDir '/wp-cli/widget-command/widget-command.php',
);
<?
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 <[email protected]>
 */
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('{#.+$}'''$version);

        if (
strpos($version'dev-') === || '-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
     */
    
public static function normalizeStability($stability)
    {
        
$stability strtolower($stability);

        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($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($version0strlen($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($version4);
        }

        
// 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})?)' self::$modifierRegex '$}i'$version$matches)) {
            
$version 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'$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($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
     */
    
public function normalizeDefaultBranch($name)
    {
        if (
$name === 'dev-master' || $name === 'dev-default' || $name === 'dev-trunk') {
            return 
'9999999-dev';
        }

        return 
$name;
    }

    
/**
     * Parses a constraint string into MultiConstraint and/or Constraint objects.
     *
     * @param string $constraints
     *
     * @return ConstraintInterface
     */
    
public function parseConstraints($constraints)
    {
        
$prettyConstraint $constraints;

        
$orConstraints preg_split('{\s*\|\|?\s*}'trim($constraints));
        if (
false === $orConstraints) {
            throw new 
\RuntimeException('Failed to preg_split string: '.$constraints);
        }
        
$orGroups = array();

        foreach (
$orConstraints as $constraints) {
            
$andConstraints preg_split('{(?<!^|as|[=>< ,]) *(?<!-)[, ](?!-) *(?!,|as|$)}'$constraints);
            if (
false === $andConstraints) {
                throw new 
\RuntimeException('Failed to preg_split string: '.$constraints);
            }
            if (
\count($andConstraints) > 1) {
                
$constraintObjects = array();
                foreach (
$andConstraints as $constraint) {
                    foreach (
$this->parseConstraint($constraint) as $parsedConstraint) {
                        
$constraintObjects[] = $parsedConstraint;
                    }
                }
            } else {
                
$constraintObjects $this->parseConstraint($andConstraints[0]);
            }

            if (
=== \count($constraintObjects)) {
                
$constraint $constraintObjects[0];
            } else {
                
$constraint = new MultiConstraint($constraintObjects);
            }

            
$orGroups[] = $constraint;
        }

        
$constraint MultiConstraint::create($orGroupsfalse);

        
$constraint->setPrettyString($prettyConstraint);

        return 
$constraint;
    }

    
/**
     * @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 $stabilitySuffix1));
            
$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$highPosition1) . '-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 $stabilitySuffix1));
            
$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$position1) . '-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$position1) . '-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 === || $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]) ? 21) . '-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;

class 
Interval
{
    
/** @var Constraint */
    
private $start;
    
/** @var Constraint */
    
private $end;

    public function 
__construct(Constraint $startConstraint $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\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->versiontrue));
                }
                if (
self::OP_NE === $otherOperator) {
                    return 
sprintf('!$b || $v !== %s'\var_export($this->versiontrue));
                }
                return 
'false';
            }

            if (
self::OP_NE === $this->operator) {
                if (
self::OP_EQ === $otherOperator) {
                    return 
sprintf('!$b || $v !== %s'\var_export($this->versiontrue));
                }
                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->versiontrue));
            }
            if (
self::OP_NE === $otherOperator) {
                return 
sprintf('$b || \version_compare($v, %s, \'!=\')'\var_export($this->versiontrue));
            }

            return 
sprintf('!$b && \version_compare(%s, $v, \'%s\')'\var_export($this->versiontrue), 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->versiontrue));
            }

            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->versiontrue), 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->versiontrue)) . $codeComparison;
            }
        } elseif (
$this->operator === self::OP_GE) {
            if (
$otherOperator === self::OP_LT) {
                return 
sprintf('!$b && \version_compare($v, %s, \'!=\') && '\var_export($this->versiontrue)) . $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-') === || 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$version2self::$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->versiontrue);
                
$this->upperBound = new Bound($this->versiontrue);
                break;
            case 
self::OP_LT:
                
$this->lowerBound Bound::zero();
                
$this->upperBound = new Bound($this->versionfalse);
                break;
            case 
self::OP_LE:
                
$this->lowerBound Bound::zero();
                
$this->upperBound = new Bound($this->versiontrue);
                break;
            case 
self::OP_GT:
                
$this->lowerBound = new Bound($this->versionfalse);
                
$this->upperBound Bound::positiveInfinity();
                break;
            case 
self::OP_GE:
                
$this->lowerBound = new Bound($this->versiontrue);
                
$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;

/**
 * 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 (
=== \count($constraints)) {
            return new 
MatchAllConstraint();
        }

        if (
=== \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($left12) === substr($right03)
                ) {
                    
$optimized true;
                    
$left = new MultiConstraint(
                        array(
                            
$left->constraints[0],
                            
$right->constraints[1],
                        ),
                        
true);
                } else {
                    
$mergedConstraints[] = $left;
                    
$left $right;
                }
            }
            if (
$optimized) {
                
$mergedConstraints[] = $left;
                return array(
$mergedConstraintsfalse);
            }
        }

        
// 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;

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 (!== $compareResult) {
            return ((
'>' === $operator) ? : -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;

/**
 * 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();
    }
}
<?
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 $candidateConstraintInterface $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 $aConstraintInterface $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']) > || $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']) === && (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+$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) === && (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($unEqualConstraintstrue);
                    } 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 (
=== \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($constraintsfalse)),
                        
$devConstraints
                    
), true);
                }

                if (
\count($constraints) === && (string)$constraints[0] === (string)Interval::fromZero()) {
                    if (
\count($devConstraints) > 1) {
                        return new 
MultiConstraint($devConstraintstrue);
                    }
                    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($constraintsfalse);
        }

        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($constraintInterval::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\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;
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 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,
    );

    
/**
     * Evaluates the expression: $constraint match $operator $version
     *
     * @param ConstraintInterface $constraint
     * @param int                 $operator
     * @phpstan-param Constraint::OP_*  $operator
     * @param string              $version
     *
     * @return mixed
     */
    
public static function match(ConstraintInterface $constraint$operator$version)
    {
        if (
self::$enabled === null) {
            
self::$enabled = !\in_array('eval'explode(',', (string) ini_get('disable_functions')), true);
        }
        if (!
self::$enabled) {
            return 
$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 
$function($versionstrpos($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 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 string[]
     */
    
public static function sort(array $versions)
    {
        return 
self::usort($versionsself::SORT_ASC);
    }

    
/**
     * Sort given array of versions in reverse.
     *
     * @param string[] $versions
     *
     * @return string[]
     */
    
public static function rsort(array $versions)
    {
        return 
self::usort($versionsself::SORT_DESC);
    }

    
/**
     * @param string[] $versions
     * @param int      $direction
     *
     * @return 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/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_psr4.php @generated by Composer

$vendorDir dirname(dirname(__FILE__));
$baseDir dirname($vendorDir);

return array(
    
'WP_CLI\\MaintenanceMode\\' => array($vendorDir '/wp-cli/maintenance-mode-command/src'),
    
'WP_CLI\\I18n\\' => array($vendorDir '/wp-cli/i18n-command/src'),
    
'WP_CLI\\Embeds\\' => array($vendorDir '/wp-cli/embed-command/src'),
    
'WP_CLI\\' => array($vendorDir '/wp-cli/entity-command/src/WP_CLI'$vendorDir '/wp-cli/shell-command/src/WP_CLI'),
    
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir '/symfony/polyfill-mbstring'),
    
'Symfony\\Polyfill\\Ctype\\' => array($vendorDir '/symfony/polyfill-ctype'),
    
'Symfony\\Component\\Process\\' => array($vendorDir '/symfony/process'),
    
'Symfony\\Component\\Finder\\' => array($vendorDir '/symfony/finder'),
    
'Symfony\\Component\\Filesystem\\' => array($vendorDir '/symfony/filesystem'),
    
'Symfony\\Component\\Console\\' => array($vendorDir '/symfony/console'),
    
'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\\Log\\' => array($vendorDir '/psr/log/Psr/Log'),
    
'Peast\\test\\' => array($vendorDir '/mck89/peast/test/Peast'),
    
'Peast\\' => array($vendorDir '/mck89/peast/lib/Peast'),
    
'Mustangostang\\' => array($vendorDir '/wp-cli/mustangostang-spyc/src'),
    
'JsonSchema\\' => array($vendorDir '/justinrainbow/json-schema/src/JsonSchema'),
    
'Gettext\\Languages\\' => array($vendorDir '/gettext/languages/src'),
    
'Gettext\\' => array($vendorDir '/gettext/gettext/src'),
    
'Composer\\XdebugHandler\\' => array($vendorDir '/composer/xdebug-handler/src'),
    
'Composer\\Semver\\' => array($vendorDir '/composer/semver/src'),
    
'Composer\\Pcre\\' => array($vendorDir '/composer/pcre/src'),
    
'Composer\\MetadataMinifier\\' => array($vendorDir '/composer/metadata-minifier/src'),
    
'Composer\\CaBundle\\' => array($vendorDir '/composer/ca-bundle/src'),
    
'Composer\\' => array($vendorDir '/composer/composer/src/Composer'),
    
'' => array($vendorDir '/wp-cli/cache-command/src'$vendorDir '/wp-cli/config-command/src'$vendorDir '/wp-cli/core-command/src'$vendorDir '/wp-cli/checksum-command/src'$vendorDir '/wp-cli/cron-command/src'$vendorDir '/wp-cli/db-command/src'$vendorDir '/wp-cli/eval-command/src'$vendorDir '/wp-cli/export-command/src'$vendorDir '/wp-cli/entity-command/src'$vendorDir '/wp-cli/extension-command/src'$vendorDir '/wp-cli/import-command/src'$vendorDir '/wp-cli/language-command/src'$vendorDir '/wp-cli/media-command/src'$vendorDir '/wp-cli/package-command/src'$vendorDir '/wp-cli/rewrite-command/src'$vendorDir '/wp-cli/role-command/src'$vendorDir '/wp-cli/search-replace-command/src'$vendorDir '/wp-cli/server-command/src'$vendorDir '/wp-cli/scaffold-command/src'$vendorDir '/wp-cli/shell-command/src'$vendorDir '/wp-cli/super-admin-command/src'$vendorDir '/wp-cli/widget-command/src'),
);
<?
php

// platform_check.php @generated by Composer

$issues = array();

if (!(
PHP_VERSION_ID >= 50600)) {
    
$issues[] = 'Your Composer dependencies require a PHP version ">= 5.6.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;
        }
    }
    
trigger_error(
        
'Composer detected issues in your platform: ' implode(' '$issues),
        
E_USER_ERROR
    
);
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
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 string|int $text
     *
     * @return bool
     */
    
public static function ctype_alnum($text)
    {
        
$text self::convert_int_to_char_for_ctype($text);

        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 string|int $text
     *
     * @return bool
     */
    
public static function ctype_alpha($text)
    {
        
$text self::convert_int_to_char_for_ctype($text);

        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 string|int $text
     *
     * @return bool
     */
    
public static function ctype_cntrl($text)
    {
        
$text self::convert_int_to_char_for_ctype($text);

        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 string|int $text
     *
     * @return bool
     */
    
public static function ctype_digit($text)
    {
        
$text self::convert_int_to_char_for_ctype($text);

        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 string|int $text
     *
     * @return bool
     */
    
public static function ctype_graph($text)
    {
        
$text self::convert_int_to_char_for_ctype($text);

        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 string|int $text
     *
     * @return bool
     */
    
public static function ctype_lower($text)
    {
        
$text self::convert_int_to_char_for_ctype($text);

        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 string|int $text
     *
     * @return bool
     */
    
public static function ctype_print($text)
    {
        
$text self::convert_int_to_char_for_ctype($text);

        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 string|int $text
     *
     * @return bool
     */
    
public static function ctype_punct($text)
    {
        
$text self::convert_int_to_char_for_ctype($text);

        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 string|int $text
     *
     * @return bool
     */
    
public static function ctype_space($text)
    {
        
$text self::convert_int_to_char_for_ctype($text);

        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 string|int $text
     *
     * @return bool
     */
    
public static function ctype_upper($text)
    {
        
$text self::convert_int_to_char_for_ctype($text);

        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 string|int $text
     *
     * @return bool
     */
    
public static function ctype_xdigit($text)
    {
        
$text self::convert_int_to_char_for_ctype($text);

        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 string|int $int
     *
     * @return mixed
     */
    
private static function convert_int_to_char_for_ctype($int)
    {
        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 <[email protected]>
 *
 * 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($input) { return p\Ctype::ctype_alnum($input); }
}
if (!
function_exists('ctype_alpha')) {
    function 
ctype_alpha($input) { return p\Ctype::ctype_alpha($input); }
}
if (!
function_exists('ctype_cntrl')) {
    function 
ctype_cntrl($input) { return p\Ctype::ctype_cntrl($input); }
}
if (!
function_exists('ctype_digit')) {
    function 
ctype_digit($input) { return p\Ctype::ctype_digit($input); }
}
if (!
function_exists('ctype_graph')) {
    function 
ctype_graph($input) { return p\Ctype::ctype_graph($input); }
}
if (!
function_exists('ctype_lower')) {
    function 
ctype_lower($input) { return p\Ctype::ctype_lower($input); }
}
if (!
function_exists('ctype_print')) {
    function 
ctype_print($input) { return p\Ctype::ctype_print($input); }
}
if (!
function_exists('ctype_punct')) {
    function 
ctype_punct($input) { return p\Ctype::ctype_punct($input); }
}
if (!
function_exists('ctype_space')) {
    function 
ctype_space($input) { return p\Ctype::ctype_space($input); }
}
if (!
function_exists('ctype_upper')) {
    function 
ctype_upper($input) { return p\Ctype::ctype_upper($input); }
}
if (!
function_exists('ctype_xdigit')) {
    function 
ctype_xdigit($input) { return p\Ctype::ctype_xdigit($input); }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Filesystem;

use 
Symfony\Component\Filesystem\Exception\IOException;
use 
Symfony\Component\Lock\Store\FlockStore;
use 
Symfony\Component\Lock\Store\SemaphoreStore;

@
trigger_error(sprintf('The %s class is deprecated since Symfony 3.4 and will be removed in 4.0. Use %s or %s instead.'LockHandler::class, SemaphoreStore::class, FlockStore::class), \E_USER_DEPRECATED);

/**
 * LockHandler class provides a simple abstraction to lock anything by means of
 * a file lock.
 *
 * A locked file is created based on the lock name when calling lock(). Other
 * lock handlers will not be able to lock the same name until it is released
 * (explicitly by calling release() or implicitly when the instance holding the
 * lock is destroyed).
 *
 * @author Grégoire Pineau <[email protected]>
 * @author Romain Neutron <[email protected]>
 * @author Nicolas Grekas <[email protected]>
 *
 * @deprecated since version 3.4, to be removed in 4.0. Use Symfony\Component\Lock\Store\SemaphoreStore or Symfony\Component\Lock\Store\FlockStore instead.
 */
class LockHandler
{
    private 
$file;
    private 
$handle;

    
/**
     * @param string      $name     The lock name
     * @param string|null $lockPath The directory to store the lock. Default values will use temporary directory
     *
     * @throws IOException If the lock directory could not be created or is not writable
     */
    
public function __construct($name$lockPath null)
    {
        
$lockPath $lockPath ?: sys_get_temp_dir();

        if (!
is_dir($lockPath)) {
            
$fs = new Filesystem();
            
$fs->mkdir($lockPath);
        }

        if (!
is_writable($lockPath)) {
            throw new 
IOException(sprintf('The directory "%s" is not writable.'$lockPath), 0null$lockPath);
        }

        
$this->file sprintf('%s/sf.%s.%s.lock'$lockPathpreg_replace('/[^a-z0-9\._-]+/i''-'$name), hash('sha256'$name));
    }

    
/**
     * Lock the resource.
     *
     * @param bool $blocking Wait until the lock is released
     *
     * @return bool Returns true if the lock was acquired, false otherwise
     *
     * @throws IOException If the lock file could not be created or opened
     */
    
public function lock($blocking false)
    {
        if (
$this->handle) {
            return 
true;
        }

        
$error null;

        
// Silence error reporting
        
set_error_handler(function ($errno$msg) use (&$error) {
            
$error $msg;
        });

        if (!
$this->handle fopen($this->file'r+') ?: fopen($this->file'r')) {
            if (
$this->handle fopen($this->file'x')) {
                
chmod($this->file0666);
            } elseif (!
$this->handle fopen($this->file'r+') ?: fopen($this->file'r')) {
                
usleep(100); // Give some time for chmod() to complete
                
$this->handle fopen($this->file'r+') ?: fopen($this->file'r');
            }
        }
        
restore_error_handler();

        if (!
$this->handle) {
            throw new 
IOException($error0null$this->file);
        }

        
// On Windows, even if PHP doc says the contrary, LOCK_NB works, see
        // https://bugs.php.net/54129
        
if (!flock($this->handle\LOCK_EX | ($blocking \LOCK_NB))) {
            
fclose($this->handle);
            
$this->handle null;

            return 
false;
        }

        return 
true;
    }

    
/**
     * Release the resource.
     */
    
public function release()
    {
        if (
$this->handle) {
            
flock($this->handle\LOCK_UN \LOCK_NB);
            
fclose($this->handle);
            
$this->handle null;
        }
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
interface IOExceptionInterface extends ExceptionInterface
{
    
/**
     * Returns the associated path for the exception.
     *
     * @return string|null The path
     */
    
public function getPath();
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 * @author Christian Gärtner <[email protected]>
 * @author Fabien Potencier <[email protected]>
 */
class IOException extends \RuntimeException implements IOExceptionInterface
{
    private 
$path;

    public function 
__construct($message$code 0\Exception $previous null$path null)
    {
        
$this->path $path;

        
parent::__construct($message$code$previous);
    }

    
/**
     * {@inheritdoc}
     */
    
public function getPath()
    {
        return 
$this->path;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
interface ExceptionInterface
{
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 * @author Christian Gärtner <[email protected]>
 */
class FileNotFoundException extends IOException
{
    public function 
__construct($message null$code 0\Exception $previous null$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 <[email protected]>
 *
 * 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\IOException;

/**
 * Provides basic utility to manipulate the file system.
 *
 * @author Fabien Potencier <[email protected]>
 */
class Filesystem
{
    private static 
$lastError;

    
/**
     * 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.
     *
     * @param string $originFile          The original filename
     * @param string $targetFile          The target filename
     * @param bool   $overwriteNewerFiles If true, target files newer than origin files are overwritten
     *
     * @throws FileNotFoundException When originFile doesn't exist
     * @throws IOException           When copy fails
     */
    
public function copy($originFile$targetFile$overwriteNewerFiles false)
    {
        
$originIsLocal stream_is_local($originFile) || === stripos($originFile'file://');
        if (
$originIsLocal && !is_file($originFile)) {
            throw new 
FileNotFoundException(sprintf('Failed to copy "%s" because file does not exist.'$originFile), 0null$originFile);
        }

        
$this->mkdir(\dirname($targetFile));

        
$doCopy true;
        if (!
$overwriteNewerFiles && null === parse_url($originFile\PHP_URL_HOST) && is_file($targetFile)) {
            
$doCopy filemtime($originFile) > filemtime($targetFile);
        }

        if (
$doCopy) {
            
// https://bugs.php.net/64634
            
if (false === $source = @fopen($originFile'r')) {
                throw new 
IOException(sprintf('Failed to copy "%s" to "%s" because source file could not be opened for reading.'$originFile$targetFile), 0null$originFile);
            }

            
// Stream context created to allow files overwrite when using FTP stream wrapper - disabled by default
            
if (false === $target = @fopen($targetFile'w'nullstream_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), 0null$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), 0null$originFile);
            }

            if (
$originIsLocal) {
                
// Like `cp`, preserve executable permission bits
                
@chmod($targetFilefileperms($targetFile) | (fileperms($originFile) & 0111));

                if (
$bytesCopied !== $bytesOrigin filesize($originFile)) {
                    throw new 
IOException(sprintf('Failed to copy the whole content of "%s" to "%s" (%g of %g bytes copied).'$originFile$targetFile$bytesCopied$bytesOrigin), 0null$originFile);
                }
            }
        }
    }

    
/**
     * Creates a directory recursively.
     *
     * @param string|iterable $dirs The directory path
     * @param int             $mode The directory mode
     *
     * @throws IOException On any directory creation failure
     */
    
public function mkdir($dirs$mode 0777)
    {
        foreach (
$this->toIterable($dirs) as $dir) {
            if (
is_dir($dir)) {
                continue;
            }

            if (!
self::box('mkdir'$dir$modetrue)) {
                if (!
is_dir($dir)) {
                    
// The directory was not created by a concurrent process. Let's throw an exception with a developer friendly error message if we have one
                    
if (self::$lastError) {
                        throw new 
IOException(sprintf('Failed to create "%s": '$dir).self::$lastError0null$dir);
                    }
                    throw new 
IOException(sprintf('Failed to create "%s".'$dir), 0null$dir);
                }
            }
        }
    }

    
/**
     * Checks the existence of files or directories.
     *
     * @param string|iterable $files A filename, an array of files, or a \Traversable instance to check
     *
     * @return bool true if the file exists, false otherwise
     */
    
public function exists($files)
    {
        
$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), 0null$file);
            }

            if (!
file_exists($file)) {
                return 
false;
            }
        }

        return 
true;
    }

    
/**
     * Sets access and modification time of file.
     *
     * @param string|iterable $files A filename, an array of files, or a \Traversable instance to create
     * @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
     *
     * @throws IOException When touch fails
     */
    
public function touch($files$time null$atime null)
    {
        foreach (
$this->toIterable($files) as $file) {
            
$touch $time ? @touch($file$time$atime) : @touch($file);
            if (
true !== $touch) {
                throw new 
IOException(sprintf('Failed to touch "%s".'$file), 0null$file);
            }
        }
    }

    
/**
     * Removes files or directories.
     *
     * @param string|iterable $files A filename, an array of files, or a \Traversable instance to remove
     *
     * @throws IOException When removal fails
     */
    
public function remove($files)
    {
        if (
$files instanceof \Traversable) {
            
$files iterator_to_array($filesfalse);
        } elseif (!
\is_array($files)) {
            
$files = [$files];
        }
        
$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)) {
                
$this->remove(new \FilesystemIterator($file\FilesystemIterator::CURRENT_AS_PATHNAME \FilesystemIterator::SKIP_DOTS));

                if (!
self::box('rmdir'$file) && file_exists($file)) {
                    throw new 
IOException(sprintf('Failed to remove directory "%s": '$file).self::$lastError);
                }
            } elseif (!
self::box('unlink'$file) && (false !== strpos(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 string|iterable $files     A filename, an array of files, or a \Traversable instance to change mode
     * @param int             $mode      The new mode (octal)
     * @param int             $umask     The mode mask (octal)
     * @param bool            $recursive Whether change the mod recursively or not
     *
     * @throws IOException When the change fails
     */
    
public function chmod($files$mode$umask 0000$recursive false)
    {
        foreach (
$this->toIterable($files) as $file) {
            if ((
\PHP_VERSION_ID 80000 || \is_int($mode)) && true !== @chmod($file$mode & ~$umask)) {
                throw new 
IOException(sprintf('Failed to chmod file "%s".'$file), 0null$file);
            }
            if (
$recursive && is_dir($file) && !is_link($file)) {
                
$this->chmod(new \FilesystemIterator($file), $mode$umasktrue);
            }
        }
    }

    
/**
     * Change the owner of an array of files or directories.
     *
     * @param string|iterable $files     A filename, an array of files, or a \Traversable instance to change owner
     * @param string|int      $user      A user name or number
     * @param bool            $recursive Whether change the owner recursively or not
     *
     * @throws IOException When the change fails
     */
    
public function chown($files$user$recursive false)
    {
        foreach (
$this->toIterable($files) as $file) {
            if (
$recursive && is_dir($file) && !is_link($file)) {
                
$this->chown(new \FilesystemIterator($file), $usertrue);
            }
            if (
is_link($file) && \function_exists('lchown')) {
                if (
true !== @lchown($file$user)) {
                    throw new 
IOException(sprintf('Failed to chown file "%s".'$file), 0null$file);
                }
            } else {
                if (
true !== @chown($file$user)) {
                    throw new 
IOException(sprintf('Failed to chown file "%s".'$file), 0null$file);
                }
            }
        }
    }

    
/**
     * Change the group of an array of files or directories.
     *
     * @param string|iterable $files     A filename, an array of files, or a \Traversable instance to change group
     * @param string|int      $group     A group name or number
     * @param bool            $recursive Whether change the group recursively or not
     *
     * @throws IOException When the change fails
     */
    
public function chgrp($files$group$recursive false)
    {
        foreach (
$this->toIterable($files) as $file) {
            if (
$recursive && is_dir($file) && !is_link($file)) {
                
$this->chgrp(new \FilesystemIterator($file), $grouptrue);
            }
            if (
is_link($file) && \function_exists('lchgrp')) {
                if (
true !== @lchgrp($file$group) || (\defined('HHVM_VERSION') && !posix_getgrnam($group))) {
                    throw new 
IOException(sprintf('Failed to chgrp file "%s".'$file), 0null$file);
                }
            } else {
                if (
true !== @chgrp($file$group)) {
                    throw new 
IOException(sprintf('Failed to chgrp file "%s".'$file), 0null$file);
                }
            }
        }
    }

    
/**
     * Renames a file or a directory.
     *
     * @param string $origin    The origin filename or directory
     * @param string $target    The new filename or directory
     * @param bool   $overwrite Whether to overwrite the target if it already exists
     *
     * @throws IOException When target file or directory already exists
     * @throws IOException When origin cannot be renamed
     */
    
public function rename($origin$target$overwrite false)
    {
        
// 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), 0null$target);
        }

        if (
true !== @rename($origin$target)) {
            if (
is_dir($origin)) {
                
// See https://bugs.php.net/54097 & https://php.net/rename#113943
                
$this->mirror($origin$targetnull, ['override' => $overwrite'delete' => $overwrite]);
                
$this->remove($origin);

                return;
            }
            throw new 
IOException(sprintf('Cannot rename "%s" to "%s".'$origin$target), 0null$target);
        }
    }

    
/**
     * Tells whether a file exists and is readable.
     *
     * @param string $filename Path to the file
     *
     * @return bool
     *
     * @throws IOException When windows path is longer than 258 characters
     */
    
private function isReadable($filename)
    {
        
$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), 0null$filename);
        }

        return 
is_readable($filename);
    }

    
/**
     * Creates a symbolic link or copy a directory.
     *
     * @param string $originDir     The origin directory path
     * @param string $targetDir     The symbolic link name
     * @param bool   $copyOnWindows Whether to copy files if on Windows
     *
     * @throws IOException When symlink fails
     */
    
public function symlink($originDir$targetDir$copyOnWindows false)
    {
        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          $originFile  The original file
     * @param string|string[] $targetFiles The target file(s)
     *
     * @throws FileNotFoundException When original file is missing or not a file
     * @throws IOException           When link fails, including if link already exists
     */
    
public function hardlink($originFile$targetFiles)
    {
        if (!
$this->exists($originFile)) {
            throw new 
FileNotFoundException(null0null$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 $origin
     * @param string $target
     * @param string $linkType Name of the link type, typically 'symbolic' or 'hard'
     */
    
private function linkException($origin$target$linkType)
    {
        if (
self::$lastError) {
            if (
'\\' === \DIRECTORY_SEPARATOR && false !== strpos(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), 0null$target);
            }
        }
        throw new 
IOException(sprintf('Failed to create "%s" link from "%s" to "%s".'$linkType$origin$target), 0null$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
     *
     * @param string $path         A filesystem path
     * @param bool   $canonicalize Whether or not to return a canonicalized path
     *
     * @return string|null
     */
    
public function readlink($path$canonicalize false)
    {
        if (!
$canonicalize && !is_link($path)) {
            return 
null;
        }

        if (
$canonicalize) {
            if (!
$this->exists($path)) {
                return 
null;
            }

            if (
'\\' === \DIRECTORY_SEPARATOR) {
                
$path readlink($path);
            }

            return 
realpath($path);
        }

        if (
'\\' === \DIRECTORY_SEPARATOR) {
            return 
realpath($path);
        }

        return 
readlink($path);
    }

    
/**
     * Given an existing path, convert it to a path relative to a given starting path.
     *
     * @param string $endPath   Absolute path of target
     * @param string $startPath Absolute path where traversal begins
     *
     * @return string Path of target relative to starting path
     */
    
public function makePathRelative($endPath$startPath)
    {
        if (!
$this->isAbsolutePath($endPath) || !$this->isAbsolutePath($startPath)) {
            @
trigger_error(sprintf('Support for passing relative paths to %s() is deprecated since Symfony 3.4 and will be removed in 4.0.'__METHOD__), \E_USER_DEPRECATED);
        }

        
// Normalize separators on Windows
        
if ('\\' === \DIRECTORY_SEPARATOR) {
            
$endPath str_replace('\\''/'$endPath);
            
$startPath str_replace('\\''/'$startPath);
        }

        
$splitDriveLetter = function ($path) {
            return (
\strlen($path) > && ':' === $path[1] && '/' === $path[2] && ctype_alpha($path[0]))
                ? [
substr($path2), strtoupper($path[0])]
                : [
$pathnull];
        };

        
$splitPath = function ($path$absolute) {
            
$result = [];

            foreach (
explode('/'trim($path'/')) as $segment) {
                if (
'..' === $segment && ($absolute || \count($result))) {
                    
array_pop($result);
                } elseif (
'.' !== $segment && '' !== $segment) {
                    
$result[] = $segment;
                }
            }

            return 
$result;
        };

        list(
$endPath$endDriveLetter) = $splitDriveLetter($endPath);
        list(
$startPath$startDriveLetter) = $splitDriveLetter($startPath);

        
$startPathArr $splitPath($startPath, static::isAbsolutePath($startPath));
        
$endPathArr $splitPath($endPath, static::isAbsolutePath($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 (=== \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 string            $originDir The origin directory
     * @param string            $targetDir The target directory
     * @param \Traversable|null $iterator  Iterator that filters which files and directories to copy, if null a recursive iterator is created
     * @param array             $options   An array of boolean options
     *                                     Valid options are:
     *                                     - $options['override'] If true, target files newer than origin files are overwritten (see copy(), defaults to false)
     *                                     - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink(), defaults to false)
     *                                     - $options['delete'] Whether to delete files that are not in the source directory (defaults to false)
     *
     * @throws IOException When file type is unknown
     */
    
public function mirror($originDir$targetDir\Traversable $iterator null$options = [])
    {
        
$targetDir rtrim($targetDir'/\\');
        
$originDir rtrim($originDir'/\\');
        
$originDirLen \strlen($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 false;
        if (isset(
$options['copy_on_windows'])) {
            
$copyOnWindows $options['copy_on_windows'];
        }

        if (
null === $iterator) {
            
$flags $copyOnWindows \FilesystemIterator::SKIP_DOTS \FilesystemIterator::FOLLOW_SYMLINKS \FilesystemIterator::SKIP_DOTS;
            
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($originDir$flags), \RecursiveIteratorIterator::SELF_FIRST);
        }

        if (
$this->exists($originDir)) {
            
$this->mkdir($targetDir);
        }

        foreach (
$iterator as $file) {
            
$target $targetDir.substr($file->getPathname(), $originDirLen);

            if (
$copyOnWindows) {
                if (
is_file($file)) {
                    
$this->copy($file$target, isset($options['override']) ? $options['override'] : false);
                } elseif (
is_dir($file)) {
                    
$this->mkdir($target);
                } else {
                    throw new 
IOException(sprintf('Unable to guess "%s" file type.'$file), 0null$file);
                }
            } else {
                if (
is_link($file)) {
                    
$this->symlink($file->getLinkTarget(), $target);
                } elseif (
is_dir($file)) {
                    
$this->mkdir($target);
                } elseif (
is_file($file)) {
                    
$this->copy($file$target, isset($options['override']) ? $options['override'] : false);
                } else {
                    throw new 
IOException(sprintf('Unable to guess "%s" file type.'$file), 0null$file);
                }
            }
        }
    }

    
/**
     * Returns whether the file path is an absolute path.
     *
     * @param string $file A file path
     *
     * @return bool
     */
    
public function isAbsolutePath($file)
    {
        return 
'' !== (string) $file && (strspn($file'/\\'01)
            || (
\strlen($file) > && ctype_alpha($file[0])
                && 
':' === $file[1]
                && 
strspn($file'/\\'21)
            )
            || 
null !== parse_url($file\PHP_URL_SCHEME)
        );
    }

    
/**
     * Creates a temporary file with support for custom stream wrappers.
     *
     * @param string $dir    The directory where the temporary filename will be created
     * @param string $prefix The prefix of the generated temporary filename
     *                       Note: Windows uses only the first three characters of prefix
     *
     * @return string The new temporary filename (with path), or throw an exception on failure
     */
    
public function tempnam($dir$prefix)
    {
        list(
$scheme$hierarchy) = $this->getSchemeAndHierarchy($dir);

        
// If no scheme or scheme is "file" or "gs" (Google Cloud) create temp file in local filesystem
        
if (null === $scheme || 'file' === $scheme || 'gs' === $scheme) {
            
$tmpFile = @tempnam($hierarchy$prefix);

            
// If tempnam failed or no scheme return the filename otherwise prepend the scheme
            
if (false !== $tmpFile) {
                if (
null !== $scheme && 'gs' !== $scheme) {
                    return 
$scheme.'://'.$tmpFile;
                }

                return 
$tmpFile;
            }

            throw new 
IOException('A temporary file could not be created.');
        }

        
// 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);

            
// Use fopen instead of file_exists as some streams do not support stat
            // Use mode 'x+' to atomically check existence and create to avoid a TOCTOU vulnerability
            
$handle = @fopen($tmpFile'x+');

            
// If unsuccessful restart the loop
            
if (false === $handle) {
                continue;
            }

            
// Close the file if it was successfully opened
            
@fclose($handle);

            return 
$tmpFile;
        }

        throw new 
IOException('A temporary file could not be created.');
    }

    
/**
     * Atomically dumps content into a file.
     *
     * @param string $filename The file to be written to
     * @param string $content  The data to write into the file
     *
     * @throws IOException if the file cannot be written to
     */
    
public function dumpFile($filename$content)
    {
        
$dir \dirname($filename);

        if (!
is_dir($dir)) {
            
$this->mkdir($dir);
        }

        if (!
is_writable($dir)) {
            throw new 
IOException(sprintf('Unable to write to the "%s" directory.'$dir), 0null$dir);
        }

        
// Will create a temp file with 0600 access rights
        // when the filesystem supports chmod.
        
$tmpFile $this->tempnam($dirbasename($filename));

        if (
false === @file_put_contents($tmpFile$content)) {
            throw new 
IOException(sprintf('Failed to write file "%s".'$filename), 0null$filename);
        }

        @
chmod($tmpFilefile_exists($filename) ? fileperms($filename) : 0666 & ~umask());

        
$this->rename($tmpFile$filenametrue);
    }

    
/**
     * Appends content to an existing file.
     *
     * @param string $filename The file to which to append content
     * @param string $content  The content to append
     *
     * @throws IOException If the file is not writable
     */
    
public function appendToFile($filename$content)
    {
        
$dir \dirname($filename);

        if (!
is_dir($dir)) {
            
$this->mkdir($dir);
        }

        if (!
is_writable($dir)) {
            throw new 
IOException(sprintf('Unable to write to the "%s" directory.'$dir), 0null$dir);
        }

        if (
false === @file_put_contents($filename$content\FILE_APPEND)) {
            throw new 
IOException(sprintf('Failed to write file "%s".'$filename), 0null$filename);
        }
    }

    
/**
     * @param mixed $files
     *
     * @return array|\Traversable
     */
    
private function toIterable($files)
    {
        return 
\is_array($files) || $files instanceof \Traversable $files : [$files];
    }

    
/**
     * Gets a 2-tuple of scheme (may be null) and hierarchical part of a filename (e.g. file:///tmp -> [file, tmp]).
     *
     * @param string $filename The filename to be parsed
     *
     * @return array The filename scheme and hierarchical part
     */
    
private function getSchemeAndHierarchy($filename)
    {
        
$components explode('://'$filename2);

        return 
=== \count($components) ? [$components[0], $components[1]] : [null$components[0]];
    }

    
/**
     * @param callable $func
     *
     * @return mixed
     */
    
private static function box($func)
    {
        
self::$lastError null;
        
set_error_handler(__CLASS__.'::handleError');
        try {
            
$result \call_user_func_array($func\array_slice(\func_get_args(), 1));
            
restore_error_handler();

            return 
$result;
        } catch (
\Throwable $e) {
        } catch (
\Exception $e) {
        }
        
restore_error_handler();

        throw 
$e;
    }

    
/**
     * @internal
     */
    
public static function handleError($type$msg)
    {
        
self::$lastError $msg;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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\CommandLoader\ContainerCommandLoader;
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\TypedReference;

/**
 * Registers console commands.
 *
 * @author Grégoire Pineau <[email protected]>
 */
class AddConsoleCommandPass implements CompilerPassInterface
{
    private 
$commandLoaderServiceId;
    private 
$commandTag;

    public function 
__construct($commandLoaderServiceId 'console.command_loader'$commandTag 'console.command')
    {
        
$this->commandLoaderServiceId $commandLoaderServiceId;
        
$this->commandTag $commandTag;
    }

    public function 
process(ContainerBuilder $container)
    {
        
$commandServices $container->findTaggedServiceIds($this->commandTagtrue);
        
$lazyCommandMap = [];
        
$lazyCommandRefs = [];
        
$serviceIds = [];
        
$lazyServiceIds = [];

        foreach (
$commandServices as $id => $tags) {
            
$definition $container->getDefinition($id);
            
$class $container->getParameterBag()->resolveValue($definition->getClass());

            
$commandId 'console.command.'.strtolower(str_replace('\\''_'$class));

            if (isset(
$tags[0]['command'])) {
                
$commandName $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$this->commandTagCommand::class));
                }
                
$commandName $class::getDefaultName();
            }

            if (
null === $commandName) {
                if (isset(
$serviceIds[$commandId]) || $container->hasAlias($commandId)) {
                    
$commandId $commandId.'_'.$id;
                }
                if (!
$definition->isPublic() || $definition->isPrivate()) {
                    
$container->setAlias($commandId$id)->setPublic(true);
                    
$id $commandId;
                }
                
$serviceIds[$commandId] = $id;

                continue;
            }

            
$serviceIds[$commandId] = $id;
            
$lazyServiceIds[$id] = true;
            unset(
$tags[0]);
            
$lazyCommandMap[$commandName] = $id;
            
$lazyCommandRefs[$id] = new TypedReference($id$class);
            
$aliases = [];

            foreach (
$tags as $tag) {
                if (isset(
$tag['command'])) {
                    
$aliases[] = $tag['command'];
                    
$lazyCommandMap[$tag['command']] = $id;
                }
            }

            
$definition->addMethodCall('setName', [$commandName]);

            if (
$aliases) {
                
$definition->addMethodCall('setAliases', [$aliases]);
            }
        }

        
$container
            
->register($this->commandLoaderServiceIdContainerCommandLoader::class)
            ->
setPublic(true)
            ->
setArguments([ServiceLocatorTagPass::register($container$lazyCommandRefs), $lazyCommandMap]);

        
$container->setParameter('console.command.ids'$serviceIds);
        
$container->setParameter('console.lazy_command.ids'$lazyServiceIds);
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class InputDefinition
{
    private 
$arguments;
    private 
$requiredCount;
    private 
$hasAnArrayArgument false;
    private 
$hasOptional;
    private 
$options;
    private 
$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.
     */
    
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
     */
    
public function setArguments($arguments = [])
    {
        
$this->arguments = [];
        
$this->requiredCount 0;
        
$this->hasOptional false;
        
$this->hasAnArrayArgument false;
        
$this->addArguments($arguments);
    }

    
/**
     * Adds an array of InputArgument objects.
     *
     * @param InputArgument[] $arguments An array of InputArgument objects
     */
    
public function addArguments($arguments = [])
    {
        if (
null !== $arguments) {
            foreach (
$arguments as $argument) {
                
$this->addArgument($argument);
            }
        }
    }

    
/**
     * @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 (
$this->hasAnArrayArgument) {
            throw new 
LogicException('Cannot add an argument after an array argument.');
        }

        if (
$argument->isRequired() && $this->hasOptional) {
            throw new 
LogicException('Cannot add a required argument after an optional one.');
        }

        if (
$argument->isArray()) {
            
$this->hasAnArrayArgument true;
        }

        if (
$argument->isRequired()) {
            ++
$this->requiredCount;
        } else {
            
$this->hasOptional true;
        }

        
$this->arguments[$argument->getName()] = $argument;
    }

    
/**
     * Returns an InputArgument by name or by position.
     *
     * @param string|int $name The InputArgument name or position
     *
     * @return InputArgument An InputArgument object
     *
     * @throws InvalidArgumentException When argument given doesn't exist
     */
    
public function getArgument($name)
    {
        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.
     *
     * @param string|int $name The InputArgument name or position
     *
     * @return bool true if the InputArgument object exists, false otherwise
     */
    
public function hasArgument($name)
    {
        
$arguments \is_int($name) ? array_values($this->arguments) : $this->arguments;

        return isset(
$arguments[$name]);
    }

    
/**
     * Gets the array of InputArgument objects.
     *
     * @return InputArgument[] An array of InputArgument objects
     */
    
public function getArguments()
    {
        return 
$this->arguments;
    }

    
/**
     * Returns the number of InputArguments.
     *
     * @return int The number of InputArguments
     */
    
public function getArgumentCount()
    {
        return 
$this->hasAnArrayArgument \PHP_INT_MAX \count($this->arguments);
    }

    
/**
     * Returns the number of required InputArguments.
     *
     * @return int The number of required InputArguments
     */
    
public function getArgumentRequiredCount()
    {
        return 
$this->requiredCount;
    }

    
/**
     * Gets the default values.
     *
     * @return array An array of default values
     */
    
public function getArgumentDefaults()
    {
        
$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
     */
    
public function setOptions($options = [])
    {
        
$this->options = [];
        
$this->shortcuts = [];
        
$this->addOptions($options);
    }

    
/**
     * Adds an array of InputOption objects.
     *
     * @param InputOption[] $options An array of InputOption objects
     */
    
public function addOptions($options = [])
    {
        foreach (
$options as $option) {
            
$this->addOption($option);
        }
    }

    
/**
     * @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 (
$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();
            }
        }
    }

    
/**
     * Returns an InputOption by name.
     *
     * @param string $name The InputOption name
     *
     * @return InputOption A InputOption object
     *
     * @throws InvalidArgumentException When option given doesn't exist
     */
    
public function getOption($name)
    {
        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).
     *
     * @param string $name The InputOption name
     *
     * @return bool true if the InputOption object exists, false otherwise
     */
    
public function hasOption($name)
    {
        return isset(
$this->options[$name]);
    }

    
/**
     * Gets the array of InputOption objects.
     *
     * @return InputOption[] An array of InputOption objects
     */
    
public function getOptions()
    {
        return 
$this->options;
    }

    
/**
     * Returns true if an InputOption object exists by shortcut.
     *
     * @param string $name The InputOption shortcut
     *
     * @return bool true if the InputOption object exists, false otherwise
     */
    
public function hasShortcut($name)
    {
        return isset(
$this->shortcuts[$name]);
    }

    
/**
     * Gets an InputOption by shortcut.
     *
     * @param string $shortcut The Shortcut name
     *
     * @return InputOption An InputOption object
     */
    
public function getOptionForShortcut($shortcut)
    {
        return 
$this->getOption($this->shortcutToName($shortcut));
    }

    
/**
     * Gets an array of default values.
     *
     * @return array An array of all default values
     */
    
public function getOptionDefaults()
    {
        
$values = [];
        foreach (
$this->options as $option) {
            
$values[$option->getName()] = $option->getDefault();
        }

        return 
$values;
    }

    
/**
     * Returns the InputOption name given a shortcut.
     *
     * @param string $shortcut The shortcut
     *
     * @return string The InputOption name
     *
     * @throws InvalidArgumentException When option given does not exist
     *
     * @internal
     */
    
public function shortcutToName($shortcut)
    {
        if (!isset(
$this->shortcuts[$shortcut])) {
            throw new 
InvalidArgumentException(sprintf('The "-%s" option does not exist.'$shortcut));
        }

        return 
$this->shortcuts[$shortcut];
    }

    
/**
     * Gets the synopsis.
     *
     * @param bool $short Whether to return the short version (with options folded) or not
     *
     * @return string The synopsis
     */
    
public function getSynopsis($short false)
    {
        
$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()) : '';
                
$elements[] = sprintf('[%s--%s%s]'$shortcut$option->getName(), $value);
            }
        }

        if (
\count($elements) && $this->getArguments()) {
            
$elements[] = '[--]';
        }

        foreach (
$this->getArguments() as $argument) {
            
$element '<'.$argument->getName().'>';
            if (!
$argument->isRequired()) {
                
$element '['.$element.']';
            } elseif (
$argument->isArray()) {
                
$element .= ' ('.$element.')';
            }

            if (
$argument->isArray()) {
                
$element .= '...';
            }

            
$elements[] = $element;
        }

        return 
implode(' '$elements);
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class StringInput extends ArgvInput
{
    const 
REGEX_STRING '([^\s]+?)(?:\s|(?<!\\\\)"|(?<!\\\\)\'|$)';
    const 
REGEX_QUOTED_STRING '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')';

    
/**
     * @param string $input A string representing the parameters from the CLI
     */
    
public function __construct($input)
    {
        
parent::__construct([]);

        
$this->setTokens($this->tokenize($input));
    }

    
/**
     * Tokenizes a string.
     *
     * @param string $input The input to tokenize
     *
     * @return array An array of tokens
     *
     * @throws InvalidArgumentException When unable to parse input (should never happen)
     */
    
private function tokenize($input)
    {
        
$tokens = [];
        
$length \strlen($input);
        
$cursor 0;
        while (
$cursor $length) {
            if (
preg_match('/\s+/A'$input$matchnull$cursor)) {
            } elseif (
preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A'$input$matchnull$cursor)) {
                
$tokens[] = $match[1].$match[2].stripcslashes(str_replace(['"\'''\'"''\'\'''""'], ''substr($match[3], 1\strlen($match[3]) - 2)));
            } elseif (
preg_match('/'.self::REGEX_QUOTED_STRING.'/A'$input$matchnull$cursor)) {
                
$tokens[] = stripcslashes(substr($match[0], 1\strlen($match[0]) - 2));
            } elseif (
preg_match('/'.self::REGEX_STRING.'/A'$input$matchnull$cursor)) {
                
$tokens[] = stripcslashes($match[1]);
            } else {
                
// should never happen
                
throw new InvalidArgumentException(sprintf('Unable to parse input near "... %s ...".'substr($input$cursor10)));
            }

            
$cursor += \strlen($match[0]);
        }

        return 
$tokens;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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;

/**
 * Represents a command line argument.
 *
 * @author Fabien Potencier <[email protected]>
 */
class InputArgument
{
    const 
REQUIRED 1;
    const 
OPTIONAL 2;
    const 
IS_ARRAY 4;

    private 
$name;
    private 
$mode;
    private 
$default;
    private 
$description;

    
/**
     * @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|string[]|null $default     The default value (for self::OPTIONAL mode only)
     *
     * @throws InvalidArgumentException When argument mode is not valid
     */
    
public function __construct($name$mode null$description ''$default null)
    {
        if (
null === $mode) {
            
$mode self::OPTIONAL;
        } elseif (!
\is_int($mode) || $mode || $mode 1) {
            throw new 
InvalidArgumentException(sprintf('Argument mode "%s" is not valid.'$mode));
        }

        
$this->name $name;
        
$this->mode $mode;
        
$this->description $description;

        
$this->setDefault($default);
    }

    
/**
     * Returns the argument name.
     *
     * @return string The argument name
     */
    
public function getName()
    {
        return 
$this->name;
    }

    
/**
     * Returns true if the argument is required.
     *
     * @return bool true if parameter mode is self::REQUIRED, false otherwise
     */
    
public function isRequired()
    {
        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()
    {
        return 
self::IS_ARRAY === (self::IS_ARRAY $this->mode);
    }

    
/**
     * Sets the default value.
     *
     * @param string|string[]|null $default The default value
     *
     * @throws LogicException When incorrect default value is given
     */
    
public function setDefault($default null)
    {
        if (
self::REQUIRED === $this->mode && 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.
     *
     * @return string|string[]|null The default value
     */
    
public function getDefault()
    {
        return 
$this->default;
    }

    
/**
     * Returns the description text.
     *
     * @return string The description text
     */
    
public function getDescription()
    {
        return 
$this->description;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
abstract class Input implements InputInterfaceStreamableInputInterface
{
    protected 
$definition;
    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();
        }
    }

    
/**
     * {@inheritdoc}
     */
    
public function bind(InputDefinition $definition)
    {
        
$this->arguments = [];
        
$this->options = [];
        
$this->definition $definition;

        
$this->parse();
    }

    
/**
     * Processes command line arguments.
     */
    
abstract protected function parse();

    
/**
     * {@inheritdoc}
     */
    
public function validate()
    {
        
$definition $this->definition;
        
$givenArguments $this->arguments;

        
$missingArguments array_filter(array_keys($definition->getArguments()), function ($argument) use ($definition$givenArguments) {
            return !
\array_key_exists($argument$givenArguments) && $definition->getArgument($argument)->isRequired();
        });

        if (
\count($missingArguments) > 0) {
            throw new 
RuntimeException(sprintf('Not enough arguments (missing: "%s").'implode(', '$missingArguments)));
        }
    }

    
/**
     * {@inheritdoc}
     */
    
public function isInteractive()
    {
        return 
$this->interactive;
    }

    
/**
     * {@inheritdoc}
     */
    
public function setInteractive($interactive)
    {
        
$this->interactive = (bool) $interactive;
    }

    
/**
     * {@inheritdoc}
     */
    
public function getArguments()
    {
        return 
array_merge($this->definition->getArgumentDefaults(), $this->arguments);
    }

    
/**
     * {@inheritdoc}
     */
    
public function getArgument($name)
    {
        if (!
$this->definition->hasArgument($name)) {
            throw new 
InvalidArgumentException(sprintf('The "%s" argument does not exist.'$name));
        }

        return isset(
$this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name)->getDefault();
    }

    
/**
     * {@inheritdoc}
     */
    
public function setArgument($name$value)
    {
        if (!
$this->definition->hasArgument($name)) {
            throw new 
InvalidArgumentException(sprintf('The "%s" argument does not exist.'$name));
        }

        
$this->arguments[$name] = $value;
    }

    
/**
     * {@inheritdoc}
     */
    
public function hasArgument($name)
    {
        return 
$this->definition->hasArgument($name);
    }

    
/**
     * {@inheritdoc}
     */
    
public function getOptions()
    {
        return 
array_merge($this->definition->getOptionDefaults(), $this->options);
    }

    
/**
     * {@inheritdoc}
     */
    
public function getOption($name)
    {
        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();
    }

    
/**
     * {@inheritdoc}
     */
    
public function setOption($name$value)
    {
        if (!
$this->definition->hasOption($name)) {
            throw new 
InvalidArgumentException(sprintf('The "%s" option does not exist.'$name));
        }

        
$this->options[$name] = $value;
    }

    
/**
     * {@inheritdoc}
     */
    
public function hasOption($name)
    {
        return 
$this->definition->hasOption($name);
    }

    
/**
     * Escapes a token through escapeshellarg if it contains unsafe chars.
     *
     * @param string $token
     *
     * @return string
     */
    
public function escapeToken($token)
    {
        return 
preg_match('{^[\w-]+$}'$token) ? $token escapeshellarg($token);
    }

    
/**
     * {@inheritdoc}
     */
    
public function setStream($stream)
    {
        
$this->stream $stream;
    }

    
/**
     * {@inheritdoc}
     */
    
public function getStream()
    {
        return 
$this->stream;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
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
     */
    
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 <[email protected]>
 *
 * 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 <[email protected]>
 */
interface InputInterface
{
    
/**
     * Returns the first argument from the raw parameters (not parsed).
     *
     * @return string|null The value of the first argument or null otherwise
     */
    
public function getFirstArgument();

    
/**
     * 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
     *
     * @return bool true if the value is contained in the raw parameters
     */
    
public function hasParameterOption($values$onlyParams false);

    
/**
     * 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 mixed        $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 The option value
     */
    
public function getParameterOption($values$default false$onlyParams false);

    
/**
     * Binds the current Input instance with the given arguments and options.
     *
     * @throws RuntimeException
     */
    
public function bind(InputDefinition $definition);

    
/**
     * Validates the input.
     *
     * @throws RuntimeException When not enough arguments are given
     */
    
public function validate();

    
/**
     * Returns all the given arguments merged with the default values.
     *
     * @return array
     */
    
public function getArguments();

    
/**
     * Returns the argument value for a given argument name.
     *
     * @param string $name The argument name
     *
     * @return string|string[]|null The argument value
     *
     * @throws InvalidArgumentException When argument given doesn't exist
     */
    
public function getArgument($name);

    
/**
     * Sets an argument value by name.
     *
     * @param string               $name  The argument name
     * @param string|string[]|null $value The argument value
     *
     * @throws InvalidArgumentException When argument given doesn't exist
     */
    
public function setArgument($name$value);

    
/**
     * Returns true if an InputArgument object exists by name or position.
     *
     * @param string|int $name The InputArgument name or position
     *
     * @return bool true if the InputArgument object exists, false otherwise
     */
    
public function hasArgument($name);

    
/**
     * Returns all the given options merged with the default values.
     *
     * @return array
     */
    
public function getOptions();

    
/**
     * Returns the option value for a given option name.
     *
     * @param string $name The option name
     *
     * @return string|string[]|bool|null The option value
     *
     * @throws InvalidArgumentException When option given doesn't exist
     */
    
public function getOption($name);

    
/**
     * Sets an option value by name.
     *
     * @param string                    $name  The option name
     * @param string|string[]|bool|null $value The option value
     *
     * @throws InvalidArgumentException When option given doesn't exist
     */
    
public function setOption($name$value);

    
/**
     * Returns true if an InputOption object exists by name.
     *
     * @param string $name The InputOption name
     *
     * @return bool true if the InputOption object exists, false otherwise
     */
    
public function hasOption($name);

    
/**
     * Is this input means interactive?
     *
     * @return bool
     */
    
public function isInteractive();

    
/**
     * Sets the input interactivity.
     *
     * @param bool $interactive If the input should be interactive
     */
    
public function setInteractive($interactive);
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
interface InputAwareInterface
{
    
/**
     * Sets the Console Input.
     */
    
public function setInput(InputInterface $input);
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class ArrayInput extends Input
{
    private 
$parameters;

    public function 
__construct(array $parametersInputDefinition $definition null)
    {
        
$this->parameters $parameters;

        
parent::__construct($definition);
    }

    
/**
     * {@inheritdoc}
     */
    
public function getFirstArgument()
    {
        foreach (
$this->parameters as $param => $value) {
            if (
$param && \is_string($param) && '-' === $param[0]) {
                continue;
            }

            return 
$value;
        }

        return 
null;
    }

    
/**
     * {@inheritdoc}
     */
    
public function hasParameterOption($values$onlyParams false)
    {
        
$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;
    }

    
/**
     * {@inheritdoc}
     */
    
public function getParameterOption($values$default false$onlyParams false)
    {
        
$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.
     *
     * @return string
     */
    
public function __toString()
    {
        
$params = [];
        foreach (
$this->parameters as $param => $val) {
            if (
$param && \is_string($param) && '-' === $param[0]) {
                if (
\is_array($val)) {
                    foreach (
$val as $v) {
                        
$params[] = $param.('' != $v '='.$this->escapeToken($v) : '');
                    }
                } else {
                    
$params[] = $param.('' != $val '='.$this->escapeToken($val) : '');
                }
            } else {
                
$params[] = \is_array($val) ? implode(' 'array_map([$this'escapeToken'], $val)) : $this->escapeToken($val);
            }
        }

        return 
implode(' '$params);
    }

    
/**
     * {@inheritdoc}
     */
    
protected function parse()
    {
        foreach (
$this->parameters as $key => $value) {
            if (
'--' === $key) {
                return;
            }
            if (
=== strpos($key'--')) {
                
$this->addLongOption(substr($key2), $value);
            } elseif (
=== strpos($key'-')) {
                
$this->addShortOption(substr($key1), $value);
            } else {
                
$this->addArgument($key$value);
            }
        }
    }

    
/**
     * Adds a short option value.
     *
     * @param string $shortcut The short option key
     * @param mixed  $value    The value for the option
     *
     * @throws InvalidOptionException When option given doesn't exist
     */
    
private function addShortOption($shortcut$value)
    {
        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.
     *
     * @param string $name  The long option key
     * @param mixed  $value The value for the option
     *
     * @throws InvalidOptionException When option given doesn't exist
     * @throws InvalidOptionException When a required value is missing
     */
    
private function addLongOption($name$value)
    {
        if (!
$this->definition->hasOption($name)) {
            throw new 
InvalidOptionException(sprintf('The "--%s" option does not exist.'$name));
        }

        
$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.
     *
     * @param string $name  The argument name
     * @param mixed  $value The value for the argument
     *
     * @throws InvalidArgumentException When argument given doesn't exist
     */
    
private function addArgument($name$value)
    {
        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 <[email protected]>
 *
 * 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 <[email protected]>
 *
 * @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 
$tokens;
    private 
$parsed;

    
/**
     * @param array|null           $argv       An array of parameters from the CLI (in the argv format)
     * @param InputDefinition|null $definition A InputDefinition instance
     */
    
public function __construct(array $argv nullInputDefinition $definition null)
    {
        
$argv null !== $argv $argv : (isset($_SERVER['argv']) ? $_SERVER['argv'] : []);

        
// strip the application name
        
array_shift($argv);

        
$this->tokens $argv;

        
parent::__construct($definition);
    }

    protected function 
setTokens(array $tokens)
    {
        
$this->tokens $tokens;
    }

    
/**
     * {@inheritdoc}
     */
    
protected function parse()
    {
        
$parseOptions true;
        
$this->parsed $this->tokens;
        while (
null !== $token array_shift($this->parsed)) {
            if (
$parseOptions && '' == $token) {
                
$this->parseArgument($token);
            } elseif (
$parseOptions && '--' == $token) {
                
$parseOptions false;
            } elseif (
$parseOptions && === strpos($token'--')) {
                
$this->parseLongOption($token);
            } elseif (
$parseOptions && '-' === $token[0] && '-' !== $token) {
                
$this->parseShortOption($token);
            } else {
                
$this->parseArgument($token);
            }
        }
    }

    
/**
     * Parses a short option.
     *
     * @param string $token The current token
     */
    
private function parseShortOption($token)
    {
        
$name substr($token1);

        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($name1));
            } else {
                
$this->parseShortOptionSet($name);
            }
        } else {
            
$this->addShortOption($namenull);
        }
    }

    
/**
     * Parses a short option set.
     *
     * @param string $name The current token
     *
     * @throws RuntimeException When option given doesn't exist
     */
    
private function parseShortOptionSet($name)
    {
        
$len \strlen($name);
        for (
$i 0$i $len; ++$i) {
            if (!
$this->definition->hasShortcut($name[$i])) {
                
$encoding mb_detect_encoding($namenulltrue);
                throw new 
RuntimeException(sprintf('The "-%s" option does not exist.'false === $encoding $name[$i] : mb_substr($name$i1$encoding)));
            }

            
$option $this->definition->getOptionForShortcut($name[$i]);
            if (
$option->acceptValue()) {
                
$this->addLongOption($option->getName(), $i === $len null substr($name$i 1));

                break;
            } else {
                
$this->addLongOption($option->getName(), null);
            }
        }
    }

    
/**
     * Parses a long option.
     *
     * @param string $token The current token
     */
    
private function parseLongOption($token)
    {
        
$name substr($token2);

        if (
false !== $pos strpos($name'=')) {
            if (
=== \strlen($value substr($name$pos 1))) {
                
// if no value after "=" then substr() returns "" since php7 only, false before
                // see https://php.net/migration70.incompatible.php#119151
                
if (\PHP_VERSION_ID 70000 && false === $value) {
                    
$value '';
                }
                
array_unshift($this->parsed$value);
            }
            
$this->addLongOption(substr($name0$pos), $value);
        } else {
            
$this->addLongOption($namenull);
        }
    }

    
/**
     * Parses an argument.
     *
     * @param string $token The current token
     *
     * @throws RuntimeException When too many arguments are given
     */
    
private function parseArgument($token)
    {
        
$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();
            if (
\count($all)) {
                throw new 
RuntimeException(sprintf('Too many arguments, expected arguments "%s".'implode('" "'array_keys($all))));
            }

            throw new 
RuntimeException(sprintf('No arguments expected, got "%s".'$token));
        }
    }

    
/**
     * Adds a short option value.
     *
     * @param string $shortcut The short option key
     * @param mixed  $value    The value for the option
     *
     * @throws RuntimeException When option given doesn't exist
     */
    
private function addShortOption($shortcut$value)
    {
        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.
     *
     * @param string $name  The long option key
     * @param mixed  $value The value for the option
     *
     * @throws RuntimeException When option given doesn't exist
     */
    
private function addLongOption($name$value)
    {
        if (!
$this->definition->hasOption($name)) {
            throw new 
RuntimeException(sprintf('The "--%s" option does not exist.'$name));
        }

        
$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;
        }
    }

    
/**
     * {@inheritdoc}
     */
    
public function getFirstArgument()
    {
        
$isOption false;
        foreach (
$this->tokens as $i => $token) {
            if (
$token && '-' === $token[0]) {
                if (
false !== strpos($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($token2) : 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;
    }

    
/**
     * {@inheritdoc}
     */
    
public function hasParameterOption($values$onlyParams false)
    {
        
$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 === strpos($value'--') ? $value.'=' $value;
                if (
$token === $value || '' !== $leading && === strpos($token$leading)) {
                    return 
true;
                }
            }
        }

        return 
false;
    }

    
/**
     * {@inheritdoc}
     */
    
public function getParameterOption($values$default false$onlyParams false)
    {
        
$values = (array) $values;
        
$tokens $this->tokens;

        while (
\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 === strpos($value'--') ? $value.'=' $value;
                if (
'' !== $leading && === strpos($token$leading)) {
                    return 
substr($token\strlen($leading));
                }
            }
        }

        return 
$default;
    }

    
/**
     * Returns a stringified representation of the args passed to the command.
     *
     * @return string
     */
    
public function __toString()
    {
        
$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 <[email protected]>
 *
 * 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;

/**
 * Represents a command line option.
 *
 * @author Fabien Potencier <[email protected]>
 */
class InputOption
{
    const 
VALUE_NONE 1;
    const 
VALUE_REQUIRED 2;
    const 
VALUE_OPTIONAL 4;
    const 
VALUE_IS_ARRAY 8;

    private 
$name;
    private 
$shortcut;
    private 
$mode;
    private 
$default;
    private 
$description;

    
/**
     * @param string                        $name        The option name
     * @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                        $description A description text
     * @param string|string[]|int|bool|null $default     The default value (must be null for self::VALUE_NONE)
     *
     * @throws InvalidArgumentException If option mode is invalid or incompatible
     */
    
public function __construct($name$shortcut null$mode null$description ''$default null)
    {
        if (
=== strpos($name'--')) {
            
$name substr($name2);
        }

        if (empty(
$name)) {
            throw new 
InvalidArgumentException('An option name cannot be empty.');
        }

        if (empty(
$shortcut)) {
            
$shortcut null;
        }

        if (
null !== $shortcut) {
            if (
\is_array($shortcut)) {
                
$shortcut implode('|'$shortcut);
            }
            
$shortcuts preg_split('{(\|)-?}'ltrim($shortcut'-'));
            
$shortcuts array_filter($shortcuts);
            
$shortcut implode('|'$shortcuts);

            if (empty(
$shortcut)) {
                throw new 
InvalidArgumentException('An option shortcut cannot be empty.');
            }
        }

        if (
null === $mode) {
            
$mode self::VALUE_NONE;
        } elseif (!
\is_int($mode) || $mode 15 || $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;

        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.');
        }

        
$this->setDefault($default);
    }

    
/**
     * Returns the option shortcut.
     *
     * @return string|null The shortcut
     */
    
public function getShortcut()
    {
        return 
$this->shortcut;
    }

    
/**
     * Returns the option name.
     *
     * @return string The name
     */
    
public function getName()
    {
        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()
    {
        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()
    {
        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()
    {
        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()
    {
        return 
self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY $this->mode);
    }

    
/**
     * Sets the default value.
     *
     * @param string|string[]|int|bool|null $default The default value
     *
     * @throws LogicException When incorrect default value is given
     */
    
public function setDefault($default null)
    {
        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() ? $default false;
    }

    
/**
     * Returns the default value.
     *
     * @return string|string[]|int|bool|null The default value
     */
    
public function getDefault()
    {
        return 
$this->default;
    }

    
/**
     * Returns the description text.
     *
     * @return string The description text
     */
    
public function getDescription()
    {
        return 
$this->description;
    }

    
/**
     * Checks whether the given option equals this one.
     *
     * @return bool
     */
    
public function equals(self $option)
    {
        return 
$option->getName() === $this->getName()
            && 
$option->getShortcut() === $this->getShortcut()
            && 
$option->getDefault() === $this->getDefault()
            && 
$option->isArray() === $this->isArray()
            && 
$option->isValueRequired() === $this->isValueRequired()
            && 
$option->isValueOptional() === $this->isValueOptional()
        ;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class Question
{
    private 
$question;
    private 
$attempts;
    private 
$hidden false;
    private 
$hiddenFallback true;
    private 
$autocompleterValues;
    private 
$validator;
    private 
$default;
    private 
$normalizer;

    
/**
     * @param string $question The question to ask to the user
     * @param mixed  $default  The default answer to return if the user enters nothing
     */
    
public function __construct($question$default null)
    {
        
$this->question $question;
        
$this->default $default;
    }

    
/**
     * Returns the question.
     *
     * @return string
     */
    
public function getQuestion()
    {
        return 
$this->question;
    }

    
/**
     * Returns the default answer.
     *
     * @return mixed
     */
    
public function getDefault()
    {
        return 
$this->default;
    }

    
/**
     * Returns whether the user response must be hidden.
     *
     * @return bool
     */
    
public function isHidden()
    {
        return 
$this->hidden;
    }

    
/**
     * Sets whether the user response must be hidden or not.
     *
     * @param bool $hidden
     *
     * @return $this
     *
     * @throws LogicException In case the autocompleter is also used
     */
    
public function setHidden($hidden)
    {
        if (
$this->autocompleterValues) {
            throw new 
LogicException('A hidden question cannot use the autocompleter.');
        }

        
$this->hidden = (bool) $hidden;

        return 
$this;
    }

    
/**
     * In case the response can not be hidden, whether to fallback on non-hidden question or not.
     *
     * @return bool
     */
    
public function isHiddenFallback()
    {
        return 
$this->hiddenFallback;
    }

    
/**
     * Sets whether to fallback on non-hidden question if the response can not be hidden.
     *
     * @param bool $fallback
     *
     * @return $this
     */
    
public function setHiddenFallback($fallback)
    {
        
$this->hiddenFallback = (bool) $fallback;

        return 
$this;
    }

    
/**
     * Gets values for the autocompleter.
     *
     * @return iterable|null
     */
    
public function getAutocompleterValues()
    {
        return 
$this->autocompleterValues;
    }

    
/**
     * Sets values for the autocompleter.
     *
     * @param iterable|null $values
     *
     * @return $this
     *
     * @throws InvalidArgumentException
     * @throws LogicException
     */
    
public function setAutocompleterValues($values)
    {
        if (
\is_array($values)) {
            
$values $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values);
        }

        if (
null !== $values && !\is_array($values) && !$values instanceof \Traversable) {
            throw new 
InvalidArgumentException('Autocompleter values can be either an array, `null` or a `Traversable` object.');
        }

        if (
$this->hidden) {
            throw new 
LogicException('A hidden question cannot use the autocompleter.');
        }

        
$this->autocompleterValues $values;

        return 
$this;
    }

    
/**
     * Sets a validator for the question.
     *
     * @return $this
     */
    
public function setValidator(callable $validator null)
    {
        
$this->validator $validator;

        return 
$this;
    }

    
/**
     * Gets the validator for the question.
     *
     * @return callable|null
     */
    
public function getValidator()
    {
        return 
$this->validator;
    }

    
/**
     * Sets the maximum number of attempts.
     *
     * Null means an unlimited number of attempts.
     *
     * @param int|null $attempts
     *
     * @return $this
     *
     * @throws InvalidArgumentException in case the number of attempts is invalid
     */
    
public function setMaxAttempts($attempts)
    {
        if (
null !== $attempts) {
            
$attempts = (int) $attempts;
            if (
$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.
     *
     * @return int|null
     */
    
public function getMaxAttempts()
    {
        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)
    {
        
$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.
     *
     * @return callable|null
     */
    
public function getNormalizer()
    {
        return 
$this->normalizer;
    }

    protected function 
isAssoc($array)
    {
        return (bool) 
\count(array_filter(array_keys($array), 'is_string'));
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class ChoiceQuestion extends Question
{
    private 
$choices;
    private 
$multiselect false;
    private 
$prompt ' > ';
    private 
$errorMessage 'Value "%s" is invalid';

    
/**
     * @param string $question The question to ask to the user
     * @param array  $choices  The list of available choices
     * @param mixed  $default  The default answer to return
     */
    
public function __construct($question, array $choices$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.
     *
     * @return array
     */
    
public function getChoices()
    {
        return 
$this->choices;
    }

    
/**
     * Sets multiselect option.
     *
     * When multiselect is set to true, multiple choices can be answered.
     *
     * @param bool $multiselect
     *
     * @return $this
     */
    
public function setMultiselect($multiselect)
    {
        
$this->multiselect $multiselect;
        
$this->setValidator($this->getDefaultValidator());

        return 
$this;
    }

    
/**
     * Returns whether the choices are multiselect.
     *
     * @return bool
     */
    
public function isMultiselect()
    {
        return 
$this->multiselect;
    }

    
/**
     * Gets the prompt for choices.
     *
     * @return string
     */
    
public function getPrompt()
    {
        return 
$this->prompt;
    }

    
/**
     * Sets the prompt for choices.
     *
     * @param string $prompt
     *
     * @return $this
     */
    
public function setPrompt($prompt)
    {
        
$this->prompt $prompt;

        return 
$this;
    }

    
/**
     * Sets the error message for invalid values.
     *
     * The error message has a string placeholder (%s) for the invalid value.
     *
     * @param string $errorMessage
     *
     * @return $this
     */
    
public function setErrorMessage($errorMessage)
    {
        
$this->errorMessage $errorMessage;
        
$this->setValidator($this->getDefaultValidator());

        return 
$this;
    }

    
/**
     * Returns the default answer validator.
     *
     * @return callable
     */
    
private function getDefaultValidator()
    {
        
$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('/^[^,]+(?:,[^,]+)*$/'$selected$matches)) {
                    throw new 
InvalidArgumentException(sprintf($errorMessage$selected));
                }

                
$selectedChoices array_map('trim'explode(','$selected));
            } else {
                
$selectedChoices = [trim($selected)];
            }

            
$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));
                }

                
$multiselectChoices[] = (string) $result;
            }

            if (
$multiselect) {
                return 
$multiselectChoices;
            }

            return 
current($multiselectChoices);
        };
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class ConfirmationQuestion extends Question
{
    private 
$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($question$default true$trueAnswerRegex '/^y/i')
    {
        
parent::__construct($question, (bool) $default);

        
$this->trueAnswerRegex $trueAnswerRegex;
        
$this->setNormalizer($this->getDefaultNormalizer());
    }

    
/**
     * Returns the default answer normalizer.
     *
     * @return callable
     */
    
private function getDefaultNormalizer()
    {
        
$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 <[email protected]>
 *
 * 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\HelpCommand;
use 
Symfony\Component\Console\Command\ListCommand;
use 
Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
use 
Symfony\Component\Console\Event\ConsoleCommandEvent;
use 
Symfony\Component\Console\Event\ConsoleErrorEvent;
use 
Symfony\Component\Console\Event\ConsoleExceptionEvent;
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\Formatter\OutputFormatter;
use 
Symfony\Component\Console\Helper\DebugFormatterHelper;
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\Input\StreamableInputInterface;
use 
Symfony\Component\Console\Output\ConsoleOutput;
use 
Symfony\Component\Console\Output\ConsoleOutputInterface;
use 
Symfony\Component\Console\Output\OutputInterface;
use 
Symfony\Component\Debug\ErrorHandler;
use 
Symfony\Component\Debug\Exception\FatalThrowableError;
use 
Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
 * 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 <[email protected]>
 */
class Application
{
    private 
$commands = [];
    private 
$wantHelps false;
    private 
$runningCommand;
    private 
$name;
    private 
$version;
    private 
$commandLoader;
    private 
$catchExceptions true;
    private 
$autoExit true;
    private 
$definition;
    private 
$helperSet;
    private 
$dispatcher;
    private 
$terminal;
    private 
$defaultCommand;
    private 
$singleCommand false;
    private 
$initialized;

    
/**
     * @param string $name    The name of the application
     * @param string $version The version of the application
     */
    
public function __construct($name 'UNKNOWN'$version 'UNKNOWN')
    {
        
$this->name $name;
        
$this->version $version;
        
$this->terminal = new Terminal();
        
$this->defaultCommand 'list';
    }

    public function 
setDispatcher(EventDispatcherInterface $dispatcher)
    {
        
$this->dispatcher $dispatcher;
    }

    public function 
setCommandLoader(CommandLoaderInterface $commandLoader)
    {
        
$this->commandLoader $commandLoader;
    }

    
/**
     * 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 nullOutputInterface $output null)
    {
        
putenv('LINES='.$this->terminal->getHeight());
        
putenv('COLUMNS='.$this->terminal->getWidth());

        if (
null === $input) {
            
$input = new ArgvInput();
        }

        if (
null === $output) {
            
$output = new ConsoleOutput();
        }

        
$renderException = function ($e) use ($output) {
            if (!
$e instanceof \Exception) {
                
$e class_exists(FatalThrowableError::class) ? new FatalThrowableError($e) : new \ErrorException($e->getMessage(), $e->getCode(), \E_ERROR$e->getFile(), $e->getLine());
            }
            if (
$output instanceof ConsoleOutputInterface) {
                
$this->renderException($e$output->getErrorOutput());
            } else {
                
$this->renderException($e$output);
            }
        };
        if (
$phpHandler set_exception_handler($renderException)) {
            
restore_exception_handler();
            if (!
\is_array($phpHandler) || !$phpHandler[0] instanceof ErrorHandler) {
                
$debugHandler true;
            } elseif (
$debugHandler $phpHandler[0]->setExceptionHandler($renderException)) {
                
$phpHandler[0]->setExceptionHandler($debugHandler);
            }
        }

        if (
null !== $this->dispatcher && $this->dispatcher->hasListeners(ConsoleEvents::EXCEPTION)) {
            @
trigger_error(sprintf('The "ConsoleEvents::EXCEPTION" event is deprecated since Symfony 3.3 and will be removed in 4.0. Listen to the "ConsoleEvents::ERROR" event instead.'), \E_USER_DEPRECATED);
        }

        
$this->configureIO($input$output);

        try {
            
$exitCode $this->doRun($input$output);
        } catch (
\Exception $e) {
            if (!
$this->catchExceptions) {
                throw 
$e;
            }

            
$renderException($e);

            
$exitCode $e->getCode();
            if (
is_numeric($exitCode)) {
                
$exitCode = (int) $exitCode;
                if (
=== $exitCode) {
                    
$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 (!
$debugHandler) {
                
$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 $inputOutputInterface $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 $e) {
            
// 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 {
            
$e $this->runningCommand null;
            
// the command name MUST be the first element of the input
            
$command $this->find($name);
        } catch (
\Exception $e) {
        } catch (
\Throwable $e) {
        }
        if (
null !== $e) {
            if (
null !== $this->dispatcher) {
                
$event = new ConsoleErrorEvent($input$output$e);
                
$this->dispatcher->dispatch(ConsoleEvents::ERROR$event);
                
$e $event->getError();

                if (
=== $event->getExitCode()) {
                    return 
0;
                }
            }

            throw 
$e;
        }

        
$this->runningCommand $command;
        
$exitCode $this->doRunCommand($command$input$output);
        
$this->runningCommand null;

        return 
$exitCode;
    }

    public function 
setHelperSet(HelperSet $helperSet)
    {
        
$this->helperSet $helperSet;
    }

    
/**
     * Get the helper set associated with the command.
     *
     * @return HelperSet The HelperSet instance associated with this command
     */
    
public function getHelperSet()
    {
        if (!
$this->helperSet) {
            
$this->helperSet $this->getDefaultHelperSet();
        }

        return 
$this->helperSet;
    }

    public function 
setDefinition(InputDefinition $definition)
    {
        
$this->definition $definition;
    }

    
/**
     * Gets the InputDefinition related to this Application.
     *
     * @return InputDefinition The InputDefinition instance
     */
    
public function getDefinition()
    {
        if (!
$this->definition) {
            
$this->definition $this->getDefaultInputDefinition();
        }

        if (
$this->singleCommand) {
            
$inputDefinition $this->definition;
            
$inputDefinition->setArguments();

            return 
$inputDefinition;
        }

        return 
$this->definition;
    }

    
/**
     * Gets the help message.
     *
     * @return string A help message
     */
    
public function getHelp()
    {
        return 
$this->getLongVersion();
    }

    
/**
     * Gets whether to catch exceptions or not during commands execution.
     *
     * @return bool Whether to catch exceptions or not during commands execution
     */
    
public function areExceptionsCaught()
    {
        return 
$this->catchExceptions;
    }

    
/**
     * Sets whether to catch exceptions or not during commands execution.
     *
     * @param bool $boolean Whether to catch exceptions or not during commands execution
     */
    
public function setCatchExceptions($boolean)
    {
        
$this->catchExceptions = (bool) $boolean;
    }

    
/**
     * Gets whether to automatically exit after a command execution or not.
     *
     * @return bool Whether to automatically exit after a command execution or not
     */
    
public function isAutoExitEnabled()
    {
        return 
$this->autoExit;
    }

    
/**
     * Sets whether to automatically exit after a command execution or not.
     *
     * @param bool $boolean Whether to automatically exit after a command execution or not
     */
    
public function setAutoExit($boolean)
    {
        
$this->autoExit = (bool) $boolean;
    }

    
/**
     * Gets the name of the application.
     *
     * @return string The application name
     */
    
public function getName()
    {
        return 
$this->name;
    }

    
/**
     * Sets the application name.
     *
     * @param string $name The application name
     */
    
public function setName($name)
    {
        
$this->name $name;
    }

    
/**
     * Gets the application version.
     *
     * @return string The application version
     */
    
public function getVersion()
    {
        return 
$this->version;
    }

    
/**
     * Sets the application version.
     *
     * @param string $version The application version
     */
    
public function setVersion($version)
    {
        
$this->version $version;
    }

    
/**
     * Returns the long version of the application.
     *
     * @return string The long application version
     */
    
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.
     *
     * @param string $name The command name
     *
     * @return Command The newly created command
     */
    
public function register($name)
    {
        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
     */
    
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 The registered command if enabled or null
     */
    
public function add(Command $command)
    {
        
$this->init();

        
$command->setApplication($this);

        if (!
$command->isEnabled()) {
            
$command->setApplication(null);

            return 
null;
        }

        
// 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_class($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.
     *
     * @param string $name The command name or alias
     *
     * @return Command A Command object
     *
     * @throws CommandNotFoundException When given command name does not exist
     */
    
public function get($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.
     *
     * @param string $name The command name or alias
     *
     * @return bool true if the command exists, false otherwise
     */
    
public function has($name)
    {
        
$this->init();

        return isset(
$this->commands[$name]) || ($this->commandLoader && $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[] An array of namespaces
     */
    
public function getNamespaces()
    {
        
$namespaces = [];
        foreach (
$this->all() as $command) {
            if (
$command->isHidden()) {
                continue;
            }

            
$namespaces array_merge($namespaces$this->extractAllNamespaces($command->getName()));

            foreach (
$command->getAliases() as $alias) {
                
$namespaces array_merge($namespaces$this->extractAllNamespaces($alias));
            }
        }

        return 
array_values(array_unique(array_filter($namespaces)));
    }

    
/**
     * Finds a registered namespace by a name or an abbreviation.
     *
     * @param string $namespace A namespace or abbreviation to search for
     *
     * @return string A registered namespace
     *
     * @throws CommandNotFoundException When namespace is incorrect or ambiguous
     */
    
public function findNamespace($namespace)
    {
        
$allNamespaces $this->getNamespaces();
        
$expr preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $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 (
== \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$alternatives);
        }

        
$exact \in_array($namespace$namespacestrue);
        if (
\count($namespaces) > && !$exact) {
            throw new 
CommandNotFoundException(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.
     *
     * @param string $name A command name or a command alias
     *
     * @return Command A Command instance
     *
     * @throws CommandNotFoundException When command name is incorrect or ambiguous
     */
    
public function find($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 preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $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($name0$pos));
            }

            
$message sprintf('Command "%s" is not defined.'$name);

            if (
$alternatives $this->findAlternatives($name$allCommands)) {
                
// remove hidden commands
                
$alternatives array_filter($alternatives, function ($name) {
                    return !
$this->get($name)->isHidden();
                });

                if (
== \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($messagearray_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);
            }));
        }

        
$exact \in_array($name$commandstrue) || isset($aliases[$name]);
        if (
\count($commands) > && !$exact) {
            
$usableWidth $this->terminal->getWidth() - 10;
            
$abbrevs array_values($commands);
            
$maxLen 0;
            foreach (
$abbrevs as $abbrev) {
                
$maxLen max(Helper::strlen($abbrev), $maxLen);
            }
            
$abbrevs array_map(function ($cmd) use ($commandList$usableWidth$maxLen) {
                if (
$commandList[$cmd]->isHidden()) {
                    return 
false;
                }

                
$abbrev str_pad($cmd$maxLen' ').' '.$commandList[$cmd]->getDescription();

                return 
Helper::strlen($abbrev) > $usableWidth Helper::substr($abbrev0$usableWidth 3).'...' $abbrev;
            }, 
array_values($commands));
            
$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));
        }

        return 
$this->get($exact $name reset($commands));
    }

    
/**
     * Gets the commands (registered in the given namespace if provided).
     *
     * The array keys are the full names and the values the command instances.
     *
     * @param string $namespace A namespace name
     *
     * @return Command[] An array of Command instances
     */
    
public function all($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($namesubstr_count($namespace':') + 1)) {
                
$commands[$name] = $command;
            }
        }

        if (
$this->commandLoader) {
            foreach (
$this->commandLoader->getNames() as $name) {
                if (!isset(
$commands[$name]) && $namespace === $this->extractNamespace($namesubstr_count($namespace':') + 1) && $this->has($name)) {
                    
$commands[$name] = $this->get($name);
                }
            }
        }

        return 
$commands;
    }

    
/**
     * Returns an array of possible abbreviations given a set of names.
     *
     * @param array $names An array of names
     *
     * @return array An array of abbreviations
     */
    
public static function getAbbreviations($names)
    {
        
$abbrevs = [];
        foreach (
$names as $name) {
            for (
$len \strlen($name); $len 0; --$len) {
                
$abbrev substr($name0$len);
                
$abbrevs[$abbrev][] = $name;
            }
        }

        return 
$abbrevs;
    }

    
/**
     * Renders a caught exception.
     */
    
public function renderException(\Exception $eOutputInterface $output)
    {
        
$output->writeln(''OutputInterface::VERBOSITY_QUIET);

        
$this->doRenderException($e$output);

        if (
null !== $this->runningCommand) {
            
$output->writeln(sprintf('<info>%s</info>'sprintf($this->runningCommand->getSynopsis(), $this->getName())), OutputInterface::VERBOSITY_QUIET);
            
$output->writeln(''OutputInterface::VERBOSITY_QUIET);
        }
    }

    protected function 
doRenderException(\Exception $eOutputInterface $output)
    {
        do {
            
$message trim($e->getMessage());
            if (
'' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
                
$title sprintf('  [%s%s]  '\get_class($e), !== ($code $e->getCode()) ? ' ('.$code.')' '');
                
$len Helper::strlen($title);
            } else {
                
$len 0;
            }

            
$width $this->terminal->getWidth() ? $this->terminal->getWidth() - \PHP_INT_MAX;
            
// HHVM only accepts 32 bits integer in str_split, even when PHP_INT_MAX is a 64 bit integer: https://github.com/facebook/hhvm/issues/1327
            
if (\defined('HHVM_VERSION') && $width << 31) {
                
$width << 31;
            }
            
$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::strlen($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>'$titlestr_repeat(' 'max(0$len Helper::strlen($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($messagesOutputInterface::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 = isset($trace[$i]['class']) ? $trace[$i]['class'] : '';
                    
$type = isset($trace[$i]['type']) ? $trace[$i]['type'] : '';
                    
$function = isset($trace[$i]['function']) ? $trace[$i]['function'] : '';
                    
$file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a';
                    
$line = isset($trace[$i]['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());
    }

    
/**
     * Tries to figure out the terminal width in which this application runs.
     *
     * @return int|null
     *
     * @deprecated since version 3.2, to be removed in 4.0. Create a Terminal instance instead.
     */
    
protected function getTerminalWidth()
    {
        @
trigger_error(sprintf('The "%s()" method is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.'__METHOD__), \E_USER_DEPRECATED);

        return 
$this->terminal->getWidth();
    }

    
/**
     * Tries to figure out the terminal height in which this application runs.
     *
     * @return int|null
     *
     * @deprecated since version 3.2, to be removed in 4.0. Create a Terminal instance instead.
     */
    
protected function getTerminalHeight()
    {
        @
trigger_error(sprintf('The "%s()" method is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.'__METHOD__), \E_USER_DEPRECATED);

        return 
$this->terminal->getHeight();
    }

    
/**
     * Tries to figure out the terminal dimensions based on the current environment.
     *
     * @return array Array containing width and height
     *
     * @deprecated since version 3.2, to be removed in 4.0. Create a Terminal instance instead.
     */
    
public function getTerminalDimensions()
    {
        @
trigger_error(sprintf('The "%s()" method is deprecated as of 3.2 and will be removed in 4.0. Create a Terminal instance instead.'__METHOD__), \E_USER_DEPRECATED);

        return [
$this->terminal->getWidth(), $this->terminal->getHeight()];
    }

    
/**
     * Sets terminal dimensions.
     *
     * Can be useful to force terminal dimensions for functional tests.
     *
     * @param int $width  The width
     * @param int $height The height
     *
     * @return $this
     *
     * @deprecated since version 3.2, to be removed in 4.0. Set the COLUMNS and LINES env vars instead.
     */
    
public function setTerminalDimensions($width$height)
    {
        @
trigger_error(sprintf('The "%s()" method is deprecated as of 3.2 and will be removed in 4.0. Set the COLUMNS and LINES env vars instead.'__METHOD__), \E_USER_DEPRECATED);

        
putenv('COLUMNS='.$width);
        
putenv('LINES='.$height);

        return 
$this;
    }

    
/**
     * Configures the input and output instances based on the user arguments and options.
     */
    
protected function configureIO(InputInterface $inputOutputInterface $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);
        } elseif (
\function_exists('posix_isatty')) {
            
$inputStream null;

            if (
$input instanceof StreamableInputInterface) {
                
$inputStream $input->getStream();
            }

            
// This check ensures that calling QuestionHelper::setInputStream() works
            // To be removed in 4.0 (in the same time as QuestionHelper::setInputStream)
            
if (!$inputStream && $this->getHelperSet()->has('question')) {
                
$inputStream $this->getHelperSet()->get('question')->getInputStream(false);
            }

            if (!@
posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) {
                
$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) || === $input->getParameterOption('--verbose'falsetrue)) {
                
$output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
                
$shellVerbosity 3;
            } elseif (
$input->hasParameterOption('-vv'true) || $input->hasParameterOption('--verbose=2'true) || === $input->getParameterOption('--verbose'falsetrue)) {
                
$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'falsetrue)) {
                
$output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
                
$shellVerbosity 1;
            }
        }

        if (-
=== $shellVerbosity) {
            
$input->setInteractive(false);
        }

        
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 $commandInputInterface $inputOutputInterface $output)
    {
        foreach (
$command->getHelperSet() as $helper) {
            if (
$helper instanceof InputAwareInterface) {
                
$helper->setInput($input);
            }
        }

        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 $e) {
            
// 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(ConsoleEvents::COMMAND$event);

            if (
$event->commandShouldRun()) {
                
$exitCode $command->run($input$output);
            } else {
                
$exitCode ConsoleCommandEvent::RETURN_CODE_DISABLED;
            }
        } catch (
\Exception $e) {
        } catch (
\Throwable $e) {
        }
        if (
null !== $e) {
            if (
$this->dispatcher->hasListeners(ConsoleEvents::EXCEPTION)) {
                
$x $e instanceof \Exception $e : new FatalThrowableError($e);
                
$event = new ConsoleExceptionEvent($command$input$output$x$x->getCode());
                
$this->dispatcher->dispatch(ConsoleEvents::EXCEPTION$event);

                if (
$x !== $event->getException()) {
                    
$e $event->getException();
                }
            }
            
$event = new ConsoleErrorEvent($input$output$e$command);
            
$this->dispatcher->dispatch(ConsoleEvents::ERROR$event);
            
$e $event->getError();

            if (
=== $exitCode $event->getExitCode()) {
                
$e null;
            }
        }

        
$event = new ConsoleTerminateEvent($command$input$output$exitCode);
        
$this->dispatcher->dispatch(ConsoleEvents::TERMINATE$event);

        if (
null !== $e) {
            throw 
$e;
        }

        return 
$event->getExitCode();
    }

    
/**
     * Gets the name of the command based on input.
     *
     * @return string|null
     */
    
protected function getCommandName(InputInterface $input)
    {
        return 
$this->singleCommand $this->defaultCommand $input->getFirstArgument();
    }

    
/**
     * Gets the default input definition.
     *
     * @return InputDefinition An InputDefinition instance
     */
    
protected function getDefaultInputDefinition()
    {
        return new 
InputDefinition([
            new 
InputArgument('command'InputArgument::REQUIRED'The command to execute'),

            new 
InputOption('--help''-h'InputOption::VALUE_NONE'Display this help message'),
            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_NONE'Force ANSI output'),
            new 
InputOption('--no-ansi'''InputOption::VALUE_NONE'Disable ANSI output'),
            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[] An array of default Command instances
     */
    
protected function getDefaultCommands()
    {
        return [new 
HelpCommand(), new ListCommand()];
    }

    
/**
     * Gets the default helper set with the helpers that should always be available.
     *
     * @return HelperSet A HelperSet instance
     */
    
protected function getDefaultHelperSet()
    {
        return new 
HelperSet([
            new 
FormatterHelper(),
            new 
DebugFormatterHelper(),
            new 
ProcessHelper(),
            new 
QuestionHelper(),
        ]);
    }

    
/**
     * Returns abbreviated suggestions in string format.
     *
     * @param array $abbrevs Abbreviated suggestions to convert
     *
     * @return string A formatted string of abbreviated suggestions
     */
    
private function getAbbreviationSuggestions($abbrevs)
    {
        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.
     *
     * @param string $name  The full name of the command
     * @param string $limit The maximum number of parts of the namespace
     *
     * @return string The namespace of the command
     */
    
public function extractNamespace($name$limit null)
    {
        
$parts explode(':'$name, -1);

        return 
implode(':'null === $limit $parts \array_slice($parts0$limit));
    }

    
/**
     * Finds alternative of $name among $collection,
     * if nothing is found in $collection, try in $abbrevs.
     *
     * @param string   $name       The string
     * @param iterable $collection The collection
     *
     * @return string[] A sorted array of similar string
     */
    
private function findAlternatives($name$collection)
    {
        
$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) / || '' !== $subname && false !== strpos($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) / || false !== strpos($item$name)) {
                
$alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev $lev;
            }
        }

        
$alternatives array_filter($alternatives, function ($lev) use ($threshold) { return $lev $threshold; });
        
ksort($alternatives\SORT_NATURAL \SORT_FLAG_CASE);

        return 
array_keys($alternatives);
    }

    
/**
     * Sets the default Command name.
     *
     * @param string $commandName     The Command name
     * @param bool   $isSingleCommand Set to true if there is only one command in this application
     *
     * @return self
     */
    
public function setDefaultCommand($commandName$isSingleCommand false)
    {
        
$this->defaultCommand $commandName;

        if (
$isSingleCommand) {
            
// Ensure the command exist
            
$this->find($commandName);

            
$this->singleCommand true;
        }

        return 
$this;
    }

    
/**
     * @internal
     */
    
public function isSingleCommand()
    {
        return 
$this->singleCommand;
    }

    private function 
splitStringByWidth($string$width)
    {
        
// 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($stringnulltrue)) {
            return 
str_split($string$width);
        }

        
$utf8String mb_convert_encoding($string'utf8'$encoding);
        
$lines = [];
        
$line '';
        foreach (
preg_split('//u'$utf8String) as $char) {
            
// test if $char could be appended to current line
            
if (mb_strwidth($line.$char'utf8') <= $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.
     *
     * @param string $name The full name of the command
     *
     * @return string[] The namespaces of the command
     */
    
private function extractAllNamespaces($name)
    {
        
// -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()
    {
        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 <[email protected]>
 *
 * 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;
use 
Symfony\Component\Console\Input\InputInterface;
use 
Symfony\Component\Console\Output\OutputInterface;
use 
Symfony\Component\Console\Output\StreamOutput;

/**
 * Eases the testing of console commands.
 *
 * @author Fabien Potencier <[email protected]>
 * @author Robin Chalas <[email protected]>
 */
class CommandTester
{
    private 
$command;
    private 
$input;
    private 
$output;
    private 
$inputs = [];
    private 
$statusCode;

    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
     *
     * @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 = [])
    {
        
// 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']);
        }

        
$this->output = new StreamOutput(fopen('php://memory''w'false));
        
$this->output->setDecorated(isset($options['decorated']) ? $options['decorated'] : false);
        if (isset(
$options['verbosity'])) {
            
$this->output->setVerbosity($options['verbosity']);
        }

        return 
$this->statusCode $this->command->run($this->input$this->output);
    }

    
/**
     * Gets the display returned by the last execution of the command.
     *
     * @param bool $normalize Whether to normalize end of lines to \n or not
     *
     * @return string The display
     */
    
public function getDisplay($normalize false)
    {
        if (
null === $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 input instance used by the last execution of the command.
     *
     * @return InputInterface The current input instance
     */
    
public function getInput()
    {
        return 
$this->input;
    }

    
/**
     * Gets the output instance used by the last execution of the command.
     *
     * @return OutputInterface The current output instance
     */
    
public function getOutput()
    {
        return 
$this->output;
    }

    
/**
     * Gets the status code returned by the last execution of the application.
     *
     * @return int The status code
     */
    
public function getStatusCode()
    {
        return 
$this->statusCode;
    }

    
/**
     * Sets the user inputs.
     *
     * @param array $inputs An array of strings representing each input
     *                      passed to the command input stream
     *
     * @return CommandTester
     */
    
public function setInputs(array $inputs)
    {
        
$this->inputs $inputs;

        return 
$this;
    }

    private static function 
createStream(array $inputs)
    {
        
$stream fopen('php://memory''r+'false);

        foreach (
$inputs as $input) {
            
fwrite($stream$input.\PHP_EOL);
        }

        
rewind($stream);

        return 
$stream;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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;
use 
Symfony\Component\Console\Input\InputInterface;
use 
Symfony\Component\Console\Output\ConsoleOutput;
use 
Symfony\Component\Console\Output\OutputInterface;
use 
Symfony\Component\Console\Output\StreamOutput;

/**
 * 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 <[email protected]>
 */
class ApplicationTester
{
    private 
$application;
    private 
$input;
    private 
$statusCode;
    
/**
     * @var OutputInterface
     */
    
private $output;
    private 
$captureStreamsIndependently false;

    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
     *
     * @param array $input   An array of arguments and options
     * @param array $options An array of options
     *
     * @return int The command exit code
     */
    
public function run(array $input$options = [])
    {
        
$this->input = new ArrayInput($input);
        if (isset(
$options['interactive'])) {
            
$this->input->setInteractive($options['interactive']);
        }

        
$this->captureStreamsIndependently \array_key_exists('capture_stderr_separately'$options) && $options['capture_stderr_separately'];
        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(
                isset(
$options['verbosity']) ? $options['verbosity'] : ConsoleOutput::VERBOSITY_NORMAL,
                isset(
$options['decorated']) ? $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->setAccessible(true);
            
$strErrProperty->setValue($this->output$errorOutput);

            
$reflectedParent $reflectedOutput->getParentClass();
            
$streamProperty $reflectedParent->getProperty('stream');
            
$streamProperty->setAccessible(true);
            
$streamProperty->setValue($this->outputfopen('php://memory''w'false));
        }

        return 
$this->statusCode $this->application->run($this->input$this->output);
    }

    
/**
     * Gets the display returned by the last execution of the application.
     *
     * @param bool $normalize Whether to normalize end of lines to \n or not
     *
     * @return string The display
     */
    
public function getDisplay($normalize false)
    {
        
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
     *
     * @return string
     */
    
public function getErrorOutput($normalize false)
    {
        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 application.
     *
     * @return InputInterface The current input instance
     */
    
public function getInput()
    {
        return 
$this->input;
    }

    
/**
     * Gets the output instance used by the last execution of the application.
     *
     * @return OutputInterface The current output instance
     */
    
public function getOutput()
    {
        return 
$this->output;
    }

    
/**
     * Gets the status code returned by the last execution of the application.
     *
     * @return int The status code
     */
    
public function getStatusCode()
    {
        return 
$this->statusCode;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class CommandNotFoundException extends \InvalidArgumentException implements ExceptionInterface
{
    private 
$alternatives;

    
/**
     * @param string     $message      Exception message to throw
     * @param array      $alternatives List of similar defined names
     * @param int        $code         Exception code
     * @param \Exception $previous     Previous exception used for the exception chaining
     */
    
public function __construct($message, array $alternatives = [], $code 0\Exception $previous null)
    {
        
parent::__construct($message$code$previous);

        
$this->alternatives $alternatives;
    }

    
/**
     * @return array A list of similar defined names
     */
    
public function getAlternatives()
    {
        return 
$this->alternatives;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class LogicException extends \LogicException implements ExceptionInterface
{
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
interface ExceptionInterface
{
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 typed in the console.
 *
 * @author Jérôme Tamarelle <[email protected]>
 */
class InvalidOptionException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 *
 * @internal
 */
class MarkdownDescriptor extends Descriptor
{
    
/**
     * {@inheritdoc}
     */
    
public function describe(OutputInterface $output$object, array $options = [])
    {
        
$decorated $output->isDecorated();
        
$output->setDecorated(false);

        
parent::describe($output$object$options);

        
$output->setDecorated($decorated);
    }

    
/**
     * {@inheritdoc}
     */
    
protected function write($content$decorated true)
    {
        
parent::write($content$decorated);
    }

    
/**
     * {@inheritdoc}
     */
    
protected function describeInputArgument(InputArgument $argument, array $options = [])
    {
        
$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)).'`'
        
);
    }

    
/**
     * {@inheritdoc}
     */
    
protected function describeInputOption(InputOption $option, array $options = [])
    {
        
$name '--'.$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"
            
.'* Default: `'.str_replace("\n"''var_export($option->getDefault(), true)).'`'
        
);
    }

    
/**
     * {@inheritdoc}
     */
    
protected function describeInputDefinition(InputDefinition $definition, array $options = [])
    {
        if (
$showArguments \count($definition->getArguments()) > 0) {
            
$this->write('### Arguments');
            foreach (
$definition->getArguments() as $argument) {
                
$this->write("\n\n");
                
$this->write($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->write($this->describeInputOption($option));
            }
        }
    }

    
/**
     * {@inheritdoc}
     */
    
protected function describeCommand(Command $command, array $options = [])
    {
        
$command->getSynopsis();
        
$command->mergeApplicationDefinition(false);

        
$this->write(
            
'`'.$command->getName()."`\n"
            
.str_repeat('-'Helper::strlen($command->getName()) + 2)."\n\n"
            
.($command->getDescription() ? $command->getDescription()."\n\n" '')
            .
'### Usage'."\n\n"
            
.array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), function ($carry$usage) {
                return 
$carry.'* `'.$usage.'`'."\n";
            })
        );

        if (
$help $command->getProcessedHelp()) {
            
$this->write("\n");
            
$this->write($help);
        }

        if (
$command->getNativeDefinition()) {
            
$this->write("\n\n");
            
$this->describeInputDefinition($command->getNativeDefinition());
        }
    }

    
/**
     * {@inheritdoc}
     */
    
protected function describeApplication(Application $application, array $options = [])
    {
        
$describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
        
$description = new ApplicationDescription($application$describedNamespace);
        
$title $this->getApplicationTitle($application);

        
$this->write($title."\n".str_repeat('='Helper::strlen($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(function ($commandName) use ($description) {
                return 
sprintf('* [`%s`](#%s)'$commandNamestr_replace(':'''$description->getCommand($commandName)->getName()));
            }, 
$namespace['commands'])));
        }

        foreach (
$description->getCommands() as $command) {
            
$this->write("\n\n");
            
$this->write($this->describeCommand($command));
        }
    }

    private function 
getApplicationTitle(Application $application)
    {
        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 <[email protected]>
 *
 * 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 <[email protected]>
 *
 * @internal
 */
class XmlDescriptor extends Descriptor
{
    
/**
     * @return \DOMDocument
     */
    
public function getInputDefinitionDocument(InputDefinition $definition)
    {
        
$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;
    }

    
/**
     * @return \DOMDocument
     */
    
public function getCommandDocument(Command $command)
    {
        
$dom = new \DOMDocument('1.0''UTF-8');
        
$dom->appendChild($commandXML $dom->createElement('command'));

        
$command->getSynopsis();
        
$command->mergeApplicationDefinition(false);

        
$commandXML->setAttribute('id'$command->getName());
        
$commandXML->setAttribute('name'$command->getName());
        
$commandXML->setAttribute('hidden'$command->isHidden() ? 0);

        
$commandXML->appendChild($usagesXML $dom->createElement('usages'));

        foreach (
array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()) as $usage) {
            
$usagesXML->appendChild($dom->createElement('usage'$usage));
        }

        
$commandXML->appendChild($descriptionXML $dom->createElement('description'));
        
$descriptionXML->appendChild($dom->createTextNode(str_replace("\n""\n "$command->getDescription())));

        
$commandXML->appendChild($helpXML $dom->createElement('help'));
        
$helpXML->appendChild($dom->createTextNode(str_replace("\n""\n "$command->getProcessedHelp())));

        
$definitionXML $this->getInputDefinitionDocument($command->getNativeDefinition());
        
$this->appendDocument($commandXML$definitionXML->getElementsByTagName('definition')->item(0));

        return 
$dom;
    }

    
/**
     * @param string|null $namespace
     *
     * @return \DOMDocument
     */
    
public function getApplicationDocument(Application $application$namespace null)
    {
        
$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$namespacetrue);

        if (
$namespace) {
            
$commandsXML->setAttribute('namespace'$namespace);
        }

        foreach (
$description->getCommands() as $command) {
            
$this->appendDocument($commandsXML$this->getCommandDocument($command));
        }

        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;
    }

    
/**
     * {@inheritdoc}
     */
    
protected function describeInputArgument(InputArgument $argument, array $options = [])
    {
        
$this->writeDocument($this->getInputArgumentDocument($argument));
    }

    
/**
     * {@inheritdoc}
     */
    
protected function describeInputOption(InputOption $option, array $options = [])
    {
        
$this->writeDocument($this->getInputOptionDocument($option));
    }

    
/**
     * {@inheritdoc}
     */
    
protected function describeInputDefinition(InputDefinition $definition, array $options = [])
    {
        
$this->writeDocument($this->getInputDefinitionDocument($definition));
    }

    
/**
     * {@inheritdoc}
     */
    
protected function describeCommand(Command $command, array $options = [])
    {
        
$this->writeDocument($this->getCommandDocument($command));
    }

    
/**
     * {@inheritdoc}
     */
    
protected function describeApplication(Application $application, array $options = [])
    {
        
$this->writeDocument($this->getApplicationDocument($application, isset($options['namespace']) ? $options['namespace'] : null));
    }

    
/**
     * Appends document children to parent node.
     */
    
private function appendDocument(\DOMNode $parentNode\DOMNode $importedParent)
    {
        foreach (
$importedParent->childNodes as $childNode) {
            
$parentNode->appendChild($parentNode->ownerDocument->importNode($childNodetrue));
        }
    }

    
/**
     * Writes DOM document.
     */
    
private function writeDocument(\DOMDocument $dom)
    {
        
$dom->formatOutput true;
        
$this->write($dom->saveXML());
    }

    
/**
     * @return \DOMDocument
     */
    
private function getInputArgumentDocument(InputArgument $argument)
    {
        
$dom = new \DOMDocument('1.0''UTF-8');

        
$dom->appendChild($objectXML $dom->createElement('argument'));
        
$objectXML->setAttribute('name'$argument->getName());
        
$objectXML->setAttribute('is_required'$argument->isRequired() ? 0);
        
$objectXML->setAttribute('is_array'$argument->isArray() ? 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;
    }

    
/**
     * @return \DOMDocument
     */
    
private function getInputOptionDocument(InputOption $option)
    {
        
$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() ? 0);
        
$objectXML->setAttribute('is_value_required'$option->isValueRequired() ? 0);
        
$objectXML->setAttribute('is_multiple'$option->isArray() ? 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));
                }
            }
        }

        return 
$dom;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
interface DescriptorInterface
{
    
/**
     * Describes an object if supported.
     *
     * @param object $object
     */
    
public function describe(OutputInterface $output$object, array $options = []);
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 *
 * @internal
 */
class JsonDescriptor extends Descriptor
{
    
/**
     * {@inheritdoc}
     */
    
protected function describeInputArgument(InputArgument $argument, array $options = [])
    {
        
$this->writeData($this->getInputArgumentData($argument), $options);
    }

    
/**
     * {@inheritdoc}
     */
    
protected function describeInputOption(InputOption $option, array $options = [])
    {
        
$this->writeData($this->getInputOptionData($option), $options);
    }

    
/**
     * {@inheritdoc}
     */
    
protected function describeInputDefinition(InputDefinition $definition, array $options = [])
    {
        
$this->writeData($this->getInputDefinitionData($definition), $options);
    }

    
/**
     * {@inheritdoc}
     */
    
protected function describeCommand(Command $command, array $options = [])
    {
        
$this->writeData($this->getCommandData($command), $options);
    }

    
/**
     * {@inheritdoc}
     */
    
protected function describeApplication(Application $application, array $options = [])
    {
        
$describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
        
$description = new ApplicationDescription($application$describedNamespacetrue);
        
$commands = [];

        foreach (
$description->getCommands() as $command) {
            
$commands[] = $this->getCommandData($command);
        }

        
$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)
    {
        
$flags = isset($options['json_encoding']) ? $options['json_encoding'] : 0;

        
$this->write(json_encode($data$flags));
    }

    
/**
     * @return array
     */
    
private function getInputArgumentData(InputArgument $argument)
    {
        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(),
        ];
    }

    
/**
     * @return array
     */
    
private function getInputOptionData(InputOption $option)
    {
        return [
            
'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(),
        ];
    }

    
/**
     * @return array
     */
    
private function getInputDefinitionData(InputDefinition $definition)
    {
        
$inputArguments = [];
        foreach (
$definition->getArguments() as $name => $argument) {
            
$inputArguments[$name] = $this->getInputArgumentData($argument);
        }

        
$inputOptions = [];
        foreach (
$definition->getOptions() as $name => $option) {
            
$inputOptions[$name] = $this->getInputOptionData($option);
        }

        return [
'arguments' => $inputArguments'options' => $inputOptions];
    }

    
/**
     * @return array
     */
    
private function getCommandData(Command $command)
    {
        
$command->getSynopsis();
        
$command->mergeApplicationDefinition(false);

        return [
            
'name' => $command->getName(),
            
'usage' => array_merge([$command->getSynopsis()], $command->getUsages(), $command->getAliases()),
            
'description' => $command->getDescription(),
            
'help' => $command->getProcessedHelp(),
            
'definition' => $this->getInputDefinitionData($command->getNativeDefinition()),
            
'hidden' => $command->isHidden(),
        ];
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 *
 * @internal
 */
class ApplicationDescription
{
    const 
GLOBAL_NAMESPACE '_global';

    private 
$application;
    private 
$namespace;
    private 
$showHidden;

    
/**
     * @var array
     */
    
private $namespaces;

    
/**
     * @var Command[]
     */
    
private $commands;

    
/**
     * @var Command[]
     */
    
private $aliases;

    
/**
     * @param string|null $namespace
     * @param bool        $showHidden
     */
    
public function __construct(Application $application$namespace null$showHidden false)
    {
        
$this->application $application;
        
$this->namespace $namespace;
        
$this->showHidden $showHidden;
    }

    
/**
     * @return array
     */
    
public function getNamespaces()
    {
        if (
null === $this->namespaces) {
            
$this->inspectApplication();
        }

        return 
$this->namespaces;
    }

    
/**
     * @return Command[]
     */
    
public function getCommands()
    {
        if (
null === $this->commands) {
            
$this->inspectApplication();
        }

        return 
$this->commands;
    }

    
/**
     * @param string $name
     *
     * @return Command
     *
     * @throws CommandNotFoundException
     */
    
public function getCommand($name)
    {
        if (!isset(
$this->commands[$name]) && !isset($this->aliases[$name])) {
            throw new 
CommandNotFoundException(sprintf('Command "%s" does not exist.'$name));
        }

        return isset(
$this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name];
    }

    private function 
inspectApplication()
    {
        
$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];
        }
    }

    
/**
     * @return array
     */
    
private function sortCommands(array $commands)
    {
        
$namespacedCommands = [];
        
$globalCommands = [];
        
$sortedCommands = [];
        foreach (
$commands as $name => $command) {
            
$key $this->application->extractNamespace($name1);
            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);
            foreach (
$namespacedCommands as $key => $commandsSet) {
                
ksort($commandsSet);
                
$sortedCommands[$key] = $commandsSet;
            }
        }

        return 
$sortedCommands;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 *
 * @internal
 */
abstract class Descriptor implements DescriptorInterface
{
    
/**
     * @var OutputInterface
     */
    
protected $output;

    
/**
     * {@inheritdoc}
     */
    
public function describe(OutputInterface $output$object, array $options = [])
    {
        
$this->output $output;

        switch (
true) {
            case 
$object instanceof InputArgument:
                
$this->describeInputArgument($object$options);
                break;
            case 
$object instanceof InputOption:
                
$this->describeInputOption($object$options);
                break;
            case 
$object instanceof InputDefinition:
                
$this->describeInputDefinition($object$options);
                break;
            case 
$object instanceof Command:
                
$this->describeCommand($object$options);
                break;
            case 
$object instanceof Application:
                
$this->describeApplication($object$options);
                break;
            default:
                throw new 
InvalidArgumentException(sprintf('Object of type "%s" is not describable.'\get_class($object)));
        }
    }

    
/**
     * Writes content to output.
     *
     * @param string $content
     * @param bool   $decorated
     */
    
protected function write($content$decorated false)
    {
        
$this->output->write($contentfalse$decorated OutputInterface::OUTPUT_NORMAL OutputInterface::OUTPUT_RAW);
    }

    
/**
     * Describes an InputArgument instance.
     *
     * @return string|mixed
     */
    
abstract protected function describeInputArgument(InputArgument $argument, array $options = []);

    
/**
     * Describes an InputOption instance.
     *
     * @return string|mixed
     */
    
abstract protected function describeInputOption(InputOption $option, array $options = []);

    
/**
     * Describes an InputDefinition instance.
     *
     * @return string|mixed
     */
    
abstract protected function describeInputDefinition(InputDefinition $definition, array $options = []);

    
/**
     * Describes a Command instance.
     *
     * @return string|mixed
     */
    
abstract protected function describeCommand(Command $command, array $options = []);

    
/**
     * Describes an Application instance.
     *
     * @return string|mixed
     */
    
abstract protected function describeApplication(Application $application, array $options = []);
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 *
 * @internal
 */
class TextDescriptor extends Descriptor
{
    
/**
     * {@inheritdoc}
     */
    
protected function describeInputArgument(InputArgument $argument, array $options = [])
    {
        if (
null !== $argument->getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) {
            
$default sprintf('<comment> [default: %s]</comment>'$this->formatDefaultValue($argument->getDefault()));
        } else {
            
$default '';
        }

        
$totalWidth = isset($options['total_width']) ? $options['total_width'] : Helper::strlen($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);
    }

    
/**
     * {@inheritdoc}
     */
    
protected function describeInputOption(InputOption $option, array $options = [])
    {
        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 = isset($options['total_width']) ? $options['total_width'] : $this->calculateTotalWidthForOptions([$option]);
        
$synopsis sprintf('%s%s',
            
$option->getShortcut() ? sprintf('-%s, '$option->getShortcut()) : '    ',
            
sprintf('--%s%s'$option->getName(), $value)
        );

        
$spacingWidth $totalWidth Helper::strlen($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);
    }

    
/**
     * {@inheritdoc}
     */
    
protected function describeInputDefinition(InputDefinition $definition, array $options = [])
    {
        
$totalWidth $this->calculateTotalWidthForOptions($definition->getOptions());
        foreach (
$definition->getArguments() as $argument) {
            
$totalWidth max($totalWidthHelper::strlen($argument->getName()));
        }

        if (
$definition->getArguments()) {
            
$this->writeText('<comment>Arguments:</comment>'$options);
            
$this->writeText("\n");
            foreach (
$definition->getArguments() as $argument) {
                
$this->describeInputArgument($argumentarray_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($optionarray_merge($options, ['total_width' => $totalWidth]));
            }
            foreach (
$laterOptions as $option) {
                
$this->writeText("\n");
                
$this->describeInputOption($optionarray_merge($options, ['total_width' => $totalWidth]));
            }
        }
    }

    
/**
     * {@inheritdoc}
     */
    
protected function describeCommand(Command $command, array $options = [])
    {
        
$command->getSynopsis(true);
        
$command->getSynopsis(false);
        
$command->mergeApplicationDefinition(false);

        
$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->getNativeDefinition();
        if (
$definition->getOptions() || $definition->getArguments()) {
            
$this->writeText("\n");
            
$this->describeInputDefinition($definition$options);
            
$this->writeText("\n");
        }

        if (
$help $command->getProcessedHelp()) {
            
$this->writeText("\n");
            
$this->writeText('<comment>Help:</comment>'$options);
            
$this->writeText("\n");
            
$this->writeText('  '.str_replace("\n""\n  "$help), $options);
            
$this->writeText("\n");
        }
    }

    
/**
     * {@inheritdoc}
     */
    
protected function describeApplication(Application $application, array $options = [])
    {
        
$describedNamespace = isset($options['namespace']) ? $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(\call_user_func_array('array_merge'array_map(function ($namespace) use ($commands) {
                return 
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'], function ($name) use ($commands) {
                    return 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::strlen($name);
                    
$command $commands[$name];
                    
$commandAliases $name === $command->getName() ? $this->getCommandAliasesText($command) : '';
                    
$this->writeText(sprintf('  <info>%s</info>%s%s'$namestr_repeat(' '$spacingWidth), $commandAliases.$command->getDescription()), $options);
                }
            }

            
$this->writeText("\n");
        }
    }

    
/**
     * {@inheritdoc}
     */
    
private function writeText($content, array $options = [])
    {
        
$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.
     *
     * @return string
     */
    
private function getCommandAliasesText(Command $command)
    {
        
$text '';
        
$aliases $command->getAliases();

        if (
$aliases) {
            
$text '['.implode('|'$aliases).'] ';
        }

        return 
$text;
    }

    
/**
     * Formats input option/argument default value.
     *
     * @param mixed $default
     *
     * @return string
     */
    
private function formatDefaultValue($default)
    {
        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 (Command|string)[] $commands
     *
     * @return int
     */
    
private function getColumnWidth(array $commands)
    {
        
$widths = [];

        foreach (
$commands as $command) {
            if (
$command instanceof Command) {
                
$widths[] = Helper::strlen($command->getName());
                foreach (
$command->getAliases() as $alias) {
                    
$widths[] = Helper::strlen($alias);
                }
            } else {
                
$widths[] = Helper::strlen($command);
            }
        }

        return 
$widths max($widths) + 0;
    }

    
/**
     * @param InputOption[] $options
     *
     * @return int
     */
    
private function calculateTotalWidthForOptions(array $options)
    {
        
$totalWidth 0;
        foreach (
$options as $option) {
            
// "-" + shortcut + ", --" + name
            
$nameLength max(Helper::strlen($option->getShortcut()), 1) + Helper::strlen($option->getName());

            if (
$option->acceptValue()) {
                
$valueLength Helper::strlen($option->getName()); // = + value
                
$valueLength += $option->isValueOptional() ? 0// [ + ]

                
$nameLength += $valueLength;
            }
            
$totalWidth max($totalWidth$nameLength);
        }

        return 
$totalWidth;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 * @author Robin Chalas <[email protected]>
 */
class ErrorListener implements EventSubscriberInterface
{
    private 
$logger;

    public function 
__construct(LoggerInterface $logger null)
    {
        
$this->logger $logger;
    }

    public function 
onConsoleError(ConsoleErrorEvent $event)
    {
        if (
null === $this->logger) {
            return;
        }

        
$error $event->getError();

        if (!
$inputString $this->getInputString($event)) {
            
$this->logger->error('An error occurred while using the console. Message: "{message}"', ['exception' => $error'message' => $error->getMessage()]);

            return;
        }

        
$this->logger->error('Error thrown while running command "{command}". Message: "{message}"', ['exception' => $error'command' => $inputString'message' => $error->getMessage()]);
    }

    public function 
onConsoleTerminate(ConsoleTerminateEvent $event)
    {
        if (
null === $this->logger) {
            return;
        }

        
$exitCode $event->getExitCode();

        if (
=== $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()
    {
        return [
            
ConsoleEvents::ERROR => ['onConsoleError', -128],
            
ConsoleEvents::TERMINATE => ['onConsoleTerminate', -128],
        ];
    }

    private static function 
getInputString(ConsoleEvent $event)
    {
        
$commandName $event->getCommand() ? $event->getCommand()->getName() : null;
        
$input $event->getInput();

        if (
method_exists($input'__toString')) {
            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 <[email protected]>
 *
 * 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 <[email protected]>
 */
class DebugFormatterHelper extends Helper
{
    private 
$colors = ['black''red''green''yellow''blue''magenta''cyan''white''default'];
    private 
$started = [];
    private 
$count = -1;

    
/**
     * Starts a debug formatting session.
     *
     * @param string $id      The id of the formatting session
     * @param string $message The message to display
     * @param string $prefix  The prefix to use
     *
     * @return string
     */
    
public function start($id$message$prefix 'RUN')
    {
        
$this->started[$id] = ['border' => ++$this->count \count($this->colors)];

        return 
sprintf("%s<bg=blue;fg=white> %s </> <fg=blue>%s</>\n"$this->getBorder($id), $prefix$message);
    }

    
/**
     * Adds progress to a formatting session.
     *
     * @param string $id          The id of the formatting session
     * @param string $buffer      The message to display
     * @param bool   $error       Whether to consider the buffer as error
     * @param string $prefix      The prefix for output
     * @param string $errorPrefix The prefix for error output
     *
     * @return string
     */
    
public function progress($id$buffer$error false$prefix 'OUT'$errorPrefix 'ERR')
    {
        
$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.
     *
     * @param string $id         The id of the formatting session
     * @param string $message    The message to display
     * @param bool   $successful Whether to consider the result as success
     * @param string $prefix     The prefix for the end output
     *
     * @return string
     */
    
public function stop($id$message$successful$prefix 'RES')
    {
        
$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;
    }

    
/**
     * @param string $id The id of the formatting session
     *
     * @return string
     */
    
private function getBorder($id)
    {
        return 
sprintf('<bg=%s> </>'$this->colors[$this->started[$id]['border']]);
    }

    
/**
     * {@inheritdoc}
     */
    
public function getName()
    {
        return 
'debug_formatter';
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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;

/**
 * Helper is the base class for all helper classes.
 *
 * @author Fabien Potencier <[email protected]>
 */
abstract class Helper implements HelperInterface
{
    protected 
$helperSet null;

    
/**
     * {@inheritdoc}
     */
    
public function setHelperSet(HelperSet $helperSet null)
    {
        
$this->helperSet $helperSet;
    }

    
/**
     * {@inheritdoc}
     */
    
public function getHelperSet()
    {
        return 
$this->helperSet;
    }

    
/**
     * Returns the length of a string, using mb_strwidth if it is available.
     *
     * @param string $string The string to check its length
     *
     * @return int The length of the string
     */
    
public static function strlen($string)
    {
        if (
false === $encoding mb_detect_encoding($stringnulltrue)) {
            return 
\strlen($string);
        }

        return 
mb_strwidth($string$encoding);
    }

    
/**
     * Returns the subset of a string, using mb_substr if it is available.
     *
     * @param string   $string String to subset
     * @param int      $from   Start offset
     * @param int|null $length Length to read
     *
     * @return string The string subset
     */
    
public static function substr($string$from$length null)
    {
        if (
false === $encoding mb_detect_encoding($stringnulltrue)) {
            return 
substr($string$from$length);
        }

        return 
mb_substr($string$from$length$encoding);
    }

    public static function 
formatTime($secs)
    {
        static 
$timeFormats = [
            [
0'< 1 sec'],
            [
1'1 sec'],
            [
2'secs'1],
            [
60'1 min'],
            [
120'mins'60],
            [
3600'1 hr'],
            [
7200'hrs'3600],
            [
86400'1 day'],
            [
172800'days'86400],
        ];

        foreach (
$timeFormats as $index => $format) {
            if (
$secs >= $format[0]) {
                if ((isset(
$timeFormats[$index 1]) && $secs $timeFormats[$index 1][0])
                    || 
$index == \count($timeFormats) - 1
                
) {
                    if (
== \count($format)) {
                        return 
$format[1];
                    }

                    return 
floor($secs $format[2]).' '.$format[1];
                }
            }
        }
    }

    public static function 
formatMemory($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);
    }

    public static function 
strlenWithoutDecoration(OutputFormatterInterface $formatter$string)
    {
        return 
self::strlen(self::removeDecoration($formatter$string));
    }

    public static function 
removeDecoration(OutputFormatterInterface $formatter$string)
    {
        
$isDecorated $formatter->isDecorated();
        
$formatter->setDecorated(false);
        
// remove <...> formatting
        
$string $formatter->format($string);
        
// remove already formatted characters
        
$string preg_replace("/\033\[[^m]*m/"''$string);
        
$formatter->setDecorated($isDecorated);

        return 
$string;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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\LogicException;
use 
Symfony\Component\Console\Output\ConsoleOutputInterface;
use 
Symfony\Component\Console\Output\OutputInterface;
use 
Symfony\Component\Console\Terminal;

/**
 * The ProgressBar provides helpers to display progress output.
 *
 * @author Fabien Potencier <[email protected]>
 * @author Chris Jones <[email protected]>
 */
final class ProgressBar
{
    private 
$barWidth 28;
    private 
$barChar;
    private 
$emptyBarChar '-';
    private 
$progressChar '>';
    private 
$format;
    private 
$internalFormat;
    private 
$redrawFreq 1;
    private 
$output;
    private 
$step 0;
    private 
$max;
    private 
$startTime;
    private 
$stepWidth;
    private 
$percent 0.0;
    private 
$formatLineCount;
    private 
$messages = [];
    private 
$overwrite true;
    private 
$terminal;
    private 
$firstRun true;

    private static 
$formatters;
    private static 
$formats;

    
/**
     * @param OutputInterface $output An OutputInterface instance
     * @param int             $max    Maximum steps (0 if unknown)
     */
    
public function __construct(OutputInterface $output$max 0)
    {
        if (
$output instanceof ConsoleOutputInterface) {
            
$output $output->getErrorOutput();
        }

        
$this->output $output;
        
$this->setMaxSteps($max);
        
$this->terminal = new Terminal();

        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->setRedrawFrequency($max 10);
        }

        
$this->startTime time();
    }

    
/**
     * Sets a placeholder formatter for a given name.
     *
     * This method also allow you to override an existing placeholder.
     *
     * @param string   $name     The placeholder name (including the delimiter char like %)
     * @param callable $callable A PHP callable
     */
    
public static function setPlaceholderFormatterDefinition($name, callable $callable)
    {
        if (!
self::$formatters) {
            
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 %)
     *
     * @return callable|null A PHP callable
     */
    
public static function getPlaceholderFormatterDefinition($name)
    {
        if (!
self::$formatters) {
            
self::$formatters self::initPlaceholderFormatters();
        }

        return isset(
self::$formatters[$name]) ? self::$formatters[$name] : null;
    }

    
/**
     * 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($name$format)
    {
        if (!
self::$formats) {
            
self::$formats self::initFormats();
        }

        
self::$formats[$name] = $format;
    }

    
/**
     * Gets the format for a given name.
     *
     * @param string $name The format name
     *
     * @return string|null A format string
     */
    
public static function getFormatDefinition($name)
    {
        if (!
self::$formats) {
            
self::$formats self::initFormats();
        }

        return isset(
self::$formats[$name]) ? 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($message$name 'message')
    {
        
$this->messages[$name] = $message;
    }

    public function 
getMessage($name 'message')
    {
        return 
$this->messages[$name];
    }

    
/**
     * Gets the progress bar start time.
     *
     * @return int The progress bar start time
     */
    
public function getStartTime()
    {
        return 
$this->startTime;
    }

    
/**
     * Gets the progress bar maximal steps.
     *
     * @return int The progress bar max steps
     */
    
public function getMaxSteps()
    {
        return 
$this->max;
    }

    
/**
     * Gets the current step position.
     *
     * @return int The progress bar step
     */
    
public function getProgress()
    {
        return 
$this->step;
    }

    
/**
     * Gets the progress bar step width.
     *
     * @return int The progress bar step width
     */
    
private function getStepWidth()
    {
        return 
$this->stepWidth;
    }

    
/**
     * Gets the current progress bar percent.
     *
     * @return float The current progress bar percent
     */
    
public function getProgressPercent()
    {
        return 
$this->percent;
    }

    
/**
     * Sets the progress bar width.
     *
     * @param int $size The progress bar size
     */
    
public function setBarWidth($size)
    {
        
$this->barWidth max(1, (int) $size);
    }

    
/**
     * Gets the progress bar width.
     *
     * @return int The progress bar size
     */
    
public function getBarWidth()
    {
        return 
$this->barWidth;
    }

    
/**
     * Sets the bar character.
     *
     * @param string $char A character
     */
    
public function setBarCharacter($char)
    {
        
$this->barChar $char;
    }

    
/**
     * Gets the bar character.
     *
     * @return string A character
     */
    
public function getBarCharacter()
    {
        if (
null === $this->barChar) {
            return 
$this->max '=' $this->emptyBarChar;
        }

        return 
$this->barChar;
    }

    
/**
     * Sets the empty bar character.
     *
     * @param string $char A character
     */
    
public function setEmptyBarCharacter($char)
    {
        
$this->emptyBarChar $char;
    }

    
/**
     * Gets the empty bar character.
     *
     * @return string A character
     */
    
public function getEmptyBarCharacter()
    {
        return 
$this->emptyBarChar;
    }

    
/**
     * Sets the progress bar character.
     *
     * @param string $char A character
     */
    
public function setProgressCharacter($char)
    {
        
$this->progressChar $char;
    }

    
/**
     * Gets the progress bar character.
     *
     * @return string A character
     */
    
public function getProgressCharacter()
    {
        return 
$this->progressChar;
    }

    
/**
     * Sets the progress bar format.
     *
     * @param string $format The format
     */
    
public function setFormat($format)
    {
        
$this->format null;
        
$this->internalFormat $format;
    }

    
/**
     * Sets the redraw frequency.
     *
     * @param int|float $freq The frequency in steps
     */
    
public function setRedrawFrequency($freq)
    {
        
$this->redrawFreq max((int) $freq1);
    }

    
/**
     * Starts the progress output.
     *
     * @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged
     */
    
public function start($max null)
    {
        
$this->startTime time();
        
$this->step 0;
        
$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($step 1)
    {
        
$this->setProgress($this->step $step);
    }

    
/**
     * Sets whether to overwrite the progressbar, false for new line.
     *
     * @param bool $overwrite
     */
    
public function setOverwrite($overwrite)
    {
        
$this->overwrite = (bool) $overwrite;
    }

    
/**
     * Sets the current progress.
     *
     * @param int $step The current progress
     */
    
public function setProgress($step)
    {
        
$step = (int) $step;

        if (
$this->max && $step $this->max) {
            
$this->max $step;
        } elseif (
$step 0) {
            
$step 0;
        }

        
$prevPeriod = (int) ($this->step $this->redrawFreq);
        
$currPeriod = (int) ($step $this->redrawFreq);
        
$this->step $step;
        
$this->percent $this->max ? (float) $this->step $this->max 0;
        if (
$prevPeriod !== $currPeriod || $this->max === $step) {
            
$this->display();
        }
    }

    
/**
     * Finishes the progress output.
     */
    
public function finish()
    {
        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()
    {
        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()
    {
        if (!
$this->overwrite) {
            return;
        }

        if (
null === $this->format) {
            
$this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
        }

        
$this->overwrite('');
    }

    
/**
     * Sets the progress bar format.
     *
     * @param string $format The format
     */
    
private function setRealFormat($format)
    {
        
// 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;
        }

        
$this->formatLineCount substr_count($this->format"\n");
    }

    
/**
     * Sets the progress bar maximal steps.
     *
     * @param int $max The progress bar max steps
     */
    
private function setMaxSteps($max)
    {
        
$this->max max(0, (int) $max);
        
$this->stepWidth $this->max Helper::strlen($this->max) : 4;
    }

    
/**
     * Overwrites a previous message to the output.
     *
     * @param string $message The message
     */
    
private function overwrite($message)
    {
        if (
$this->overwrite) {
            if (!
$this->firstRun) {
                
// Erase previous lines
                
if ($this->formatLineCount 0) {
                    
$message str_repeat("\x1B[1A\x1B[2K"$this->formatLineCount).$message;
                }

                
// Move the cursor to the beginning of the line and erase the line
                
$message "\x0D\x1B[2K$message";
            }
        } elseif (
$this->step 0) {
            
$message \PHP_EOL.$message;
        }

        
$this->firstRun false;

        
$this->output->write($message);
    }

    private function 
determineBestFormat()
    {
        switch (
$this->output->getVerbosity()) {
            
// OutputInterface::VERBOSITY_QUIET: display is disabled anyway
            
case OutputInterface::VERBOSITY_VERBOSE:
                return 
$this->max 'verbose' 'verbose_nomax';
            case 
OutputInterface::VERBOSITY_VERY_VERBOSE:
                return 
$this->max 'very_verbose' 'very_verbose_nomax';
            case 
OutputInterface::VERBOSITY_DEBUG:
                return 
$this->max 'debug' 'debug_nomax';
            default:
                return 
$this->max 'normal' 'normal_nomax';
        }
    }

    private static function 
initPlaceholderFormatters()
    {
        return [
            
'bar' => function (self $barOutputInterface $output) {
                
$completeBars floor($bar->getMaxSteps() > $bar->getProgressPercent() * $bar->getBarWidth() : $bar->getProgress() % $bar->getBarWidth());
                
$display str_repeat($bar->getBarCharacter(), $completeBars);
                if (
$completeBars $bar->getBarWidth()) {
                    
$emptyBars $bar->getBarWidth() - $completeBars Helper::strlenWithoutDecoration($output->getFormatter(), $bar->getProgressCharacter());
                    
$display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars);
                }

                return 
$display;
            },
            
'elapsed' => function (self $bar) {
                return 
Helper::formatTime(time() - $bar->getStartTime());
            },
            
'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.');
                }

                if (!
$bar->getProgress()) {
                    
$remaining 0;
                } else {
                    
$remaining round((time() - $bar->getStartTime()) / $bar->getProgress() * ($bar->getMaxSteps() - $bar->getProgress()));
                }

                return 
Helper::formatTime($remaining);
            },
            
'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.');
                }

                if (!
$bar->getProgress()) {
                    
$estimated 0;
                } else {
                    
$estimated round((time() - $bar->getStartTime()) / $bar->getProgress() * $bar->getMaxSteps());
                }

                return 
Helper::formatTime($estimated);
            },
            
'memory' => function (self $bar) {
                return 
Helper::formatMemory(memory_get_usage(true));
            },
            
'current' => function (self $bar) {
                return 
str_pad($bar->getProgress(), $bar->getStepWidth(), ' '\STR_PAD_LEFT);
            },
            
'max' => function (self $bar) {
                return 
$bar->getMaxSteps();
            },
            
'percent' => function (self $bar) {
                return 
floor($bar->getProgressPercent() * 100);
            },
        ];
    }

    private static function 
initFormats()
    {
        return [
            
'normal' => ' %current%/%max% [%bar%] %percent:3s%%',
            
'normal_nomax' => ' %current% [%bar%]',

            
'verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%',
            
'verbose_nomax' => ' %current% [%bar%] %elapsed:6s%',

            
'very_verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%',
            
'very_verbose_nomax' => ' %current% [%bar%] %elapsed:6s%',

            
'debug' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%',
            
'debug_nomax' => ' %current% [%bar%] %elapsed:6s% %memory:6s%',
        ];
    }

    
/**
     * @return string
     */
    
private function buildLine()
    {
        
$regex "{%([a-z\-_]+)(?:\:([^%]+))?%}i";
        
$callback = function ($matches) {
            if (
$formatter $this::getPlaceholderFormatterDefinition($matches[1])) {
                
$text \call_user_func($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(function ($subLine) {
            return 
Helper::strlenWithoutDecoration($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 <[email protected]>
 *
 * 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 <[email protected]>
 */
interface HelperInterface
{
    
/**
     * Sets the helper set associated with this helper.
     */
    
public function setHelperSet(HelperSet $helperSet null);

    
/**
     * Gets the helper set associated with this helper.
     *
     * @return HelperSet A HelperSet instance
     */
    
public function getHelperSet();

    
/**
     * Returns the canonical name of this helper.
     *
     * @return string The canonical name
     */
    
public function getName();
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 * @author Саша Стаменковић <[email protected]>
 */
class TableStyle
{
    private 
$paddingChar ' ';
    private 
$horizontalBorderChar '-';
    private 
$verticalBorderChar '|';
    private 
$crossingChar '+';
    private 
$cellHeaderFormat '<info>%s</info>';
    private 
$cellRowFormat '%s';
    private 
$cellRowContentFormat ' %s ';
    private 
$borderFormat '%s';
    private 
$padType \STR_PAD_RIGHT;

    
/**
     * Sets padding character, used for cell padding.
     *
     * @param string $paddingChar
     *
     * @return $this
     */
    
public function setPaddingChar($paddingChar)
    {
        if (!
$paddingChar) {
            throw new 
LogicException('The padding char must not be empty.');
        }

        
$this->paddingChar $paddingChar;

        return 
$this;
    }

    
/**
     * Gets padding character, used for cell padding.
     *
     * @return string
     */
    
public function getPaddingChar()
    {
        return 
$this->paddingChar;
    }

    
/**
     * Sets horizontal border character.
     *
     * @param string $horizontalBorderChar
     *
     * @return $this
     */
    
public function setHorizontalBorderChar($horizontalBorderChar)
    {
        
$this->horizontalBorderChar $horizontalBorderChar;

        return 
$this;
    }

    
/**
     * Gets horizontal border character.
     *
     * @return string
     */
    
public function getHorizontalBorderChar()
    {
        return 
$this->horizontalBorderChar;
    }

    
/**
     * Sets vertical border character.
     *
     * @param string $verticalBorderChar
     *
     * @return $this
     */
    
public function setVerticalBorderChar($verticalBorderChar)
    {
        
$this->verticalBorderChar $verticalBorderChar;

        return 
$this;
    }

    
/**
     * Gets vertical border character.
     *
     * @return string
     */
    
public function getVerticalBorderChar()
    {
        return 
$this->verticalBorderChar;
    }

    
/**
     * Sets crossing character.
     *
     * @param string $crossingChar
     *
     * @return $this
     */
    
public function setCrossingChar($crossingChar)
    {
        
$this->crossingChar $crossingChar;

        return 
$this;
    }

    
/**
     * Gets crossing character.
     *
     * @return string
     */
    
public function getCrossingChar()
    {
        return 
$this->crossingChar;
    }

    
/**
     * Sets header cell format.
     *
     * @param string $cellHeaderFormat
     *
     * @return $this
     */
    
public function setCellHeaderFormat($cellHeaderFormat)
    {
        
$this->cellHeaderFormat $cellHeaderFormat;

        return 
$this;
    }

    
/**
     * Gets header cell format.
     *
     * @return string
     */
    
public function getCellHeaderFormat()
    {
        return 
$this->cellHeaderFormat;
    }

    
/**
     * Sets row cell format.
     *
     * @param string $cellRowFormat
     *
     * @return $this
     */
    
public function setCellRowFormat($cellRowFormat)
    {
        
$this->cellRowFormat $cellRowFormat;

        return 
$this;
    }

    
/**
     * Gets row cell format.
     *
     * @return string
     */
    
public function getCellRowFormat()
    {
        return 
$this->cellRowFormat;
    }

    
/**
     * Sets row cell content format.
     *
     * @param string $cellRowContentFormat
     *
     * @return $this
     */
    
public function setCellRowContentFormat($cellRowContentFormat)
    {
        
$this->cellRowContentFormat $cellRowContentFormat;

        return 
$this;
    }

    
/**
     * Gets row cell content format.
     *
     * @return string
     */
    
public function getCellRowContentFormat()
    {
        return 
$this->cellRowContentFormat;
    }

    
/**
     * Sets table border format.
     *
     * @param string $borderFormat
     *
     * @return $this
     */
    
public function setBorderFormat($borderFormat)
    {
        
$this->borderFormat $borderFormat;

        return 
$this;
    }

    
/**
     * Gets table border format.
     *
     * @return string
     */
    
public function getBorderFormat()
    {
        return 
$this->borderFormat;
    }

    
/**
     * Sets cell padding type.
     *
     * @param int $padType STR_PAD_*
     *
     * @return $this
     */
    
public function setPadType($padType)
    {
        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.
     *
     * @return int
     */
    
public function getPadType()
    {
        return 
$this->padType;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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\OutputFormatterStyle;
use 
Symfony\Component\Console\Input\InputInterface;
use 
Symfony\Component\Console\Input\StreamableInputInterface;
use 
Symfony\Component\Console\Output\ConsoleOutputInterface;
use 
Symfony\Component\Console\Output\OutputInterface;
use 
Symfony\Component\Console\Question\ChoiceQuestion;
use 
Symfony\Component\Console\Question\Question;
use 
Symfony\Component\Console\Terminal;

/**
 * The QuestionHelper class provides helpers to interact with the user.
 *
 * @author Fabien Potencier <[email protected]>
 */
class QuestionHelper extends Helper
{
    private 
$inputStream;
    private static 
$shell;
    private static 
$stty true;

    
/**
     * 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 $inputOutputInterface $outputQuestion $question)
    {
        if (
$output instanceof ConsoleOutputInterface) {
            
$output $output->getErrorOutput();
        }

        if (!
$input->isInteractive()) {
            
$default $question->getDefault();

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

            if (
$validator $question->getValidator()) {
                return 
\call_user_func($question->getValidator(), $default);
            } elseif (
$question instanceof ChoiceQuestion) {
                
$choices $question->getChoices();

                if (!
$question->isMultiselect()) {
                    return isset(
$choices[$default]) ? $choices[$default] : $default;
                }

                
$default explode(','$default);
                foreach (
$default as $k => $v) {
                    
$v trim($v);
                    
$default[$k] = isset($choices[$v]) ? $choices[$v] : $v;
                }
            }

            return 
$default;
        }

        if (
$input instanceof StreamableInputInterface && $stream $input->getStream()) {
            
$this->inputStream $stream;
        }

        if (!
$question->getValidator()) {
            return 
$this->doAsk($output$question);
        }

        
$interviewer = function () use ($output$question) {
            return 
$this->doAsk($output$question);
        };

        return 
$this->validateAttempts($interviewer$output$question);
    }

    
/**
     * Sets the input stream to read from when interacting with the user.
     *
     * This is mainly useful for testing purpose.
     *
     * @deprecated since version 3.2, to be removed in 4.0. Use
     *             StreamableInputInterface::setStream() instead.
     *
     * @param resource $stream The input stream
     *
     * @throws InvalidArgumentException In case the stream is not a resource
     */
    
public function setInputStream($stream)
    {
        @
trigger_error(sprintf('The %s() method is deprecated since Symfony 3.2 and will be removed in 4.0. Use %s::setStream() instead.'__METHOD__StreamableInputInterface::class), \E_USER_DEPRECATED);

        if (!
\is_resource($stream)) {
            throw new 
InvalidArgumentException('Input stream must be a valid resource.');
        }

        
$this->inputStream $stream;
    }

    
/**
     * Returns the helper's input stream.
     *
     * @deprecated since version 3.2, to be removed in 4.0. Use
     *             StreamableInputInterface::getStream() instead.
     *
     * @return resource
     */
    
public function getInputStream()
    {
        if (
=== \func_num_args() || func_get_arg(0)) {
            @
trigger_error(sprintf('The %s() method is deprecated since Symfony 3.2 and will be removed in 4.0. Use %s::getStream() instead.'__METHOD__StreamableInputInterface::class), \E_USER_DEPRECATED);
        }

        return 
$this->inputStream;
    }

    
/**
     * {@inheritdoc}
     */
    
public function getName()
    {
        return 
'question';
    }

    
/**
     * Prevents usage of stty.
     */
    
public static function disableStty()
    {
        
self::$stty false;
    }

    
/**
     * Asks the question to the user.
     *
     * @return bool|mixed|string|null
     *
     * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
     */
    
private function doAsk(OutputInterface $outputQuestion $question)
    {
        
$this->writePrompt($output$question);

        
$inputStream $this->inputStream ?: \STDIN;
        
$autocomplete $question->getAutocompleterValues();

        if (
\function_exists('sapi_windows_cp_set')) {
            
// Codepage used by cmd.exe on Windows to allow special characters (éàüñ).
            
@sapi_windows_cp_set(1252);
        }

        if (
null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) {
            
$ret false;
            if (
$question->isHidden()) {
                try {
                    
$ret trim($this->getHiddenResponse($output$inputStream));
                } catch (
RuntimeException $e) {
                    if (!
$question->isHiddenFallback()) {
                        throw 
$e;
                    }
                }
            }

            if (
false === $ret) {
                
$ret fgets($inputStream4096);
                if (
false === $ret) {
                    throw new 
RuntimeException('Aborted.');
                }
                
$ret trim($ret);
            }
        } else {
            
$ret trim($this->autocomplete($output$question$inputStream\is_array($autocomplete) ? $autocomplete iterator_to_array($autocompletefalse)));
        }

        
$ret \strlen($ret) > $ret $question->getDefault();

        if (
$normalizer $question->getNormalizer()) {
            return 
$normalizer($ret);
        }

        return 
$ret;
    }

    
/**
     * Outputs the question prompt.
     */
    
protected function writePrompt(OutputInterface $outputQuestion $question)
    {
        
$message $question->getQuestion();

        if (
$question instanceof ChoiceQuestion) {
            
$output->writeln(array_merge([
                
$question->getQuestion(),
            ], 
$this->formatChoiceQuestionChoices($question'info')));

            
$message $question->getPrompt();
        }

        
$output->write($message);
    }

    
/**
     * @param string $tag
     *
     * @return string[]
     */
    
protected function formatChoiceQuestionChoices(ChoiceQuestion $question$tag)
    {
        
$messages = [];

        
$maxWidth max(array_map('self::strlen'array_keys($choices $question->getChoices())));

        foreach (
$choices as $key => $value) {
            
$padding str_repeat(' '$maxWidth self::strlen($key));

            
$messages[] = sprintf("  [<$tag>%s$padding</$tag>] %s"$key$value);
        }

        return 
$messages;
    }

    
/**
     * Outputs an error message.
     */
    
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
     *
     * @return string
     */
    
private function autocomplete(OutputInterface $outputQuestion $question$inputStream, array $autocomplete)
    {
        
$fullChoice '';
        
$ret '';

        
$i 0;
        
$ofs = -1;
        
$matches $autocomplete;
        
$numMatches \count($matches);

        
$sttyMode shell_exec('stty -g');

        
// 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)) {
            
$c fread($inputStream1);

            
// 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(sprintf('stty %s'$sttyMode));
                throw new 
RuntimeException('Aborted.');
            } elseif (
"\177" === $c) { // Backspace Character
                
if (=== $numMatches && !== $i) {
                    --
$i;
                    
$fullChoice self::substr($fullChoice0$i);
                    
// Move cursor backwards
                    
$output->write("\033[1D");
                }

                if (
=== $i) {
                    
$ofs = -1;
                    
$matches $autocomplete;
                    
$numMatches \count($matches);
                } else {
                    
$numMatches 0;
                }

                
// Pop the last character off the end of our string
                
$ret self::substr($ret0$i);
            } elseif (
"\033" === $c) {
                
// Did we read an escape sequence?
                
$c .= fread($inputStream2);

                
// A = Up Arrow. B = Down Arrow
                
if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) {
                    if (
'A' === $c[2] && -=== $ofs) {
                        
$ofs 0;
                    }

                    if (
=== $numMatches) {
                        continue;
                    }

                    
$ofs += ('A' === $c[2]) ? -1;
                    
$ofs = ($numMatches $ofs) % $numMatches;
                }
            } elseif (
\ord($c) < 32) {
                if (
"\t" === $c || "\n" === $c) {
                    if (
$numMatches && -!== $ofs) {
                        
$ret $matches[$ofs];
                        
// Echo out remaining chars for current match
                        
$remainingCharacters substr($ret\strlen(trim($this->mostRecentlyEnteredValue($fullChoice))));
                        
$output->write($remainingCharacters);
                        
$fullChoice .= $remainingCharacters;
                        
$i self::strlen($fullChoice);
                    }

                    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 as $value) {
                    
// If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
                    
if (=== strpos($value$tempRet)) {
                        
$matches[$numMatches++] = $value;
                    }
                }
            }

            
// Erase characters from cursor to end of line
            
$output->write("\033[K");

            if (
$numMatches && -!== $ofs) {
                
// Save cursor position
                
$output->write("\0337");
                
// Write highlighted text, complete the partially entered response
                
$charactersEntered \strlen(trim($this->mostRecentlyEnteredValue($fullChoice)));
                
$output->write('<hl>'.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $charactersEntered)).'</hl>');
                
// Restore cursor position
                
$output->write("\0338");
            }
        }

        
// Reset stty so it behaves normally again
        
shell_exec(sprintf('stty %s'$sttyMode));

        return 
$fullChoice;
    }

    private function 
mostRecentlyEnteredValue($entered)
    {
        
// Determine the most recent value that the user entered
        
if (false === strpos($entered',')) {
            return 
$entered;
        }

        
$choices explode(','$entered);
        if (
\strlen($lastChoice trim($choices[\count($choices) - 1])) > 0) {
            return 
$lastChoice;
        }

        return 
$entered;
    }

    
/**
     * Gets a hidden response from user.
     *
     * @param OutputInterface $output      An Output instance
     * @param resource        $inputStream The handler resource
     *
     * @return string The answer
     *
     * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
     */
    
private function getHiddenResponse(OutputInterface $output$inputStream)
    {
        if (
'\\' === \DIRECTORY_SEPARATOR) {
            
$exe __DIR__.'/../Resources/bin/hiddeninput.exe';

            
// handle code running from a phar
            
if ('phar:' === substr(__FILE__05)) {
                
$tmpExe sys_get_temp_dir().'/hiddeninput.exe';
                
copy($exe$tmpExe);
                
$exe $tmpExe;
            }

            
$value rtrim(shell_exec($exe));
            
$output->writeln('');

            if (isset(
$tmpExe)) {
                
unlink($tmpExe);
            }

            return 
$value;
        }

        if (
self::$stty && Terminal::hasSttyAvailable()) {
            
$sttyMode shell_exec('stty -g');

            
shell_exec('stty -echo');
            
$value fgets($inputStream4096);
            
shell_exec(sprintf('stty %s'$sttyMode));

            if (
false === $value) {
                throw new 
RuntimeException('Aborted.');
            }

            
$value trim($value);
            
$output->writeln('');

            return 
$value;
        }

        if (
false !== $shell $this->getShell()) {
            
$readCmd 'csh' === $shell 'set mypassword = $<' 'read -r mypassword';
            
$command sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'"$shell$readCmd);
            
$value rtrim(shell_exec($command));
            
$output->writeln('');

            return 
$value;
        }

        throw new 
RuntimeException('Unable to hide the response.');
    }

    
/**
     * Validates an attempt.
     *
     * @param callable        $interviewer A callable that will ask for a question and return the result
     * @param OutputInterface $output      An Output instance
     * @param Question        $question    A Question instance
     *
     * @return mixed The validated response
     *
     * @throws \Exception In case the max number of attempts has been reached and no valid response has been given
     */
    
private function validateAttempts(callable $interviewerOutputInterface $outputQuestion $question)
    {
        
$error null;
        
$attempts $question->getMaxAttempts();
        while (
null === $attempts || $attempts--) {
            if (
null !== $error) {
                
$this->writeError($output$error);
            }

            try {
                return 
\call_user_func($question->getValidator(), $interviewer());
            } catch (
RuntimeException $e) {
                throw 
$e;
            } catch (
\Exception $error) {
            }
        }

        throw 
$error;
    }

    
/**
     * Returns a valid unix shell.
     *
     * @return string|bool The valid shell name, false in case no valid shell is found
     */
    
private function getShell()
    {
        if (
null !== self::$shell) {
            return 
self::$shell;
        }

        
self::$shell false;

        if (
file_exists('/usr/bin/env')) {
            
// handle other OSs with bash/zsh/ksh/csh if available to hide the answer
            
$test "/usr/bin/env %s -c 'echo OK' 2> /dev/null";
            foreach ([
'bash''zsh''ksh''csh'] as $sh) {
                if (
'OK' === rtrim(shell_exec(sprintf($test$sh)))) {
                    
self::$shell $sh;
                    break;
                }
            }
        }

        return 
self::$shell;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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\Output\OutputInterface;

/**
 * Provides helpers to display a table.
 *
 * @author Fabien Potencier <[email protected]>
 * @author Саша Стаменковић <[email protected]>
 * @author Abdellatif Ait boudad <[email protected]>
 * @author Max Grigorian <[email protected]>
 */
class Table
{
    
/**
     * Table headers.
     */
    
private $headers = [];

    
/**
     * Table rows.
     */
    
private $rows = [];

    
/**
     * Column widths cache.
     */
    
private $effectiveColumnWidths = [];

    
/**
     * Number of columns cache.
     *
     * @var int
     */
    
private $numberOfColumns;

    
/**
     * @var OutputInterface
     */
    
private $output;

    
/**
     * @var TableStyle
     */
    
private $style;

    
/**
     * @var array
     */
    
private $columnStyles = [];

    
/**
     * User set column widths.
     *
     * @var array
     */
    
private $columnWidths = [];

    private static 
$styles;

    public function 
__construct(OutputInterface $output)
    {
        
$this->output $output;

        if (!
self::$styles) {
            
self::$styles self::initStyles();
        }

        
$this->setStyle('default');
    }

    
/**
     * Sets a style definition.
     *
     * @param string     $name  The style name
     * @param TableStyle $style A TableStyle instance
     */
    
public static function setStyleDefinition($nameTableStyle $style)
    {
        if (!
self::$styles) {
            
self::$styles self::initStyles();
        }

        
self::$styles[$name] = $style;
    }

    
/**
     * Gets a style definition by name.
     *
     * @param string $name The style name
     *
     * @return TableStyle
     */
    
public static function getStyleDefinition($name)
    {
        if (!
self::$styles) {
            
self::$styles self::initStyles();
        }

        if (isset(
self::$styles[$name])) {
            return 
self::$styles[$name];
        }

        throw new 
InvalidArgumentException(sprintf('Style "%s" is not defined.'$name));
    }

    
/**
     * Sets table style.
     *
     * @param TableStyle|string $name The style name or a TableStyle instance
     *
     * @return $this
     */
    
public function setStyle($name)
    {
        
$this->style $this->resolveStyle($name);

        return 
$this;
    }

    
/**
     * Gets the current table style.
     *
     * @return TableStyle
     */
    
public function getStyle()
    {
        return 
$this->style;
    }

    
/**
     * Sets table column style.
     *
     * @param int               $columnIndex Column index
     * @param TableStyle|string $name        The style name or a TableStyle instance
     *
     * @return $this
     */
    
public function setColumnStyle($columnIndex$name)
    {
        
$columnIndex = (int) $columnIndex;

        
$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.
     *
     * @param int $columnIndex Column index
     *
     * @return TableStyle
     */
    
public function getColumnStyle($columnIndex)
    {
        if (isset(
$this->columnStyles[$columnIndex])) {
            return 
$this->columnStyles[$columnIndex];
        }

        return 
$this->getStyle();
    }

    
/**
     * Sets the minimum width of a column.
     *
     * @param int $columnIndex Column index
     * @param int $width       Minimum column width in characters
     *
     * @return $this
     */
    
public function setColumnWidth($columnIndex$width)
    {
        
$this->columnWidths[(int) $columnIndex] = (int) $width;

        return 
$this;
    }

    
/**
     * Sets the minimum width of all columns.
     *
     * @return $this
     */
    
public function setColumnWidths(array $widths)
    {
        
$this->columnWidths = [];
        foreach (
$widths as $index => $width) {
            
$this->setColumnWidth($index$width);
        }

        return 
$this;
    }

    public function 
setHeaders(array $headers)
    {
        
$headers array_values($headers);
        if (!empty(
$headers) && !\is_array($headers[0])) {
            
$headers = [$headers];
        }

        
$this->headers $headers;

        return 
$this;
    }

    public function 
setRows(array $rows)
    {
        
$this->rows = [];

        return 
$this->addRows($rows);
    }

    public function 
addRows(array $rows)
    {
        foreach (
$rows as $row) {
            
$this->addRow($row);
        }

        return 
$this;
    }

    public function 
addRow($row)
    {
        if (
$row instanceof TableSeparator) {
            
$this->rows[] = $row;

            return 
$this;
        }

        if (!
\is_array($row)) {
            throw new 
InvalidArgumentException('A row must be an array or a TableSeparator instance.');
        }

        
$this->rows[] = array_values($row);

        return 
$this;
    }

    public function 
setRow($column, array $row)
    {
        
$this->rows[$column] = $row;

        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 |
     *     +---------------+-----------------------+------------------+
     */
    
public function render()
    {
        
$this->calculateNumberOfColumns();
        
$rows $this->buildTableRows($this->rows);
        
$headers $this->buildTableRows($this->headers);

        
$this->calculateColumnsWidth(array_merge($headers$rows));

        
$this->renderRowSeparator();
        if (!empty(
$headers)) {
            foreach (
$headers as $header) {
                
$this->renderRow($header$this->style->getCellHeaderFormat());
                
$this->renderRowSeparator();
            }
        }
        foreach (
$rows as $row) {
            if (
$row instanceof TableSeparator) {
                
$this->renderRowSeparator();
            } else {
                
$this->renderRow($row$this->style->getCellRowFormat());
            }
        }
        if (!empty(
$rows)) {
            
$this->renderRowSeparator();
        }

        
$this->cleanup();
    }

    
/**
     * Renders horizontal header separator.
     *
     * Example:
     *
     *     +-----+-----------+-------+
     */
    
private function renderRowSeparator()
    {
        if (
=== $count $this->numberOfColumns) {
            return;
        }

        if (!
$this->style->getHorizontalBorderChar() && !$this->style->getCrossingChar()) {
            return;
        }

        
$markup $this->style->getCrossingChar();
        for (
$column 0$column $count; ++$column) {
            
$markup .= str_repeat($this->style->getHorizontalBorderChar(), $this->effectiveColumnWidths[$column]).$this->style->getCrossingChar();
        }

        
$this->output->writeln(sprintf($this->style->getBorderFormat(), $markup));
    }

    
/**
     * Renders vertical column separator.
     */
    
private function renderColumnSeparator()
    {
        return 
sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar());
    }

    
/**
     * Renders table row.
     *
     * Example:
     *
     *     | 9971-5-0210-0 | A Tale of Two Cities  | Charles Dickens  |
     *
     * @param string $cellFormat
     */
    
private function renderRow(array $row$cellFormat)
    {
        if (empty(
$row)) {
            return;
        }

        
$rowContent $this->renderColumnSeparator();
        foreach (
$this->getRowColumns($row) as $column) {
            
$rowContent .= $this->renderCell($row$column$cellFormat);
            
$rowContent .= $this->renderColumnSeparator();
        }
        
$this->output->writeln($rowContent);
    }

    
/**
     * Renders table cell with padding.
     *
     * @param int    $column
     * @param string $cellFormat
     */
    
private function renderCell(array $row$column$cellFormat)
    {
        
$cell = isset($row[$column]) ? $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
        
if (false !== $encoding mb_detect_encoding($cellnulltrue)) {
            
$width += \strlen($cell) - mb_strwidth($cell$encoding);
        }

        
$style $this->getColumnStyle($column);

        if (
$cell instanceof TableSeparator) {
            return 
sprintf($style->getBorderFormat(), str_repeat($style->getHorizontalBorderChar(), $width));
        }

        
$width += Helper::strlen($cell) - Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell);
        
$content sprintf($style->getCellRowContentFormat(), $cell);

        return 
sprintf($cellFormatstr_pad($content$width$style->getPaddingChar(), $style->getPadType()));
    }

    
/**
     * Calculate number of columns for this table.
     */
    
private function calculateNumberOfColumns()
    {
        if (
null !== $this->numberOfColumns) {
            return;
        }

        
$columns = [0];
        foreach (
array_merge($this->headers$this->rows) as $row) {
            if (
$row instanceof TableSeparator) {
                continue;
            }

            
$columns[] = $this->getNumberOfColumns($row);
        }

        
$this->numberOfColumns max($columns);
    }

    private function 
buildTableRows($rows)
    {
        
$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) {
                if (!
strstr($cell"\n")) {
                    continue;
                }
                
$lines explode("\n"str_replace("\n""<fg=default;bg=default>\n</>"$cell));
                foreach (
$lines as $lineKey => $line) {
                    if (
$cell instanceof TableCell) {
                        
$line = new TableCell($line, ['colspan' => $cell->getColspan()]);
                    }
                    if (
=== $lineKey) {
                        
$rows[$rowKey][$column] = $line;
                    } else {
                        
$unmergedRows[$rowKey][$lineKey][$column] = $line;
                    }
                }
            }
        }

        
$tableRows = [];
        foreach (
$rows as $rowKey => $row) {
            
$tableRows[] = $this->fillCells($row);
            if (isset(
$unmergedRows[$rowKey])) {
                
$tableRows array_merge($tableRows$unmergedRows[$rowKey]);
            }
        }

        return 
$tableRows;
    }

    
/**
     * fill rows that contains rowspan > 1.
     *
     * @param int $line
     *
     * @return array
     *
     * @throws InvalidArgumentException
     */
    
private function fillNextRows(array $rows$line)
    {
        
$unmergedRows = [];
        foreach (
$rows[$line] as $column => $cell) {
            if (
null !== $cell && !$cell instanceof TableCell && !is_scalar($cell) && !(\is_object($cell) && method_exists($cell'__toString'))) {
                throw new 
InvalidArgumentException(sprintf('A cell must be a TableCell, a scalar or an object implementing "__toString()", "%s" given.'\gettype($cell)));
            }
            if (
$cell instanceof TableCell && $cell->getRowspan() > 1) {
                
$nbLines $cell->getRowspan() - 1;
                
$lines = [$cell];
                if (
strstr($cell"\n")) {
                    
$lines explode("\n"str_replace("\n""<fg=default;bg=default>\n</>"$cell));
                    
$nbLines \count($lines) > $nbLines substr_count($cell"\n") : $nbLines;

                    
$rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan()]);
                    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 = isset($lines[$unmergedRowKey $line]) ? $lines[$unmergedRowKey $line] : '';
                    
$unmergedRows[$unmergedRowKey][$column] = new TableCell($value, ['colspan' => $cell->getColspan()]);
                    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], $cellKey0, [$cell]);
                }
            } else {
                
$row $this->copyRow($rows$unmergedRowKey 1);
                foreach (
$unmergedRow as $column => $cell) {
                    if (!empty(
$cell)) {
                        
$row[$column] = $unmergedRow[$column];
                    }
                }
                
array_splice($rows$unmergedRowKey0, [$row]);
            }
        }

        return 
$rows;
    }

    
/**
     * fill cells for a row that contains colspan > 1.
     *
     * @return array
     */
    
private function fillCells($row)
    {
        
$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;
    }

    
/**
     * @param int $line
     *
     * @return array
     */
    
private function copyRow(array $rows$line)
    {
        
$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.
     *
     * @return int
     */
    
private function getNumberOfColumns(array $row)
    {
        
$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.
     *
     * @return array
     */
    
private function getRowColumns(array $row)
    {
        
$columns range(0$this->numberOfColumns 1);
        foreach (
$row as $cellKey => $cell) {
            if (
$cell instanceof TableCell && $cell->getColspan() > 1) {
                
// exclude grouped columns.
                
$columns array_diff($columnsrange($cellKey 1$cellKey $cell->getColspan() - 1));
            }
        }

        return 
$columns;
    }

    
/**
     * Calculates columns widths.
     */
    
private function calculateColumnsWidth(array $rows)
    {
        for (
$column 0$column $this->numberOfColumns; ++$column) {
            
$lengths = [];
            foreach (
$rows 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::strlen($textContent);
                        if (
$textLength 0) {
                            
$contentColumns str_split($textContentceil($textLength $cell->getColspan()));
                            foreach (
$contentColumns as $position => $content) {
                                
$row[$i $position] = $content;
                            }
                        }
                    }
                }

                
$lengths[] = $this->getCellWidth($row$column);
            }

            
$this->effectiveColumnWidths[$column] = max($lengths) + Helper::strlen($this->style->getCellRowContentFormat()) - 2;
        }
    }

    
/**
     * Gets column width.
     *
     * @return int
     */
    
private function getColumnSeparatorWidth()
    {
        return 
Helper::strlen(sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar()));
    }

    
/**
     * Gets cell width.
     *
     * @param int $column
     *
     * @return int
     */
    
private function getCellWidth(array $row$column)
    {
        
$cellWidth 0;

        if (isset(
$row[$column])) {
            
$cell $row[$column];
            
$cellWidth Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell);
        }

        
$columnWidth = isset($this->columnWidths[$column]) ? $this->columnWidths[$column] : 0;

        return 
max($cellWidth$columnWidth);
    }

    
/**
     * Called after rendering to cleanup cache data.
     */
    
private function cleanup()
    {
        
$this->effectiveColumnWidths = [];
        
$this->numberOfColumns null;
    }

    private static function 
initStyles()
    {
        
$borderless = new TableStyle();
        
$borderless
            
->setHorizontalBorderChar('=')
            ->
setVerticalBorderChar(' ')
            ->
setCrossingChar(' ')
        ;

        
$compact = new TableStyle();
        
$compact
            
->setHorizontalBorderChar('')
            ->
setVerticalBorderChar(' ')
            ->
setCrossingChar('')
            ->
setCellRowContentFormat('%s')
        ;

        
$styleGuide = new TableStyle();
        
$styleGuide
            
->setHorizontalBorderChar('-')
            ->
setVerticalBorderChar(' ')
            ->
setCrossingChar(' ')
            ->
setCellHeaderFormat('%s')
        ;

        return [
            
'default' => new TableStyle(),
            
'borderless' => $borderless,
            
'compact' => $compact,
            
'symfony-style-guide' => $styleGuide,
        ];
    }

    private function 
resolveStyle($name)
    {
        if (
$name instanceof TableStyle) {
            return 
$name;
        }

        if (isset(
self::$styles[$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 <[email protected]>
 *
 * 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 <[email protected]>
 */
abstract class InputAwareHelper extends Helper implements InputAwareInterface
{
    protected 
$input;

    
/**
     * {@inheritdoc}
     */
    
public function setInput(InputInterface $input)
    {
        
$this->input $input;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class TableSeparator extends TableCell
{
    public function 
__construct(array $options = [])
    {
        
parent::__construct(''$options);
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class ProgressIndicator
{
    private 
$output;
    private 
$startTime;
    private 
$format;
    private 
$message;
    private 
$indicatorValues;
    private 
$indicatorCurrent;
    private 
$indicatorChangeInterval;
    private 
$indicatorUpdateTime;
    private 
$started false;

    private static 
$formatters;
    private static 
$formats;

    
/**
     * @param string|null $format                  Indicator format
     * @param int         $indicatorChangeInterval Change interval in milliseconds
     * @param array|null  $indicatorValues         Animated indicator characters
     */
    
public function __construct(OutputInterface $output$format null$indicatorChangeInterval 100$indicatorValues null)
    {
        
$this->output $output;

        if (
null === $format) {
            
$format $this->determineBestFormat();
        }

        if (
null === $indicatorValues) {
            
$indicatorValues = ['-''\\''|''/'];
        }

        
$indicatorValues array_values($indicatorValues);

        if (
\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.
     *
     * @param string|null $message
     */
    
public function setMessage($message)
    {
        
$this->message $message;

        
$this->display();
    }

    
/**
     * Starts the indicator output.
     *
     * @param $message
     */
    
public function start($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.
     */
    
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.
     *
     * @param $message
     */
    
public function finish($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.
     *
     * @param string $name The format name
     *
     * @return string|null A format string
     */
    
public static function getFormatDefinition($name)
    {
        if (!
self::$formats) {
            
self::$formats self::initFormats();
        }

        return isset(
self::$formats[$name]) ? self::$formats[$name] : null;
    }

    
/**
     * Sets a placeholder formatter for a given name.
     *
     * This method also allow you to override an existing placeholder.
     *
     * @param string   $name     The placeholder name (including the delimiter char like %)
     * @param callable $callable A PHP callable
     */
    
public static function setPlaceholderFormatterDefinition($name$callable)
    {
        if (!
self::$formatters) {
            
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 %)
     *
     * @return callable|null A PHP callable
     */
    
public static function getPlaceholderFormatterDefinition($name)
    {
        if (!
self::$formatters) {
            
self::$formatters self::initPlaceholderFormatters();
        }

        return isset(
self::$formatters[$name]) ? self::$formatters[$name] : null;
    }

    private function 
display()
    {
        if (
OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
            return;
        }

        
$self $this;

        
$this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) use ($self) {
            if (
$formatter $self::getPlaceholderFormatterDefinition($matches[1])) {
                return 
\call_user_func($formatter$self);
            }

            return 
$matches[0];
        }, 
$this->format));
    }

    private function 
determineBestFormat()
    {
        switch (
$this->output->getVerbosity()) {
            
// OutputInterface::VERBOSITY_QUIET: display is disabled anyway
            
case OutputInterface::VERBOSITY_VERBOSE:
                return 
$this->output->isDecorated() ? 'verbose' 'verbose_no_ansi';
            case 
OutputInterface::VERBOSITY_VERY_VERBOSE:
            case 
OutputInterface::VERBOSITY_DEBUG:
                return 
$this->output->isDecorated() ? 'very_verbose' 'very_verbose_no_ansi';
            default:
                return 
$this->output->isDecorated() ? 'normal' 'normal_no_ansi';
        }
    }

    
/**
     * Overwrites a previous message to the output.
     *
     * @param string $message The message
     */
    
private function overwrite($message)
    {
        if (
$this->output->isDecorated()) {
            
$this->output->write("\x0D\x1B[2K");
            
$this->output->write($message);
        } else {
            
$this->output->writeln($message);
        }
    }

    private function 
getCurrentTimeInMilliseconds()
    {
        return 
round(microtime(true) * 1000);
    }

    private static function 
initPlaceholderFormatters()
    {
        return [
            
'indicator' => function (self $indicator) {
                return 
$indicator->indicatorValues[$indicator->indicatorCurrent \count($indicator->indicatorValues)];
            },
            
'message' => function (self $indicator) {
                return 
$indicator->message;
            },
            
'elapsed' => function (self $indicator) {
                return 
Helper::formatTime(time() - $indicator->startTime);
            },
            
'memory' => function () {
                return 
Helper::formatMemory(memory_get_usage(true));
            },
        ];
    }

    private static function 
initFormats()
    {
        return [
            
'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%)',
        ];
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class TableCell
{
    private 
$value;
    private 
$options = [
        
'rowspan' => 1,
        
'colspan' => 1,
    ];

    
/**
     * @param string $value
     */
    
public function __construct($value '', array $options = [])
    {
        if (
is_numeric($value) && !\is_string($value)) {
            
$value = (string) $value;
        }

        
$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)));
        }

        
$this->options array_merge($this->options$options);
    }

    
/**
     * Returns the cell value.
     *
     * @return string
     */
    
public function __toString()
    {
        return 
$this->value;
    }

    
/**
     * Gets number of colspan.
     *
     * @return int
     */
    
public function getColspan()
    {
        return (int) 
$this->options['colspan'];
    }

    
/**
     * Gets number of rowspan.
     *
     * @return int
     */
    
public function getRowspan()
    {
        return (int) 
$this->options['rowspan'];
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class FormatterHelper extends Helper
{
    
/**
     * Formats a message within a section.
     *
     * @param string $section The section name
     * @param string $message The message
     * @param string $style   The style to apply to the section
     *
     * @return string The format section
     */
    
public function formatSection($section$message$style 'info')
    {
        return 
sprintf('<%s>[%s]</%s> %s'$style$section$style$message);
    }

    
/**
     * Formats a message as a block of text.
     *
     * @param string|array $messages The message to write in the block
     * @param string       $style    The style to apply to the whole block
     * @param bool         $large    Whether to return a large block
     *
     * @return string The formatter message
     */
    
public function formatBlock($messages$style$large false)
    {
        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::strlen($message) + ($large 2), $len);
        }

        
$messages $large ? [str_repeat(' '$len)] : [];
        for (
$i 0; isset($lines[$i]); ++$i) {
            
$messages[] = $lines[$i].str_repeat(' '$len self::strlen($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.
     *
     * @param string $message
     * @param int    $length
     * @param string $suffix
     *
     * @return string
     */
    
public function truncate($message$length$suffix '...')
    {
        
$computedLength $length self::strlen($suffix);

        if (
$computedLength self::strlen($message)) {
            return 
$message;
        }

        return 
self::substr($message0$length).$suffix;
    }

    
/**
     * {@inheritdoc}
     */
    
public function getName()
    {
        return 
'formatter';
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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\Command\Command;
use 
Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * HelperSet represents a set of helpers to be used with a command.
 *
 * @author Fabien Potencier <[email protected]>
 */
class HelperSet implements \IteratorAggregate
{
    
/**
     * @var Helper[]
     */
    
private $helpers = [];
    private 
$command;

    
/**
     * @param Helper[] $helpers An array of helper
     */
    
public function __construct(array $helpers = [])
    {
        foreach (
$helpers as $alias => $helper) {
            
$this->set($helper\is_int($alias) ? null $alias);
        }
    }

    
/**
     * Sets a helper.
     *
     * @param HelperInterface $helper The helper instance
     * @param string          $alias  An alias
     */
    
public function set(HelperInterface $helper$alias null)
    {
        
$this->helpers[$helper->getName()] = $helper;
        if (
null !== $alias) {
            
$this->helpers[$alias] = $helper;
        }

        
$helper->setHelperSet($this);
    }

    
/**
     * Returns true if the helper if defined.
     *
     * @param string $name The helper name
     *
     * @return bool true if the helper is defined, false otherwise
     */
    
public function has($name)
    {
        return isset(
$this->helpers[$name]);
    }

    
/**
     * Gets a helper value.
     *
     * @param string $name The helper name
     *
     * @return HelperInterface The helper instance
     *
     * @throws InvalidArgumentException if the helper is not defined
     */
    
public function get($name)
    {
        if (!
$this->has($name)) {
            throw new 
InvalidArgumentException(sprintf('The helper "%s" is not defined.'$name));
        }

        return 
$this->helpers[$name];
    }

    public function 
setCommand(Command $command null)
    {
        
$this->command $command;
    }

    
/**
     * Gets the command associated with this helper set.
     *
     * @return Command A Command instance
     */
    
public function getCommand()
    {
        return 
$this->command;
    }

    
/**
     * @return Helper[]
     */
    
public function getIterator()
    {
        return new 
\ArrayIterator($this->helpers);
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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\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 <[email protected]>
 */
class DescriptorHelper extends Helper
{
    
/**
     * @var DescriptorInterface[]
     */
    
private $descriptors = [];

    public function 
__construct()
    {
        
$this
            
->register('txt', new TextDescriptor())
            ->
register('xml', new XmlDescriptor())
            ->
register('json', new JsonDescriptor())
            ->
register('md', new MarkdownDescriptor())
        ;
    }

    
/**
     * Describes an object if supported.
     *
     * Available options are:
     * * format: string, the output format name
     * * raw_text: boolean, sets output type as raw
     *
     * @param object $object
     *
     * @throws InvalidArgumentException when the given format is not supported
     */
    
public function describe(OutputInterface $output$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.
     *
     * @param string $format
     *
     * @return $this
     */
    
public function register($formatDescriptorInterface $descriptor)
    {
        
$this->descriptors[$format] = $descriptor;

        return 
$this;
    }

    
/**
     * {@inheritdoc}
     */
    
public function getName()
    {
        return 
'descriptor';
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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\LogicException;
use 
Symfony\Component\Console\Formatter\OutputFormatter;
use 
Symfony\Component\Console\Input\InputInterface;
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 <[email protected]>
 */
class SymfonyQuestionHelper extends QuestionHelper
{
    
/**
     * {@inheritdoc}
     *
     * To be removed in 4.0
     */
    
public function ask(InputInterface $inputOutputInterface $outputQuestion $question)
    {
        
$validator $question->getValidator();
        
$question->setValidator(function ($value) use ($validator) {
            if (
null !== $validator) {
                
$value $validator($value);
            } else {
                
// make required
                
if (!\is_array($value) && !\is_bool($value) && === \strlen($value)) {
                    @
trigger_error('The default question validator is deprecated since Symfony 3.3 and will not be used anymore in version 4.0. Set a custom question validator if needed.'\E_USER_DEPRECATED);

                    throw new 
LogicException('A value is required.');
                }
            }

            return 
$value;
        });

        return 
parent::ask($input$output$question);
    }

    
/**
     * {@inheritdoc}
     */
    
protected function writePrompt(OutputInterface $outputQuestion $question)
    {
        
$text OutputFormatter::escapeTrailingBackslash($question->getQuestion());
        
$default $question->getDefault();

        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>]:'$textOutputFormatter::escape(implode(', '$default)));

                break;

            case 
$question instanceof ChoiceQuestion:
                
$choices $question->getChoices();
                
$text sprintf(' <info>%s</info> [<comment>%s</comment>]:'$textOutputFormatter::escape(isset($choices[$default]) ? $choices[$default] : $default));

                break;

            default:
                
$text sprintf(' <info>%s</info> [<comment>%s</comment>]:'$textOutputFormatter::escape($default));
        }

        
$output->writeln($text);

        
$prompt ' > ';

        if (
$question instanceof ChoiceQuestion) {
            
$output->writeln($this->formatChoiceQuestionChoices($question'comment'));

            
$prompt $question->getPrompt();
        }

        
$output->write($prompt);
    }

    
/**
     * {@inheritdoc}
     */
    
protected function writeError(OutputInterface $output\Exception $error)
    {
        if (
$output instanceof SymfonyStyle) {
            
$output->newLine();
            
$output->error($error->getMessage());

            return;
        }

        
parent::writeError($output$error);
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class ProcessHelper extends Helper
{
    
/**
     * Runs an external process.
     *
     * @param OutputInterface      $output    An OutputInterface instance
     * @param string|array|Process $cmd       An instance of Process or an array of arguments to escape and run or a command to run
     * @param string|null          $error     An error message that must be displayed if something went wrong
     * @param callable|null        $callback  A PHP callback to run whenever there is some
     *                                        output available on STDOUT or STDERR
     * @param int                  $verbosity The threshold for verbosity
     *
     * @return Process The process that ran
     */
    
public function run(OutputInterface $output$cmd$error null, callable $callback null$verbosity OutputInterface::VERBOSITY_VERY_VERBOSE)
    {
        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) {
            
$process $cmd;
        } else {
            
$process = new Process($cmd);
        }

        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);

        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 OutputInterface $output   An OutputInterface instance
     * @param string|Process  $cmd      An instance of Process or a command to run
     * @param string|null     $error    An error message that must be displayed if something went wrong
     * @param callable|null   $callback A PHP callback to run whenever there is some
     *                                  output available on STDOUT or STDERR
     *
     * @return Process The process that ran
     *
     * @throws ProcessFailedException
     *
     * @see run()
     */
    
public function mustRun(OutputInterface $output$cmd$error null, callable $callback null)
    {
        
$process $this->run($output$cmd$error$callback);

        if (!
$process->isSuccessful()) {
            throw new 
ProcessFailedException($process);
        }

        return 
$process;
    }

    
/**
     * Wraps a Process callback to add debugging output.
     *
     * @param OutputInterface $output   An OutputInterface interface
     * @param Process         $process  The Process
     * @param callable|null   $callback A PHP callable
     *
     * @return callable
     */
    
public function wrapCallback(OutputInterface $outputProcess $process, callable $callback null)
    {
        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) {
                
\call_user_func($callback$type$buffer);
            }
        };
    }

    private function 
escapeString($str)
    {
        return 
str_replace('<''\\<'$str);
    }

    
/**
     * {@inheritdoc}
     */
    
public function getName()
    {
        return 
'process';
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
interface CommandLoaderInterface
{
    
/**
     * Loads a command.
     *
     * @param string $name
     *
     * @return Command
     *
     * @throws CommandNotFoundException
     */
    
public function get($name);

    
/**
     * Checks if a command exists.
     *
     * @param string $name
     *
     * @return bool
     */
    
public function has($name);

    
/**
     * @return string[] All registered command names
     */
    
public function getNames();
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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\Exception\CommandNotFoundException;

/**
 * Loads commands from a PSR-11 container.
 *
 * @author Robin Chalas <[email protected]>
 */
class ContainerCommandLoader implements CommandLoaderInterface
{
    private 
$container;
    private 
$commandMap;

    
/**
     * @param ContainerInterface $container  A container from which to load command services
     * @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;
    }

    
/**
     * {@inheritdoc}
     */
    
public function get($name)
    {
        if (!
$this->has($name)) {
            throw new 
CommandNotFoundException(sprintf('Command "%s" does not exist.'$name));
        }

        return 
$this->container->get($this->commandMap[$name]);
    }

    
/**
     * {@inheritdoc}
     */
    
public function has($name)
    {
        return isset(
$this->commandMap[$name]) && $this->container->has($this->commandMap[$name]);
    }

    
/**
     * {@inheritdoc}
     */
    
public function getNames()
    {
        return 
array_keys($this->commandMap);
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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\Exception\CommandNotFoundException;

/**
 * A simple command loader using factories to instantiate commands lazily.
 *
 * @author Maxime Steinhausser <[email protected]>
 */
class FactoryCommandLoader implements CommandLoaderInterface
{
    private 
$factories;

    
/**
     * @param callable[] $factories Indexed by command names
     */
    
public function __construct(array $factories)
    {
        
$this->factories $factories;
    }

    
/**
     * {@inheritdoc}
     */
    
public function has($name)
    {
        return isset(
$this->factories[$name]);
    }

    
/**
     * {@inheritdoc}
     */
    
public function get($name)
    {
        if (!isset(
$this->factories[$name])) {
            throw new 
CommandNotFoundException(sprintf('Command "%s" does not exist.'$name));
        }

        
$factory $this->factories[$name];

        return 
$factory();
    }

    
/**
     * {@inheritdoc}
     */
    
public function getNames()
    {
        return 
array_keys($this->factories);
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 *
 * @see https://www.php-fig.org/psr/psr-3/
 */
class ConsoleLogger extends AbstractLogger
{
    const 
INFO 'info';
    const 
ERROR 'error';

    private 
$output;
    private 
$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 
$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 
$errored false;

    public function 
__construct(OutputInterface $output, array $verbosityLevelMap = [], array $formatLevelMap = [])
    {
        
$this->output $output;
        
$this->verbosityLevelMap $verbosityLevelMap $this->verbosityLevelMap;
        
$this->formatLevelMap $formatLevelMap $this->formatLevelMap;
    }

    
/**
     * {@inheritdoc}
     */
    
public function log($level$message, array $context = [])
    {
        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.
     *
     * @return bool
     */
    
public function hasErrored()
    {
        return 
$this->errored;
    }

    
/**
     * Interpolates context values into the message placeholders.
     *
     * @author PHP Framework Interoperability Group
     *
     * @param string $message
     *
     * @return string
     */
    
private function interpolate($message, array $context)
    {
        if (
false === strpos($message'{')) {
            return 
$message;
        }

        
$replacements = [];
        foreach (
$context as $key => $val) {
            if (
null === $val || is_scalar($val) || (\is_object($val) && method_exists($val'__toString'))) {
                
$replacements["{{$key}}"] = $val;
            } elseif (
$val instanceof \DateTimeInterface) {
                
$replacements["{{$key}}"] = $val->format(\DateTime::RFC3339);
            } elseif (
\is_object($val)) {
                
$replacements["{{$key}}"] = '[object '.\get_class($val).']';
            } else {
                
$replacements["{{$key}}"] = '['.\gettype($val).']';
            }
        }

        return 
strtr($message$replacements);
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
{
    private 
$stderr;

    
/**
     * @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($verbosity self::VERBOSITY_NORMAL$decorated nullOutputFormatterInterface $formatter null)
    {
        
parent::__construct($this->openOutputStream(), $verbosity$decorated$formatter);

        
$actualDecorated $this->isDecorated();
        
$this->stderr = new StreamOutput($this->openErrorStream(), $verbosity$decorated$this->getFormatter());

        if (
null === $decorated) {
            
$this->setDecorated($actualDecorated && $this->stderr->isDecorated());
        }
    }

    
/**
     * {@inheritdoc}
     */
    
public function setDecorated($decorated)
    {
        
parent::setDecorated($decorated);
        
$this->stderr->setDecorated($decorated);
    }

    
/**
     * {@inheritdoc}
     */
    
public function setFormatter(OutputFormatterInterface $formatter)
    {
        
parent::setFormatter($formatter);
        
$this->stderr->setFormatter($formatter);
    }

    
/**
     * {@inheritdoc}
     */
    
public function setVerbosity($level)
    {
        
parent::setVerbosity($level);
        
$this->stderr->setVerbosity($level);
    }

    
/**
     * {@inheritdoc}
     */
    
public function getErrorOutput()
    {
        return 
$this->stderr;
    }

    
/**
     * {@inheritdoc}
     */
    
public function setErrorOutput(OutputInterface $error)
    {
        
$this->stderr $error;
    }

    
/**
     * Returns true if current environment supports writing console output to
     * STDOUT.
     *
     * @return bool
     */
    
protected function hasStdoutSupport()
    {
        return 
false === $this->isRunningOS400();
    }

    
/**
     * Returns true if current environment supports writing console output to
     * STDERR.
     *
     * @return bool
     */
    
protected function hasStderrSupport()
    {
        return 
false === $this->isRunningOS400();
    }

    
/**
     * Checks if current executing environment is IBM iSeries (OS400), which
     * doesn't properly convert character-encodings between ASCII to EBCDIC.
     *
     * @return bool
     */
    
private function isRunningOS400()
    {
        
$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');
        }

        return @
fopen('php://stdout''w') ?: fopen('php://output''w');
    }

    
/**
     * @return resource
     */
    
private function openErrorStream()
    {
        return 
fopen($this->hasStderrSupport() ? 'php://stderr' 'php://output''w');
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
interface OutputInterface
{
    const 
VERBOSITY_QUIET 16;
    const 
VERBOSITY_NORMAL 32;
    const 
VERBOSITY_VERBOSE 64;
    const 
VERBOSITY_VERY_VERBOSE 128;
    const 
VERBOSITY_DEBUG 256;

    const 
OUTPUT_NORMAL 1;
    const 
OUTPUT_RAW 2;
    const 
OUTPUT_PLAIN 4;

    
/**
     * Writes a message to the output.
     *
     * @param string|array $messages The message as an array of strings or a single string
     * @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
     */
    
public function write($messages$newline false$options 0);

    
/**
     * Writes a message to the output and adds a newline at the end.
     *
     * @param string|array $messages The message as an array of strings or a single string
     * @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
     */
    
public function writeln($messages$options 0);

    
/**
     * Sets the verbosity of the output.
     *
     * @param int $level The level of verbosity (one of the VERBOSITY constants)
     */
    
public function setVerbosity($level);

    
/**
     * Gets the current verbosity of the output.
     *
     * @return int The current level of verbosity (one of the VERBOSITY constants)
     */
    
public function getVerbosity();

    
/**
     * Returns whether verbosity is quiet (-q).
     *
     * @return bool true if verbosity is set to VERBOSITY_QUIET, false otherwise
     */
    
public function isQuiet();

    
/**
     * Returns whether verbosity is verbose (-v).
     *
     * @return bool true if verbosity is set to VERBOSITY_VERBOSE, false otherwise
     */
    
public function isVerbose();

    
/**
     * Returns whether verbosity is very verbose (-vv).
     *
     * @return bool true if verbosity is set to VERBOSITY_VERY_VERBOSE, false otherwise
     */
    
public function isVeryVerbose();

    
/**
     * Returns whether verbosity is debug (-vvv).
     *
     * @return bool true if verbosity is set to VERBOSITY_DEBUG, false otherwise
     */
    
public function isDebug();

    
/**
     * Sets the decorated flag.
     *
     * @param bool $decorated Whether to decorate the messages
     */
    
public function setDecorated($decorated);

    
/**
     * Gets the decorated flag.
     *
     * @return bool true if the output will decorate messages, false otherwise
     */
    
public function isDecorated();

    public function 
setFormatter(OutputFormatterInterface $formatter);

    
/**
     * Returns current output formatter instance.
     *
     * @return OutputFormatterInterface
     */
    
public function getFormatter();
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class BufferedOutput extends Output
{
    private 
$buffer '';

    
/**
     * Empties buffer and returns its content.
     *
     * @return string
     */
    
public function fetch()
    {
        
$content $this->buffer;
        
$this->buffer '';

        return 
$content;
    }

    
/**
     * {@inheritdoc}
     */
    
protected function doWrite($message$newline)
    {
        
$this->buffer .= $message;

        if (
$newline) {
            
$this->buffer .= \PHP_EOL;
        }
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
abstract class Output implements OutputInterface
{
    private 
$verbosity;
    private 
$formatter;

    
/**
     * @param int                           $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($verbosity self::VERBOSITY_NORMAL$decorated falseOutputFormatterInterface $formatter null)
    {
        
$this->verbosity null === $verbosity self::VERBOSITY_NORMAL $verbosity;
        
$this->formatter $formatter ?: new OutputFormatter();
        
$this->formatter->setDecorated($decorated);
    }

    
/**
     * {@inheritdoc}
     */
    
public function setFormatter(OutputFormatterInterface $formatter)
    {
        
$this->formatter $formatter;
    }

    
/**
     * {@inheritdoc}
     */
    
public function getFormatter()
    {
        return 
$this->formatter;
    }

    
/**
     * {@inheritdoc}
     */
    
public function setDecorated($decorated)
    {
        
$this->formatter->setDecorated($decorated);
    }

    
/**
     * {@inheritdoc}
     */
    
public function isDecorated()
    {
        return 
$this->formatter->isDecorated();
    }

    
/**
     * {@inheritdoc}
     */
    
public function setVerbosity($level)
    {
        
$this->verbosity = (int) $level;
    }

    
/**
     * {@inheritdoc}
     */
    
public function getVerbosity()
    {
        return 
$this->verbosity;
    }

    
/**
     * {@inheritdoc}
     */
    
public function isQuiet()
    {
        return 
self::VERBOSITY_QUIET === $this->verbosity;
    }

    
/**
     * {@inheritdoc}
     */
    
public function isVerbose()
    {
        return 
self::VERBOSITY_VERBOSE <= $this->verbosity;
    }

    
/**
     * {@inheritdoc}
     */
    
public function isVeryVerbose()
    {
        return 
self::VERBOSITY_VERY_VERBOSE <= $this->verbosity;
    }

    
/**
     * {@inheritdoc}
     */
    
public function isDebug()
    {
        return 
self::VERBOSITY_DEBUG <= $this->verbosity;
    }

    
/**
     * {@inheritdoc}
     */
    
public function writeln($messages$options self::OUTPUT_NORMAL)
    {
        
$this->write($messagestrue$options);
    }

    
/**
     * {@inheritdoc}
     */
    
public function write($messages$newline false$options self::OUTPUT_NORMAL)
    {
        
$messages = (array) $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.
     *
     * @param string $message A message to write to the output
     * @param bool   $newline Whether to add a newline or not
     */
    
abstract protected function doWrite($message$newline);
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 output stream.
 *
 * @author Dariusz Górecki <[email protected]>
 */
interface ConsoleOutputInterface extends OutputInterface
{
    
/**
     * Gets the OutputInterface for errors.
     *
     * @return OutputInterface
     */
    
public function getErrorOutput();

    public function 
setErrorOutput(OutputInterface $error);
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class StreamOutput extends Output
{
    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$verbosity self::VERBOSITY_NORMAL$decorated nullOutputFormatterInterface $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;

        if (
null === $decorated) {
            
$decorated $this->hasColorSupport();
        }

        
parent::__construct($verbosity$decorated$formatter);
    }

    
/**
     * Gets the stream attached to this StreamOutput instance.
     *
     * @return resource A stream resource
     */
    
public function getStream()
    {
        return 
$this->stream;
    }

    
/**
     * {@inheritdoc}
     */
    
protected function doWrite($message$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()
    {
        if (
'Hyper' === getenv('TERM_PROGRAM')) {
            return 
true;
        }

        if (
\DIRECTORY_SEPARATOR === '\\') {
            return (
\function_exists('sapi_windows_vt100_support')
                && @
sapi_windows_vt100_support($this->stream))
                || 
false !== getenv('ANSICON')
                || 
'ON' === getenv('ConEmuANSI')
                || 
'xterm' === getenv('TERM');
        }

        if (
\function_exists('stream_isatty')) {
            return @
stream_isatty($this->stream);
        }

        if (
\function_exists('posix_isatty')) {
            return @
posix_isatty($this->stream);
        }

        
$stat = @fstat($this->stream);
        
// Check if formatted mode is S_IFCHR
        
return $stat 0020000 === ($stat['mode'] & 0170000) : false;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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;

/**
 * NullOutput suppresses all output.
 *
 *     $output = new NullOutput();
 *
 * @author Fabien Potencier <[email protected]>
 * @author Tobias Schultze <http://tobion.de>
 */
class NullOutput implements OutputInterface
{
    
/**
     * {@inheritdoc}
     */
    
public function setFormatter(OutputFormatterInterface $formatter)
    {
        
// do nothing
    
}

    
/**
     * {@inheritdoc}
     */
    
public function getFormatter()
    {
        
// to comply with the interface we must return a OutputFormatterInterface
        
return new OutputFormatter();
    }

    
/**
     * {@inheritdoc}
     */
    
public function setDecorated($decorated)
    {
        
// do nothing
    
}

    
/**
     * {@inheritdoc}
     */
    
public function isDecorated()
    {
        return 
false;
    }

    
/**
     * {@inheritdoc}
     */
    
public function setVerbosity($level)
    {
        
// do nothing
    
}

    
/**
     * {@inheritdoc}
     */
    
public function getVerbosity()
    {
        return 
self::VERBOSITY_QUIET;
    }

    
/**
     * {@inheritdoc}
     */
    
public function isQuiet()
    {
        return 
true;
    }

    
/**
     * {@inheritdoc}
     */
    
public function isVerbose()
    {
        return 
false;
    }

    
/**
     * {@inheritdoc}
     */
    
public function isVeryVerbose()
    {
        return 
false;
    }

    
/**
     * {@inheritdoc}
     */
    
public function isDebug()
    {
        return 
false;
    }

    
/**
     * {@inheritdoc}
     */
    
public function writeln($messages$options self::OUTPUT_NORMAL)
    {
        
// do nothing
    
}

    
/**
     * {@inheritdoc}
     */
    
public function write($messages$newline false$options self::OUTPUT_NORMAL)
    {
        
// do nothing
    
}
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console;

class 
Terminal
{
    private static 
$width;
    private static 
$height;
    private static 
$stty;

    
/**
     * Gets the terminal width.
     *
     * @return int
     */
    
public function getWidth()
    {
        
$width getenv('COLUMNS');
        if (
false !== $width) {
            return (int) 
trim($width);
        }

        if (
null === self::$width) {
            
self::initDimensions();
        }

        return 
self::$width ?: 80;
    }

    
/**
     * Gets the terminal height.
     *
     * @return int
     */
    
public function getHeight()
    {
        
$height getenv('LINES');
        if (
false !== $height) {
            return (int) 
trim($height);
        }

        if (
null === self::$height) {
            
self::initDimensions();
        }

        return 
self::$height ?: 50;
    }

    
/**
     * @internal
     *
     * @return bool
     */
    
public static function hasSttyAvailable()
    {
        if (
null !== self::$stty) {
            return 
self::$stty;
        }

        
// skip check if exec function is disabled
        
if (!\function_exists('exec')) {
            return 
false;
        }

        
exec('stty 2>&1'$output$exitcode);

        return 
self::$stty === $exitcode;
    }

    private static function 
initDimensions()
    {
        if (
'\\' === \DIRECTORY_SEPARATOR) {
            if (
preg_match('/^(\d+)x(\d+)(?: \((\d+)x(\d+)\))?$/'trim(getenv('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()
    {
        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()
    {
        if (
$sttyString self::getSttyColumns()) {
            if (
preg_match('/rows.(\d+);.columns.(\d+);/i'$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/i'$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()
    {
        
$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.
     *
     * @return string|null
     */
    
private static function getSttyColumns()
    {
        return 
self::readFromProcess('stty -a | grep columns');
    }

    
/**
     * @param string $command
     *
     * @return string|null
     */
    
private static function readFromProcess($command)
    {
        if (!
\function_exists('proc_open')) {
            return 
null;
        }

        
$descriptorspec = [
            
=> ['pipe''w'],
            
=> ['pipe''w'],
        ];

        
$process proc_open($command$descriptorspec$pipesnullnull, ['suppress_errors' => true]);
        if (!
\is_resource($process)) {
            return 
null;
        }

        
$info stream_get_contents($pipes[1]);
        
fclose($pipes[1]);
        
fclose($pipes[2]);
        
proc_close($process);

        return 
$info;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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\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 <[email protected]>
 */
class HelpCommand extends Command
{
    private 
$command;

    
/**
     * {@inheritdoc}
     */
    
protected function configure()
    {
        
$this->ignoreValidationErrors();

        
$this
            
->setName('help')
            ->
setDefinition([
                new 
InputArgument('command_name'InputArgument::OPTIONAL'The command name''help'),
                new 
InputOption('format'nullInputOption::VALUE_REQUIRED'The output format (txt, xml, json, or md)''txt'),
                new 
InputOption('raw'nullInputOption::VALUE_NONE'To output raw command help'),
            ])
            ->
setDescription('Displays help for a command')
            ->
setHelp(<<<'EOF'
The <info>%command.name%</info> command displays help for a given command:

  <info>php %command.full_name% list</info>

You can also output the help in other formats by using the <comment>--format</comment> option:

  <info>php %command.full_name% --format=xml list</info>

To display the list of available commands, please use the <info>list</info> command.
EOF
            )
        ;
    }

    public function 
setCommand(Command $command)
    {
        
$this->command $command;
    }

    
/**
     * {@inheritdoc}
     */
    
protected function execute(InputInterface $inputOutputInterface $output)
    {
        if (
null === $this->command) {
            
$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'),
        ]);

        
$this->command null;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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\Exception\ExceptionInterface;
use 
Symfony\Component\Console\Exception\InvalidArgumentException;
use 
Symfony\Component\Console\Exception\LogicException;
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 <[email protected]>
 */
class Command
{
    
/**
     * @var string|null The default command name
     */
    
protected static $defaultName;

    private 
$application;
    private 
$name;
    private 
$processTitle;
    private 
$aliases = [];
    private 
$definition;
    private 
$hidden false;
    private 
$help '';
    private 
$description '';
    private 
$ignoreValidationErrors false;
    private 
$applicationDefinitionMerged false;
    private 
$applicationDefinitionMergedWithArgs false;
    private 
$code;
    private 
$synopsis = [];
    private 
$usages = [];
    private 
$helperSet;

    
/**
     * @return string|null The default command name or null when no default name is set
     */
    
public static function getDefaultName()
    {
        
$class = static::class;
        
$r = new \ReflectionProperty($class'defaultName');

        return 
$class === $r->class ? static::$defaultName null;
    }

    
/**
     * @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($name null)
    {
        
$this->definition = new InputDefinition();

        if (
null !== $name || null !== $name = static::getDefaultName()) {
            
$this->setName($name);
        }

        
$this->configure();
    }

    
/**
     * Ignores validation errors.
     *
     * This is mainly useful for the help command.
     */
    
public function ignoreValidationErrors()
    {
        
$this->ignoreValidationErrors true;
    }

    public function 
setApplication(Application $application null)
    {
        
$this->application $application;
        if (
$application) {
            
$this->setHelperSet($application->getHelperSet());
        } else {
            
$this->helperSet null;
        }
    }

    public function 
setHelperSet(HelperSet $helperSet)
    {
        
$this->helperSet $helperSet;
    }

    
/**
     * Gets the helper set.
     *
     * @return HelperSet|null A HelperSet instance
     */
    
public function getHelperSet()
    {
        return 
$this->helperSet;
    }

    
/**
     * Gets the application instance for this command.
     *
     * @return Application|null An Application instance
     */
    
public function getApplication()
    {
        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 can not
     * run properly under the current conditions.
     *
     * @return bool
     */
    
public function isEnabled()
    {
        return 
true;
    }

    
/**
     * Configures the current command.
     */
    
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|null null or 0 if everything went fine, or an error code
     *
     * @throws LogicException When this abstract method is not implemented
     *
     * @see setCode()
     */
    
protected function execute(InputInterface $inputOutputInterface $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.
     */
    
protected function interact(InputInterface $inputOutputInterface $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()
     */
    
protected function initialize(InputInterface $inputOutputInterface $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 \Exception When binding input fails. Bypass this by calling {@link ignoreValidationErrors()}.
     *
     * @see setCode()
     * @see execute()
     */
    
public function run(InputInterface $inputOutputInterface $output)
    {
        
// force the creation of the synopsis before the merge with the app definition
        
$this->getSynopsis(true);
        
$this->getSynopsis(false);

        
// add the application arguments and options
        
$this->mergeApplicationDefinition();

        
// bind the input against the command specific arguments/options
        
try {
            
$input->bind($this->definition);
        } 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 \call_user_func($this->code$input$output);
        } else {
            
$statusCode $this->execute($input$output);
        }

        return 
is_numeric($statusCode) ? (int) $statusCode 0;
    }

    
/**
     * 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)
    {
        if (
$code instanceof \Closure) {
            
$r = new \ReflectionFunction($code);
            if (
null === $r->getClosureThis()) {
                if (
\PHP_VERSION_ID 70000) {
                    
// Bug in PHP5: https://bugs.php.net/64761
                    // This means that we cannot bind static closures and therefore we must
                    // ignore any errors here.  There is no way to test if the closure is
                    // bindable.
                    
$code = @\Closure::bind($code$this);
                } else {
                    
$code \Closure::bind($code$this);
                }
            }
        }

        
$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
     */
    
public function mergeApplicationDefinition($mergeArgs true)
    {
        if (
null === $this->application || (true === $this->applicationDefinitionMerged && ($this->applicationDefinitionMergedWithArgs || !$mergeArgs))) {
            return;
        }

        
$this->definition->addOptions($this->application->getDefinition()->getOptions());

        
$this->applicationDefinitionMerged true;

        if (
$mergeArgs) {
            
$currentArguments $this->definition->getArguments();
            
$this->definition->setArguments($this->application->getDefinition()->getArguments());
            
$this->definition->addArguments($currentArguments);

            
$this->applicationDefinitionMergedWithArgs true;
        }
    }

    
/**
     * Sets an array of argument and option instances.
     *
     * @param array|InputDefinition $definition An array of argument and option instances or a definition instance
     *
     * @return $this
     */
    
public function setDefinition($definition)
    {
        if (
$definition instanceof InputDefinition) {
            
$this->definition $definition;
        } else {
            
$this->definition->setDefinition($definition);
        }

        
$this->applicationDefinitionMerged false;

        return 
$this;
    }

    
/**
     * Gets the InputDefinition attached to this Command.
     *
     * @return InputDefinition An InputDefinition instance
     */
    
public function getDefinition()
    {
        if (
null === $this->definition) {
            throw new 
LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', static::class));
        }

        return 
$this->definition;
    }

    
/**
     * 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.
     *
     * @return InputDefinition An InputDefinition instance
     */
    
public function getNativeDefinition()
    {
        return 
$this->getDefinition();
    }

    
/**
     * Adds an argument.
     *
     * @param string               $name        The argument name
     * @param int|null             $mode        The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL
     * @param string               $description A description text
     * @param string|string[]|null $default     The default value (for InputArgument::OPTIONAL mode only)
     *
     * @throws InvalidArgumentException When argument mode is not valid
     *
     * @return $this
     */
    
public function addArgument($name$mode null$description ''$default null)
    {
        
$this->definition->addArgument(new InputArgument($name$mode$description$default));

        return 
$this;
    }

    
/**
     * Adds an option.
     *
     * @param string                        $name        The option name
     * @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 InputOption::VALUE_* constants
     * @param string                        $description A description text
     * @param string|string[]|int|bool|null $default     The default value (must be null for InputOption::VALUE_NONE)
     *
     * @throws InvalidArgumentException If option mode is invalid or incompatible
     *
     * @return $this
     */
    
public function addOption($name$shortcut null$mode null$description ''$default null)
    {
        
$this->definition->addOption(new InputOption($name$shortcut$mode$description$default));

        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');
     *
     * @param string $name The command name
     *
     * @return $this
     *
     * @throws InvalidArgumentException When the name is invalid
     */
    
public function setName($name)
    {
        
$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.
     *
     * @param string $title The process title
     *
     * @return $this
     */
    
public function setProcessTitle($title)
    {
        
$this->processTitle $title;

        return 
$this;
    }

    
/**
     * Returns the command name.
     *
     * @return string|null
     */
    
public function getName()
    {
        return 
$this->name;
    }

    
/**
     * @param bool $hidden Whether or not the command should be hidden from the list of commands
     *
     * @return Command The current instance
     */
    
public function setHidden($hidden)
    {
        
$this->hidden = (bool) $hidden;

        return 
$this;
    }

    
/**
     * @return bool whether the command should be publicly shown or not
     */
    
public function isHidden()
    {
        return 
$this->hidden;
    }

    
/**
     * Sets the description for the command.
     *
     * @param string $description The description for the command
     *
     * @return $this
     */
    
public function setDescription($description)
    {
        
$this->description $description;

        return 
$this;
    }

    
/**
     * Returns the description for the command.
     *
     * @return string The description for the command
     */
    
public function getDescription()
    {
        return 
$this->description;
    }

    
/**
     * Sets the help for the command.
     *
     * @param string $help The help for the command
     *
     * @return $this
     */
    
public function setHelp($help)
    {
        
$this->help $help;

        return 
$this;
    }

    
/**
     * Returns the help for the command.
     *
     * @return string The help for the command
     */
    
public function getHelp()
    {
        return 
$this->help;
    }

    
/**
     * Returns the processed help for the command replacing the %command.name% and
     * %command.full_name% patterns with the real values dynamically.
     *
     * @return string The processed help for the command
     */
    
public function getProcessedHelp()
    {
        
$name $this->name;
        
$isSingleCommand $this->application && $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($aliases)
    {
        if (!
\is_array($aliases) && !$aliases instanceof \Traversable) {
            throw new 
InvalidArgumentException('$aliases must be an array or an instance of \Traversable.');
        }

        foreach (
$aliases as $alias) {
            
$this->validateName($alias);
        }

        
$this->aliases $aliases;

        return 
$this;
    }

    
/**
     * Returns the aliases for the command.
     *
     * @return array An array of aliases for the command
     */
    
public function getAliases()
    {
        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
     *
     * @return string The synopsis
     */
    
public function getSynopsis($short false)
    {
        
$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.
     *
     * @param string $usage The usage, it'll be prefixed with the command name
     *
     * @return $this
     */
    
public function addUsage($usage)
    {
        if (
!== strpos($usage$this->name)) {
            
$usage sprintf('%s %s'$this->name$usage);
        }

        
$this->usages[] = $usage;

        return 
$this;
    }

    
/**
     * Returns alternative usages of the command.
     *
     * @return array
     */
    
public function getUsages()
    {
        return 
$this->usages;
    }

    
/**
     * Gets a helper instance by name.
     *
     * @param string $name The helper name
     *
     * @return mixed The helper value
     *
     * @throws LogicException           if no HelperSet is defined
     * @throws InvalidArgumentException if the helper is not defined
     */
    
public function getHelper($name)
    {
        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 ":".
     *
     * @param string $name
     *
     * @throws InvalidArgumentException When the name is invalid
     */
    
private function validateName($name)
    {
        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 <[email protected]>
 *
 * 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\Console\Exception\RuntimeException;
use 
Symfony\Component\Lock\Factory;
use 
Symfony\Component\Lock\Lock;
use 
Symfony\Component\Lock\Store\FlockStore;
use 
Symfony\Component\Lock\Store\SemaphoreStore;

/**
 * Basic lock feature for commands.
 *
 * @author Geoffrey Brier <[email protected]>
 */
trait LockableTrait
{
    
/** @var Lock */
    
private $lock;

    
/**
     * Locks a command.
     *
     * @return bool
     */
    
private function lock($name null$blocking false)
    {
        if (!
class_exists(SemaphoreStore::class)) {
            throw new 
RuntimeException('To enable the locking feature you must install the symfony/lock component.');
        }

        if (
null !== $this->lock) {
            throw new 
LogicException('A lock is already in place.');
        }

        if (
SemaphoreStore::isSupported($blocking)) {
            
$store = new SemaphoreStore();
        } else {
            
$store = new FlockStore();
        }

        
$this->lock = (new Factory($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()
    {
        if (
$this->lock) {
            
$this->lock->release();
            
$this->lock null;
        }
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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\Helper\DescriptorHelper;
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;

/**
 * ListCommand displays the list of all available commands for the application.
 *
 * @author Fabien Potencier <[email protected]>
 */
class ListCommand extends Command
{
    
/**
     * {@inheritdoc}
     */
    
protected function configure()
    {
        
$this
            
->setName('list')
            ->
setDefinition($this->createDefinition())
            ->
setDescription('Lists commands')
            ->
setHelp(<<<'EOF'
The <info>%command.name%</info> command lists all commands:

  <info>php %command.full_name%</info>

You can also display the commands for a specific namespace:

  <info>php %command.full_name% test</info>

You can also output the information in other formats by using the <comment>--format</comment> option:

  <info>php %command.full_name% --format=xml</info>

It's also possible to get raw list of commands (useful for embedding command runner):

  <info>php %command.full_name% --raw</info>
EOF
            )
        ;
    }

    
/**
     * {@inheritdoc}
     */
    
public function getNativeDefinition()
    {
        return 
$this->createDefinition();
    }

    
/**
     * {@inheritdoc}
     */
    
protected function execute(InputInterface $inputOutputInterface $output)
    {
        
$helper = new DescriptorHelper();
        
$helper->describe($output$this->getApplication(), [
            
'format' => $input->getOption('format'),
            
'raw_text' => $input->getOption('raw'),
            
'namespace' => $input->getArgument('namespace'),
        ]);
    }

    
/**
     * {@inheritdoc}
     */
    
private function createDefinition()
    {
        return new 
InputDefinition([
            new 
InputArgument('namespace'InputArgument::OPTIONAL'The namespace name'),
            new 
InputOption('raw'nullInputOption::VALUE_NONE'To output raw command list'),
            new 
InputOption('format'nullInputOption::VALUE_REQUIRED'The output format (txt, xml, json, or md)''txt'),
        ]);
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
interface StyleInterface
{
    
/**
     * Formats a command title.
     *
     * @param string $message
     */
    
public function title($message);

    
/**
     * Formats a section title.
     *
     * @param string $message
     */
    
public function section($message);

    
/**
     * Formats a list.
     */
    
public function listing(array $elements);

    
/**
     * Formats informational text.
     *
     * @param string|array $message
     */
    
public function text($message);

    
/**
     * Formats a success result bar.
     *
     * @param string|array $message
     */
    
public function success($message);

    
/**
     * Formats an error result bar.
     *
     * @param string|array $message
     */
    
public function error($message);

    
/**
     * Formats an warning result bar.
     *
     * @param string|array $message
     */
    
public function warning($message);

    
/**
     * Formats a note admonition.
     *
     * @param string|array $message
     */
    
public function note($message);

    
/**
     * Formats a caution admonition.
     *
     * @param string|array $message
     */
    
public function caution($message);

    
/**
     * Formats a table.
     */
    
public function table(array $headers, array $rows);

    
/**
     * Asks a question.
     *
     * @param string        $question
     * @param string|null   $default
     * @param callable|null $validator
     *
     * @return mixed
     */
    
public function ask($question$default null$validator null);

    
/**
     * Asks a question with the user input hidden.
     *
     * @param string        $question
     * @param callable|null $validator
     *
     * @return mixed
     */
    
public function askHidden($question$validator null);

    
/**
     * Asks for confirmation.
     *
     * @param string $question
     * @param bool   $default
     *
     * @return bool
     */
    
public function confirm($question$default true);

    
/**
     * Asks a choice question.
     *
     * @param string          $question
     * @param string|int|null $default
     *
     * @return mixed
     */
    
public function choice($question, array $choices$default null);

    
/**
     * Add newline(s).
     *
     * @param int $count The number of newlines
     */
    
public function newLine($count 1);

    
/**
     * Starts the progress output.
     *
     * @param int $max Maximum steps (0 if unknown)
     */
    
public function progressStart($max 0);

    
/**
     * Advances the progress output X steps.
     *
     * @param int $step Number of steps to advance
     */
    
public function progressAdvance($step 1);

    
/**
     * Finishes the progress output.
     */
    
public function progressFinish();
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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\RuntimeException;
use 
Symfony\Component\Console\Formatter\OutputFormatter;
use 
Symfony\Component\Console\Helper\Helper;
use 
Symfony\Component\Console\Helper\ProgressBar;
use 
Symfony\Component\Console\Helper\SymfonyQuestionHelper;
use 
Symfony\Component\Console\Helper\Table;
use 
Symfony\Component\Console\Input\InputInterface;
use 
Symfony\Component\Console\Output\BufferedOutput;
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\Terminal;

/**
 * Output decorator helpers for the Symfony Style Guide.
 *
 * @author Kevin Bond <[email protected]>
 */
class SymfonyStyle extends OutputStyle
{
    const 
MAX_LINE_LENGTH 120;

    private 
$input;
    private 
$questionHelper;
    private 
$progressBar;
    private 
$lineLength;
    private 
$bufferedOutput;

    public function 
__construct(InputInterface $inputOutputInterface $output)
    {
        
$this->input $input;
        
$this->bufferedOutput = new BufferedOutput($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($output);
    }

    
/**
     * Formats a message as a block of text.
     *
     * @param string|array $messages The message to write in the block
     * @param string|null  $type     The block type (added in [] on first line)
     * @param string|null  $style    The style to apply to the whole block
     * @param string       $prefix   The prefix for the block
     * @param bool         $padding  Whether to add vertical padding
     * @param bool         $escape   Whether to escape the message
     */
    
public function block($messages$type null$style null$prefix ' '$padding false$escape true)
    {
        
$messages \is_array($messages) ? array_values($messages) : [$messages];

        
$this->autoPrependBlock();
        
$this->writeln($this->createBlock($messages$type$style$prefix$padding$escape));
        
$this->newLine();
    }

    
/**
     * {@inheritdoc}
     */
    
public function title($message)
    {
        
$this->autoPrependBlock();
        
$this->writeln([
            
sprintf('<comment>%s</>'OutputFormatter::escapeTrailingBackslash($message)),
            
sprintf('<comment>%s</>'str_repeat('='Helper::strlenWithoutDecoration($this->getFormatter(), $message))),
        ]);
        
$this->newLine();
    }

    
/**
     * {@inheritdoc}
     */
    
public function section($message)
    {
        
$this->autoPrependBlock();
        
$this->writeln([
            
sprintf('<comment>%s</>'OutputFormatter::escapeTrailingBackslash($message)),
            
sprintf('<comment>%s</>'str_repeat('-'Helper::strlenWithoutDecoration($this->getFormatter(), $message))),
        ]);
        
$this->newLine();
    }

    
/**
     * {@inheritdoc}
     */
    
public function listing(array $elements)
    {
        
$this->autoPrependText();
        
$elements array_map(function ($element) {
            return 
sprintf(' * %s'$element);
        }, 
$elements);

        
$this->writeln($elements);
        
$this->newLine();
    }

    
/**
     * {@inheritdoc}
     */
    
public function text($message)
    {
        
$this->autoPrependText();

        
$messages \is_array($message) ? array_values($message) : [$message];
        foreach (
$messages as $message) {
            
$this->writeln(sprintf(' %s'$message));
        }
    }

    
/**
     * Formats a command comment.
     *
     * @param string|array $message
     */
    
public function comment($message)
    {
        
$this->block($messagenullnull'<fg=default;bg=default> // </>'falsefalse);
    }

    
/**
     * {@inheritdoc}
     */
    
public function success($message)
    {
        
$this->block($message'OK''fg=black;bg=green'' 'true);
    }

    
/**
     * {@inheritdoc}
     */
    
public function error($message)
    {
        
$this->block($message'ERROR''fg=white;bg=red'' 'true);
    }

    
/**
     * {@inheritdoc}
     */
    
public function warning($message)
    {
        
$this->block($message'WARNING''fg=white;bg=red'' 'true);
    }

    
/**
     * {@inheritdoc}
     */
    
public function note($message)
    {
        
$this->block($message'NOTE''fg=yellow'' ! ');
    }

    
/**
     * {@inheritdoc}
     */
    
public function caution($message)
    {
        
$this->block($message'CAUTION''fg=white;bg=red'' ! 'true);
    }

    
/**
     * {@inheritdoc}
     */
    
public function table(array $headers, array $rows)
    {
        
$style = clone Table::getStyleDefinition('symfony-style-guide');
        
$style->setCellHeaderFormat('<info>%s</info>');

        
$table = new Table($this);
        
$table->setHeaders($headers);
        
$table->setRows($rows);
        
$table->setStyle($style);

        
$table->render();
        
$this->newLine();
    }

    
/**
     * {@inheritdoc}
     */
    
public function ask($question$default null$validator null)
    {
        
$question = new Question($question$default);
        
$question->setValidator($validator);

        return 
$this->askQuestion($question);
    }

    
/**
     * {@inheritdoc}
     */
    
public function askHidden($question$validator null)
    {
        
$question = new Question($question);

        
$question->setHidden(true);
        
$question->setValidator($validator);

        return 
$this->askQuestion($question);
    }

    
/**
     * {@inheritdoc}
     */
    
public function confirm($question$default true)
    {
        return 
$this->askQuestion(new ConfirmationQuestion($question$default));
    }

    
/**
     * {@inheritdoc}
     */
    
public function choice($question, array $choices$default null)
    {
        if (
null !== $default) {
            
$values array_flip($choices);
            
$default = isset($values[$default]) ? $values[$default] : $default;
        }

        return 
$this->askQuestion(new ChoiceQuestion($question$choices$default));
    }

    
/**
     * {@inheritdoc}
     */
    
public function progressStart($max 0)
    {
        
$this->progressBar $this->createProgressBar($max);
        
$this->progressBar->start();
    }

    
/**
     * {@inheritdoc}
     */
    
public function progressAdvance($step 1)
    {
        
$this->getProgressBar()->advance($step);
    }

    
/**
     * {@inheritdoc}
     */
    
public function progressFinish()
    {
        
$this->getProgressBar()->finish();
        
$this->newLine(2);
        
$this->progressBar null;
    }

    
/**
     * {@inheritdoc}
     */
    
public function createProgressBar($max 0)
    {
        
$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;
    }

    
/**
     * @return mixed
     */
    
public function askQuestion(Question $question)
    {
        if (
$this->input->isInteractive()) {
            
$this->autoPrependBlock();
        }

        if (!
$this->questionHelper) {
            
$this->questionHelper = new SymfonyQuestionHelper();
        }

        
$answer $this->questionHelper->ask($this->input$this$question);

        if (
$this->input->isInteractive()) {
            
$this->newLine();
            
$this->bufferedOutput->write("\n");
        }

        return 
$answer;
    }

    
/**
     * {@inheritdoc}
     */
    
public function writeln($messages$type self::OUTPUT_NORMAL)
    {
        
parent::writeln($messages$type);
        
$this->bufferedOutput->writeln($this->reduceBuffer($messages), $type);
    }

    
/**
     * {@inheritdoc}
     */
    
public function write($messages$newline false$type self::OUTPUT_NORMAL)
    {
        
parent::write($messages$newline$type);
        
$this->bufferedOutput->write($this->reduceBuffer($messages), $newline$type);
    }

    
/**
     * {@inheritdoc}
     */
    
public function newLine($count 1)
    {
        
parent::newLine($count);
        
$this->bufferedOutput->write(str_repeat("\n"$count));
    }

    
/**
     * Returns a new instance which makes use of stderr if available.
     *
     * @return self
     */
    
public function getErrorStyle()
    {
        return new 
self($this->input$this->getErrorOutput());
    }

    
/**
     * @return ProgressBar
     */
    
private function getProgressBar()
    {
        if (!
$this->progressBar) {
            throw new 
RuntimeException('The ProgressBar is not started.');
        }

        return 
$this->progressBar;
    }

    private function 
autoPrependBlock()
    {
        
$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(substr_count($chars"\n"));
    }

    private function 
autoPrependText()
    {
        
$fetched $this->bufferedOutput->fetch();
        
//Prepend new line if last char isn't EOL:
        
if ("\n" !== substr($fetched, -1)) {
            
$this->newLine();
        }
    }

    private function 
reduceBuffer($messages)
    {
        
// We need to know if the two last chars are PHP_EOL
        // Preserve the last 4 chars inserted (PHP_EOL on windows is two chars) in the history buffer
        
return array_map(function ($value) {
            return 
substr($value, -4);
        }, 
array_merge([$this->bufferedOutput->fetch()], (array) $messages));
    }

    private function 
createBlock($messages$type null$style null$prefix ' '$padding false$escape false)
    {
        
$indentLength 0;
        
$prefixLength Helper::strlenWithoutDecoration($this->getFormatter(), $prefix);
        
$lines = [];

        if (
null !== $type) {
            
$type sprintf('[%s] '$type);
            
$indentLength \strlen($type);
            
$lineIndentation str_repeat(' '$indentLength);
        }

        
// wrap and add newlines for each element
        
foreach ($messages as $key => $message) {
            if (
$escape) {
                
$message OutputFormatter::escape($message);
            }

            
$lines array_merge($linesexplode(\PHP_EOLwordwrap($message$this->lineLength $prefixLength $indentLength\PHP_EOLtrue)));

            if (
\count($messages) > && $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(' '$this->lineLength Helper::strlenWithoutDecoration($this->getFormatter(), $line));

            if (
$style) {
                
$line sprintf('<%s>%s</>'$style$line);
            }
        }

        return 
$lines;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
abstract class OutputStyle implements OutputInterfaceStyleInterface
{
    private 
$output;

    public function 
__construct(OutputInterface $output)
    {
        
$this->output $output;
    }

    
/**
     * {@inheritdoc}
     */
    
public function newLine($count 1)
    {
        
$this->output->write(str_repeat(\PHP_EOL$count));
    }

    
/**
     * @param int $max
     *
     * @return ProgressBar
     */
    
public function createProgressBar($max 0)
    {
        return new 
ProgressBar($this->output$max);
    }

    
/**
     * {@inheritdoc}
     */
    
public function write($messages$newline false$type self::OUTPUT_NORMAL)
    {
        
$this->output->write($messages$newline$type);
    }

    
/**
     * {@inheritdoc}
     */
    
public function writeln($messages$type self::OUTPUT_NORMAL)
    {
        
$this->output->writeln($messages$type);
    }

    
/**
     * {@inheritdoc}
     */
    
public function setVerbosity($level)
    {
        
$this->output->setVerbosity($level);
    }

    
/**
     * {@inheritdoc}
     */
    
public function getVerbosity()
    {
        return 
$this->output->getVerbosity();
    }

    
/**
     * {@inheritdoc}
     */
    
public function setDecorated($decorated)
    {
        
$this->output->setDecorated($decorated);
    }

    
/**
     * {@inheritdoc}
     */
    
public function isDecorated()
    {
        return 
$this->output->isDecorated();
    }

    
/**
     * {@inheritdoc}
     */
    
public function setFormatter(OutputFormatterInterface $formatter)
    {
        
$this->output->setFormatter($formatter);
    }

    
/**
     * {@inheritdoc}
     */
    
public function getFormatter()
    {
        return 
$this->output->getFormatter();
    }

    
/**
     * {@inheritdoc}
     */
    
public function isQuiet()
    {
        return 
$this->output->isQuiet();
    }

    
/**
     * {@inheritdoc}
     */
    
public function isVerbose()
    {
        return 
$this->output->isVerbose();
    }

    
/**
     * {@inheritdoc}
     */
    
public function isVeryVerbose()
    {
        return 
$this->output->isVeryVerbose();
    }

    
/**
     * {@inheritdoc}
     */
    
public function isDebug()
    {
        return 
$this->output->isDebug();
    }

    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 <[email protected]>
 *
 * 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;

/**
 * Formatter style class for defining styles.
 *
 * @author Konstantin Kudryashov <[email protected]>
 */
class OutputFormatterStyle implements OutputFormatterStyleInterface
{
    private static 
$availableForegroundColors = [
        
'black' => ['set' => 30'unset' => 39],
        
'red' => ['set' => 31'unset' => 39],
        
'green' => ['set' => 32'unset' => 39],
        
'yellow' => ['set' => 33'unset' => 39],
        
'blue' => ['set' => 34'unset' => 39],
        
'magenta' => ['set' => 35'unset' => 39],
        
'cyan' => ['set' => 36'unset' => 39],
        
'white' => ['set' => 37'unset' => 39],
        
'default' => ['set' => 39'unset' => 39],
    ];
    private static 
$availableBackgroundColors = [
        
'black' => ['set' => 40'unset' => 49],
        
'red' => ['set' => 41'unset' => 49],
        
'green' => ['set' => 42'unset' => 49],
        
'yellow' => ['set' => 43'unset' => 49],
        
'blue' => ['set' => 44'unset' => 49],
        
'magenta' => ['set' => 45'unset' => 49],
        
'cyan' => ['set' => 46'unset' => 49],
        
'white' => ['set' => 47'unset' => 49],
        
'default' => ['set' => 49'unset' => 49],
    ];
    private static 
$availableOptions = [
        
'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 
$foreground;
    private 
$background;
    private 
$options = [];

    
/**
     * Initializes output formatter style.
     *
     * @param string|null $foreground The style foreground color name
     * @param string|null $background The style background color name
     * @param array       $options    The style options
     */
    
public function __construct($foreground null$background null, array $options = [])
    {
        if (
null !== $foreground) {
            
$this->setForeground($foreground);
        }
        if (
null !== $background) {
            
$this->setBackground($background);
        }
        if (
\count($options)) {
            
$this->setOptions($options);
        }
    }

    
/**
     * {@inheritdoc}
     */
    
public function setForeground($color null)
    {
        if (
null === $color) {
            
$this->foreground null;

            return;
        }

        if (!isset(static::
$availableForegroundColors[$color])) {
            throw new 
InvalidArgumentException(sprintf('Invalid foreground color specified: "%s". Expected one of (%s).'$colorimplode(', 'array_keys(static::$availableForegroundColors))));
        }

        
$this->foreground = static::$availableForegroundColors[$color];
    }

    
/**
     * {@inheritdoc}
     */
    
public function setBackground($color null)
    {
        if (
null === $color) {
            
$this->background null;

            return;
        }

        if (!isset(static::
$availableBackgroundColors[$color])) {
            throw new 
InvalidArgumentException(sprintf('Invalid background color specified: "%s". Expected one of (%s).'$colorimplode(', 'array_keys(static::$availableBackgroundColors))));
        }

        
$this->background = static::$availableBackgroundColors[$color];
    }

    
/**
     * {@inheritdoc}
     */
    
public function setOption($option)
    {
        if (!isset(static::
$availableOptions[$option])) {
            throw new 
InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s).'$optionimplode(', 'array_keys(static::$availableOptions))));
        }

        if (!
\in_array(static::$availableOptions[$option], $this->options)) {
            
$this->options[] = static::$availableOptions[$option];
        }
    }

    
/**
     * {@inheritdoc}
     */
    
public function unsetOption($option)
    {
        if (!isset(static::
$availableOptions[$option])) {
            throw new 
InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s).'$optionimplode(', 'array_keys(static::$availableOptions))));
        }

        
$pos array_search(static::$availableOptions[$option], $this->options);
        if (
false !== $pos) {
            unset(
$this->options[$pos]);
        }
    }

    
/**
     * {@inheritdoc}
     */
    
public function setOptions(array $options)
    {
        
$this->options = [];

        foreach (
$options as $option) {
            
$this->setOption($option);
        }
    }

    
/**
     * {@inheritdoc}
     */
    
public function apply($text)
    {
        
$setCodes = [];
        
$unsetCodes = [];

        if (
null !== $this->foreground) {
            
$setCodes[] = $this->foreground['set'];
            
$unsetCodes[] = $this->foreground['unset'];
        }
        if (
null !== $this->background) {
            
$setCodes[] = $this->background['set'];
            
$unsetCodes[] = $this->background['unset'];
        }
        if (
\count($this->options)) {
            foreach (
$this->options as $option) {
                
$setCodes[] = $option['set'];
                
$unsetCodes[] = $option['unset'];
            }
        }

        if (
=== \count($setCodes)) {
            return 
$text;
        }

        return 
sprintf("\033[%sm%s\033[%sm"implode(';'$setCodes), $textimplode(';'$unsetCodes));
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
interface OutputFormatterStyleInterface
{
    
/**
     * Sets style foreground color.
     *
     * @param string|null $color The color name
     */
    
public function setForeground($color null);

    
/**
     * Sets style background color.
     *
     * @param string $color The color name
     */
    
public function setBackground($color null);

    
/**
     * Sets some specific style option.
     *
     * @param string $option The option name
     */
    
public function setOption($option);

    
/**
     * Unsets some specific style option.
     *
     * @param string $option The option name
     */
    
public function unsetOption($option);

    
/**
     * Sets multiple style options at once.
     */
    
public function setOptions(array $options);

    
/**
     * Applies the style to a given text.
     *
     * @param string $text The text to style
     *
     * @return string
     */
    
public function apply($text);
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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;

/**
 * Formatter class for console output.
 *
 * @author Konstantin Kudryashov <[email protected]>
 */
class OutputFormatter implements OutputFormatterInterface
{
    private 
$decorated;
    private 
$styles = [];
    private 
$styleStack;

    
/**
     * Escapes "<" special char in given text.
     *
     * @param string $text Text to escape
     *
     * @return string Escaped text
     */
    
public static function escape($text)
    {
        
$text preg_replace('/([^\\\\]?)</''$1\\<'$text);

        return 
self::escapeTrailingBackslash($text);
    }

    
/**
     * Escapes trailing "\" in given text.
     *
     * @param string $text Text to escape
     *
     * @return string Escaped text
     *
     * @internal
     */
    
public static function escapeTrailingBackslash($text)
    {
        if (
'\\' === substr($text, -1)) {
            
$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 bool                            $decorated Whether this formatter should actually decorate strings
     * @param OutputFormatterStyleInterface[] $styles    Array of "name => FormatterStyle" instances
     */
    
public function __construct($decorated false, array $styles = [])
    {
        
$this->decorated = (bool) $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();
    }

    
/**
     * {@inheritdoc}
     */
    
public function setDecorated($decorated)
    {
        
$this->decorated = (bool) $decorated;
    }

    
/**
     * {@inheritdoc}
     */
    
public function isDecorated()
    {
        return 
$this->decorated;
    }

    
/**
     * {@inheritdoc}
     */
    
public function setStyle($nameOutputFormatterStyleInterface $style)
    {
        
$this->styles[strtolower($name)] = $style;
    }

    
/**
     * {@inheritdoc}
     */
    
public function hasStyle($name)
    {
        return isset(
$this->styles[strtolower($name)]);
    }

    
/**
     * {@inheritdoc}
     */
    
public function getStyle($name)
    {
        if (!
$this->hasStyle($name)) {
            throw new 
InvalidArgumentException(sprintf('Undefined style: "%s".'$name));
        }

        return 
$this->styles[strtolower($name)];
    }

    
/**
     * {@inheritdoc}
     */
    
public function format($message)
    {
        
$message = (string) $message;
        
$offset 0;
        
$output '';
        
$tagRegex '[a-z][a-z0-9,_=;-]*+';
        
preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#ix"$message$matches\PREG_OFFSET_CAPTURE);
        foreach (
$matches[0] as $i => $match) {
            
$pos $match[1];
            
$text $match[0];

            if (
!= $pos && '\\' == $message[$pos 1]) {
                continue;
            }

            
// add the text up to the next tag
            
$output .= $this->applyCurrentStyle(substr($message$offset$pos $offset));
            
$offset $pos \strlen($text);

            
// opening tag?
            
if ($open '/' != $text[1]) {
                
$tag $matches[1][$i][0];
            } else {
                
$tag = isset($matches[3][$i][0]) ? $matches[3][$i][0] : '';
            }

            if (!
$open && !$tag) {
                
// </>
                
$this->styleStack->pop();
            } elseif (
false === $style $this->createStyleFromString($tag)) {
                
$output .= $this->applyCurrentStyle($text);
            } elseif (
$open) {
                
$this->styleStack->push($style);
            } else {
                
$this->styleStack->pop($style);
            }
        }

        
$output .= $this->applyCurrentStyle(substr($message$offset));

        if (
false !== strpos($output"\0")) {
            return 
strtr($output, ["\0" => '\\''\\<' => '<']);
        }

        return 
str_replace('\\<''<'$output);
    }

    
/**
     * @return OutputFormatterStyleStack
     */
    
public function getStyleStack()
    {
        return 
$this->styleStack;
    }

    
/**
     * Tries to create new style instance from string.
     *
     * @param string $string
     *
     * @return OutputFormatterStyle|false false if string is not format string
     */
    
private function createStyleFromString($string)
    {
        if (isset(
$this->styles[$string])) {
            return 
$this->styles[$string];
        }

        if (!
preg_match_all('/([^=]+)=([^;]+)(;|$)/'$string$matches\PREG_SET_ORDER)) {
            return 
false;
        }

        
$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 (
'options' === $match[0]) {
                
preg_match_all('([^,;]+)'strtolower($match[1]), $options);
                
$options array_shift($options);
                foreach (
$options as $option) {
                    try {
                        
$style->setOption($option);
                    } catch (
\InvalidArgumentException $e) {
                        @
trigger_error(sprintf('Unknown style options are deprecated since Symfony 3.2 and will be removed in 4.0. Exception "%s".'$e->getMessage()), \E_USER_DEPRECATED);

                        return 
false;
                    }
                }
            } else {
                return 
false;
            }
        }

        return 
$style;
    }

    
/**
     * Applies current style from stack to text, if must be applied.
     *
     * @param string $text Input text
     *
     * @return string Styled text
     */
    
private function applyCurrentStyle($text)
    {
        return 
$this->isDecorated() && \strlen($text) > $this->styleStack->getCurrent()->apply($text) : $text;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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;

/**
 * @author Jean-François Simon <[email protected]>
 */
class OutputFormatterStyleStack
{
    
/**
     * @var OutputFormatterStyleInterface[]
     */
    
private $styles;

    private 
$emptyStyle;

    public function 
__construct(OutputFormatterStyleInterface $emptyStyle null)
    {
        
$this->emptyStyle $emptyStyle ?: new OutputFormatterStyle();
        
$this->reset();
    }

    
/**
     * Resets stack (ie. empty internal arrays).
     */
    
public function reset()
    {
        
$this->styles = [];
    }

    
/**
     * Pushes a style in the stack.
     */
    
public function push(OutputFormatterStyleInterface $style)
    {
        
$this->styles[] = $style;
    }

    
/**
     * Pops a style from the stack.
     *
     * @return OutputFormatterStyleInterface
     *
     * @throws InvalidArgumentException When style tags incorrectly nested
     */
    
public function pop(OutputFormatterStyleInterface $style null)
    {
        if (empty(
$this->styles)) {
            return 
$this->emptyStyle;
        }

        if (
null === $style) {
            return 
array_pop($this->styles);
        }

        foreach (
array_reverse($this->stylestrue) as $index => $stackedStyle) {
            if (
$style->apply('') === $stackedStyle->apply('')) {
                
$this->styles \array_slice($this->styles0$index);

                return 
$stackedStyle;
            }
        }

        throw new 
InvalidArgumentException('Incorrectly nested style tag found.');
    }

    
/**
     * Computes current style with stacks top codes.
     *
     * @return OutputFormatterStyle
     */
    
public function getCurrent()
    {
        if (empty(
$this->styles)) {
            return 
$this->emptyStyle;
        }

        return 
$this->styles[\count($this->styles) - 1];
    }

    
/**
     * @return $this
     */
    
public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle)
    {
        
$this->emptyStyle $emptyStyle;

        return 
$this;
    }

    
/**
     * @return OutputFormatterStyleInterface
     */
    
public function getEmptyStyle()
    {
        return 
$this->emptyStyle;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
interface OutputFormatterInterface
{
    
/**
     * Sets the decorated flag.
     *
     * @param bool $decorated Whether to decorate the messages or not
     */
    
public function setDecorated($decorated);

    
/**
     * Gets the decorated flag.
     *
     * @return bool true if the output will decorate messages, false otherwise
     */
    
public function isDecorated();

    
/**
     * Sets a new style.
     *
     * @param string                        $name  The style name
     * @param OutputFormatterStyleInterface $style The style instance
     */
    
public function setStyle($nameOutputFormatterStyleInterface $style);

    
/**
     * Checks if output formatter has style with specified name.
     *
     * @param string $name
     *
     * @return bool
     */
    
public function hasStyle($name);

    
/**
     * Gets style options from style with specified name.
     *
     * @param string $name
     *
     * @return OutputFormatterStyleInterface
     *
     * @throws \InvalidArgumentException When style isn't defined
     */
    
public function getStyle($name);

    
/**
     * Formats a message according to the given styles.
     *
     * @param string $message The message to style
     *
     * @return string The styled message
     */
    
public function format($message);
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class ConsoleTerminateEvent extends ConsoleEvent
{
    
/**
     * The exit code of the command.
     *
     * @var int
     */
    
private $exitCode;

    public function 
__construct(Command $commandInputInterface $inputOutputInterface $output$exitCode)
    {
        
parent::__construct($command$input$output);

        
$this->setExitCode($exitCode);
    }

    
/**
     * Sets the exit code.
     *
     * @param int $exitCode The command exit code
     */
    
public function setExitCode($exitCode)
    {
        
$this->exitCode = (int) $exitCode;
    }

    
/**
     * Gets the exit code.
     *
     * @return int The command exit code
     */
    
public function getExitCode()
    {
        return 
$this->exitCode;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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\Component\EventDispatcher\Event;

/**
 * Allows to inspect input and output of a command.
 *
 * @author Francesco Levorato <[email protected]>
 */
class ConsoleEvent extends Event
{
    protected 
$command;

    private 
$input;
    private 
$output;

    public function 
__construct(Command $command nullInputInterface $inputOutputInterface $output)
    {
        
$this->command $command;
        
$this->input $input;
        
$this->output $output;
    }

    
/**
     * Gets the command that is executed.
     *
     * @return Command|null A Command instance
     */
    
public function getCommand()
    {
        return 
$this->command;
    }

    
/**
     * Gets the input instance.
     *
     * @return InputInterface An InputInterface instance
     */
    
public function getInput()
    {
        return 
$this->input;
    }

    
/**
     * Gets the output instance.
     *
     * @return OutputInterface An OutputInterface instance
     */
    
public function getOutput()
    {
        return 
$this->output;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Event;

@
trigger_error(sprintf('The "%s" class is deprecated since Symfony 3.3 and will be removed in 4.0. Use the ConsoleErrorEvent instead.'ConsoleExceptionEvent::class), \E_USER_DEPRECATED);

use 
Symfony\Component\Console\Command\Command;
use 
Symfony\Component\Console\Input\InputInterface;
use 
Symfony\Component\Console\Output\OutputInterface;

/**
 * Allows to handle exception thrown in a command.
 *
 * @author Fabien Potencier <[email protected]>
 *
 * @deprecated since version 3.3, to be removed in 4.0. Use ConsoleErrorEvent instead.
 */
class ConsoleExceptionEvent extends ConsoleEvent
{
    private 
$exception;
    private 
$exitCode;

    public function 
__construct(Command $commandInputInterface $inputOutputInterface $output\Exception $exception$exitCode)
    {
        
parent::__construct($command$input$output);

        
$this->setException($exception);
        
$this->exitCode = (int) $exitCode;
    }

    
/**
     * Returns the thrown exception.
     *
     * @return \Exception The thrown exception
     */
    
public function getException()
    {
        return 
$this->exception;
    }

    
/**
     * Replaces the thrown exception.
     *
     * This exception will be thrown if no response is set in the event.
     *
     * @param \Exception $exception The thrown exception
     */
    
public function setException(\Exception $exception)
    {
        
$this->exception $exception;
    }

    
/**
     * Gets the exit code.
     *
     * @return int The command exit code
     */
    
public function getExitCode()
    {
        return 
$this->exitCode;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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\Exception\InvalidArgumentException;
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 <[email protected]>
 */
final class ConsoleErrorEvent extends ConsoleEvent
{
    private 
$error;
    private 
$exitCode;

    public function 
__construct(InputInterface $inputOutputInterface $output$errorCommand $command null)
    {
        
parent::__construct($command$input$output);

        
$this->setError($error);
    }

    
/**
     * Returns the thrown error/exception.
     *
     * @return \Throwable
     */
    
public function getError()
    {
        return 
$this->error;
    }

    
/**
     * Replaces the thrown error/exception.
     *
     * @param \Throwable $error
     */
    
public function setError($error)
    {
        if (!
$error instanceof \Throwable && !$error instanceof \Exception) {
            throw new 
InvalidArgumentException(sprintf('The error passed to ConsoleErrorEvent must be an instance of \Throwable or \Exception, "%s" was passed instead.'\is_object($error) ? \get_class($error) : \gettype($error)));
        }

        
$this->error $error;
    }

    
/**
     * Sets the exit code.
     *
     * @param int $exitCode The command exit code
     */
    
public function setExitCode($exitCode)
    {
        
$this->exitCode = (int) $exitCode;

        
$r = new \ReflectionProperty($this->error'code');
        
$r->setAccessible(true);
        
$r->setValue($this->error$this->exitCode);
    }

    
/**
     * Gets the exit code.
     *
     * @return int The command exit code
     */
    
public function getExitCode()
    {
        return 
null !== $this->exitCode $this->exitCode : (\is_int($this->error->getCode()) && !== $this->error->getCode() ? $this->error->getCode() : 1);
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 changing the input.
 *
 * @author Fabien Potencier <[email protected]>
 */
class ConsoleCommandEvent extends ConsoleEvent
{
    
/**
     * The return code for skipped commands, this will also be passed into the terminate event.
     */
    
const RETURN_CODE_DISABLED 113;

    
/**
     * Indicates if the command should be run or skipped.
     */
    
private $commandShouldRun true;

    
/**
     * Disables the command, so it won't be run.
     *
     * @return bool
     */
    
public function disableCommand()
    {
        return 
$this->commandShouldRun false;
    }

    
/**
     * Enables the command.
     *
     * @return bool
     */
    
public function enableCommand()
    {
        return 
$this->commandShouldRun true;
    }

    
/**
     * Returns true if the command is runnable, false otherwise.
     *
     * @return bool
     */
    
public function commandShouldRun()
    {
        return 
$this->commandShouldRun;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console;

/**
 * Contains all events dispatched by an Application.
 *
 * @author Francesco Levorato <[email protected]>
 */
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 handled to the command.
     *
     * @Event("Symfony\Component\Console\Event\ConsoleCommandEvent")
     */
    
const COMMAND 'console.command';

    
/**
     * The TERMINATE event allows you to attach listeners after a command is
     * executed by the console.
     *
     * @Event("Symfony\Component\Console\Event\ConsoleTerminateEvent")
     */
    
const TERMINATE 'console.terminate';

    
/**
     * The EXCEPTION event occurs when an uncaught exception appears
     * while executing Command#run().
     *
     * This event allows you to deal with the exception or
     * to modify the thrown exception.
     *
     * @Event("Symfony\Component\Console\Event\ConsoleExceptionEvent")
     *
     * @deprecated The console.exception event is deprecated since version 3.3 and will be removed in 4.0. Use the console.error event instead.
     */
    
const EXCEPTION 'console.exception';

    
/**
     * 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")
     */
    
const ERROR 'console.error';
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class SplFileInfo extends \SplFileInfo
{
    private 
$relativePath;
    private 
$relativePathname;

    
/**
     * @param string $file             The file name
     * @param string $relativePath     The relative path
     * @param string $relativePathname The relative path name
     */
    
public function __construct($file$relativePath$relativePathname)
    {
        
parent::__construct($file);
        
$this->relativePath $relativePath;
        
$this->relativePathname $relativePathname;
    }

    
/**
     * Returns the relative path.
     *
     * This path does not contain the file name.
     *
     * @return string the relative path
     */
    
public function getRelativePath()
    {
        return 
$this->relativePath;
    }

    
/**
     * Returns the relative path name.
     *
     * This path contains the file name.
     *
     * @return string the relative path name
     */
    
public function getRelativePathname()
    {
        return 
$this->relativePathname;
    }

    
/**
     * Returns the contents of the file.
     *
     * @return string the contents of the file
     *
     * @throws \RuntimeException
     */
    
public function getContents()
    {
        
set_error_handler(function ($type$msg) use (&$error) { $error $msg; });
        
$content file_get_contents($this->getPathname());
        
restore_error_handler();
        if (
false === $content) {
            throw new 
\RuntimeException($error);
        }

        return 
$content;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 *
 * @deprecated since 3.3, to be removed in 4.0.
 */
interface ExceptionInterface
{
    
/**
     * @return \Symfony\Component\Finder\Adapter\AdapterInterface
     */
    
public function getAdapter();
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class AccessDeniedException extends \UnexpectedValueException
{
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class DateComparator extends Comparator
{
    
/**
     * @param string $test A comparison string
     *
     * @throws \InvalidArgumentException If the test is not understood
     */
    
public function __construct($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 \DateTime($matches[2]);
            
$target $date->format('U');
        } catch (
\Exception $e) {
            throw new 
\InvalidArgumentException(sprintf('"%s" is not a valid date.'$matches[2]));
        }

        
$operator = isset($matches[1]) ? $matches[1] : '==';
        if (
'since' === $operator || 'after' === $operator) {
            
$operator '>';
        }

        if (
'until' === $operator || 'before' === $operator) {
            
$operator '<';
        }

        
$this->setOperator($operator);
        
$this->setTarget($target);
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Comparator;

/**
 * Comparator.
 *
 * @author Fabien Potencier <[email protected]>
 */
class Comparator
{
    private 
$target;
    private 
$operator '==';

    
/**
     * Gets the target value.
     *
     * @return string The target value
     */
    
public function getTarget()
    {
        return 
$this->target;
    }

    
/**
     * Sets the target value.
     *
     * @param string $target The target value
     */
    
public function setTarget($target)
    {
        
$this->target $target;
    }

    
/**
     * Gets the comparison operator.
     *
     * @return string The operator
     */
    
public function getOperator()
    {
        return 
$this->operator;
    }

    
/**
     * Sets the comparison operator.
     *
     * @param string $operator A valid operator
     *
     * @throws \InvalidArgumentException
     */
    
public function setOperator($operator)
    {
        if (!
$operator) {
            
$operator '==';
        }

        if (!
\in_array($operator, ['>''<''>=''<=''==''!='])) {
            throw new 
\InvalidArgumentException(sprintf('Invalid operator "%s".'$operator));
        }

        
$this->operator $operator;
    }

    
/**
     * Tests against the target.
     *
     * @param mixed $test A test value
     *
     * @return bool
     */
    
public function test($test)
    {
        switch (
$this->operator) {
            case 
'>':
                return 
$test $this->target;
            case 
'>=':
                return 
$test >= $this->target;
            case 
'<':
                return 
$test $this->target;
            case 
'<=':
                return 
$test <= $this->target;
            case 
'!=':
                return 
$test != $this->target;
        }

        return 
$test == $this->target;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]> PHP port
 * @author    Richard Clamp <[email protected]> Perl version
 * @copyright 2004-2005 Fabien Potencier <[email protected]>
 * @copyright 2002 Richard Clamp <[email protected]>
 *
 * @see http://physics.nist.gov/cuu/Units/binary.html
 */
class NumberComparator extends Comparator
{
    
/**
     * @param string|int $test A comparison string or an integer
     *
     * @throws \InvalidArgumentException If the test is not understood
     */
    
public function __construct($test)
    {
        if (!
preg_match('#^\s*(==|!=|[<>]=?)?\s*([0-9\.]+)\s*([kmg]i?)?\s*$#i'$test$matches)) {
            throw new 
\InvalidArgumentException(sprintf('Don\'t understand "%s" as a number test.'$test));
        }

        
$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;
            }
        }

        
$this->setTarget($target);
        
$this->setOperator(isset($matches[1]) ? $matches[1] : '==');
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class DateRangeFilterIterator extends FilterIterator
{
    private 
$comparators = [];

    
/**
     * @param \Iterator        $iterator    The Iterator to filter
     * @param DateComparator[] $comparators An array of DateComparator instances
     */
    
public function __construct(\Iterator $iterator, array $comparators)
    {
        
$this->comparators $comparators;

        
parent::__construct($iterator);
    }

    
/**
     * Filters the iterator values.
     *
     * @return bool true if the value should be kept, false otherwise
     */
    
public function accept()
    {
        
$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 <[email protected]>
 *
 * 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 <[email protected]>
 */
class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator
{
    
/**
     * @var bool
     */
    
private $ignoreUnreadableDirs;

    
/**
     * @var bool
     */
    
private $rewindable;

    
// these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations
    
private $rootPath;
    private 
$subPath;
    private 
$directorySeparator '/';

    
/**
     * @param string $path
     * @param int    $flags
     * @param bool   $ignoreUnreadableDirs
     *
     * @throws \RuntimeException
     */
    
public function __construct($path$flags$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.
     *
     * @return SplFileInfo File information
     */
    
public function current()
    {
        
// the logic here avoids redoing the same work in all iterations

        
if (null === $subPathname $this->subPath) {
            
$subPathname $this->subPath = (string) $this->getSubPath();
        }
        if (
'' !== $subPathname) {
            
$subPathname .= $this->directorySeparator;
        }
        
$subPathname .= $this->getFilename();

        if (
'/' !== $basePath $this->rootPath) {
            
$basePath .= $this->directorySeparator;
        }

        return new 
SplFileInfo($basePath.$subPathname$this->subPath$subPathname);
    }

    
/**
     * @return \RecursiveIterator
     *
     * @throws AccessDeniedException
     */
    
public function getChildren()
    {
        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->rewindable = &$this->rewindable;
                
$children->rootPath $this->rootPath;
            }

            return 
$children;
        } catch (
\UnexpectedValueException $e) {
            if (
$this->ignoreUnreadableDirs) {
                
// If directory is unreadable and finder is set to ignore it, a fake empty content is returned.
                
return new \RecursiveArrayIterator([]);
            } else {
                throw new 
AccessDeniedException($e->getMessage(), $e->getCode(), $e);
            }
        }
    }

    
/**
     * Do nothing for non rewindable stream.
     */
    
public function rewind()
    {
        if (
false === $this->isRewindable()) {
            return;
        }

        
// @see https://bugs.php.net/68557
        
if (\PHP_VERSION_ID 50523 || \PHP_VERSION_ID >= 50600 && \PHP_VERSION_ID 50607) {
            
parent::next();
        }

        
parent::rewind();
    }

    
/**
     * Checks if the stream is rewindable.
     *
     * @return bool true when the stream is rewindable, false otherwise
     */
    
public function isRewindable()
    {
        if (
null !== $this->rewindable) {
            return 
$this->rewindable;
        }

        
// workaround for an HHVM bug, should be removed when https://github.com/facebook/hhvm/issues/7281 is fixed
        
if ('' === $this->getPath()) {
            return 
$this->rewindable false;
        }

        if (
false !== $stream = @opendir($this->getPath())) {
            
$infos stream_get_meta_data($stream);
            
closedir($stream);

            if (
$infos['seekable']) {
                return 
$this->rewindable true;
            }
        }

        return 
$this->rewindable false;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class FilenameFilterIterator extends MultiplePcreFilterIterator
{
    
/**
     * Filters the iterator values.
     *
     * @return bool true if the value should be kept, false otherwise
     */
    
public function accept()
    {
        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
     *
     * @return string regexp corresponding to a given glob or regexp
     */
    
protected function toRegex($str)
    {
        return 
$this->isRegex($str) ? $str Glob::toRegex($str);
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class SizeRangeFilterIterator extends FilterIterator
{
    private 
$comparators = [];

    
/**
     * @param \Iterator          $iterator    The Iterator to filter
     * @param NumberComparator[] $comparators An array of NumberComparator instances
     */
    
public function __construct(\Iterator $iterator, array $comparators)
    {
        
$this->comparators $comparators;

        
parent::__construct($iterator);
    }

    
/**
     * Filters the iterator values.
     *
     * @return bool true if the value should be kept, false otherwise
     */
    
public function accept()
    {
        
$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 <[email protected]>
 *
 * 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 <[email protected]>
 */
class CustomFilterIterator extends FilterIterator
{
    private 
$filters = [];

    
/**
     * @param \Iterator  $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.
     *
     * @return bool true if the value should be kept, false otherwise
     */
    
public function accept()
    {
        
$fileinfo $this->current();

        foreach (
$this->filters as $filter) {
            if (
false === \call_user_func($filter$fileinfo)) {
                return 
false;
            }
        }

        return 
true;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class SortableIterator implements \IteratorAggregate
{
    const 
SORT_BY_NAME 1;
    const 
SORT_BY_TYPE 2;
    const 
SORT_BY_ACCESSED_TIME 3;
    const 
SORT_BY_CHANGED_TIME 4;
    const 
SORT_BY_MODIFIED_TIME 5;

    private 
$iterator;
    private 
$sort;

    
/**
     * @param \Traversable $iterator The Iterator to filter
     * @param int|callable $sort     The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a PHP callback)
     *
     * @throws \InvalidArgumentException
     */
    
public function __construct(\Traversable $iterator$sort)
    {
        
$this->iterator $iterator;

        if (
self::SORT_BY_NAME === $sort) {
            
$this->sort = static function ($a$b) {
                return 
strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
            };
        } elseif (
self::SORT_BY_TYPE === $sort) {
            
$this->sort = static function ($a$b) {
                if (
$a->isDir() && $b->isFile()) {
                    return -
1;
                } elseif (
$a->isFile() && $b->isDir()) {
                    return 
1;
                }

                return 
strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
            };
        } elseif (
self::SORT_BY_ACCESSED_TIME === $sort) {
            
$this->sort = static function ($a$b) {
                return 
$a->getATime() - $b->getATime();
            };
        } elseif (
self::SORT_BY_CHANGED_TIME === $sort) {
            
$this->sort = static function ($a$b) {
                return 
$a->getCTime() - $b->getCTime();
            };
        } elseif (
self::SORT_BY_MODIFIED_TIME === $sort) {
            
$this->sort = static function ($a$b) {
                return 
$a->getMTime() - $b->getMTime();
            };
        } elseif (
\is_callable($sort)) {
            
$this->sort $sort;
        } else {
            throw new 
\InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.');
        }
    }

    public function 
getIterator()
    {
        
$array iterator_to_array($this->iteratortrue);
        
uasort($array$this->sort);

        return new 
\ArrayIterator($array);
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * FilecontentFilterIterator filters files by their contents using patterns (regexps or strings).
 *
 * @author Fabien Potencier  <[email protected]>
 * @author Włodzimierz Gajda <[email protected]>
 */
class FilecontentFilterIterator extends MultiplePcreFilterIterator
{
    
/**
     * Filters the iterator values.
     *
     * @return bool true if the value should be kept, false otherwise
     */
    
public function accept()
    {
        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
     *
     * @return string regexp corresponding to a given string or regexp
     */
    
protected function toRegex($str)
    {
        return 
$this->isRegex($str) ? $str '/'.preg_quote($str'/').'/';
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * This iterator just overrides the rewind method in order to correct a PHP bug,
 * which existed before version 5.5.23/5.6.7.
 *
 * @see https://bugs.php.net/68557
 *
 * @author Alex Bogomazov
 *
 * @deprecated since 3.4, to be removed in 4.0.
 */
abstract class FilterIterator extends \FilterIterator
{
    
/**
     * This is a workaround for the problem with \FilterIterator leaving inner \FilesystemIterator in wrong state after
     * rewind in some cases.
     *
     * @see FilterIterator::rewind()
     */
    
public function rewind()
    {
        if (
\PHP_VERSION_ID 50607 || (\PHP_VERSION_ID 50523 && \PHP_VERSION_ID 50600)) {
            
parent::rewind();

            return;
        }

        
$iterator $this;
        while (
$iterator instanceof \OuterIterator) {
            
$innerIterator $iterator->getInnerIterator();

            if (
$innerIterator instanceof RecursiveDirectoryIterator) {
                
// this condition is necessary for iterators to work properly with non-local filesystems like ftp
                
if ($innerIterator->isRewindable()) {
                    
$innerIterator->next();
                    
$innerIterator->rewind();
                }
            } elseif (
$innerIterator instanceof \FilesystemIterator) {
                
$innerIterator->next();
                
$innerIterator->rewind();
            }

            
$iterator $innerIterator;
        }

        
parent::rewind();
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * ExcludeDirectoryFilterIterator filters out directories.
 *
 * @author Fabien Potencier <[email protected]>
 */
class ExcludeDirectoryFilterIterator extends FilterIterator implements \RecursiveIterator
{
    private 
$iterator;
    private 
$isRecursive;
    private 
$excludedDirs = [];
    private 
$excludedPattern;

    
/**
     * @param \Iterator $iterator    The Iterator to filter
     * @param string[]  $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) {
            
$directory rtrim($directory'/');
            if (!
$this->isRecursive || false !== strpos($directory'/')) {
                
$patterns[] = preg_quote($directory'#');
            } else {
                
$this->excludedDirs[$directory] = true;
            }
        }
        if (
$patterns) {
            
$this->excludedPattern '#(?:^|/)(?:'.implode('|'$patterns).')(?:/|$)#';
        }

        
parent::__construct($iterator);
    }

    
/**
     * Filters the iterator values.
     *
     * @return bool True if the value should be kept, false otherwise
     */
    
public function accept()
    {
        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);
        }

        return 
true;
    }

    public function 
hasChildren()
    {
        return 
$this->isRecursive && $this->iterator->hasChildren();
    }

    public function 
getChildren()
    {
        
$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 <[email protected]>
 *
 * 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 <[email protected]>
 */
abstract class MultiplePcreFilterIterator extends FilterIterator
{
    protected 
$matchRegexps = [];
    protected 
$noMatchRegexps = [];

    
/**
     * @param \Iterator $iterator        The Iterator to filter
     * @param array     $matchPatterns   An array of patterns that need to match
     * @param array     $noMatchPatterns An array of patterns that need to not match
     */
    
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.
     *
     * @param string $string The string to be matched against filters
     *
     * @return bool
     */
    
protected function isAccepted($string)
    {
        
// 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.
     *
     * @param string $str
     *
     * @return bool Whether the given string is a regex
     */
    
protected function isRegex($str)
    {
        if (
preg_match('/^(.{3,}?)[imsxuADU]*$/'$str$m)) {
            
$start substr($m[1], 01);
            
$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.
     *
     * @param string $str Pattern
     *
     * @return string regexp corresponding to a given string
     */
    
abstract protected function toRegex($str);
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * PathFilterIterator filters files by path patterns (e.g. some/special/dir).
 *
 * @author Fabien Potencier  <[email protected]>
 * @author Włodzimierz Gajda <[email protected]>
 */
class PathFilterIterator extends MultiplePcreFilterIterator
{
    
/**
     * Filters the iterator values.
     *
     * @return bool true if the value should be kept, false otherwise
     */
    
public function accept()
    {
        
$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
     *
     * @return string regexp corresponding to a given string or regexp
     */
    
protected function toRegex($str)
    {
        return 
$this->isRegex($str) ? $str '/'.preg_quote($str'/').'/';
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class DepthRangeFilterIterator extends FilterIterator
{
    private 
$minDepth 0;

    
/**
     * @param \RecursiveIteratorIterator $iterator The Iterator to filter
     * @param int                        $minDepth The min depth
     * @param int                        $maxDepth The max depth
     */
    
public function __construct(\RecursiveIteratorIterator $iterator$minDepth 0$maxDepth \PHP_INT_MAX)
    {
        
$this->minDepth $minDepth;
        
$iterator->setMaxDepth(\PHP_INT_MAX === $maxDepth ? -$maxDepth);

        
parent::__construct($iterator);
    }

    
/**
     * Filters the iterator values.
     *
     * @return bool true if the value should be kept, false otherwise
     */
    
public function accept()
    {
        return 
$this->getInnerIterator()->getDepth() >= $this->minDepth;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class FileTypeFilterIterator extends FilterIterator
{
    const 
ONLY_FILES 1;
    const 
ONLY_DIRECTORIES 2;

    private 
$mode;

    
/**
     * @param \Iterator $iterator The Iterator to filter
     * @param int       $mode     The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES)
     */
    
public function __construct(\Iterator $iterator$mode)
    {
        
$this->mode $mode;

        
parent::__construct($iterator);
    }

    
/**
     * Filters the iterator values.
     *
     * @return bool true if the value should be kept, false otherwise
     */
    
public function accept()
    {
        
$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 <[email protected]>
 *
 * 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\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\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 <[email protected]>
 */
class Finder implements \IteratorAggregate\Countable
{
    const 
IGNORE_VCS_FILES 1;
    const 
IGNORE_DOT_FILES 2;

    private 
$mode 0;
    private 
$names = [];
    private 
$notNames = [];
    private 
$exclude = [];
    private 
$filters = [];
    private 
$depths = [];
    private 
$sizes = [];
    private 
$followLinks false;
    private 
$sort false;
    private 
$ignore 0;
    private 
$dirs = [];
    private 
$dates = [];
    private 
$iterators = [];
    private 
$contains = [];
    private 
$notContains = [];
    private 
$paths = [];
    private 
$notPaths = [];
    private 
$ignoreUnreadableDirs false;

    private static 
$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.
     *
     * @return static
     */
    
public static function create()
    {
        return new static();
    }

    
/**
     * Restricts the matching to directories only.
     *
     * @return $this
     */
    
public function directories()
    {
        
$this->mode Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES;

        return 
$this;
    }

    
/**
     * Restricts the matching to files only.
     *
     * @return $this
     */
    
public function files()
    {
        
$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.
     *
     * @param string|int $level The depth level expression
     *
     * @return $this
     *
     * @see DepthRangeFilterIterator
     * @see NumberComparator
     */
    
public function depth($level)
    {
        
$this->depths[] = new Comparator\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');
     *
     * @param string $date A date range string
     *
     * @return $this
     *
     * @see strtotime
     * @see DateRangeFilterIterator
     * @see DateComparator
     */
    
public function date($date)
    {
        
$this->dates[] = new Comparator\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
     *     $finder->name('test.php')
     *
     * @param string $pattern A pattern (a regexp, a glob, or a string)
     *
     * @return $this
     *
     * @see FilenameFilterIterator
     */
    
public function name($pattern)
    {
        
$this->names[] = $pattern;

        return 
$this;
    }

    
/**
     * Adds rules that files must not match.
     *
     * @param string $pattern A pattern (a regexp, a glob, or a string)
     *
     * @return $this
     *
     * @see FilenameFilterIterator
     */
    
public function notName($pattern)
    {
        
$this->notNames[] = $pattern;

        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')
     *
     * @param string $pattern A pattern (string or regexp)
     *
     * @return $this
     *
     * @see FilecontentFilterIterator
     */
    
public function contains($pattern)
    {
        
$this->contains[] = $pattern;

        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')
     *
     * @param string $pattern A pattern (string or regexp)
     *
     * @return $this
     *
     * @see FilecontentFilterIterator
     */
    
public function notContains($pattern)
    {
        
$this->notContains[] = $pattern;

        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
     *
     * Use only / as dirname separator.
     *
     * @param string $pattern A pattern (a regexp or a string)
     *
     * @return $this
     *
     * @see FilenameFilterIterator
     */
    
public function path($pattern)
    {
        
$this->paths[] = $pattern;

        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
     *
     * Use only / as dirname separator.
     *
     * @param string $pattern A pattern (a regexp or a string)
     *
     * @return $this
     *
     * @see FilenameFilterIterator
     */
    
public function notPath($pattern)
    {
        
$this->notPaths[] = $pattern;

        return 
$this;
    }

    
/**
     * Adds tests for file sizes.
     *
     *     $finder->size('> 10K');
     *     $finder->size('<= 1Ki');
     *     $finder->size(4);
     *
     * @param string|int $size A size range string or an integer
     *
     * @return $this
     *
     * @see SizeRangeFilterIterator
     * @see NumberComparator
     */
    
public function size($size)
    {
        
$this->sizes[] = new Comparator\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($dirs)
    {
        
$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.
     *
     * @param bool $ignoreDotFiles Whether to exclude "hidden" files or not
     *
     * @return $this
     *
     * @see ExcludeDirectoryFilterIterator
     */
    
public function ignoreDotFiles($ignoreDotFiles)
    {
        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.
     *
     * @param bool $ignoreVCS Whether to exclude VCS files or not
     *
     * @return $this
     *
     * @see ExcludeDirectoryFilterIterator
     */
    
public function ignoreVCS($ignoreVCS)
    {
        if (
$ignoreVCS) {
            
$this->ignore |= static::IGNORE_VCS_FILES;
        } else {
            
$this->ignore &= ~static::IGNORE_VCS_FILES;
        }

        return 
$this;
    }

    
/**
     * Adds VCS patterns.
     *
     * @see ignoreVCS()
     *
     * @param string|string[] $pattern VCS patterns to ignore
     */
    
public static function addVCSPattern($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)
    {
        
$this->sort $closure;

        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()
    {
        
$this->sort Iterator\SortableIterator::SORT_BY_NAME;

        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()
    {
        
$this->sort Iterator\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()
    {
        
$this->sort Iterator\SortableIterator::SORT_BY_ACCESSED_TIME;

        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()
    {
        
$this->sort Iterator\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()
    {
        
$this->sort Iterator\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.
     *
     * @return $this
     *
     * @see CustomFilterIterator
     */
    
public function filter(\Closure $closure)
    {
        
$this->filters[] = $closure;

        return 
$this;
    }

    
/**
     * Forces the following of symlinks.
     *
     * @return $this
     */
    
public function followLinks()
    {
        
$this->followLinks true;

        return 
$this;
    }

    
/**
     * Tells finder to ignore unreadable directories.
     *
     * By default, scanning unreadable directories content throws an AccessDeniedException.
     *
     * @param bool $ignore
     *
     * @return $this
     */
    
public function ignoreUnreadableDirs($ignore true)
    {
        
$this->ignoreUnreadableDirs = (bool) $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 \InvalidArgumentException if one of the directories does not exist
     */
    
public function in($dirs)
    {
        
$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_merge($resolvedDirsarray_map([$this'normalizeDir'], $glob));
            } else {
                throw new 
\InvalidArgumentException(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|SplFileInfo[] An iterator
     *
     * @throws \LogicException if the in() method has not been called
     */
    
public function getIterator()
    {
        if (
=== \count($this->dirs) && === \count($this->iterators)) {
            throw new 
\LogicException('You must call one of in() or append() methods before iterating over a Finder.');
        }

        if (
=== \count($this->dirs) && === \count($this->iterators)) {
            return 
$this->searchInDirectory($this->dirs[0]);
        }

        
$iterator = new \AppendIterator();
        foreach (
$this->dirs as $dir) {
            
$iterator->append($this->searchInDirectory($dir));
        }

        foreach (
$this->iterators as $it) {
            
$iterator->append($it);
        }

        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 $iterator
     *
     * @return $this
     *
     * @throws \InvalidArgumentException when the given argument is not iterable
     */
    
public function append($iterator)
    {
        if (
$iterator instanceof \IteratorAggregate) {
            
$this->iterators[] = $iterator->getIterator();
        } elseif (
$iterator instanceof \Iterator) {
            
$this->iterators[] = $iterator;
        } elseif (
$iterator instanceof \Traversable || \is_array($iterator)) {
            
$it = new \ArrayIterator();
            foreach (
$iterator as $file) {
                
$it->append($file instanceof \SplFileInfo $file : new \SplFileInfo($file));
            }
            
$this->iterators[] = $it;
        } else {
            throw new 
\InvalidArgumentException('Finder::append() method wrong argument type.');
        }

        return 
$this;
    }

    
/**
     * Check if any results were found.
     *
     * @return bool
     */
    
public function hasResults()
    {
        foreach (
$this->getIterator() as $_) {
            return 
true;
        }

        return 
false;
    }

    
/**
     * Counts all the results collected by the iterators.
     *
     * @return int
     */
    
public function count()
    {
        return 
iterator_count($this->getIterator());
    }

    
/**
     * @param string $dir
     *
     * @return \Iterator
     */
    
private function searchInDirectory($dir)
    {
        
$exclude $this->exclude;
        
$notPaths $this->notPaths;

        if (static::
IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES $this->ignore)) {
            
$exclude array_merge($excludeself::$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 Iterator\ExcludeDirectoryFilterIterator($iterator$exclude);
        }

        
$iterator = new \RecursiveIteratorIterator($iterator\RecursiveIteratorIterator::SELF_FIRST);

        if (
$minDepth || $maxDepth \PHP_INT_MAX) {
            
$iterator = new Iterator\DepthRangeFilterIterator($iterator$minDepth$maxDepth);
        }

        if (
$this->mode) {
            
$iterator = new Iterator\FileTypeFilterIterator($iterator$this->mode);
        }

        if (
$this->names || $this->notNames) {
            
$iterator = new Iterator\FilenameFilterIterator($iterator$this->names$this->notNames);
        }

        if (
$this->contains || $this->notContains) {
            
$iterator = new Iterator\FilecontentFilterIterator($iterator$this->contains$this->notContains);
        }

        if (
$this->sizes) {
            
$iterator = new Iterator\SizeRangeFilterIterator($iterator$this->sizes);
        }

        if (
$this->dates) {
            
$iterator = new Iterator\DateRangeFilterIterator($iterator$this->dates);
        }

        if (
$this->filters) {
            
$iterator = new Iterator\CustomFilterIterator($iterator$this->filters);
        }

        if (
$this->paths || $notPaths) {
            
$iterator = new Iterator\PathFilterIterator($iterator$this->paths$notPaths);
        }

        if (
$this->sort) {
            
$iteratorAggregate = new Iterator\SortableIterator($iterator$this->sort);
            
$iterator $iteratorAggregate->getIterator();
        }

        return 
$iterator;
    }

    
/**
     * Normalizes given directory names by removing trailing slashes.
     *
     * Excluding: (s)ftp:// or ssh2.(s)ftp:// wrapper
     *
     * @param string $dir
     *
     * @return string
     */
    
private function normalizeDir($dir)
    {
        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 <[email protected]>
 *
 * 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 <[email protected]> PHP port
 * @author     Richard Clamp <[email protected]> Perl version
 * @copyright  2004-2005 Fabien Potencier <[email protected]>
 * @copyright  2002 Richard Clamp <[email protected]>
 */
class Glob
{
    
/**
     * Returns a regexp which is the equivalent of the glob pattern.
     *
     * @param string $glob                The glob pattern
     * @param bool   $strictLeadingDot
     * @param bool   $strictWildcardSlash
     * @param string $delimiter           Optional delimiter
     *
     * @return string regex The regexp
     */
    
public static function toRegex($glob$strictLeadingDot true$strictWildcardSlash true$delimiter '#')
    {
        
$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 += + 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;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use Symfony\Polyfill\Mbstring as p;

if (!
function_exists('mb_convert_variables')) {
    
/**
     * Convert character code in variable(s)
     */
    
function mb_convert_variables($to_encoding$from_encoding, &$var, &...$vars)
    {
        
$vars = [&$var, ...$vars];

        
$ok true;
        
array_walk_recursive($vars, function (&$v) use (&$ok$to_encoding$from_encoding) {
            if (
false === $v p\Mbstring::mb_convert_encoding($v$to_encoding$from_encoding)) {
                
$ok false;
            }
        });

        return 
$ok $from_encoding false;
    }
}
<?
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';
<?
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',
  
'ij' => 'IJ',
  
'ĵ' => 'Ĵ',
  
'ķ' => 'Ķ',
  
'ĺ' => 'Ĺ',
  
'ļ' => 'Ļ',
  
'ľ' => 'Ľ',
  
'ŀ' => 'Ŀ',
  
'ł' => 'Ł',
  
'ń' => 'Ń',
  
'ņ' => 'Ņ',
  
'ň' => 'Ň',
  
'ŋ' => 'Ŋ',
  
'ō' => 'Ō',
  
'ŏ' => 'Ŏ',
  
'ő' => 'Ő',
  
'œ' => 'Œ',
  
'ŕ' => 'Ŕ',
  
'ŗ' => 'Ŗ',
  
'ř' => 'Ř',
  
'ś' => 'Ś',
  
'ŝ' => 'Ŝ',
  
'ş' => 'Ş',
  
'š' => 'Š',
  
'ţ' => 'Ţ',
  
'ť' => 'Ť',
  
'ŧ' => 'Ŧ',
  
'ũ' => 'Ũ',
  
'ū' => 'Ū',
  
'ŭ' => 'Ŭ',
  
'ů' => 'Ů',
  
'ű' => 'Ű',
  
'ų' => 'Ų',
  
'ŵ' => 'Ŵ',
  
'ŷ' => 'Ŷ',
  
'ź' => 'Ź',
  
'ż' => 'Ż',
  
'ž' => 'Ž',
  
'ſ' => 'S',
  
'ƀ' => 'Ƀ',
  
'ƃ' => 'Ƃ',
  
'ƅ' => 'Ƅ',
  
'ƈ' => 'Ƈ',
  
'ƌ' => 'Ƌ',
  
'ƒ' => 'Ƒ',
  
'ƕ' => 'Ƕ',
  
'ƙ' => 'Ƙ',
  
'ƚ' => 'Ƚ',
  
'ƞ' => 'Ƞ',
  
'ơ' => 'Ơ',
  
'ƣ' => 'Ƣ',
  
'ƥ' => 'Ƥ',
  
'ƨ' => 'Ƨ',
  
'ƭ' => 'Ƭ',
  
'ư' => 'Ư',
  
'ƴ' => 'Ƴ',
  
'ƶ' => 'Ƶ',
  
'ƹ' => 'Ƹ',
  
'ƽ' => 'Ƽ',
  
'ƿ' => 'Ƿ',
  
'Dž' => 'DŽ',
  
'dž' => 'DŽ',
  
'Lj' => 'LJ',
  
'lj' => 'LJ',
  
'Nj' => 'NJ',
  
'nj' => 'NJ',
  
'ǎ' => 'Ǎ',
  
'ǐ' => 'Ǐ',
  
'ǒ' => 'Ǒ',
  
'ǔ' => 'Ǔ',
  
'ǖ' => 'Ǖ',
  
'ǘ' => 'Ǘ',
  
'ǚ' => 'Ǚ',
  
'ǜ' => 'Ǜ',
  
'ǝ' => 'Ǝ',
  
'ǟ' => 'Ǟ',
  
'ǡ' => 'Ǡ',
  
'ǣ' => 'Ǣ',
  
'ǥ' => 'Ǥ',
  
'ǧ' => 'Ǧ',
  
'ǩ' => 'Ǩ',
  
'ǫ' => 'Ǫ',
  
'ǭ' => 'Ǭ',
  
'ǯ' => 'Ǯ',
  
'Dz' => 'DZ',
  
'dz' => 'DZ',
  
'ǵ' => 'Ǵ',
  
'ǹ' => 'Ǹ',
  
'ǻ' => 'Ǻ',
  
'ǽ' => 'Ǽ',
  
'ǿ' => 'Ǿ',
  
'ȁ' => 'Ȁ',
  
'ȃ' => 'Ȃ',
  
'ȅ' => 'Ȅ',
  
'ȇ' => 'Ȇ',
  
'ȉ' => 'Ȉ',
  
'ȋ' => 'Ȋ',
  
'ȍ' => 'Ȍ',
  
'ȏ' => 'Ȏ',
  
'ȑ' => 'Ȑ',
  
'ȓ' => 'Ȓ',
  
'ȕ' => 'Ȕ',
  
'ȗ' => 'Ȗ',
  
'ș' => 'Ș',
  
'ț' => 'Ț',
  
'ȝ' => 'Ȝ',
  
'ȟ' => 'Ȟ',
  
'ȣ' => 'Ȣ',
  
'ȥ' => 'Ȥ',
  
'ȧ' => 'Ȧ',
  
'ȩ' => 'Ȩ',
  
'ȫ' => 'Ȫ',
  
'ȭ' => 'Ȭ',
  
'ȯ' => 'Ȯ',
  
'ȱ' => 'Ȱ',
  
'ȳ' => 'Ȳ',
  
'ȼ' => 'Ȼ',
  
'ȿ' => 'Ȿ',
  
'ɀ' => 'Ɀ',
  
'ɂ' => 'Ɂ',
  
'ɇ' => 'Ɇ',
  
'ɉ' => 'Ɉ',
  
'ɋ' => 'Ɋ',
  
'ɍ' => 'Ɍ',
  
'ɏ' => 'Ɏ',
  
'ɐ' => 'Ɐ',
  
'ɑ' => 'Ɑ',
  
'ɒ' => 'Ɒ',
  
'ɓ' => 'Ɓ',
  
'ɔ' => 'Ɔ',
  
'ɖ' => 'Ɖ',
  
'ɗ' => 'Ɗ',
  
'ə' => 'Ə',
  
'ɛ' => 'Ɛ',
  
'ɜ' => 'Ɜ',
  
'ɠ' => 'Ɠ',
  
'ɡ' => 'Ɡ',
  
'ɣ' => 'Ɣ',
  
'ɥ' => 'Ɥ',
  
'ɦ' => 'Ɦ',
  
'ɨ' => 'Ɨ',
  
'ɩ' => 'Ɩ',
  
'ɪ' => 'Ɪ',
  
'ɫ' => 'Ɫ',
  
'ɬ' => 'Ɬ',
  
'ɯ' => 'Ɯ',
  
'ɱ' => 'Ɱ',
  
'ɲ' => 'Ɲ',
  
'ɵ' => 'Ɵ',
  
'ɽ' => 'Ɽ',
  
'ʀ' => 'Ʀ',
  
'ʂ' => 'Ʂ',
  
'ʃ' => 'Ʃ',
  
'ʇ' => 'Ʇ',
  
'ʈ' => 'Ʈ',
  
'ʉ' => 'Ʉ',
  
'ʊ' => 'Ʊ',
  
'ʋ' => 'Ʋ',
  
'ʌ' => 'Ʌ',
  
'ʒ' => 'Ʒ',
  
'ʝ' => 'Ʝ',
  
'ʞ' => 'Ʞ',
  
'ͅ' => 'Ι',
  
'ͱ' => 'Ͱ',
  
'ͳ' => 'Ͳ',
  
'ͷ' => 'Ͷ',
  
'ͻ' => 'Ͻ',
  
'ͼ' => 'Ͼ',
  
'ͽ' => 'Ͽ',
  
'ά' => 'Ά',
  
'έ' => 'Έ',
  
'ή' => 'Ή',
  
'ί' => 'Ί',
  
'α' => 'Α',
  
'β' => 'Β',
  
'γ' => 'Γ',
  
'δ' => 'Δ',
  
'ε' => 'Ε',
  
'ζ' => 'Ζ',
  
'η' => 'Η',
  
'θ' => 'Θ',
  
'ι' => 'Ι',
  
'κ' => 'Κ',
  
'λ' => 'Λ',
  
'μ' => 'Μ',
  
'ν' => 'Ν',
  
'ξ' => 'Ξ',
  
'ο' => 'Ο',
  
'π' => 'Π',
  
'ρ' => 'Ρ',
  
'ς' => 'Σ',
  
'σ' => 'Σ',
  
'τ' => 'Τ',
  
'υ' => 'Υ',
  
'φ' => 'Φ',
  
'χ' => 'Χ',
  
'ψ' => 'Ψ',
  
'ω' => 'Ω',
  
'ϊ' => 'Ϊ',
  
'ϋ' => 'Ϋ',
  
'ό' => 'Ό',
  
'ύ' => 'Ύ',
  
'ώ' => 'Ώ',
  
'ϐ' => 'Β',
  
'ϑ' => 'Θ',
  
'ϕ' => 'Φ',
  
'ϖ' => 'Π',
  
'ϗ' => 'Ϗ',
  
'ϙ' => 'Ϙ',
  
'ϛ' => 'Ϛ',
  
'ϝ' => 'Ϝ',
  
'ϟ' => 'Ϟ',
  
'ϡ' => 'Ϡ',
  
'ϣ' => 'Ϣ',
  
'ϥ' => 'Ϥ',
  
'ϧ' => 'Ϧ',
  
'ϩ' => 'Ϩ',
  
'ϫ' => 'Ϫ',
  
'ϭ' => 'Ϭ',
  
'ϯ' => 'Ϯ',
  
'ϰ' => 'Κ',
  
'ϱ' => 'Ρ',
  
'ϲ' => 'Ϲ',
  
'ϳ' => 'Ϳ',
  
'ϵ' => 'Ε',
  
'ϸ' => 'Ϸ',
  
'ϻ' => 'Ϻ',
  
'а' => 'А',
  
'б' => 'Б',
  
'в' => 'В',
  
'г' => 'Г',
  
'д' => 'Д',
  
'е' => 'Е',
  
'ж' => 'Ж',
  
'з' => 'З',
  
'и' => 'И',
  
'й' => 'Й',
  
'к' => 'К',
  
'л' => 'Л',
  
'м' => 'М',
  
'н' => 'Н',
  
'о' => 'О',
  
'п' => 'П',
  
'р' => 'Р',
  
'с' => 'С',
  
'т' => 'Т',
  
'у' => 'У',
  
'ф' => 'Ф',
  
'х' => 'Х',
  
'ц' => 'Ц',
  
'ч' => 'Ч',
  
'ш' => 'Ш',
  
'щ' => 'Щ',
  
'ъ' => 'Ъ',
  
'ы' => 'Ы',
  
'ь' => 'Ь',
  
'э' => 'Э',
  
'ю' => 'Ю',
  
'я' => 'Я',
  
'ѐ' => 'Ѐ',
  
'ё' => 'Ё',
  
'ђ' => 'Ђ',
  
'ѓ' => 'Ѓ',
  
'є' => 'Є',
  
'ѕ' => 'Ѕ',
  
'і' => 'І',
  
'ї' => 'Ї',
  
'ј' => 'Ј',
  
'љ' => 'Љ',
  
'њ' => 'Њ',
  
'ћ' => 'Ћ',
  
'ќ' => 'Ќ',
  
'ѝ' => 'Ѝ',
  
'ў' => 'Ў',
  
'џ' => 'Џ',
  
'ѡ' => 'Ѡ',
  
'ѣ' => 'Ѣ',
  
'ѥ' => 'Ѥ',
  
'ѧ' => 'Ѧ',
  
'ѩ' => 'Ѩ',
  
'ѫ' => 'Ѫ',
  
'ѭ' => 'Ѭ',
  
'ѯ' => 'Ѯ',
  
'ѱ' => 'Ѱ',
  
'ѳ' => 'Ѳ',
  
'ѵ' => 'Ѵ',
  
'ѷ' => 'Ѷ',
  
'ѹ' => 'Ѹ',
  
'ѻ' => 'Ѻ',
  
'ѽ' => 'Ѽ',
  
'ѿ' => 'Ѿ',
  
'ҁ' => 'Ҁ',
  
'ҋ' => 'Ҋ',
  
'ҍ' => 'Ҍ',
  
'ҏ' => 'Ҏ',
  
'ґ' => 'Ґ',
  
'ғ' => 'Ғ',
  
'ҕ' => 'Ҕ',
  
'җ' => 'Җ',
  
'ҙ' => 'Ҙ',
  
'қ' => 'Қ',
  
'ҝ' => 'Ҝ',
  
'ҟ' => 'Ҟ',
  
'ҡ' => 'Ҡ',
  
'ң' => 'Ң',
  
'ҥ' => 'Ҥ',
  
'ҧ' => 'Ҧ',
  
'ҩ' => 'Ҩ',
  
'ҫ' => 'Ҫ',
  
'ҭ' => 'Ҭ',
  
'ү' => 'Ү',
  
'ұ' => 'Ұ',
  
'ҳ' => 'Ҳ',
  
'ҵ' => 'Ҵ',
  
'ҷ' => 'Ҷ',
  
'ҹ' => 'Ҹ',
  
'һ' => 'Һ',
  
'ҽ' => 'Ҽ',
  
'ҿ' => 'Ҿ',
  
'ӂ' => 'Ӂ',
  
'ӄ' => 'Ӄ',
  
'ӆ' => 'Ӆ',
  
'ӈ' => 'Ӈ',
  
'ӊ' => 'Ӊ',
  
'ӌ' => 'Ӌ',
  
'ӎ' => 'Ӎ',
  
'ӏ' => 'Ӏ',
  
'ӑ' => 'Ӑ',
  
'ӓ' => 'Ӓ',
  
'ӕ' => 'Ӕ',
  
'ӗ' => 'Ӗ',
  
'ә' => 'Ә',
  
'ӛ' => 'Ӛ',
  
'ӝ' => 'Ӝ',
  
'ӟ' => 'Ӟ',
  
'ӡ' => 'Ӡ',
  
'ӣ' => 'Ӣ',
  
'ӥ' => 'Ӥ',
  
'ӧ' => 'Ӧ',
  
'ө' => 'Ө',
  
'ӫ' => 'Ӫ',
  
'ӭ' => 'Ӭ',
  
'ӯ' => 'Ӯ',
  
'ӱ' => 'Ӱ',
  
'ӳ' => 'Ӳ',
  
'ӵ' => 'Ӵ',
  
'ӷ' => 'Ӷ',
  
'ӹ' => 'Ӹ',
  
'ӻ' => 'Ӻ',
  
'ӽ' => 'Ӽ',
  
'ӿ' => 'Ӿ',
  
'ԁ' => 'Ԁ',
  
'ԃ' => 'Ԃ',
  
'ԅ' => 'Ԅ',
  
'ԇ' => 'Ԇ',
  
'ԉ' => 'Ԉ',
  
'ԋ' => 'Ԋ',
  
'ԍ' => 'Ԍ',
  
'ԏ' => 'Ԏ',
  
'ԑ' => 'Ԑ',
  
'ԓ' => 'Ԓ',
  
'ԕ' => 'Ԕ',
  
'ԗ' => 'Ԗ',
  
'ԙ' => 'Ԙ',
  
'ԛ' => 'Ԛ',
  
'ԝ' => 'Ԝ',
  
'ԟ' => 'Ԟ',
  
'ԡ' => 'Ԡ',
  
'ԣ' => 'Ԣ',
  
'ԥ' => 'Ԥ',
  
'ԧ' => 'Ԧ',
  
'ԩ' => 'Ԩ',
  
'ԫ' => 'Ԫ',
  
'ԭ' => 'Ԭ',
  
'ԯ' => 'Ԯ',
  
'ա' => 'Ա',
  
'բ' => 'Բ',
  
'գ' => 'Գ',
  
'դ' => 'Դ',
  
'ե' => 'Ե',
  
'զ' => 'Զ',
  
'է' => 'Է',
  
'ը' => 'Ը',
  
'թ' => 'Թ',
  
'ժ' => 'Ժ',
  
'ի' => 'Ի',
  
'լ' => 'Լ',
  
'խ' => 'Խ',
  
'ծ' => 'Ծ',
  
'կ' => 'Կ',
  
'հ' => 'Հ',
  
'ձ' => 'Ձ',
  
'ղ' => 'Ղ',
  
'ճ' => 'Ճ',
  
'մ' => 'Մ',
  
'յ' => 'Յ',
  
'ն' => 'Ն',
  
'շ' => 'Շ',
  
'ո' => 'Ո',
  
'չ' => 'Չ',
  
'պ' => 'Պ',
  
'ջ' => 'Ջ',
  
'ռ' => 'Ռ',
  
'ս' => 'Ս',
  
'վ' => 'Վ',
  
'տ' => 'Տ',
  
'ր' => 'Ր',
  
'ց' => 'Ց',
  
'ւ' => 'Ւ',
  
'փ' => 'Փ',
  
'ք' => 'Ք',
  
'օ' => 'Օ',
  
'ֆ' => 'Ֆ',
  
'ა' => 'Ა',
  
'ბ' => 'Ბ',
  
'გ' => 'Გ',
  
'დ' => 'Დ',
  
'ე' => 'Ე',
  
'ვ' => 'Ვ',
  
'ზ' => 'Ზ',
  
'თ' => 'Თ',
  
'ი' => 'Ი',
  
'კ' => 'Კ',
  
'ლ' => 'Ლ',
  
'მ' => 'Მ',
  
'ნ' => 'Ნ',
  
'ო' => 'Ო',
  
'პ' => 'Პ',
  
'ჟ' => 'Ჟ',
  
'რ' => 'Რ',
  
'ს' => 'Ს',
  
'ტ' => 'Ტ',
  
'უ' => 'Უ',
  
'ფ' => 'Ფ',
  
'ქ' => 'Ქ',
  
'ღ' => 'Ღ',
  
'ყ' => 'Ყ',
  
'შ' => 'Შ',
  
'ჩ' => 'Ჩ',
  
'ც' => 'Ც',
  
'ძ' => 'Ძ',
  
'წ' => 'Წ',
  
'ჭ' => 'Ჭ',
  
'ხ' => 'Ხ',
  
'ჯ' => 'Ჯ',
  
'ჰ' => 'Ჰ',
  
'ჱ' => 'Ჱ',
  
'ჲ' => 'Ჲ',
  
'ჳ' => 'Ჳ',
  
'ჴ' => 'Ჴ',
  
'ჵ' => 'Ჵ',
  
'ჶ' => 'Ჶ',
  
'ჷ' => 'Ჷ',
  
'ჸ' => 'Ჸ',
  
'ჹ' => 'Ჹ',
  
'ჺ' => 'Ჺ',
  
'ჽ' => 'Ჽ',
  
'ჾ' => 'Ჾ',
  
'ჿ' => 'Ჿ',
  
'ᏸ' => 'Ᏸ',
  
'ᏹ' => 'Ᏹ',
  
'ᏺ' => 'Ᏺ',
  
'ᏻ' => 'Ᏻ',
  
'ᏼ' => 'Ᏼ',
  
'ᏽ' => 'Ᏽ',
  
'ᲀ' => 'В',
  
'ᲁ' => 'Д',
  
'ᲂ' => 'О',
  
'ᲃ' => 'С',
  
'ᲄ' => 'Т',
  
'ᲅ' => 'Т',
  
'ᲆ' => 'Ъ',
  
'ᲇ' => 'Ѣ',
  
'ᲈ' => 'Ꙋ',
  
'ᵹ' => 'Ᵹ',
  
'ᵽ' => 'Ᵽ',
  
'ᶎ' => 'Ᶎ',
  
'ḁ' => 'Ḁ',
  
'ḃ' => 'Ḃ',
  
'ḅ' => 'Ḅ',
  
'ḇ' => 'Ḇ',
  
'ḉ' => 'Ḉ',
  
'ḋ' => 'Ḋ',
  
'ḍ' => 'Ḍ',
  
'ḏ' => 'Ḏ',
  
'ḑ' => 'Ḑ',
  
'ḓ' => 'Ḓ',
  
'ḕ' => 'Ḕ',
  
'ḗ' => 'Ḗ',
  
'ḙ' => 'Ḙ',
  
'ḛ' => 'Ḛ',
  
'ḝ' => 'Ḝ',
  
'ḟ' => 'Ḟ',
  
'ḡ' => 'Ḡ',
  
'ḣ' => 'Ḣ',
  
'ḥ' => 'Ḥ',
  
'ḧ' => 'Ḧ',
  
'ḩ' => 'Ḩ',
  
'ḫ' => 'Ḫ',
  
'ḭ' => 'Ḭ',
  
'ḯ' => 'Ḯ',
  
'ḱ' => 'Ḱ',
  
'ḳ' => 'Ḳ',
  
'ḵ' => 'Ḵ',
  
'ḷ' => 'Ḷ',
  
'ḹ' => 'Ḹ',
  
'ḻ' => 'Ḻ',
  
'ḽ' => 'Ḽ',
  
'ḿ' => 'Ḿ',
  
'ṁ' => 'Ṁ',
  
'ṃ' => 'Ṃ',
  
'ṅ' => 'Ṅ',
  
'ṇ' => 'Ṇ',
  
'ṉ' => 'Ṉ',
  
'ṋ' => 'Ṋ',
  
'ṍ' => 'Ṍ',
  
'ṏ' => 'Ṏ',
  
'ṑ' => 'Ṑ',
  
'ṓ' => 'Ṓ',
  
'ṕ' => 'Ṕ',
  
'ṗ' => 'Ṗ',
  
'ṙ' => 'Ṙ',
  
'ṛ' => 'Ṛ',
  
'ṝ' => 'Ṝ',
  
'ṟ' => 'Ṟ',
  
'ṡ' => 'Ṡ',
  
'ṣ' => 'Ṣ',
  
'ṥ' => 'Ṥ',
  
'ṧ' => 'Ṧ',
  
'ṩ' => 'Ṩ',
  
'ṫ' => 'Ṫ',
  
'ṭ' => 'Ṭ',
  
'ṯ' => 'Ṯ',
  
'ṱ' => 'Ṱ',
  
'ṳ' => 'Ṳ',
  
'ṵ' => 'Ṵ',
  
'ṷ' => 'Ṷ',
  
'ṹ' => 'Ṹ',
  
'ṻ' => 'Ṻ',
  
'ṽ' => 'Ṽ',
  
'ṿ' => 'Ṿ',
  
'ẁ' => 'Ẁ',
  
'ẃ' => 'Ẃ',
  
'ẅ' => 'Ẅ',
  
'ẇ' => 'Ẇ',
  
'ẉ' => 'Ẉ',
  
'ẋ' => 'Ẋ',
  
'ẍ' => 'Ẍ',
  
'ẏ' => 'Ẏ',
  
'ẑ' => 'Ẑ',
  
'ẓ' => 'Ẓ',
  
'ẕ' => 'Ẕ',
  
'ẛ' => 'Ṡ',
  
'ạ' => 'Ạ',
  
'ả' => 'Ả',
  
'ấ' => 'Ấ',
  
'ầ' => 'Ầ',
  
'ẩ' => 'Ẩ',
  
'ẫ' => 'Ẫ',
  
'ậ' => 'Ậ',
  
'ắ' => 'Ắ',
  
'ằ' => 'Ằ',
  
'ẳ' => 'Ẳ',
  
'ẵ' => 'Ẵ',
  
'ặ' => 'Ặ',
  
'ẹ' => 'Ẹ',
  
'ẻ' => 'Ẻ',
  
'ẽ' => 'Ẽ',
  
'ế' => 'Ế',
  
'ề' => 'Ề',
  
'ể' => 'Ể',
  
'ễ' => 'Ễ',
  
'ệ' => 'Ệ',
  
'ỉ' => 'Ỉ',
  
'ị' => 'Ị',
  
'ọ' => 'Ọ',
  
'ỏ' => 'Ỏ',
  
'ố' => 'Ố',
  
'ồ' => 'Ồ',
  
'ổ' => 'Ổ',
  
'ỗ' => 'Ỗ',
  
'ộ' => 'Ộ',
  
'ớ' => 'Ớ',
  
'ờ' => 'Ờ',
  
'ở' => 'Ở',
  
'ỡ' => 'Ỡ',
  
'ợ' => 'Ợ',
  
'ụ' => 'Ụ',
  
'ủ' => 'Ủ',
  
'ứ' => 'Ứ',
  
'ừ' => 'Ừ',
  
'ử' => 'Ử',
  
'ữ' => 'Ữ',
  
'ự' => 'Ự',
  
'ỳ' => 'Ỳ',
  
'ỵ' => 'Ỵ',
  
'ỷ' => 'Ỷ',
  
'ỹ' => 'Ỹ',
  
'ỻ' => 'Ỻ',
  
'ỽ' => 'Ỽ',
  
'ỿ' => 'Ỿ',
  
'ἀ' => 'Ἀ',
  
'ἁ' => 'Ἁ',
  
'ἂ' => 'Ἂ',
  
'ἃ' => 'Ἃ',
  
'ἄ' => 'Ἄ',
  
'ἅ' => 'Ἅ',
  
'ἆ' => 'Ἆ',
  
'ἇ' => 'Ἇ',
  
'ἐ' => 'Ἐ',
  
'ἑ' => 'Ἑ',
  
'ἒ' => 'Ἒ',
  
'ἓ' => 'Ἓ',
  
'ἔ' => 'Ἔ',
  
'ἕ' => 'Ἕ',
  
'ἠ' => 'Ἠ',
  
'ἡ' => 'Ἡ',
  
'ἢ' => 'Ἢ',
  
'ἣ' => 'Ἣ',
  
'ἤ' => 'Ἤ',
  
'ἥ' => 'Ἥ',
  
'ἦ' => 'Ἦ',
  
'ἧ' => 'Ἧ',
  
'ἰ' => 'Ἰ',
  
'ἱ' => 'Ἱ',
  
'ἲ' => 'Ἲ',
  
'ἳ' => 'Ἳ',
  
'ἴ' => 'Ἴ',
  
'ἵ' => 'Ἵ',
  
'ἶ' => 'Ἶ',
  
'ἷ' => 'Ἷ',
  
'ὀ' => 'Ὀ',
  
'ὁ' => 'Ὁ',
  
'ὂ' => 'Ὂ',
  
'ὃ' => 'Ὃ',
  
'ὄ' => 'Ὄ',
  
'ὅ' => 'Ὅ',
  
'ὑ' => 'Ὑ',
  
'ὓ' => 'Ὓ',
  
'ὕ' => 'Ὕ',
  
'ὗ' => 'Ὗ',
  
'ὠ' => 'Ὠ',
  
'ὡ' => 'Ὡ',
  
'ὢ' => 'Ὢ',
  
'ὣ' => 'Ὣ',
  
'ὤ' => 'Ὤ',
  
'ὥ' => 'Ὥ',
  
'ὦ' => 'Ὦ',
  
'ὧ' => 'Ὧ',
  
'ὰ' => 'Ὰ',
  
'ά' => 'Ά',
  
'ὲ' => 'Ὲ',
  
'έ' => 'Έ',
  
'ὴ' => 'Ὴ',
  
'ή' => 'Ή',
  
'ὶ' => 'Ὶ',
  
'ί' => 'Ί',
  
'ὸ' => 'Ὸ',
  
'ό' => 'Ό',
  
'ὺ' => 'Ὺ',
  
'ύ' => 'Ύ',
  
'ὼ' => 'Ὼ',
  
'ώ' => 'Ώ',
  
'ᾀ' => 'ᾈ',
  
'ᾁ' => 'ᾉ',
  
'ᾂ' => 'ᾊ',
  
'ᾃ' => 'ᾋ',
  
'ᾄ' => 'ᾌ',
  
'ᾅ' => 'ᾍ',
  
'ᾆ' => 'ᾎ',
  
'ᾇ' => 'ᾏ',
  
'ᾐ' => 'ᾘ',
  
'ᾑ' => 'ᾙ',
  
'ᾒ' => 'ᾚ',
  
'ᾓ' => 'ᾛ',
  
'ᾔ' => 'ᾜ',
  
'ᾕ' => 'ᾝ',
  
'ᾖ' => 'ᾞ',
  
'ᾗ' => 'ᾟ',
  
'ᾠ' => 'ᾨ',
  
'ᾡ' => 'ᾩ',
  
'ᾢ' => 'ᾪ',
  
'ᾣ' => 'ᾫ',
  
'ᾤ' => 'ᾬ',
  
'ᾥ' => 'ᾭ',
  
'ᾦ' => 'ᾮ',
  
'ᾧ' => 'ᾯ',
  
'ᾰ' => 'Ᾰ',
  
'ᾱ' => 'Ᾱ',
  
'ᾳ' => 'ᾼ',
  
'ι' => 'Ι',
  
'ῃ' => 'ῌ',
  
'ῐ' => 'Ῐ',
  
'ῑ' => 'Ῑ',
  
'ῠ' => 'Ῠ',
  
'ῡ' => 'Ῡ',
  
'ῥ' => 'Ῥ',
  
'ῳ' => 'ῼ',
  
'ⅎ' => 'Ⅎ',
  
'ⅰ' => 'Ⅰ',
  
'ⅱ' => 'Ⅱ',
  
'ⅲ' => 'Ⅲ',
  
'ⅳ' => 'Ⅳ',
  
'ⅴ' => 'Ⅴ',
  
'ⅵ' => 'Ⅵ',
  
'ⅶ' => 'Ⅶ',
  
'ⅷ' => 'Ⅷ',
  
'ⅸ' => 'Ⅸ',
  
'ⅹ' => 'Ⅹ',
  
'ⅺ' => 'Ⅺ',
  
'ⅻ' => 'Ⅻ',
  
'ⅼ' => 'Ⅼ',
  
'ⅽ' => 'Ⅽ',
  
'ⅾ' => 'Ⅾ',
  
'ⅿ' => 'Ⅿ',
  
'ↄ' => 'Ↄ',
  
'ⓐ' => 'Ⓐ',
  
'ⓑ' => 'Ⓑ',
  
'ⓒ' => 'Ⓒ',
  
'ⓓ' => 'Ⓓ',
  
'ⓔ' => 'Ⓔ',
  
'ⓕ' => 'Ⓕ',
  
'ⓖ' => 'Ⓖ',
  
'ⓗ' => 'Ⓗ',
  
'ⓘ' => 'Ⓘ',
  
'ⓙ' => 'Ⓙ',
  
'ⓚ' => 'Ⓚ',
  
'ⓛ' => 'Ⓛ',
  
'ⓜ' => 'Ⓜ',
  
'ⓝ' => 'Ⓝ',
  
'ⓞ' => 'Ⓞ',
  
'ⓟ' => 'Ⓟ',
  
'ⓠ' => 'Ⓠ',
  
'ⓡ' => 'Ⓡ',
  
'ⓢ' => 'Ⓢ',
  
'ⓣ' => 'Ⓣ',
  
'ⓤ' => 'Ⓤ',
  
'ⓥ' => 'Ⓥ',
  
'ⓦ' => 'Ⓦ',
  
'ⓧ' => 'Ⓧ',
  
'ⓨ' => 'Ⓨ',
  
'ⓩ' => 'Ⓩ',
  
'ⰰ' => 'Ⰰ',
  
'ⰱ' => 'Ⰱ',
  
'ⰲ' => 'Ⰲ',
  
'ⰳ' => 'Ⰳ',
  
'ⰴ' => 'Ⰴ',
  
'ⰵ' => 'Ⰵ',
  
'ⰶ' => 'Ⰶ',
  
'ⰷ' => 'Ⰷ',
  
'ⰸ' => 'Ⰸ',
  
'ⰹ' => 'Ⰹ',
  
'ⰺ' => 'Ⰺ',
  
'ⰻ' => 'Ⰻ',
  
'ⰼ' => 'Ⰼ',
  
'ⰽ' => 'Ⰽ',
  
'ⰾ' => 'Ⰾ',
  
'ⰿ' => 'Ⰿ',
  
'ⱀ' => 'Ⱀ',
  
'ⱁ' => 'Ⱁ',
  
'ⱂ' => 'Ⱂ',
  
'ⱃ' => 'Ⱃ',
  
'ⱄ' => 'Ⱄ',
  
'ⱅ' => 'Ⱅ',
  
'ⱆ' => 'Ⱆ',
  
'ⱇ' => 'Ⱇ',
  
'ⱈ' => 'Ⱈ',
  
'ⱉ' => 'Ⱉ',
  
'ⱊ' => 'Ⱊ',
  
'ⱋ' => 'Ⱋ',
  
'ⱌ' => 'Ⱌ',
  
'ⱍ' => 'Ⱍ',
  
'ⱎ' => 'Ⱎ',
  
'ⱏ' => 'Ⱏ',
  
'ⱐ' => 'Ⱐ',
  
'ⱑ' => 'Ⱑ',
  
'ⱒ' => 'Ⱒ',
  
'ⱓ' => 'Ⱓ',
  
'ⱔ' => 'Ⱔ',
  
'ⱕ' => 'Ⱕ',
  
'ⱖ' => 'Ⱖ',
  
'ⱗ' => 'Ⱗ',
  
'ⱘ' => 'Ⱘ',
  
'ⱙ' => 'Ⱙ',
  
'ⱚ' => 'Ⱚ',
  
'ⱛ' => 'Ⱛ',
  
'ⱜ' => 'Ⱜ',
  
'ⱝ' => 'Ⱝ',
  
'ⱞ' => 'Ⱞ',
  
'ⱡ' => 'Ⱡ',
  
'ⱥ' => 'Ⱥ',
  
'ⱦ' => 'Ⱦ',
  
'ⱨ' => 'Ⱨ',
  
'ⱪ' => 'Ⱪ',
  
'ⱬ' => 'Ⱬ',
  
'ⱳ' => 'Ⱳ',
  
'ⱶ' => 'Ⱶ',
  
'ⲁ' => 'Ⲁ',
  
'ⲃ' => 'Ⲃ',
  
'ⲅ' => 'Ⲅ',
  
'ⲇ' => 'Ⲇ',
  
'ⲉ' => 'Ⲉ',
  
'ⲋ' => 'Ⲋ',
  
'ⲍ' => 'Ⲍ',
  
'ⲏ' => 'Ⲏ',
  
'ⲑ' => 'Ⲑ',
  
'ⲓ' => 'Ⲓ',
  
'ⲕ' => 'Ⲕ',
  
'ⲗ' => 'Ⲗ',
  
'ⲙ' => 'Ⲙ',
  
'ⲛ' => 'Ⲛ',
  
'ⲝ' => 'Ⲝ',
  
'ⲟ' => 'Ⲟ',
  
'ⲡ' => 'Ⲡ',
  
'ⲣ' => 'Ⲣ',
  
'ⲥ' => 'Ⲥ',
  
'ⲧ' => 'Ⲧ',
  
'ⲩ' => 'Ⲩ',
  
'ⲫ' => 'Ⲫ',
  
'ⲭ' => 'Ⲭ',
  
'ⲯ' => 'Ⲯ',
  
'ⲱ' => 'Ⲱ',
  
'ⲳ' => 'Ⲳ',
  
'ⲵ' => 'Ⲵ',
  
'ⲷ' => 'Ⲷ',
  
'ⲹ' => 'Ⲹ',
  
'ⲻ' => 'Ⲻ',
  
'ⲽ' => 'Ⲽ',
  
'ⲿ' => 'Ⲿ',
  
'ⳁ' => 'Ⳁ',
  
'ⳃ' => 'Ⳃ',
  
'ⳅ' => 'Ⳅ',
  
'ⳇ' => 'Ⳇ',
  
'ⳉ' => 'Ⳉ',
  
'ⳋ' => 'Ⳋ',
  
'ⳍ' => 'Ⳍ',
  
'ⳏ' => 'Ⳏ',
  
'ⳑ' => 'Ⳑ',
  
'ⳓ' => 'Ⳓ',
  
'ⳕ' => 'Ⳕ',
  
'ⳗ' => 'Ⳗ',
  
'ⳙ' => 'Ⳙ',
  
'ⳛ' => 'Ⳛ',
  
'ⳝ' => 'Ⳝ',
  
'ⳟ' => 'Ⳟ',
  
'ⳡ' => 'Ⳡ',
  
'ⳣ' => 'Ⳣ',
  
'ⳬ' => 'Ⳬ',
  
'ⳮ' => 'Ⳮ',
  
'ⳳ' => 'Ⳳ',
  
'ⴀ' => 'Ⴀ',
  
'ⴁ' => 'Ⴁ',
  
'ⴂ' => 'Ⴂ',
  
'ⴃ' => 'Ⴃ',
  
'ⴄ' => 'Ⴄ',
  
'ⴅ' => 'Ⴅ',
  
'ⴆ' => 'Ⴆ',
  
'ⴇ' => 'Ⴇ',
  
'ⴈ' => 'Ⴈ',
  
'ⴉ' => 'Ⴉ',
  
'ⴊ' => 'Ⴊ',
  
'ⴋ' => 'Ⴋ',
  
'ⴌ' => 'Ⴌ',
  
'ⴍ' => 'Ⴍ',
  
'ⴎ' => 'Ⴎ',
  
'ⴏ' => 'Ⴏ',
  
'ⴐ' => 'Ⴐ',
  
'ⴑ' => 'Ⴑ',
  
'ⴒ' => 'Ⴒ',
  
'ⴓ' => 'Ⴓ',
  
'ⴔ' => 'Ⴔ',
  
'ⴕ' => 'Ⴕ',
  
'ⴖ' => 'Ⴖ',
  
'ⴗ' => 'Ⴗ',
  
'ⴘ' => 'Ⴘ',
  
'ⴙ' => 'Ⴙ',
  
'ⴚ' => 'Ⴚ',
  
'ⴛ' => 'Ⴛ',
  
'ⴜ' => 'Ⴜ',
  
'ⴝ' => 'Ⴝ',
  
'ⴞ' => 'Ⴞ',
  
'ⴟ' => 'Ⴟ',
  
'ⴠ' => 'Ⴠ',
  
'ⴡ' => 'Ⴡ',
  
'ⴢ' => 'Ⴢ',
  
'ⴣ' => 'Ⴣ',
  
'ⴤ' => 'Ⴤ',
  
'ⴥ' => 'Ⴥ',
  
'ⴧ' => 'Ⴧ',
  
'ⴭ' => 'Ⴭ',
  
'ꙁ' => 'Ꙁ',
  
'ꙃ' => 'Ꙃ',
  
'ꙅ' => 'Ꙅ',
  
'ꙇ' => 'Ꙇ',
  
'ꙉ' => 'Ꙉ',
  
'ꙋ' => 'Ꙋ',
  
'ꙍ' => 'Ꙍ',
  
'ꙏ' => 'Ꙏ',
  
'ꙑ' => 'Ꙑ',
  
'ꙓ' => 'Ꙓ',
  
'ꙕ' => 'Ꙕ',
  
'ꙗ' => 'Ꙗ',
  
'ꙙ' => 'Ꙙ',
  
'ꙛ' => 'Ꙛ',
  
'ꙝ' => 'Ꙝ',
  
'ꙟ' => 'Ꙟ',
  
'ꙡ' => 'Ꙡ',
  
'ꙣ' => 'Ꙣ',
  
'ꙥ' => 'Ꙥ',
  
'ꙧ' => 'Ꙧ',
  
'ꙩ' => 'Ꙩ',
  
'ꙫ' => 'Ꙫ',
  
'ꙭ' => 'Ꙭ',
  
'ꚁ' => 'Ꚁ',
  
'ꚃ' => 'Ꚃ',
  
'ꚅ' => 'Ꚅ',
  
'ꚇ' => 'Ꚇ',
  
'ꚉ' => 'Ꚉ',
  
'ꚋ' => 'Ꚋ',
  
'ꚍ' => 'Ꚍ',
  
'ꚏ' => 'Ꚏ',
  
'ꚑ' => 'Ꚑ',
  
'ꚓ' => 'Ꚓ',
  
'ꚕ' => 'Ꚕ',
  
'ꚗ' => 'Ꚗ',
  
'ꚙ' => 'Ꚙ',
  
'ꚛ' => 'Ꚛ',
  
'ꜣ' => 'Ꜣ',
  
'ꜥ' => 'Ꜥ',
  
'ꜧ' => 'Ꜧ',
  
'ꜩ' => 'Ꜩ',
  
'ꜫ' => 'Ꜫ',
  
'ꜭ' => 'Ꜭ',
  
'ꜯ' => 'Ꜯ',
  
'ꜳ' => 'Ꜳ',
  
'ꜵ' => 'Ꜵ',
  
'ꜷ' => 'Ꜷ',
  
'ꜹ' => 'Ꜹ',
  
'ꜻ' => 'Ꜻ',
  
'ꜽ' => 'Ꜽ',
  
'ꜿ' => 'Ꜿ',
  
'ꝁ' => 'Ꝁ',
  
'ꝃ' => 'Ꝃ',
  
'ꝅ' => 'Ꝅ',
  
'ꝇ' => 'Ꝇ',
  
'ꝉ' => 'Ꝉ',
  
'ꝋ' => 'Ꝋ',
  
'ꝍ' => 'Ꝍ',
  
'ꝏ' => 'Ꝏ',
  
'ꝑ' => 'Ꝑ',
  
'ꝓ' => 'Ꝓ',
  
'ꝕ' => 'Ꝕ',
  
'ꝗ' => 'Ꝗ',
  
'ꝙ' => 'Ꝙ',
  
'ꝛ' => 'Ꝛ',
  
'ꝝ' => 'Ꝝ',
  
'ꝟ' => 'Ꝟ',
  
'ꝡ' => 'Ꝡ',
  
'ꝣ' => 'Ꝣ',
  
'ꝥ' => 'Ꝥ',
  
'ꝧ' => 'Ꝧ',
  
'ꝩ' => 'Ꝩ',
  
'ꝫ' => 'Ꝫ',
  
'ꝭ' => 'Ꝭ',
  
'ꝯ' => 'Ꝯ',
  
'ꝺ' => 'Ꝺ',
  
'ꝼ' => 'Ꝼ',
  
'ꝿ' => 'Ꝿ',
  
'ꞁ' => 'Ꞁ',
  
'ꞃ' => 'Ꞃ',
  
'ꞅ' => 'Ꞅ',
  
'ꞇ' => 'Ꞇ',
  
'ꞌ' => 'Ꞌ',
  
'ꞑ' => 'Ꞑ',
  
'ꞓ' => 'Ꞓ',
  
'ꞔ' => 'Ꞔ',
  
'ꞗ' => 'Ꞗ',
  
'ꞙ' => 'Ꞙ',
  
'ꞛ' => 'Ꞛ',
  
'ꞝ' => 'Ꞝ',
  
'ꞟ' => 'Ꞟ',
  
'ꞡ' => 'Ꞡ',
  
'ꞣ' => 'Ꞣ',
  
'ꞥ' => 'Ꞥ',
  
'ꞧ' => 'Ꞧ',
  
'ꞩ' => 'Ꞩ',
  
'ꞵ' => 'Ꞵ',
  
'ꞷ' => 'Ꞷ',
  
'ꞹ' => 'Ꞹ',
  
'ꞻ' => 'Ꞻ',
  
'ꞽ' => 'Ꞽ',
  
'ꞿ' => 'Ꞿ',
  
'ꟃ' => 'Ꟃ',
  
'ꟈ' => 'Ꟈ',
  
'ꟊ' => 'Ꟊ',
  
'ꟶ' => 'Ꟶ',
  
'ꭓ' => 'Ꭓ',
  
'ꭰ' => 'Ꭰ',
  
'ꭱ' => 'Ꭱ',
  
'ꭲ' => 'Ꭲ',
  
'ꭳ' => 'Ꭳ',
  
'ꭴ' => 'Ꭴ',
  
'ꭵ' => 'Ꭵ',
  
'ꭶ' => 'Ꭶ',
  
'ꭷ' => 'Ꭷ',
  
'ꭸ' => 'Ꭸ',
  
'ꭹ' => 'Ꭹ',
  
'ꭺ' => 'Ꭺ',
  
'ꭻ' => 'Ꭻ',
  
'ꭼ' => 'Ꭼ',
  
'ꭽ' => 'Ꭽ',
  
'ꭾ' => 'Ꭾ',
  
'ꭿ' => 'Ꭿ',
  
'ꮀ' => 'Ꮀ',
  
'ꮁ' => 'Ꮁ',
  
'ꮂ' => 'Ꮂ',
  
'ꮃ' => 'Ꮃ',
  
'ꮄ' => 'Ꮄ',
  
'ꮅ' => 'Ꮅ',
  
'ꮆ' => 'Ꮆ',
  
'ꮇ' => 'Ꮇ',
  
'ꮈ' => 'Ꮈ',
  
'ꮉ' => 'Ꮉ',
  
'ꮊ' => 'Ꮊ',
  
'ꮋ' => 'Ꮋ',
  
'ꮌ' => 'Ꮌ',
  
'ꮍ' => 'Ꮍ',
  
'ꮎ' => 'Ꮎ',
  
'ꮏ' => 'Ꮏ',
  
'ꮐ' => 'Ꮐ',
  
'ꮑ' => 'Ꮑ',
  
'ꮒ' => 'Ꮒ',
  
'ꮓ' => 'Ꮓ',
  
'ꮔ' => 'Ꮔ',
  
'ꮕ' => 'Ꮕ',
  
'ꮖ' => 'Ꮖ',
  
'ꮗ' => 'Ꮗ',
  
'ꮘ' => 'Ꮘ',
  
'ꮙ' => 'Ꮙ',
  
'ꮚ' => 'Ꮚ',
  
'ꮛ' => 'Ꮛ',
  
'ꮜ' => 'Ꮜ',
  
'ꮝ' => 'Ꮝ',
  
'ꮞ' => 'Ꮞ',
  
'ꮟ' => 'Ꮟ',
  
'ꮠ' => 'Ꮠ',
  
'ꮡ' => 'Ꮡ',
  
'ꮢ' => 'Ꮢ',
  
'ꮣ' => 'Ꮣ',
  
'ꮤ' => 'Ꮤ',
  
'ꮥ' => 'Ꮥ',
  
'ꮦ' => 'Ꮦ',
  
'ꮧ' => 'Ꮧ',
  
'ꮨ' => 'Ꮨ',
  
'ꮩ' => 'Ꮩ',
  
'ꮪ' => 'Ꮪ',
  
'ꮫ' => 'Ꮫ',
  
'ꮬ' => 'Ꮬ',
  
'ꮭ' => 'Ꮭ',
  
'ꮮ' => 'Ꮮ',
  
'ꮯ' => 'Ꮯ',
  
'ꮰ' => 'Ꮰ',
  
'ꮱ' => 'Ꮱ',
  
'ꮲ' => 'Ꮲ',
  
'ꮳ' => 'Ꮳ',
  
'ꮴ' => 'Ꮴ',
  
'ꮵ' => 'Ꮵ',
  
'ꮶ' => 'Ꮶ',
  
'ꮷ' => 'Ꮷ',
  
'ꮸ' => 'Ꮸ',
  
'ꮹ' => 'Ꮹ',
  
'ꮺ' => 'Ꮺ',
  
'ꮻ' => 'Ꮻ',
  
'ꮼ' => 'Ꮼ',
  
'ꮽ' => 'Ꮽ',
  
'ꮾ' => 'Ꮾ',
  
'ꮿ' => 'Ꮿ',
  
'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',
  
'𐐨' => '𐐀',
  
'𐐩' => '𐐁',
  
'𐐪' => '𐐂',
  
'𐐫' => '𐐃',
  
'𐐬' => '𐐄',
  
'𐐭' => '𐐅',
  
'𐐮' => '𐐆',
  
'𐐯' => '𐐇',
  
'𐐰' => '𐐈',
  
'𐐱' => '𐐉',
  
'𐐲' => '𐐊',
  
'𐐳' => '𐐋',
  
'𐐴' => '𐐌',
  
'𐐵' => '𐐍',
  
'𐐶' => '𐐎',
  
'𐐷' => '𐐏',
  
'𐐸' => '𐐐',
  
'𐐹' => '𐐑',
  
'𐐺' => '𐐒',
  
'𐐻' => '𐐓',
  
'𐐼' => '𐐔',
  
'𐐽' => '𐐕',
  
'𐐾' => '𐐖',
  
'𐐿' => '𐐗',
  
'𐑀' => '𐐘',
  
'𐑁' => '𐐙',
  
'𐑂' => '𐐚',
  
'𐑃' => '𐐛',
  
'𐑄' => '𐐜',
  
'𐑅' => '𐐝',
  
'𐑆' => '𐐞',
  
'𐑇' => '𐐟',
  
'𐑈' => '𐐠',
  
'𐑉' => '𐐡',
  
'𐑊' => '𐐢',
  
'𐑋' => '𐐣',
  
'𐑌' => '𐐤',
  
'𐑍' => '𐐥',
  
'𐑎' => '𐐦',
  
'𐑏' => '𐐧',
  
'𐓘' => '𐒰',
  
'𐓙' => '𐒱',
  
'𐓚' => '𐒲',
  
'𐓛' => '𐒳',
  
'𐓜' => '𐒴',
  
'𐓝' => '𐒵',
  
'𐓞' => '𐒶',
  
'𐓟' => '𐒷',
  
'𐓠' => '𐒸',
  
'𐓡' => '𐒹',
  
'𐓢' => '𐒺',
  
'𐓣' => '𐒻',
  
'𐓤' => '𐒼',
  
'𐓥' => '𐒽',
  
'𐓦' => '𐒾',
  
'𐓧' => '𐒿',
  
'𐓨' => '𐓀',
  
'𐓩' => '𐓁',
  
'𐓪' => '𐓂',
  
'𐓫' => '𐓃',
  
'𐓬' => '𐓄',
  
'𐓭' => '𐓅',
  
'𐓮' => '𐓆',
  
'𐓯' => '𐓇',
  
'𐓰' => '𐓈',
  
'𐓱' => '𐓉',
  
'𐓲' => '𐓊',
  
'𐓳' => '𐓋',
  
'𐓴' => '𐓌',
  
'𐓵' => '𐓍',
  
'𐓶' => '𐓎',
  
'𐓷' => '𐓏',
  
'𐓸' => '𐓐',
  
'𐓹' => '𐓑',
  
'𐓺' => '𐓒',
  
'𐓻' => '𐓓',
  
'𐳀' => '𐲀',
  
'𐳁' => '𐲁',
  
'𐳂' => '𐲂',
  
'𐳃' => '𐲃',
  
'𐳄' => '𐲄',
  
'𐳅' => '𐲅',
  
'𐳆' => '𐲆',
  
'𐳇' => '𐲇',
  
'𐳈' => '𐲈',
  
'𐳉' => '𐲉',
  
'𐳊' => '𐲊',
  
'𐳋' => '𐲋',
  
'𐳌' => '𐲌',
  
'𐳍' => '𐲍',
  
'𐳎' => '𐲎',
  
'𐳏' => '𐲏',
  
'𐳐' => '𐲐',
  
'𐳑' => '𐲑',
  
'𐳒' => '𐲒',
  
'𐳓' => '𐲓',
  
'𐳔' => '𐲔',
  
'𐳕' => '𐲕',
  
'𐳖' => '𐲖',
  
'𐳗' => '𐲗',
  
'𐳘' => '𐲘',
  
'𐳙' => '𐲙',
  
'𐳚' => '𐲚',
  
'𐳛' => '𐲛',
  
'𐳜' => '𐲜',
  
'𐳝' => '𐲝',
  
'𐳞' => '𐲞',
  
'𐳟' => '𐲟',
  
'𐳠' => '𐲠',
  
'𐳡' => '𐲡',
  
'𐳢' => '𐲢',
  
'𐳣' => '𐲣',
  
'𐳤' => '𐲤',
  
'𐳥' => '𐲥',
  
'𐳦' => '𐲦',
  
'𐳧' => '𐲧',
  
'𐳨' => '𐲨',
  
'𐳩' => '𐲩',
  
'𐳪' => '𐲪',
  
'𐳫' => '𐲫',
  
'𐳬' => '𐲬',
  
'𐳭' => '𐲭',
  
'𐳮' => '𐲮',
  
'𐳯' => '𐲯',
  
'𐳰' => '𐲰',
  
'𐳱' => '𐲱',
  
'𐳲' => '𐲲',
  
'𑣀' => '𑢠',
  
'𑣁' => '𑢡',
  
'𑣂' => '𑢢',
  
'𑣃' => '𑢣',
  
'𑣄' => '𑢤',
  
'𑣅' => '𑢥',
  
'𑣆' => '𑢦',
  
'𑣇' => '𑢧',
  
'𑣈' => '𑢨',
  
'𑣉' => '𑢩',
  
'𑣊' => '𑢪',
  
'𑣋' => '𑢫',
  
'𑣌' => '𑢬',
  
'𑣍' => '𑢭',
  
'𑣎' => '𑢮',
  
'𑣏' => '𑢯',
  
'𑣐' => '𑢰',
  
'𑣑' => '𑢱',
  
'𑣒' => '𑢲',
  
'𑣓' => '𑢳',
  
'𑣔' => '𑢴',
  
'𑣕' => '𑢵',
  
'𑣖' => '𑢶',
  
'𑣗' => '𑢷',
  
'𑣘' => '𑢸',
  
'𑣙' => '𑢹',
  
'𑣚' => '𑢺',
  
'𑣛' => '𑢻',
  
'𑣜' => '𑢼',
  
'𑣝' => '𑢽',
  
'𑣞' => '𑢾',
  
'𑣟' => '𑢿',
  
'𖹠' => '𖹀',
  
'𖹡' => '𖹁',
  
'𖹢' => '𖹂',
  
'𖹣' => '𖹃',
  
'𖹤' => '𖹄',
  
'𖹥' => '𖹅',
  
'𖹦' => '𖹆',
  
'𖹧' => '𖹇',
  
'𖹨' => '𖹈',
  
'𖹩' => '𖹉',
  
'𖹪' => '𖹊',
  
'𖹫' => '𖹋',
  
'𖹬' => '𖹌',
  
'𖹭' => '𖹍',
  
'𖹮' => '𖹎',
  
'𖹯' => '𖹏',
  
'𖹰' => '𖹐',
  
'𖹱' => '𖹑',
  
'𖹲' => '𖹒',
  
'𖹳' => '𖹓',
  
'𖹴' => '𖹔',
  
'𖹵' => '𖹕',
  
'𖹶' => '𖹖',
  
'𖹷' => '𖹗',
  
'𖹸' => '𖹘',
  
'𖹹' => '𖹙',
  
'𖹺' => '𖹚',
  
'𖹻' => '𖹛',
  
'𖹼' => '𖹜',
  
'𖹽' => '𖹝',
  
'𖹾' => '𖹞',
  
'𖹿' => '𖹟',
  
'𞤢' => '𞤀',
  
'𞤣' => '𞤁',
  
'𞤤' => '𞤂',
  
'𞤥' => '𞤃',
  
'𞤦' => '𞤄',
  
'𞤧' => '𞤅',
  
'𞤨' => '𞤆',
  
'𞤩' => '𞤇',
  
'𞤪' => '𞤈',
  
'𞤫' => '𞤉',
  
'𞤬' => '𞤊',
  
'𞤭' => '𞤋',
  
'𞤮' => '𞤌',
  
'𞤯' => '𞤍',
  
'𞤰' => '𞤎',
  
'𞤱' => '𞤏',
  
'𞤲' => '𞤐',
  
'𞤳' => '𞤑',
  
'𞤴' => '𞤒',
  
'𞤵' => '𞤓',
  
'𞤶' => '𞤔',
  
'𞤷' => '𞤕',
  
'𞤸' => '𞤖',
  
'𞤹' => '𞤗',
  
'𞤺' => '𞤘',
  
'𞤻' => '𞤙',
  
'𞤼' => '𞤚',
  
'𞤽' => '𞤛',
  
'𞤾' => '𞤜',
  
'𞤿' => '𞤝',
  
'𞥀' => '𞤞',
  
'𞥁' => '𞤟',
  
'𞥂' => '𞤠',
  
'𞥃' => '𞤡',
);
<?
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',
  
'IJ' => 'ij',
  
'Ĵ' => 'ĵ',
  
'Ķ' => 'ķ',
  
'Ĺ' => 'ĺ',
  
'Ļ' => 'ļ',
  
'Ľ' => 'ľ',
  
'Ŀ' => 'ŀ',
  
'Ł' => 'ł',
  
'Ń' => 'ń',
  
'Ņ' => 'ņ',
  
'Ň' => 'ň',
  
'Ŋ' => 'ŋ',
  
'Ō' => 'ō',
  
'Ŏ' => 'ŏ',
  
'Ő' => 'ő',
  
'Œ' => 'œ',
  
'Ŕ' => 'ŕ',
  
'Ŗ' => 'ŗ',
  
'Ř' => 'ř',
  
'Ś' => 'ś',
  
'Ŝ' => 'ŝ',
  
'Ş' => 'ş',
  
'Š' => 'š',
  
'Ţ' => 'ţ',
  
'Ť' => 'ť',
  
'Ŧ' => 'ŧ',
  
'Ũ' => 'ũ',
  
'Ū' => 'ū',
  
'Ŭ' => 'ŭ',
  
'Ů' => 'ů',
  
'Ű' => 'ű',
  
'Ų' => 'ų',
  
'Ŵ' => 'ŵ',
  
'Ŷ' => 'ŷ',
  
'Ÿ' => 'ÿ',
  
'Ź' => 'ź',
  
'Ż' => 'ż',
  
'Ž' => 'ž',
  
'Ɓ' => 'ɓ',
  
'Ƃ' => 'ƃ',
  
'Ƅ' => 'ƅ',
  
'Ɔ' => 'ɔ',
  
'Ƈ' => 'ƈ',
  
'Ɖ' => 'ɖ',
  
'Ɗ' => 'ɗ',
  
'Ƌ' => 'ƌ',
  
'Ǝ' => 'ǝ',
  
'Ə' => 'ə',
  
'Ɛ' => 'ɛ',
  
'Ƒ' => 'ƒ',
  
'Ɠ' => 'ɠ',
  
'Ɣ' => 'ɣ',
  
'Ɩ' => 'ɩ',
  
'Ɨ' => 'ɨ',
  
'Ƙ' => 'ƙ',
  
'Ɯ' => 'ɯ',
  
'Ɲ' => 'ɲ',
  
'Ɵ' => 'ɵ',
  
'Ơ' => 'ơ',
  
'Ƣ' => 'ƣ',
  
'Ƥ' => 'ƥ',
  
'Ʀ' => 'ʀ',
  
'Ƨ' => 'ƨ',
  
'Ʃ' => 'ʃ',
  
'Ƭ' => 'ƭ',
  
'Ʈ' => 'ʈ',
  
'Ư' => 'ư',
  
'Ʊ' => 'ʊ',
  
'Ʋ' => 'ʋ',
  
'Ƴ' => 'ƴ',
  
'Ƶ' => 'ƶ',
  
'Ʒ' => 'ʒ',
  
'Ƹ' => 'ƹ',
  
'Ƽ' => 'ƽ',
  
'DŽ' => 'dž',
  
'Dž' => 'dž',
  
'LJ' => 'lj',
  
'Lj' => 'lj',
  
'NJ' => 'nj',
  
'Nj' => 'nj',
  
'Ǎ' => 'ǎ',
  
'Ǐ' => 'ǐ',
  
'Ǒ' => 'ǒ',
  
'Ǔ' => 'ǔ',
  
'Ǖ' => 'ǖ',
  
'Ǘ' => 'ǘ',
  
'Ǚ' => 'ǚ',
  
'Ǜ' => 'ǜ',
  
'Ǟ' => 'ǟ',
  
'Ǡ' => 'ǡ',
  
'Ǣ' => 'ǣ',
  
'Ǥ' => 'ǥ',
  
'Ǧ' => 'ǧ',
  
'Ǩ' => 'ǩ',
  
'Ǫ' => 'ǫ',
  
'Ǭ' => 'ǭ',
  
'Ǯ' => 'ǯ',
  
'DZ' => 'dz',
  
'Dz' => 'dz',
  
'Ǵ' => 'ǵ',
  
'Ƕ' => 'ƕ',
  
'Ƿ' => 'ƿ',
  
'Ǹ' => 'ǹ',
  
'Ǻ' => 'ǻ',
  
'Ǽ' => 'ǽ',
  
'Ǿ' => 'ǿ',
  
'Ȁ' => 'ȁ',
  
'Ȃ' => 'ȃ',
  
'Ȅ' => 'ȅ',
  
'Ȇ' => 'ȇ',
  
'Ȉ' => 'ȉ',
  
'Ȋ' => 'ȋ',
  
'Ȍ' => 'ȍ',
  
'Ȏ' => 'ȏ',
  
'Ȑ' => 'ȑ',
  
'Ȓ' => 'ȓ',
  
'Ȕ' => 'ȕ',
  
'Ȗ' => 'ȗ',
  
'Ș' => 'ș',
  
'Ț' => 'ț',
  
'Ȝ' => 'ȝ',
  
'Ȟ' => 'ȟ',
  
'Ƞ' => 'ƞ',
  
'Ȣ' => 'ȣ',
  
'Ȥ' => 'ȥ',
  
'Ȧ' => 'ȧ',
  
'Ȩ' => 'ȩ',
  
'Ȫ' => 'ȫ',
  
'Ȭ' => 'ȭ',
  
'Ȯ' => 'ȯ',
  
'Ȱ' => 'ȱ',
  
'Ȳ' => 'ȳ',
  
'Ⱥ' => 'ⱥ',
  
'Ȼ' => 'ȼ',
  
'Ƚ' => 'ƚ',
  
'Ⱦ' => 'ⱦ',
  
'Ɂ' => 'ɂ',
  
'Ƀ' => 'ƀ',
  
'Ʉ' => 'ʉ',
  
'Ʌ' => 'ʌ',
  
'Ɇ' => 'ɇ',
  
'Ɉ' => 'ɉ',
  
'Ɋ' => 'ɋ',
  
'Ɍ' => 'ɍ',
  
'Ɏ' => 'ɏ',
  
'Ͱ' => 'ͱ',
  
'Ͳ' => 'ͳ',
  
'Ͷ' => 'ͷ',
  
'Ϳ' => 'ϳ',
  
'Ά' => 'ά',
  
'Έ' => 'έ',
  
'Ή' => 'ή',
  
'Ί' => 'ί',
  
'Ό' => 'ό',
  
'Ύ' => 'ύ',
  
'Ώ' => 'ώ',
  
'Α' => 'α',
  
'Β' => 'β',
  
'Γ' => 'γ',
  
'Δ' => 'δ',
  
'Ε' => 'ε',
  
'Ζ' => 'ζ',
  
'Η' => 'η',
  
'Θ' => 'θ',
  
'Ι' => 'ι',
  
'Κ' => 'κ',
  
'Λ' => 'λ',
  
'Μ' => 'μ',
  
'Ν' => 'ν',
  
'Ξ' => 'ξ',
  
'Ο' => 'ο',
  
'Π' => 'π',
  
'Ρ' => 'ρ',
  
'Σ' => 'σ',
  
'Τ' => 'τ',
  
'Υ' => 'υ',
  
'Φ' => 'φ',
  
'Χ' => 'χ',
  
'Ψ' => 'ψ',
  
'Ω' => 'ω',
  
'Ϊ' => 'ϊ',
  
'Ϋ' => 'ϋ',
  
'Ϗ' => 'ϗ',
  
'Ϙ' => 'ϙ',
  
'Ϛ' => 'ϛ',
  
'Ϝ' => 'ϝ',
  
'Ϟ' => 'ϟ',
  
'Ϡ' => 'ϡ',
  
'Ϣ' => 'ϣ',
  
'Ϥ' => 'ϥ',
  
'Ϧ' => 'ϧ',
  
'Ϩ' => 'ϩ',
  
'Ϫ' => 'ϫ',
  
'Ϭ' => 'ϭ',
  
'Ϯ' => 'ϯ',
  
'ϴ' => 'θ',
  
'Ϸ' => 'ϸ',
  
'Ϲ' => 'ϲ',
  
'Ϻ' => 'ϻ',
  
'Ͻ' => 'ͻ',
  
'Ͼ' => 'ͼ',
  
'Ͽ' => 'ͽ',
  
'Ѐ' => 'ѐ',
  
'Ё' => 'ё',
  
'Ђ' => 'ђ',
  
'Ѓ' => 'ѓ',
  
'Є' => 'є',
  
'Ѕ' => 'ѕ',
  
'І' => 'і',
  
'Ї' => 'ї',
  
'Ј' => 'ј',
  
'Љ' => 'љ',
  
'Њ' => 'њ',
  
'Ћ' => 'ћ',
  
'Ќ' => 'ќ',
  
'Ѝ' => 'ѝ',
  
'Ў' => 'ў',
  
'Џ' => 'џ',
  
'А' => 'а',
  
'Б' => 'б',
  
'В' => 'в',
  
'Г' => 'г',
  
'Д' => 'д',
  
'Е' => 'е',
  
'Ж' => 'ж',
  
'З' => 'з',
  
'И' => 'и',
  
'Й' => 'й',
  
'К' => 'к',
  
'Л' => 'л',
  
'М' => 'м',
  
'Н' => 'н',
  
'О' => 'о',
  
'П' => 'п',
  
'Р' => 'р',
  
'С' => 'с',
  
'Т' => 'т',
  
'У' => 'у',
  
'Ф' => 'ф',
  
'Х' => 'х',
  
'Ц' => 'ц',
  
'Ч' => 'ч',
  
'Ш' => 'ш',
  
'Щ' => 'щ',
  
'Ъ' => 'ъ',
  
'Ы' => 'ы',
  
'Ь' => 'ь',
  
'Э' => 'э',
  
'Ю' => 'ю',
  
'Я' => 'я',
  
'Ѡ' => 'ѡ',
  
'Ѣ' => 'ѣ',
  
'Ѥ' => 'ѥ',
  
'Ѧ' => 'ѧ',
  
'Ѩ' => 'ѩ',
  
'Ѫ' => 'ѫ',
  
'Ѭ' => 'ѭ',
  
'Ѯ' => 'ѯ',
  
'Ѱ' => 'ѱ',
  
'Ѳ' => 'ѳ',
  
'Ѵ' => 'ѵ',
  
'Ѷ' => 'ѷ',
  
'Ѹ' => 'ѹ',
  
'Ѻ' => 'ѻ',
  
'Ѽ' => 'ѽ',
  
'Ѿ' => 'ѿ',
  
'Ҁ' => 'ҁ',
  
'Ҋ' => 'ҋ',
  
'Ҍ' => 'ҍ',
  
'Ҏ' => 'ҏ',
  
'Ґ' => 'ґ',
  
'Ғ' => 'ғ',
  
'Ҕ' => 'ҕ',
  
'Җ' => 'җ',
  
'Ҙ' => 'ҙ',
  
'Қ' => 'қ',
  
'Ҝ' => 'ҝ',
  
'Ҟ' => 'ҟ',
  
'Ҡ' => 'ҡ',
  
'Ң' => 'ң',
  
'Ҥ' => 'ҥ',
  
'Ҧ' => 'ҧ',
  
'Ҩ' => 'ҩ',
  
'Ҫ' => 'ҫ',
  
'Ҭ' => 'ҭ',
  
'Ү' => 'ү',
  
'Ұ' => 'ұ',
  
'Ҳ' => 'ҳ',
  
'Ҵ' => 'ҵ',
  
'Ҷ' => 'ҷ',
  
'Ҹ' => 'ҹ',
  
'Һ' => 'һ',
  
'Ҽ' => 'ҽ',
  
'Ҿ' => 'ҿ',
  
'Ӏ' => 'ӏ',
  
'Ӂ' => 'ӂ',
  
'Ӄ' => 'ӄ',
  
'Ӆ' => 'ӆ',
  
'Ӈ' => 'ӈ',
  
'Ӊ' => 'ӊ',
  
'Ӌ' => 'ӌ',
  
'Ӎ' => 'ӎ',
  
'Ӑ' => 'ӑ',
  
'Ӓ' => 'ӓ',
  
'Ӕ' => 'ӕ',
  
'Ӗ' => 'ӗ',
  
'Ә' => 'ә',
  
'Ӛ' => 'ӛ',
  
'Ӝ' => 'ӝ',
  
'Ӟ' => 'ӟ',
  
'Ӡ' => 'ӡ',
  
'Ӣ' => 'ӣ',
  
'Ӥ' => 'ӥ',
  
'Ӧ' => 'ӧ',
  
'Ө' => 'ө',
  
'Ӫ' => 'ӫ',
  
'Ӭ' => 'ӭ',
  
'Ӯ' => 'ӯ',
  
'Ӱ' => 'ӱ',
  
'Ӳ' => 'ӳ',
  
'Ӵ' => 'ӵ',
  
'Ӷ' => 'ӷ',
  
'Ӹ' => 'ӹ',
  
'Ӻ' => 'ӻ',
  
'Ӽ' => 'ӽ',
  
'Ӿ' => 'ӿ',
  
'Ԁ' => 'ԁ',
  
'Ԃ' => 'ԃ',
  
'Ԅ' => 'ԅ',
  
'Ԇ' => 'ԇ',
  
'Ԉ' => 'ԉ',
  
'Ԋ' => 'ԋ',
  
'Ԍ' => 'ԍ',
  
'Ԏ' => 'ԏ',
  
'Ԑ' => 'ԑ',
  
'Ԓ' => 'ԓ',
  
'Ԕ' => 'ԕ',
  
'Ԗ' => 'ԗ',
  
'Ԙ' => 'ԙ',
  
'Ԛ' => 'ԛ',
  
'Ԝ' => 'ԝ',
  
'Ԟ' => 'ԟ',
  
'Ԡ' => 'ԡ',
  
'Ԣ' => 'ԣ',
  
'Ԥ' => 'ԥ',
  
'Ԧ' => 'ԧ',
  
'Ԩ' => 'ԩ',
  
'Ԫ' => 'ԫ',
  
'Ԭ' => 'ԭ',
  
'Ԯ' => 'ԯ',
  
'Ա' => 'ա',
  
'Բ' => 'բ',
  
'Գ' => 'գ',
  
'Դ' => 'դ',
  
'Ե' => 'ե',
  
'Զ' => 'զ',
  
'Է' => 'է',
  
'Ը' => 'ը',
  
'Թ' => 'թ',
  
'Ժ' => 'ժ',
  
'Ի' => 'ի',
  
'Լ' => 'լ',
  
'Խ' => 'խ',
  
'Ծ' => 'ծ',
  
'Կ' => 'կ',
  
'Հ' => 'հ',
  
'Ձ' => 'ձ',
  
'Ղ' => 'ղ',
  
'Ճ' => 'ճ',
  
'Մ' => 'մ',
  
'Յ' => 'յ',
  
'Ն' => 'ն',
  
'Շ' => 'շ',
  
'Ո' => 'ո',
  
'Չ' => 'չ',
  
'Պ' => 'պ',
  
'Ջ' => 'ջ',
  
'Ռ' => 'ռ',
  
'Ս' => 'ս',
  
'Վ' => 'վ',
  
'Տ' => 'տ',
  
'Ր' => 'ր',
  
'Ց' => 'ց',
  
'Ւ' => 'ւ',
  
'Փ' => 'փ',
  
'Ք' => 'ք',
  
'Օ' => 'օ',
  
'Ֆ' => 'ֆ',
  
'Ⴀ' => 'ⴀ',
  
'Ⴁ' => 'ⴁ',
  
'Ⴂ' => 'ⴂ',
  
'Ⴃ' => 'ⴃ',
  
'Ⴄ' => 'ⴄ',
  
'Ⴅ' => 'ⴅ',
  
'Ⴆ' => 'ⴆ',
  
'Ⴇ' => 'ⴇ',
  
'Ⴈ' => 'ⴈ',
  
'Ⴉ' => 'ⴉ',
  
'Ⴊ' => 'ⴊ',
  
'Ⴋ' => 'ⴋ',
  
'Ⴌ' => 'ⴌ',
  
'Ⴍ' => 'ⴍ',
  
'Ⴎ' => 'ⴎ',
  
'Ⴏ' => 'ⴏ',
  
'Ⴐ' => 'ⴐ',
  
'Ⴑ' => 'ⴑ',
  
'Ⴒ' => 'ⴒ',
  
'Ⴓ' => 'ⴓ',
  
'Ⴔ' => 'ⴔ',
  
'Ⴕ' => 'ⴕ',
  
'Ⴖ' => 'ⴖ',
  
'Ⴗ' => 'ⴗ',
  
'Ⴘ' => 'ⴘ',
  
'Ⴙ' => 'ⴙ',
  
'Ⴚ' => 'ⴚ',
  
'Ⴛ' => 'ⴛ',
  
'Ⴜ' => 'ⴜ',
  
'Ⴝ' => 'ⴝ',
  
'Ⴞ' => 'ⴞ',
  
'Ⴟ' => 'ⴟ',
  
'Ⴠ' => 'ⴠ',
  
'Ⴡ' => 'ⴡ',
  
'Ⴢ' => 'ⴢ',
  
'Ⴣ' => 'ⴣ',
  
'Ⴤ' => 'ⴤ',
  
'Ⴥ' => 'ⴥ',
  
'Ⴧ' => 'ⴧ',
  
'Ⴭ' => 'ⴭ',
  
'Ꭰ' => 'ꭰ',
  
'Ꭱ' => 'ꭱ',
  
'Ꭲ' => 'ꭲ',
  
'Ꭳ' => 'ꭳ',
  
'Ꭴ' => 'ꭴ',
  
'Ꭵ' => 'ꭵ',
  
'Ꭶ' => 'ꭶ',
  
'Ꭷ' => 'ꭷ',
  
'Ꭸ' => 'ꭸ',
  
'Ꭹ' => 'ꭹ',
  
'Ꭺ' => 'ꭺ',
  
'Ꭻ' => 'ꭻ',
  
'Ꭼ' => 'ꭼ',
  
'Ꭽ' => 'ꭽ',
  
'Ꭾ' => 'ꭾ',
  
'Ꭿ' => 'ꭿ',
  
'Ꮀ' => 'ꮀ',
  
'Ꮁ' => 'ꮁ',
  
'Ꮂ' => 'ꮂ',
  
'Ꮃ' => 'ꮃ',
  
'Ꮄ' => 'ꮄ',
  
'Ꮅ' => 'ꮅ',
  
'Ꮆ' => 'ꮆ',
  
'Ꮇ' => 'ꮇ',
  
'Ꮈ' => 'ꮈ',
  
'Ꮉ' => 'ꮉ',
  
'Ꮊ' => 'ꮊ',
  
'Ꮋ' => 'ꮋ',
  
'Ꮌ' => 'ꮌ',
  
'Ꮍ' => 'ꮍ',
  
'Ꮎ' => 'ꮎ',
  
'Ꮏ' => 'ꮏ',
  
'Ꮐ' => 'ꮐ',
  
'Ꮑ' => 'ꮑ',
  
'Ꮒ' => 'ꮒ',
  
'Ꮓ' => 'ꮓ',
  
'Ꮔ' => 'ꮔ',
  
'Ꮕ' => 'ꮕ',
  
'Ꮖ' => 'ꮖ',
  
'Ꮗ' => 'ꮗ',
  
'Ꮘ' => 'ꮘ',
  
'Ꮙ' => 'ꮙ',
  
'Ꮚ' => 'ꮚ',
  
'Ꮛ' => 'ꮛ',
  
'Ꮜ' => 'ꮜ',
  
'Ꮝ' => 'ꮝ',
  
'Ꮞ' => 'ꮞ',
  
'Ꮟ' => 'ꮟ',
  
'Ꮠ' => 'ꮠ',
  
'Ꮡ' => 'ꮡ',
  
'Ꮢ' => 'ꮢ',
  
'Ꮣ' => 'ꮣ',
  
'Ꮤ' => 'ꮤ',
  
'Ꮥ' => 'ꮥ',
  
'Ꮦ' => 'ꮦ',
  
'Ꮧ' => 'ꮧ',
  
'Ꮨ' => 'ꮨ',
  
'Ꮩ' => 'ꮩ',
  
'Ꮪ' => 'ꮪ',
  
'Ꮫ' => 'ꮫ',
  
'Ꮬ' => 'ꮬ',
  
'Ꮭ' => 'ꮭ',
  
'Ꮮ' => 'ꮮ',
  
'Ꮯ' => 'ꮯ',
  
'Ꮰ' => 'ꮰ',
  
'Ꮱ' => 'ꮱ',
  
'Ꮲ' => 'ꮲ',
  
'Ꮳ' => 'ꮳ',
  
'Ꮴ' => 'ꮴ',
  
'Ꮵ' => 'ꮵ',
  
'Ꮶ' => 'ꮶ',
  
'Ꮷ' => 'ꮷ',
  
'Ꮸ' => 'ꮸ',
  
'Ꮹ' => 'ꮹ',
  
'Ꮺ' => 'ꮺ',
  
'Ꮻ' => 'ꮻ',
  
'Ꮼ' => 'ꮼ',
  
'Ꮽ' => 'ꮽ',
  
'Ꮾ' => 'ꮾ',
  
'Ꮿ' => 'ꮿ',
  
'Ᏸ' => 'ᏸ',
  
'Ᏹ' => 'ᏹ',
  
'Ᏺ' => 'ᏺ',
  
'Ᏻ' => 'ᏻ',
  
'Ᏼ' => 'ᏼ',
  
'Ᏽ' => 'ᏽ',
  
'Ა' => 'ა',
  
'Ბ' => 'ბ',
  
'Გ' => 'გ',
  
'Დ' => 'დ',
  
'Ე' => 'ე',
  
'Ვ' => 'ვ',
  
'Ზ' => 'ზ',
  
'Თ' => 'თ',
  
'Ი' => 'ი',
  
'Კ' => 'კ',
  
'Ლ' => 'ლ',
  
'Მ' => 'მ',
  
'Ნ' => 'ნ',
  
'Ო' => 'ო',
  
'Პ' => 'პ',
  
'Ჟ' => 'ჟ',
  
'Რ' => 'რ',
  
'Ს' => 'ს',
  
'Ტ' => 'ტ',
  
'Უ' => 'უ',
  
'Ფ' => 'ფ',
  
'Ქ' => 'ქ',
  
'Ღ' => 'ღ',
  
'Ყ' => 'ყ',
  
'Შ' => 'შ',
  
'Ჩ' => 'ჩ',
  
'Ც' => 'ც',
  
'Ძ' => 'ძ',
  
'Წ' => 'წ',
  
'Ჭ' => 'ჭ',
  
'Ხ' => 'ხ',
  
'Ჯ' => 'ჯ',
  
'Ჰ' => 'ჰ',
  
'Ჱ' => 'ჱ',
  
'Ჲ' => 'ჲ',
  
'Ჳ' => 'ჳ',
  
'Ჴ' => 'ჴ',
  
'Ჵ' => 'ჵ',
  
'Ჶ' => 'ჶ',
  
'Ჷ' => 'ჷ',
  
'Ჸ' => 'ჸ',
  
'Ჹ' => 'ჹ',
  
'Ჺ' => 'ჺ',
  
'Ჽ' => 'ჽ',
  
'Ჾ' => 'ჾ',
  
'Ჿ' => 'ჿ',
  
'Ḁ' => 'ḁ',
  
'Ḃ' => 'ḃ',
  
'Ḅ' => 'ḅ',
  
'Ḇ' => 'ḇ',
  
'Ḉ' => 'ḉ',
  
'Ḋ' => 'ḋ',
  
'Ḍ' => 'ḍ',
  
'Ḏ' => 'ḏ',
  
'Ḑ' => 'ḑ',
  
'Ḓ' => 'ḓ',
  
'Ḕ' => 'ḕ',
  
'Ḗ' => 'ḗ',
  
'Ḙ' => 'ḙ',
  
'Ḛ' => 'ḛ',
  
'Ḝ' => 'ḝ',
  
'Ḟ' => 'ḟ',
  
'Ḡ' => 'ḡ',
  
'Ḣ' => 'ḣ',
  
'Ḥ' => 'ḥ',
  
'Ḧ' => 'ḧ',
  
'Ḩ' => 'ḩ',
  
'Ḫ' => 'ḫ',
  
'Ḭ' => 'ḭ',
  
'Ḯ' => 'ḯ',
  
'Ḱ' => 'ḱ',
  
'Ḳ' => 'ḳ',
  
'Ḵ' => 'ḵ',
  
'Ḷ' => 'ḷ',
  
'Ḹ' => 'ḹ',
  
'Ḻ' => 'ḻ',
  
'Ḽ' => 'ḽ',
  
'Ḿ' => 'ḿ',
  
'Ṁ' => 'ṁ',
  
'Ṃ' => 'ṃ',
  
'Ṅ' => 'ṅ',
  
'Ṇ' => 'ṇ',
  
'Ṉ' => 'ṉ',
  
'Ṋ' => 'ṋ',
  
'Ṍ' => 'ṍ',
  
'Ṏ' => 'ṏ',
  
'Ṑ' => 'ṑ',
  
'Ṓ' => 'ṓ',
  
'Ṕ' => 'ṕ',
  
'Ṗ' => 'ṗ',
  
'Ṙ' => 'ṙ',
  
'Ṛ' => 'ṛ',
  
'Ṝ' => 'ṝ',
  
'Ṟ' => 'ṟ',
  
'Ṡ' => 'ṡ',
  
'Ṣ' => 'ṣ',
  
'Ṥ' => 'ṥ',
  
'Ṧ' => 'ṧ',
  
'Ṩ' => 'ṩ',
  
'Ṫ' => 'ṫ',
  
'Ṭ' => 'ṭ',
  
'Ṯ' => 'ṯ',
  
'Ṱ' => 'ṱ',
  
'Ṳ' => 'ṳ',
  
'Ṵ' => 'ṵ',
  
'Ṷ' => 'ṷ',
  
'Ṹ' => 'ṹ',
  
'Ṻ' => 'ṻ',
  
'Ṽ' => 'ṽ',
  
'Ṿ' => 'ṿ',
  
'Ẁ' => 'ẁ',
  
'Ẃ' => 'ẃ',
  
'Ẅ' => 'ẅ',
  
'Ẇ' => 'ẇ',
  
'Ẉ' => 'ẉ',
  
'Ẋ' => 'ẋ',
  
'Ẍ' => 'ẍ',
  
'Ẏ' => 'ẏ',
  
'Ẑ' => 'ẑ',
  
'Ẓ' => 'ẓ',
  
'Ẕ' => 'ẕ',
  
'ẞ' => 'ß',
  
'Ạ' => 'ạ',
  
'Ả' => 'ả',
  
'Ấ' => 'ấ',
  
'Ầ' => 'ầ',
  
'Ẩ' => 'ẩ',
  
'Ẫ' => 'ẫ',
  
'Ậ' => 'ậ',
  
'Ắ' => 'ắ',
  
'Ằ' => 'ằ',
  
'Ẳ' => 'ẳ',
  
'Ẵ' => 'ẵ',
  
'Ặ' => 'ặ',
  
'Ẹ' => 'ẹ',
  
'Ẻ' => 'ẻ',
  
'Ẽ' => 'ẽ',
  
'Ế' => 'ế',
  
'Ề' => 'ề',
  
'Ể' => 'ể',
  
'Ễ' => 'ễ',
  
'Ệ' => 'ệ',
  
'Ỉ' => 'ỉ',
  
'Ị' => 'ị',
  
'Ọ' => 'ọ',
  
'Ỏ' => 'ỏ',
  
'Ố' => 'ố',
  
'Ồ' => 'ồ',
  
'Ổ' => 'ổ',
  
'Ỗ' => 'ỗ',
  
'Ộ' => 'ộ',
  
'Ớ' => 'ớ',
  
'Ờ' => 'ờ',
  
'Ở' => 'ở',
  
'Ỡ' => 'ỡ',
  
'Ợ' => 'ợ',
  
'Ụ' => 'ụ',
  
'Ủ' => 'ủ',
  
'Ứ' => 'ứ',
  
'Ừ' => 'ừ',
  
'Ử' => 'ử',
  
'Ữ' => 'ữ',
  
'Ự' => 'ự',
  
'Ỳ' => 'ỳ',
  
'Ỵ' => 'ỵ',
  
'Ỷ' => 'ỷ',
  
'Ỹ' => 'ỹ',
  
'Ỻ' => 'ỻ',
  
'Ỽ' => 'ỽ',
  
'Ỿ' => 'ỿ',
  
'Ἀ' => 'ἀ',
  
'Ἁ' => 'ἁ',
  
'Ἂ' => 'ἂ',
  
'Ἃ' => 'ἃ',
  
'Ἄ' => 'ἄ',
  
'Ἅ' => 'ἅ',
  
'Ἆ' => 'ἆ',
  
'Ἇ' => 'ἇ',
  
'Ἐ' => 'ἐ',
  
'Ἑ' => 'ἑ',
  
'Ἒ' => 'ἒ',
  
'Ἓ' => 'ἓ',
  
'Ἔ' => 'ἔ',
  
'Ἕ' => 'ἕ',
  
'Ἠ' => 'ἠ',
  
'Ἡ' => 'ἡ',
  
'Ἢ' => 'ἢ',
  
'Ἣ' => 'ἣ',
  
'Ἤ' => 'ἤ',
  
'Ἥ' => 'ἥ',
  
'Ἦ' => 'ἦ',
  
'Ἧ' => 'ἧ',
  
'Ἰ' => 'ἰ',
  
'Ἱ' => 'ἱ',
  
'Ἲ' => 'ἲ',
  
'Ἳ' => 'ἳ',
  
'Ἴ' => 'ἴ',
  
'Ἵ' => 'ἵ',
  
'Ἶ' => 'ἶ',
  
'Ἷ' => 'ἷ',
  
'Ὀ' => 'ὀ',
  
'Ὁ' => 'ὁ',
  
'Ὂ' => 'ὂ',
  
'Ὃ' => 'ὃ',
  
'Ὄ' => 'ὄ',
  
'Ὅ' => 'ὅ',
  
'Ὑ' => 'ὑ',
  
'Ὓ' => 'ὓ',
  
'Ὕ' => 'ὕ',
  
'Ὗ' => 'ὗ',
  
'Ὠ' => 'ὠ',
  
'Ὡ' => 'ὡ',
  
'Ὢ' => 'ὢ',
  
'Ὣ' => 'ὣ',
  
'Ὤ' => 'ὤ',
  
'Ὥ' => 'ὥ',
  
'Ὦ' => 'ὦ',
  
'Ὧ' => 'ὧ',
  
'ᾈ' => 'ᾀ',
  
'ᾉ' => 'ᾁ',
  
'ᾊ' => 'ᾂ',
  
'ᾋ' => 'ᾃ',
  
'ᾌ' => 'ᾄ',
  
'ᾍ' => 'ᾅ',
  
'ᾎ' => 'ᾆ',
  
'ᾏ' => 'ᾇ',
  
'ᾘ' => 'ᾐ',
  
'ᾙ' => 'ᾑ',
  
'ᾚ' => 'ᾒ',
  
'ᾛ' => 'ᾓ',
  
'ᾜ' => 'ᾔ',
  
'ᾝ' => 'ᾕ',
  
'ᾞ' => 'ᾖ',
  
'ᾟ' => 'ᾗ',
  
'ᾨ' => 'ᾠ',
  
'ᾩ' => 'ᾡ',
  
'ᾪ' => 'ᾢ',
  
'ᾫ' => 'ᾣ',
  
'ᾬ' => 'ᾤ',
  
'ᾭ' => 'ᾥ',
  
'ᾮ' => 'ᾦ',
  
'ᾯ' => 'ᾧ',
  
'Ᾰ' => 'ᾰ',
  
'Ᾱ' => 'ᾱ',
  
'Ὰ' => 'ὰ',
  
'Ά' => 'ά',
  
'ᾼ' => 'ᾳ',
  
'Ὲ' => 'ὲ',
  
'Έ' => 'έ',
  
'Ὴ' => 'ὴ',
  
'Ή' => 'ή',
  
'ῌ' => 'ῃ',
  
'Ῐ' => 'ῐ',
  
'Ῑ' => 'ῑ',
  
'Ὶ' => 'ὶ',
  
'Ί' => 'ί',
  
'Ῠ' => 'ῠ',
  
'Ῡ' => 'ῡ',
  
'Ὺ' => 'ὺ',
  
'Ύ' => 'ύ',
  
'Ῥ' => 'ῥ',
  
'Ὸ' => 'ὸ',
  
'Ό' => 'ό',
  
'Ὼ' => 'ὼ',
  
'Ώ' => 'ώ',
  
'ῼ' => 'ῳ',
  
'Ω' => 'ω',
  
'K' => 'k',
  
'Å' => 'å',
  
'Ⅎ' => 'ⅎ',
  
'Ⅰ' => 'ⅰ',
  
'Ⅱ' => 'ⅱ',
  
'Ⅲ' => 'ⅲ',
  
'Ⅳ' => 'ⅳ',
  
'Ⅴ' => 'ⅴ',
  
'Ⅵ' => 'ⅵ',
  
'Ⅶ' => 'ⅶ',
  
'Ⅷ' => 'ⅷ',
  
'Ⅸ' => 'ⅸ',
  
'Ⅹ' => 'ⅹ',
  
'Ⅺ' => 'ⅺ',
  
'Ⅻ' => 'ⅻ',
  
'Ⅼ' => 'ⅼ',
  
'Ⅽ' => 'ⅽ',
  
'Ⅾ' => 'ⅾ',
  
'Ⅿ' => 'ⅿ',
  
'Ↄ' => 'ↄ',
  
'Ⓐ' => 'ⓐ',
  
'Ⓑ' => 'ⓑ',
  
'Ⓒ' => 'ⓒ',
  
'Ⓓ' => 'ⓓ',
  
'Ⓔ' => 'ⓔ',
  
'Ⓕ' => 'ⓕ',
  
'Ⓖ' => 'ⓖ',
  
'Ⓗ' => 'ⓗ',
  
'Ⓘ' => 'ⓘ',
  
'Ⓙ' => 'ⓙ',
  
'Ⓚ' => 'ⓚ',
  
'Ⓛ' => 'ⓛ',
  
'Ⓜ' => 'ⓜ',
  
'Ⓝ' => 'ⓝ',
  
'Ⓞ' => 'ⓞ',
  
'Ⓟ' => 'ⓟ',
  
'Ⓠ' => 'ⓠ',
  
'Ⓡ' => 'ⓡ',
  
'Ⓢ' => 'ⓢ',
  
'Ⓣ' => 'ⓣ',
  
'Ⓤ' => 'ⓤ',
  
'Ⓥ' => 'ⓥ',
  
'Ⓦ' => 'ⓦ',
  
'Ⓧ' => 'ⓧ',
  
'Ⓨ' => 'ⓨ',
  
'Ⓩ' => 'ⓩ',
  
'Ⰰ' => 'ⰰ',
  
'Ⰱ' => 'ⰱ',
  
'Ⰲ' => 'ⰲ',
  
'Ⰳ' => 'ⰳ',
  
'Ⰴ' => 'ⰴ',
  
'Ⰵ' => 'ⰵ',
  
'Ⰶ' => 'ⰶ',
  
'Ⰷ' => 'ⰷ',
  
'Ⰸ' => 'ⰸ',
  
'Ⰹ' => 'ⰹ',
  
'Ⰺ' => 'ⰺ',
  
'Ⰻ' => 'ⰻ',
  
'Ⰼ' => 'ⰼ',
  
'Ⰽ' => 'ⰽ',
  
'Ⰾ' => 'ⰾ',
  
'Ⰿ' => 'ⰿ',
  
'Ⱀ' => 'ⱀ',
  
'Ⱁ' => 'ⱁ',
  
'Ⱂ' => 'ⱂ',
  
'Ⱃ' => 'ⱃ',
  
'Ⱄ' => 'ⱄ',
  
'Ⱅ' => 'ⱅ',
  
'Ⱆ' => 'ⱆ',
  
'Ⱇ' => 'ⱇ',
  
'Ⱈ' => 'ⱈ',
  
'Ⱉ' => 'ⱉ',
  
'Ⱊ' => 'ⱊ',
  
'Ⱋ' => 'ⱋ',
  
'Ⱌ' => 'ⱌ',
  
'Ⱍ' => 'ⱍ',
  
'Ⱎ' => 'ⱎ',
  
'Ⱏ' => 'ⱏ',
  
'Ⱐ' => 'ⱐ',
  
'Ⱑ' => 'ⱑ',
  
'Ⱒ' => 'ⱒ',
  
'Ⱓ' => 'ⱓ',
  
'Ⱔ' => 'ⱔ',
  
'Ⱕ' => 'ⱕ',
  
'Ⱖ' => 'ⱖ',
  
'Ⱗ' => 'ⱗ',
  
'Ⱘ' => 'ⱘ',
  
'Ⱙ' => 'ⱙ',
  
'Ⱚ' => 'ⱚ',
  
'Ⱛ' => 'ⱛ',
  
'Ⱜ' => 'ⱜ',
  
'Ⱝ' => 'ⱝ',
  
'Ⱞ' => 'ⱞ',
  
'Ⱡ' => 'ⱡ',
  
'Ɫ' => 'ɫ',
  
'Ᵽ' => 'ᵽ',
  
'Ɽ' => 'ɽ',
  
'Ⱨ' => 'ⱨ',
  
'Ⱪ' => 'ⱪ',
  
'Ⱬ' => 'ⱬ',
  
'Ɑ' => 'ɑ',
  
'Ɱ' => 'ɱ',
  
'Ɐ' => 'ɐ',
  
'Ɒ' => 'ɒ',
  
'Ⱳ' => 'ⱳ',
  
'Ⱶ' => 'ⱶ',
  
'Ȿ' => 'ȿ',
  
'Ɀ' => 'ɀ',
  
'Ⲁ' => 'ⲁ',
  
'Ⲃ' => 'ⲃ',
  
'Ⲅ' => 'ⲅ',
  
'Ⲇ' => 'ⲇ',
  
'Ⲉ' => 'ⲉ',
  
'Ⲋ' => 'ⲋ',
  
'Ⲍ' => 'ⲍ',
  
'Ⲏ' => 'ⲏ',
  
'Ⲑ' => 'ⲑ',
  
'Ⲓ' => 'ⲓ',
  
'Ⲕ' => 'ⲕ',
  
'Ⲗ' => 'ⲗ',
  
'Ⲙ' => 'ⲙ',
  
'Ⲛ' => 'ⲛ',
  
'Ⲝ' => 'ⲝ',
  
'Ⲟ' => 'ⲟ',
  
'Ⲡ' => 'ⲡ',
  
'Ⲣ' => 'ⲣ',
  
'Ⲥ' => 'ⲥ',
  
'Ⲧ' => 'ⲧ',
  
'Ⲩ' => 'ⲩ',
  
'Ⲫ' => 'ⲫ',
  
'Ⲭ' => 'ⲭ',
  
'Ⲯ' => 'ⲯ',
  
'Ⲱ' => 'ⲱ',
  
'Ⲳ' => 'ⲳ',
  
'Ⲵ' => 'ⲵ',
  
'Ⲷ' => 'ⲷ',
  
'Ⲹ' => 'ⲹ',
  
'Ⲻ' => 'ⲻ',
  
'Ⲽ' => 'ⲽ',
  
'Ⲿ' => 'ⲿ',
  
'Ⳁ' => 'ⳁ',
  
'Ⳃ' => 'ⳃ',
  
'Ⳅ' => 'ⳅ',
  
'Ⳇ' => 'ⳇ',
  
'Ⳉ' => 'ⳉ',
  
'Ⳋ' => 'ⳋ',
  
'Ⳍ' => 'ⳍ',
  
'Ⳏ' => 'ⳏ',
  
'Ⳑ' => 'ⳑ',
  
'Ⳓ' => 'ⳓ',
  
'Ⳕ' => 'ⳕ',
  
'Ⳗ' => 'ⳗ',
  
'Ⳙ' => 'ⳙ',
  
'Ⳛ' => 'ⳛ',
  
'Ⳝ' => 'ⳝ',
  
'Ⳟ' => 'ⳟ',
  
'Ⳡ' => 'ⳡ',
  
'Ⳣ' => 'ⳣ',
  
'Ⳬ' => 'ⳬ',
  
'Ⳮ' => 'ⳮ',
  
'Ⳳ' => 'ⳳ',
  
'Ꙁ' => 'ꙁ',
  
'Ꙃ' => 'ꙃ',
  
'Ꙅ' => 'ꙅ',
  
'Ꙇ' => 'ꙇ',
  
'Ꙉ' => 'ꙉ',
  
'Ꙋ' => 'ꙋ',
  
'Ꙍ' => 'ꙍ',
  
'Ꙏ' => 'ꙏ',
  
'Ꙑ' => 'ꙑ',
  
'Ꙓ' => 'ꙓ',
  
'Ꙕ' => 'ꙕ',
  
'Ꙗ' => 'ꙗ',
  
'Ꙙ' => 'ꙙ',
  
'Ꙛ' => 'ꙛ',
  
'Ꙝ' => 'ꙝ',
  
'Ꙟ' => 'ꙟ',
  
'Ꙡ' => 'ꙡ',
  
'Ꙣ' => 'ꙣ',
  
'Ꙥ' => 'ꙥ',
  
'Ꙧ' => 'ꙧ',
  
'Ꙩ' => 'ꙩ',
  
'Ꙫ' => 'ꙫ',
  
'Ꙭ' => 'ꙭ',
  
'Ꚁ' => 'ꚁ',
  
'Ꚃ' => 'ꚃ',
  
'Ꚅ' => 'ꚅ',
  
'Ꚇ' => 'ꚇ',
  
'Ꚉ' => 'ꚉ',
  
'Ꚋ' => 'ꚋ',
  
'Ꚍ' => 'ꚍ',
  
'Ꚏ' => 'ꚏ',
  
'Ꚑ' => 'ꚑ',
  
'Ꚓ' => 'ꚓ',
  
'Ꚕ' => 'ꚕ',
  
'Ꚗ' => 'ꚗ',
  
'Ꚙ' => 'ꚙ',
  
'Ꚛ' => 'ꚛ',
  
'Ꜣ' => 'ꜣ',
  
'Ꜥ' => 'ꜥ',
  
'Ꜧ' => 'ꜧ',
  
'Ꜩ' => 'ꜩ',
  
'Ꜫ' => 'ꜫ',
  
'Ꜭ' => 'ꜭ',
  
'Ꜯ' => 'ꜯ',
  
'Ꜳ' => 'ꜳ',
  
'Ꜵ' => 'ꜵ',
  
'Ꜷ' => 'ꜷ',
  
'Ꜹ' => 'ꜹ',
  
'Ꜻ' => 'ꜻ',
  
'Ꜽ' => 'ꜽ',
  
'Ꜿ' => 'ꜿ',
  
'Ꝁ' => 'ꝁ',
  
'Ꝃ' => 'ꝃ',
  
'Ꝅ' => 'ꝅ',
  
'Ꝇ' => 'ꝇ',
  
'Ꝉ' => 'ꝉ',
  
'Ꝋ' => 'ꝋ',
  
'Ꝍ' => 'ꝍ',
  
'Ꝏ' => 'ꝏ',
  
'Ꝑ' => 'ꝑ',
  
'Ꝓ' => 'ꝓ',
  
'Ꝕ' => 'ꝕ',
  
'Ꝗ' => 'ꝗ',
  
'Ꝙ' => 'ꝙ',
  
'Ꝛ' => 'ꝛ',
  
'Ꝝ' => 'ꝝ',
  
'Ꝟ' => 'ꝟ',
  
'Ꝡ' => 'ꝡ',
  
'Ꝣ' => 'ꝣ',
  
'Ꝥ' => 'ꝥ',
  
'Ꝧ' => 'ꝧ',
  
'Ꝩ' => 'ꝩ',
  
'Ꝫ' => 'ꝫ',
  
'Ꝭ' => 'ꝭ',
  
'Ꝯ' => 'ꝯ',
  
'Ꝺ' => 'ꝺ',
  
'Ꝼ' => 'ꝼ',
  
'Ᵹ' => 'ᵹ',
  
'Ꝿ' => 'ꝿ',
  
'Ꞁ' => 'ꞁ',
  
'Ꞃ' => 'ꞃ',
  
'Ꞅ' => 'ꞅ',
  
'Ꞇ' => 'ꞇ',
  
'Ꞌ' => 'ꞌ',
  
'Ɥ' => 'ɥ',
  
'Ꞑ' => 'ꞑ',
  
'Ꞓ' => 'ꞓ',
  
'Ꞗ' => 'ꞗ',
  
'Ꞙ' => 'ꞙ',
  
'Ꞛ' => 'ꞛ',
  
'Ꞝ' => 'ꞝ',
  
'Ꞟ' => 'ꞟ',
  
'Ꞡ' => 'ꞡ',
  
'Ꞣ' => 'ꞣ',
  
'Ꞥ' => 'ꞥ',
  
'Ꞧ' => 'ꞧ',
  
'Ꞩ' => 'ꞩ',
  
'Ɦ' => 'ɦ',
  
'Ɜ' => 'ɜ',
  
'Ɡ' => 'ɡ',
  
'Ɬ' => 'ɬ',
  
'Ɪ' => 'ɪ',
  
'Ʞ' => 'ʞ',
  
'Ʇ' => 'ʇ',
  
'Ʝ' => 'ʝ',
  
'Ꭓ' => 'ꭓ',
  
'Ꞵ' => 'ꞵ',
  
'Ꞷ' => 'ꞷ',
  
'Ꞹ' => 'ꞹ',
  
'Ꞻ' => 'ꞻ',
  
'Ꞽ' => 'ꞽ',
  
'Ꞿ' => 'ꞿ',
  
'Ꟃ' => 'ꟃ',
  
'Ꞔ' => 'ꞔ',
  
'Ʂ' => 'ʂ',
  
'Ᶎ' => 'ᶎ',
  
'Ꟈ' => 'ꟈ',
  
'Ꟊ' => 'ꟊ',
  
'Ꟶ' => 'ꟶ',
  
'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',
  
'𐐀' => '𐐨',
  
'𐐁' => '𐐩',
  
'𐐂' => '𐐪',
  
'𐐃' => '𐐫',
  
'𐐄' => '𐐬',
  
'𐐅' => '𐐭',
  
'𐐆' => '𐐮',
  
'𐐇' => '𐐯',
  
'𐐈' => '𐐰',
  
'𐐉' => '𐐱',
  
'𐐊' => '𐐲',
  
'𐐋' => '𐐳',
  
'𐐌' => '𐐴',
  
'𐐍' => '𐐵',
  
'𐐎' => '𐐶',
  
'𐐏' => '𐐷',
  
'𐐐' => '𐐸',
  
'𐐑' => '𐐹',
  
'𐐒' => '𐐺',
  
'𐐓' => '𐐻',
  
'𐐔' => '𐐼',
  
'𐐕' => '𐐽',
  
'𐐖' => '𐐾',
  
'𐐗' => '𐐿',
  
'𐐘' => '𐑀',
  
'𐐙' => '𐑁',
  
'𐐚' => '𐑂',
  
'𐐛' => '𐑃',
  
'𐐜' => '𐑄',
  
'𐐝' => '𐑅',
  
'𐐞' => '𐑆',
  
'𐐟' => '𐑇',
  
'𐐠' => '𐑈',
  
'𐐡' => '𐑉',
  
'𐐢' => '𐑊',
  
'𐐣' => '𐑋',
  
'𐐤' => '𐑌',
  
'𐐥' => '𐑍',
  
'𐐦' => '𐑎',
  
'𐐧' => '𐑏',
  
'𐒰' => '𐓘',
  
'𐒱' => '𐓙',
  
'𐒲' => '𐓚',
  
'𐒳' => '𐓛',
  
'𐒴' => '𐓜',
  
'𐒵' => '𐓝',
  
'𐒶' => '𐓞',
  
'𐒷' => '𐓟',
  
'𐒸' => '𐓠',
  
'𐒹' => '𐓡',
  
'𐒺' => '𐓢',
  
'𐒻' => '𐓣',
  
'𐒼' => '𐓤',
  
'𐒽' => '𐓥',
  
'𐒾' => '𐓦',
  
'𐒿' => '𐓧',
  
'𐓀' => '𐓨',
  
'𐓁' => '𐓩',
  
'𐓂' => '𐓪',
  
'𐓃' => '𐓫',
  
'𐓄' => '𐓬',
  
'𐓅' => '𐓭',
  
'𐓆' => '𐓮',
  
'𐓇' => '𐓯',
  
'𐓈' => '𐓰',
  
'𐓉' => '𐓱',
  
'𐓊' => '𐓲',
  
'𐓋' => '𐓳',
  
'𐓌' => '𐓴',
  
'𐓍' => '𐓵',
  
'𐓎' => '𐓶',
  
'𐓏' => '𐓷',
  
'𐓐' => '𐓸',
  
'𐓑' => '𐓹',
  
'𐓒' => '𐓺',
  
'𐓓' => '𐓻',
  
'𐲀' => '𐳀',
  
'𐲁' => '𐳁',
  
'𐲂' => '𐳂',
  
'𐲃' => '𐳃',
  
'𐲄' => '𐳄',
  
'𐲅' => '𐳅',
  
'𐲆' => '𐳆',
  
'𐲇' => '𐳇',
  
'𐲈' => '𐳈',
  
'𐲉' => '𐳉',
  
'𐲊' => '𐳊',
  
'𐲋' => '𐳋',
  
'𐲌' => '𐳌',
  
'𐲍' => '𐳍',
  
'𐲎' => '𐳎',
  
'𐲏' => '𐳏',
  
'𐲐' => '𐳐',
  
'𐲑' => '𐳑',
  
'𐲒' => '𐳒',
  
'𐲓' => '𐳓',
  
'𐲔' => '𐳔',
  
'𐲕' => '𐳕',
  
'𐲖' => '𐳖',
  
'𐲗' => '𐳗',
  
'𐲘' => '𐳘',
  
'𐲙' => '𐳙',
  
'𐲚' => '𐳚',
  
'𐲛' => '𐳛',
  
'𐲜' => '𐳜',
  
'𐲝' => '𐳝',
  
'𐲞' => '𐳞',
  
'𐲟' => '𐳟',
  
'𐲠' => '𐳠',
  
'𐲡' => '𐳡',
  
'𐲢' => '𐳢',
  
'𐲣' => '𐳣',
  
'𐲤' => '𐳤',
  
'𐲥' => '𐳥',
  
'𐲦' => '𐳦',
  
'𐲧' => '𐳧',
  
'𐲨' => '𐳨',
  
'𐲩' => '𐳩',
  
'𐲪' => '𐳪',
  
'𐲫' => '𐳫',
  
'𐲬' => '𐳬',
  
'𐲭' => '𐳭',
  
'𐲮' => '𐳮',
  
'𐲯' => '𐳯',
  
'𐲰' => '𐳰',
  
'𐲱' => '𐳱',
  
'𐲲' => '𐳲',
  
'𑢠' => '𑣀',
  
'𑢡' => '𑣁',
  
'𑢢' => '𑣂',
  
'𑢣' => '𑣃',
  
'𑢤' => '𑣄',
  
'𑢥' => '𑣅',
  
'𑢦' => '𑣆',
  
'𑢧' => '𑣇',
  
'𑢨' => '𑣈',
  
'𑢩' => '𑣉',
  
'𑢪' => '𑣊',
  
'𑢫' => '𑣋',
  
'𑢬' => '𑣌',
  
'𑢭' => '𑣍',
  
'𑢮' => '𑣎',
  
'𑢯' => '𑣏',
  
'𑢰' => '𑣐',
  
'𑢱' => '𑣑',
  
'𑢲' => '𑣒',
  
'𑢳' => '𑣓',
  
'𑢴' => '𑣔',
  
'𑢵' => '𑣕',
  
'𑢶' => '𑣖',
  
'𑢷' => '𑣗',
  
'𑢸' => '𑣘',
  
'𑢹' => '𑣙',
  
'𑢺' => '𑣚',
  
'𑢻' => '𑣛',
  
'𑢼' => '𑣜',
  
'𑢽' => '𑣝',
  
'𑢾' => '𑣞',
  
'𑢿' => '𑣟',
  
'𖹀' => '𖹠',
  
'𖹁' => '𖹡',
  
'𖹂' => '𖹢',
  
'𖹃' => '𖹣',
  
'𖹄' => '𖹤',
  
'𖹅' => '𖹥',
  
'𖹆' => '𖹦',
  
'𖹇' => '𖹧',
  
'𖹈' => '𖹨',
  
'𖹉' => '𖹩',
  
'𖹊' => '𖹪',
  
'𖹋' => '𖹫',
  
'𖹌' => '𖹬',
  
'𖹍' => '𖹭',
  
'𖹎' => '𖹮',
  
'𖹏' => '𖹯',
  
'𖹐' => '𖹰',
  
'𖹑' => '𖹱',
  
'𖹒' => '𖹲',
  
'𖹓' => '𖹳',
  
'𖹔' => '𖹴',
  
'𖹕' => '𖹵',
  
'𖹖' => '𖹶',
  
'𖹗' => '𖹷',
  
'𖹘' => '𖹸',
  
'𖹙' => '𖹹',
  
'𖹚' => '𖹺',
  
'𖹛' => '𖹻',
  
'𖹜' => '𖹼',
  
'𖹝' => '𖹽',
  
'𖹞' => '𖹾',
  
'𖹟' => '𖹿',
  
'𞤀' => '𞤢',
  
'𞤁' => '𞤣',
  
'𞤂' => '𞤤',
  
'𞤃' => '𞤥',
  
'𞤄' => '𞤦',
  
'𞤅' => '𞤧',
  
'𞤆' => '𞤨',
  
'𞤇' => '𞤩',
  
'𞤈' => '𞤪',
  
'𞤉' => '𞤫',
  
'𞤊' => '𞤬',
  
'𞤋' => '𞤭',
  
'𞤌' => '𞤮',
  
'𞤍' => '𞤯',
  
'𞤎' => '𞤰',
  
'𞤏' => '𞤱',
  
'𞤐' => '𞤲',
  
'𞤑' => '𞤳',
  
'𞤒' => '𞤴',
  
'𞤓' => '𞤵',
  
'𞤔' => '𞤶',
  
'𞤕' => '𞤷',
  
'𞤖' => '𞤸',
  
'𞤗' => '𞤹',
  
'𞤘' => '𞤺',
  
'𞤙' => '𞤻',
  
'𞤚' => '𞤼',
  
'𞤛' => '𞤽',
  
'𞤜' => '𞤾',
  
'𞤝' => '𞤿',
  
'𞤞' => '𞥀',
  
'𞤟' => '𞥁',
  
'𞤠' => '𞥂',
  
'𞤡' => '𞥃',
);
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use Symfony\Polyfill\Mbstring as p;

if (!
function_exists('mb_convert_encoding')) {
    function 
mb_convert_encoding($string$to_encoding$from_encoding null) { return p\Mbstring::mb_convert_encoding($string$to_encoding$from_encoding); }
}
if (!
function_exists('mb_decode_mimeheader')) {
    function 
mb_decode_mimeheader($string) { return p\Mbstring::mb_decode_mimeheader($string); }
}
if (!
function_exists('mb_encode_mimeheader')) {
    function 
mb_encode_mimeheader($string$charset null$transfer_encoding null$newline null$indent null) { 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 = array()) { parse_str($string$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 '') { return p\Mbstring::mb_http_input($type); }
}

if (
PHP_VERSION_ID >= 80000) {
    require_once 
__DIR__.'/Resources/mb_convert_variables.php8';
} elseif (!
function_exists('mb_convert_variables')) {
    function 
mb_convert_variables($toEncoding$fromEncoding, &$a null, &$b null, &$c null, &$d null, &$e null, &$f null) { return p\Mbstring::mb_convert_variables($toEncoding$fromEncoding$a$b$c$d$e$f); }
}

if (!
function_exists('mb_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 (
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 <[email protected]>
 *
 * 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
 *
 * 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 <[email protected]>
 *
 * @internal
 */
final class Mbstring
{
    const 
MB_CASE_FOLD PHP_INT_MAX;

    private static 
$encodingList = array('ASCII''UTF-8');
    private static 
$language 'neutral';
    private static 
$internalEncoding 'UTF-8';
    private static 
$caseFold = array(
        array(
'µ''ſ'"\xCD\x85"'ς'"\xCF\x90""\xCF\x91""\xCF\x95""\xCF\x96""\xCF\xB0""\xCF\xB1""\xCF\xB5""\xE1\xBA\x9B""\xE1\xBE\xBE"),
        array(
'μ''s''ι',        'σ''β',        'θ',        'φ',        'π',        'κ',        'ρ',        'ε',        "\xE1\xB9\xA1"'ι'),
    );

    public static function 
mb_convert_encoding($s$toEncoding$fromEncoding null)
    {
        if (
\is_array($fromEncoding) || false !== strpos($fromEncoding',')) {
            
$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]+/', array(__CLASS__'html_encoding_callback'), $s);
        }

        if (
'HTML-ENTITIES' === $fromEncoding) {
            
$s html_entity_decode($sENT_COMPAT'UTF-8');
            
$fromEncoding 'UTF-8';
        }

        return 
iconv($fromEncoding$toEncoding.'//IGNORE'$s);
    }

    public static function 
mb_convert_variables($toEncoding$fromEncoding, &$a null, &$b null, &$c null, &$d null, &$e null, &$f null)
    {
        
$vars = array(&$a, &$b, &$c, &$d, &$e, &$f);

        
$ok true;
        
array_walk_recursive($vars, function (&$v) use (&$ok$toEncoding$fromEncoding) {
            if (
false === $v Mbstring::mb_convert_encoding($v$toEncoding$fromEncoding)) {
                
$ok false;
            }
        });

        return 
$ok $fromEncoding false;
    }

    public static function 
mb_decode_mimeheader($s)
    {
        return 
iconv_mime_decode($s2self::$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) || !$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 
Mbstring::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) || !$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 = array("\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" $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, array(__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) {
                    
$s str_replace(self::$caseFold[0], self::$caseFold[1], $s);
                }

                static 
$lower null;
                if (
null === $lower) {
                    
$lower self::getData('lowerCase');
                }
                
$map $lower;
            }

            static 
$ulenMask = array("\xC0" => 2"\xD0" => 2"\xE0" => 3"\xF0" => 4);

            
$i 0;
            
$len \strlen($s);

            while (
$i $len) {
                
$ulen $s[$i] < "\x80" $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;
        }

        
$encoding self::getEncoding($encoding);

        if (
'UTF-8' === $encoding || false !== @iconv($encoding$encoding' ')) {
            
self::$internalEncoding $encoding;

            return 
true;
        }

        return 
false;
    }

    public static function 
mb_language($lang null)
    {
        if (
null === $lang) {
            return 
self::$language;
        }

        switch (
$lang strtolower($lang)) {
            case 
'uni':
            case 
'neutral':
                
self::$language $lang;

                return 
true;
        }

        return 
false;
    }

    public static function 
mb_list_encodings()
    {
        return array(
'UTF-8');
    }

    public static function 
mb_encoding_aliases($encoding)
    {
        switch (
strtoupper($encoding)) {
            case 
'UTF8':
            case 
'UTF-8':
                return array(
'utf8');
        }

        return 
false;
    }

    public static function 
mb_check_encoding($var null$encoding null)
    {
        if (
null === $encoding) {
            if (
null === $var) {
                return 
false;
            }
            
$encoding self::$internalEncoding;
        }

        return 
self::mb_detect_encoding($var, array($encoding)) || false !== @iconv($encoding$encoding$var);
    }

    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 (
=== 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) {
            
trigger_error(__METHOD__.': Empty delimiter'E_USER_WARNING);

            return 
false;
        }

        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 (
$offset += self::mb_strlen($needle)) {
                    
$haystack self::mb_substr($haystack0$offset$encoding);
                }
                
$offset 0;
            } else {
                
$haystack self::mb_substr($haystack$offset2147483647$encoding);
            }
        }

        
$pos iconv_strrpos($haystack$needle$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 (
$split_length = (int) $split_length) {
            
trigger_error('The length of each segment must be greater than zero'E_USER_WARNING);

            return 
false;
        }

        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$stringnullPREG_SPLIT_DELIM_CAPTURE PREG_SPLIT_NO_EMPTY);
        }

        
$result = array();
        
$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($sMB_CASE_LOWER$encoding);
    }

    public static function 
mb_strtoupper($s$encoding null)
    {
        return 
self::mb_convert_case($sMB_CASE_UPPER$encoding);
    }

    public static function 
mb_substitute_character($c null)
    {
        if (
=== strcasecmp($c'none')) {
            return 
true;
        }

        return 
null !== $c false 'none';
    }

    public static function 
mb_substr($s$start$length null$encoding null)
    {
        
$encoding self::getEncoding($encoding);
        if (
'CP850' === $encoding || 'ASCII' === $encoding) {
            return (string) 
substr($s$startnull === $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 self::mb_convert_case($haystackself::MB_CASE_FOLD$encoding);
        
$needle self::mb_convert_case($needleself::MB_CASE_FOLD$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$needle0$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($needle01$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($needle01$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($haystackself::MB_CASE_FOLD$encoding);
        
$needle self::mb_convert_case($needleself::MB_CASE_FOLD$encoding);

        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($haystack0$pos);
        }

        return 
substr($haystack$pos);
    }

    public static function 
mb_get_info($type 'all')
    {
        
$info = array(
            
'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 >> 0x3F).\chr(0x80 $code 0x3F);
        } else {
            
$s \chr(0xF0 $code >> 18).\chr(0x80 $code >> 12 0x3F).\chr(0x80 $code >> 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 (
=== \strlen($s)) {
            return 
\ord($s);
        }

        
$code = ($s unpack('C*'substr($s04))) ? $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;
    }

    private static function 
getSubpart($pos$part$haystack$encoding)
    {
        if (
false === $pos) {
            return 
false;
        }
        if (
$part) {
            return 
self::mb_substr($haystack0$pos$encoding);
        }

        return 
self::mb_substr($haystack$posnull$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;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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;

/**
 * PhpProcess runs a PHP script in an independent process.
 *
 *     $p = new PhpProcess('<?php echo "foo"; ?>');
 *     $p->run();
 *     print $p->getOutput()."\n";
 *
 * @author Fabien Potencier <[email protected]>
 */
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       $options An array of options for proc_open
     */
    
public function __construct($script$cwd null, array $env null$timeout 60, array $options null)
    {
        
$executableFinder = new PhpExecutableFinder();
        if (
false === $php $executableFinder->find(false)) {
            
$php null;
        } else {
            
$php 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;
        }
        if (
null !== $options) {
            @
trigger_error(sprintf('The $options parameter of the %s constructor is deprecated since Symfony 3.3 and will be removed in 4.0.'__CLASS__), \E_USER_DEPRECATED);
        }

        
parent::__construct($php$cwd$env$script$timeout$options);
    }

    
/**
     * Sets the path to the PHP binary to use.
     */
    
public function setPhpBinary($php)
    {
        
$this->setCommandLine($php);
    }

    
/**
     * {@inheritdoc}
     */
    
public function start(callable $callback null/*, array $env = []*/)
    {
        if (
null === $this->getCommandLine()) {
            throw new 
RuntimeException('Unable to find the PHP executable.');
        }
        
$env \func_num_args() ? func_get_arg(1) : null;

        
parent::start($callback$env);
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process;

@
trigger_error(sprintf('The %s class is deprecated since Symfony 3.4 and will be removed in 4.0. Use the Process class instead.'ProcessBuilder::class), \E_USER_DEPRECATED);

use 
Symfony\Component\Process\Exception\InvalidArgumentException;
use 
Symfony\Component\Process\Exception\LogicException;

/**
 * @author Kris Wallsmith <[email protected]>
 *
 * @deprecated since version 3.4, to be removed in 4.0. Use the Process class instead.
 */
class ProcessBuilder
{
    private 
$arguments;
    private 
$cwd;
    private 
$env = [];
    private 
$input;
    private 
$timeout 60;
    private 
$options;
    private 
$inheritEnv true;
    private 
$prefix = [];
    private 
$outputDisabled false;

    
/**
     * @param string[] $arguments An array of arguments
     */
    
public function __construct(array $arguments = [])
    {
        
$this->arguments $arguments;
    }

    
/**
     * Creates a process builder instance.
     *
     * @param string[] $arguments An array of arguments
     *
     * @return static
     */
    
public static function create(array $arguments = [])
    {
        return new static(
$arguments);
    }

    
/**
     * Adds an unescaped argument to the command string.
     *
     * @param string $argument A command argument
     *
     * @return $this
     */
    
public function add($argument)
    {
        
$this->arguments[] = $argument;

        return 
$this;
    }

    
/**
     * Adds a prefix to the command string.
     *
     * The prefix is preserved when resetting arguments.
     *
     * @param string|array $prefix A command prefix or an array of command prefixes
     *
     * @return $this
     */
    
public function setPrefix($prefix)
    {
        
$this->prefix \is_array($prefix) ? $prefix : [$prefix];

        return 
$this;
    }

    
/**
     * Sets the arguments of the process.
     *
     * Arguments must not be escaped.
     * Previous arguments are removed.
     *
     * @param string[] $arguments
     *
     * @return $this
     */
    
public function setArguments(array $arguments)
    {
        
$this->arguments $arguments;

        return 
$this;
    }

    
/**
     * Sets the working directory.
     *
     * @param string|null $cwd The working directory
     *
     * @return $this
     */
    
public function setWorkingDirectory($cwd)
    {
        
$this->cwd $cwd;

        return 
$this;
    }

    
/**
     * Sets whether environment variables will be inherited or not.
     *
     * @param bool $inheritEnv
     *
     * @return $this
     */
    
public function inheritEnvironmentVariables($inheritEnv true)
    {
        
$this->inheritEnv $inheritEnv;

        return 
$this;
    }

    
/**
     * Sets an environment variable.
     *
     * Setting a variable overrides its previous value. Use `null` to unset a
     * defined environment variable.
     *
     * @param string      $name  The variable name
     * @param string|null $value The variable value
     *
     * @return $this
     */
    
public function setEnv($name$value)
    {
        
$this->env[$name] = $value;

        return 
$this;
    }

    
/**
     * Adds a set of environment variables.
     *
     * Already existing environment variables with the same name will be
     * overridden by the new values passed to this method. Pass `null` to unset
     * a variable.
     *
     * @param array $variables The variables
     *
     * @return $this
     */
    
public function addEnvironmentVariables(array $variables)
    {
        
$this->env array_replace($this->env$variables);

        return 
$this;
    }

    
/**
     * Sets the input of the process.
     *
     * @param resource|string|int|float|bool|\Traversable|null $input The input content
     *
     * @return $this
     *
     * @throws InvalidArgumentException In case the argument is invalid
     */
    
public function setInput($input)
    {
        
$this->input ProcessUtils::validateInput(__METHOD__$input);

        return 
$this;
    }

    
/**
     * Sets the process timeout.
     *
     * To disable the timeout, set this value to null.
     *
     * @param float|null $timeout
     *
     * @return $this
     *
     * @throws InvalidArgumentException
     */
    
public function setTimeout($timeout)
    {
        if (
null === $timeout) {
            
$this->timeout null;

            return 
$this;
        }

        
$timeout = (float) $timeout;

        if (
$timeout 0) {
            throw new 
InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
        }

        
$this->timeout $timeout;

        return 
$this;
    }

    
/**
     * Adds a proc_open option.
     *
     * @param string $name  The option name
     * @param string $value The option value
     *
     * @return $this
     */
    
public function setOption($name$value)
    {
        
$this->options[$name] = $value;

        return 
$this;
    }

    
/**
     * Disables fetching output and error output from the underlying process.
     *
     * @return $this
     */
    
public function disableOutput()
    {
        
$this->outputDisabled true;

        return 
$this;
    }

    
/**
     * Enables fetching output and error output from the underlying process.
     *
     * @return $this
     */
    
public function enableOutput()
    {
        
$this->outputDisabled false;

        return 
$this;
    }

    
/**
     * Creates a Process instance and returns it.
     *
     * @return Process
     *
     * @throws LogicException In case no arguments have been provided
     */
    
public function getProcess()
    {
        if (
=== \count($this->prefix) && === \count($this->arguments)) {
            throw new 
LogicException('You must add() command arguments before calling getProcess().');
        }

        
$arguments array_merge($this->prefix$this->arguments);
        
$process = new Process($arguments$this->cwd$this->env$this->input$this->timeout$this->options);
        
// to preserve the BC with symfony <3.3, we convert the array structure
        // to a string structure to avoid the prefixing with the exec command
        
$process->setCommandLine($process->getCommandLine());

        if (
$this->inheritEnv) {
            
$process->inheritEnvironmentVariables();
        }
        if (
$this->outputDisabled) {
            
$process->disableOutput();
        }

        return 
$process;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 * @author Johannes M. Schmitt <[email protected]>
 */
class PhpExecutableFinder
{
    private 
$executableFinder;

    public function 
__construct()
    {
        
$this->executableFinder = new ExecutableFinder();
    }

    
/**
     * Finds The PHP executable.
     *
     * @param bool $includeArgs Whether or not include command arguments
     *
     * @return string|false The PHP executable path or false if it cannot be found
     */
    
public function find($includeArgs true)
    {
        
$args $this->findArguments();
        
$args $includeArgs && $args ' '.implode(' '$args) : '';

        
// HHVM support
        
if (\defined('HHVM_VERSION')) {
            return (
getenv('PHP_BINARY') ?: \PHP_BINARY).$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)) {
                return 
false;
            }

            return 
$php;
        }

        if (
$php getenv('PHP_PEAR_PHP_BIN')) {
            if (@
is_executable($php)) {
                return 
$php;
            }
        }

        if (@
is_executable($php \PHP_BINDIR.('\\' === \DIRECTORY_SEPARATOR '\\php.exe' '/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.
     *
     * @return array The PHP executable arguments
     */
    
public function findArguments()
    {
        
$arguments = [];

        if (
\defined('HHVM_VERSION')) {
            
$arguments[] = '--php';
        } elseif (
'phpdbg' === \PHP_SAPI) {
            
$arguments[] = '-qrr';
        }

        return 
$arguments;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class LogicException extends \LogicException implements ExceptionInterface
{
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class ProcessFailedException extends RuntimeException
{
    private 
$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;
    }

    public function 
getProcess()
    {
        return 
$this->process;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
interface ExceptionInterface
{
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class ProcessTimedOutException extends RuntimeException
{
    const 
TYPE_GENERAL 1;
    const 
TYPE_IDLE 2;

    private 
$process;
    private 
$timeoutType;

    public function 
__construct(Process $process$timeoutType)
    {
        
$this->process $process;
        
$this->timeoutType $timeoutType;

        
parent::__construct(sprintf(
            
'The process "%s" exceeded the timeout of %s seconds.',
            
$process->getCommandLine(),
            
$this->getExceededTimeout()
        ));
    }

    public function 
getProcess()
    {
        return 
$this->process;
    }

    public function 
isGeneralTimeout()
    {
        return 
self::TYPE_GENERAL === $this->timeoutType;
    }

    public function 
isIdleTimeout()
    {
        return 
self::TYPE_IDLE === $this->timeoutType;
    }

    public function 
getExceededTimeout()
    {
        switch (
$this->timeoutType) {
            case 
self::TYPE_GENERAL:
                return 
$this->process->getTimeout();

            case 
self::TYPE_IDLE:
                return 
$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 <[email protected]>
 *
 * 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 <[email protected]>
 */
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 *
 * @internal
 */
interface PipesInterface
{
    const 
CHUNK_SIZE 16384;

    
/**
     * Returns an array of descriptors for the use of proc_open.
     *
     * @return array
     */
    
public function getDescriptors();

    
/**
     * Returns an array of filenames indexed by their related stream in case these pipes use temporary files.
     *
     * @return string[]
     */
    
public function getFiles();

    
/**
     * 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($blocking$close false);

    
/**
     * Returns if the current state has open file handles or pipes.
     *
     * @return bool
     */
    
public function areOpen();

    
/**
     * Returns if pipes are able to read output.
     *
     * @return bool
     */
    
public function haveReadSupport();

    
/**
     * Closes file handles and pipes.
     */
    
public function close();
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 *
 * @internal
 */
class UnixPipes extends AbstractPipes
{
    private 
$ttyMode;
    private 
$ptyMode;
    private 
$haveReadSupport;

    public function 
__construct($ttyMode$ptyMode$input$haveReadSupport)
    {
        
$this->ttyMode = (bool) $ttyMode;
        
$this->ptyMode = (bool) $ptyMode;
        
$this->haveReadSupport = (bool) $haveReadSupport;

        
parent::__construct($input);
    }

    public function 
__destruct()
    {
        
$this->close();
    }

    
/**
     * {@inheritdoc}
     */
    
public function getDescriptors()
    {
        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'],
                [
'pty'],
            ];
        }

        return [
            [
'pipe''r'],
            [
'pipe''w'], // stdout
            
['pipe''w'], // stderr
        
];
    }

    
/**
     * {@inheritdoc}
     */
    
public function getFiles()
    {
        return [];
    }

    
/**
     * {@inheritdoc}
     */
    
public function readAndWrite($blocking$close false)
    {
        
$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$e0$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->pipestrue)] = '';

            do {
                
$data = @fread($pipeself::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;
    }

    
/**
     * {@inheritdoc}
     */
    
public function haveReadSupport()
    {
        return 
$this->haveReadSupport;
    }

    
/**
     * {@inheritdoc}
     */
    
public function areOpen()
    {
        return (bool) 
$this->pipes;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 *
 * @internal
 */
class WindowsPipes extends AbstractPipes
{
    private 
$files = [];
    private 
$fileHandles = [];
    private 
$lockHandles = [];
    private 
$readBytes = [
        
Process::STDOUT => 0,
        
Process::STDERR => 0,
    ];
    private 
$haveReadSupport;

    public function 
__construct($input$haveReadSupport)
    {
        
$this->haveReadSupport = (bool) $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 (!
fclose(fopen($file'w')) || !$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 
__destruct()
    {
        
$this->close();
    }

    
/**
     * {@inheritdoc}
     */
    
public function getDescriptors()
    {
        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'],
        ];
    }

    
/**
     * {@inheritdoc}
     */
    
public function getFiles()
    {
        return 
$this->files;
    }

    
/**
     * {@inheritdoc}
     */
    
public function readAndWrite($blocking$close false)
    {
        
$this->unblock();
        
$w $this->write();
        
$read $r $e = [];

        if (
$blocking) {
            if (
$w) {
                @
stream_select($r$w$e0Process::TIMEOUT_PRECISION 1E6);
            } elseif (
$this->fileHandles) {
                
usleep(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($fileHandle0);
                
fclose($fileHandle);
                
flock($this->lockHandles[$type], \LOCK_UN);
                
fclose($this->lockHandles[$type]);
                unset(
$this->fileHandles[$type], $this->lockHandles[$type]);
            }
        }

        return 
$read;
    }

    
/**
     * {@inheritdoc}
     */
    
public function haveReadSupport()
    {
        return 
$this->haveReadSupport;
    }

    
/**
     * {@inheritdoc}
     */
    
public function areOpen()
    {
        return 
$this->pipes && $this->fileHandles;
    }

    
/**
     * {@inheritdoc}
     */
    
public function close()
    {
        
parent::close();
        foreach (
$this->fileHandles as $type => $handle) {
            
ftruncate($handle0);
            
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 <[email protected]>
 *
 * 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 <[email protected]>
 *
 * @internal
 */
abstract class AbstractPipes implements PipesInterface
{
    public 
$pipes = [];

    private 
$inputBuffer '';
    private 
$input;
    private 
$blocked true;
    private 
$lastError;

    
/**
     * @param resource|string|int|float|bool|\Iterator|null $input
     */
    
public function __construct($input)
    {
        if (
\is_resource($input) || $input instanceof \Iterator) {
            
$this->input $input;
        } elseif (
\is_string($input)) {
            
$this->inputBuffer $input;
        } else {
            
$this->inputBuffer = (string) $input;
        }
    }

    
/**
     * {@inheritdoc}
     */
    
public function close()
    {
        foreach (
$this->pipes as $pipe) {
            
fclose($pipe);
        }
        
$this->pipes = [];
    }

    
/**
     * Returns true if a system call has been interrupted.
     *
     * @return bool
     */
    
protected function hasSystemCallBeenInterrupted()
    {
        
$lastError $this->lastError;
        
$this->lastError null;

        
// stream_select returns false when the `select` system call is interrupted by an incoming signal
        
return null !== $lastError && false !== stripos($lastError'interrupted system call');
    }

    
/**
     * Unblocks streams.
     */
    
protected function unblock()
    {
        if (!
$this->blocked) {
            return;
        }

        foreach (
$this->pipes as $pipe) {
            
stream_set_blocking($pipe0);
        }
        if (
\is_resource($this->input)) {
            
stream_set_blocking($this->input0);
        }

        
$this->blocked false;
    }

    
/**
     * Writes input to stdin.
     *
     * @return array|null
     *
     * @throws InvalidArgumentException When an input iterator yields a non supported value
     */
    
protected function write()
    {
        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($input0);
            } 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_class($this->input), \gettype($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$e00)) {
            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) {
                for (;;) {
                    
$data fread($inputself::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($type$msg)
    {
        
$this->lastError $msg;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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 <[email protected]>
 */
class InputStream implements \IteratorAggregate
{
    
/** @var callable|null */
    
private $onEmpty null;
    private 
$input = [];
    private 
$open true;

    
/**
     * Sets a callback that is called when the write buffer becomes empty.
     */
    
public function onEmpty(callable $onEmpty null)
    {
        
$this->onEmpty $onEmpty;
    }

    
/**
     * 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
     */
    
public function write($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.
     */
    
public function close()
    {
        
$this->open false;
    }

    
/**
     * Tells whether the write buffer is closed or not.
     */
    
public function isClosed()
    {
        return !
$this->open;
    }

    public function 
getIterator()
    {
        
$this->open true;

        while (
$this->open || $this->input) {
            if (!
$this->input) {
                yield 
'';
                continue;
            }
            
$current array_shift($this->input);

            if (
$current instanceof \Iterator) {
                foreach (
$current as $cur) {
                    yield 
$cur;
                }
            } 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 <[email protected]>
 *
 * 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 <[email protected]>
 * @author Johannes M. Schmitt <[email protected]>
 */
class ExecutableFinder
{
    private 
$suffixes = ['.exe''.bat''.cmd''.com'];

    
/**
     * Replaces default suffixes of executable.
     */
    
public function setSuffixes(array $suffixes)
    {
        
$this->suffixes $suffixes;
    }

    
/**
     * Adds new possible suffix to check for executable.
     *
     * @param string $suffix
     */
    
public function addSuffix($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
     *
     * @return string|null The executable path or default value
     */
    
public function find($name$default null, array $extraDirs = [])
    {
        if (
ini_get('open_basedir')) {
            
$searchPath array_merge(explode(\PATH_SEPARATORini_get('open_basedir')), $extraDirs);
            
$dirs = [];
            foreach (
$searchPath as $path) {
                
// Silencing against https://bugs.php.net/69240
                
if (@is_dir($path)) {
                    
$dirs[] = $path;
                } else {
                    if (
basename($path) == $name && @is_executable($path)) {
                        return 
$path;
                    }
                }
            }
        } else {
            
$dirs array_merge(
                
explode(\PATH_SEPARATORgetenv('PATH') ?: getenv('Path')),
                
$extraDirs
            
);
        }

        
$suffixes = [''];
        if (
'\\' === \DIRECTORY_SEPARATOR) {
            
$pathExt getenv('PATHEXT');
            
$suffixes array_merge($pathExt explode(\PATH_SEPARATOR$pathExt) : $this->suffixes$suffixes);
        }
        foreach (
$suffixes as $suffix) {
            foreach (
$dirs as $dir) {
                if (@
is_file($file $dir.\DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === \DIRECTORY_SEPARATOR || @is_executable($file))) {
                    return 
$file;
                }
            }
        }

        return 
$default;
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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ň <[email protected]>
 */
class ProcessUtils
{
    
/**
     * This class should not be instantiated.
     */
    
private function __construct()
    {
    }

    
/**
     * Escapes a string to be used as a shell argument.
     *
     * @param string $argument The argument that will be escaped
     *
     * @return string The escaped argument
     *
     * @deprecated since version 3.3, to be removed in 4.0. Use a command line array or give env vars to the `Process::start/run()` method instead.
     */
    
public static function escapeArgument($argument)
    {
        @
trigger_error('The '.__METHOD__.'() method is deprecated since Symfony 3.3 and will be removed in 4.0. Use a command line array or give env vars to the Process::start/run() method instead.'\E_USER_DEPRECATED);

        
//Fix for PHP bug #43784 escapeshellarg removes % from given string
        //Fix for PHP bug #49446 escapeshellarg doesn't work on Windows
        //@see https://bugs.php.net/43784
        //@see https://bugs.php.net/49446
        
if ('\\' === \DIRECTORY_SEPARATOR) {
            if (
'' === $argument) {
                return 
escapeshellarg($argument);
            }

            
$escapedArgument '';
            
$quote false;
            foreach (
preg_split('/(")/'$argument, -1\PREG_SPLIT_NO_EMPTY \PREG_SPLIT_DELIM_CAPTURE) as $part) {
                if (
'"' === $part) {
                    
$escapedArgument .= '\\"';
                } elseif (
self::isSurroundedBy($part'%')) {
                    
// Avoid environment variable expansion
                    
$escapedArgument .= '^%"'.substr($part1, -1).'"^%';
                } else {
                    
// escape trailing backslash
                    
if ('\\' === substr($part, -1)) {
                        
$part .= '\\';
                    }
                    
$quote true;
                    
$escapedArgument .= $part;
                }
            }
            if (
$quote) {
                
$escapedArgument '"'.$escapedArgument.'"';
            }

            return 
$escapedArgument;
        }

        return 
"'".str_replace("'""'\\''"$argument)."'";
    }

    
/**
     * 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
     *
     * @return mixed The validated input
     *
     * @throws InvalidArgumentException In case the input is not valid
     */
    
public static function validateInput($caller$input)
    {
        if (
null !== $input) {
            if (
\is_resource($input)) {
                return 
$input;
            }
            if (
\is_string($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;
    }

    private static function 
isSurroundedBy($arg$char)
    {
        return 
\strlen($arg) && $char === $arg[0] && $char === $arg[\strlen($arg) - 1];
    }
}
<?
php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <[email protected]>
 *
 * 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\ProcessTimedOutException;
use 
Symfony\Component\Process\Exception\RuntimeException;
use 
Symfony\Component\Process\Pipes\PipesInterface;
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 <[email protected]>
 * @author Romain Neutron <[email protected]>
 */
class Process implements \IteratorAggregate
{
    const 
ERR 'err';
    const 
OUT 'out';

    const 
STATUS_READY 'ready';
    const 
STATUS_STARTED 'started';
    const 
STATUS_TERMINATED 'terminated';

    const 
STDIN 0;
    const 
STDOUT 1;
    const 
STDERR 2;

    
// Timeout Precision in seconds.
    
const TIMEOUT_PRECISION 0.2;

    const 
ITER_NON_BLOCKING 1// By default, iterating over outputs is a blocking call, use this flag to make it non-blocking
    
const ITER_KEEP_OUTPUT 2;  // By default, outputs are cleared while iterating, use this flag to keep them in memory
    
const ITER_SKIP_OUT 4;     // Use this flag to skip STDOUT while iterating
    
const ITER_SKIP_ERR 8;     // Use this flag to skip STDERR while iterating

    
private $callback;
    private 
$hasCallback false;
    private 
$commandline;
    private 
$cwd;
    private 
$env;
    private 
$input;
    private 
$starttime;
    private 
$lastOutputTime;
    private 
$timeout;
    private 
$idleTimeout;
    private 
$options = ['suppress_errors' => true];
    private 
$exitcode;
    private 
$fallbackStatus = [];
    private 
$processInformation;
    private 
$outputDisabled false;
    private 
$stdout;
    private 
$stderr;
    private 
$enhanceWindowsCompatibility true;
    private 
$enhanceSigchildCompatibility;
    private 
$process;
    private 
$status self::STATUS_READY;
    private 
$incrementalOutputOffset 0;
    private 
$incrementalErrorOutputOffset 0;
    private 
$tty false;
    private 
$pty;
    private 
$inheritEnv false;

    private 
$useFileHandles false;
    
/** @var PipesInterface */
    
private $processPipes;

    private 
$latestSignal;

    private static 
$sigchild;

    
/**
     * Exit codes translation table.
     *
     * User-defined errors must use exit codes in the 64-113 range.
     */
    
public static $exitCodes = [
        
=> 'OK',
        
=> 'General error',
        
=> '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 string|array   $commandline The command line to run
     * @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|null     $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
     * @param array          $options     An array of options for proc_open
     *
     * @throws RuntimeException When proc_open is not installed
     */
    
public function __construct($commandline$cwd null, array $env null$input null$timeout 60, array $options null)
    {
        if (!
\function_exists('proc_open')) {
            throw new 
RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.');
        }

        
$this->commandline $commandline;
        
$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->useFileHandles '\\' === \DIRECTORY_SEPARATOR;
        
$this->pty false;
        
$this->enhanceSigchildCompatibility '\\' !== \DIRECTORY_SEPARATOR && $this->isSigchildEnabled();
        if (
null !== $options) {
            @
trigger_error(sprintf('The $options parameter of the %s constructor is deprecated since Symfony 3.3 and will be removed in 4.0.'__CLASS__), \E_USER_DEPRECATED);
            
$this->options array_replace($this->options$options);
        }
    }

    public function 
__destruct()
    {
        
$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 stopped after receiving signal
     * @throws LogicException   In case a callback is provided and output has been disabled
     *
     * @final since version 3.3
     */
    
public function run($callback null/*, array $env = []*/)
    {
        
$env \func_num_args() ? func_get_arg(1) : null;
        
$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 RuntimeException       if PHP was compiled with --enable-sigchild and the enhanced sigchild compatibility mode is not enabled
     * @throws ProcessFailedException if the process didn't terminate successfully
     *
     * @final since version 3.3
     */
    
public function mustRun(callable $callback null/*, array $env = []*/)
    {
        if (!
$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
            throw new 
RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.');
        }
        
$env \func_num_args() ? func_get_arg(1) : null;

        if (
!== $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
     *
     * @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.');
        }
        if (
<= \func_num_args()) {
            
$env func_get_arg(1);
        } else {
            if (
__CLASS__ !== static::class) {
                
$r = new \ReflectionMethod($this__FUNCTION__);
                if (
__CLASS__ !== $r->getDeclaringClass()->getName() && ($r->getNumberOfParameters() || 'env' !== $r->getParameters()[1]->name)) {
                    @
trigger_error(sprintf('The %s::start() method expects a second "$env" argument since Symfony 3.3. It will be made mandatory in 4.0.', static::class), \E_USER_DEPRECATED);
                }
            }
            
$env null;
        }

        
$this->resetProcessData();
        
$this->starttime $this->lastOutputTime microtime(true);
        
$this->callback $this->buildCallback($callback);
        
$this->hasCallback null !== $callback;
        
$descriptors $this->getDescriptors();
        
$inheritEnv $this->inheritEnv;

        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;
            }
        }

        if (
null === $env) {
            
$env $this->env;
        } else {
            if (
$this->env) {
                
$env += $this->env;
            }
            
$inheritEnv true;
        }

        if (
null !== $env && $inheritEnv) {
            
$env += $this->getDefaultEnv();
        } elseif (
null !== $env) {
            @
trigger_error('Not inheriting environment variables is deprecated since Symfony 3.3 and will always happen in 4.0. Set "Process::inheritEnvironmentVariables()" to true instead.'\E_USER_DEPRECATED);
        } else {
            
$env $this->getDefaultEnv();
        }
        if (
'\\' === \DIRECTORY_SEPARATOR && $this->enhanceWindowsCompatibility) {
            
$this->options['bypass_shell'] = true;
            
$commandline $this->prepareWindowsCommandLine($commandline$env);
        } elseif (!
$this->useFileHandles && $this->enhanceSigchildCompatibility && $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; code=$?; echo $code >&3; exit $code';

            
// Workaround for the bug, when PTS functionality is enabled.
            // @see : https://bugs.php.net/69442
            
$ptsWorkaround fopen(__FILE__'r');
        }
        if (
\defined('HHVM_VERSION')) {
            
$envPairs $env;
        } else {
            
$envPairs = [];
            foreach (
$env as $k => $v) {
                if (
false !== $v) {
                    
$envPairs[] = $k.'='.$v;
                }
            }
        }

        if (!
is_dir($this->cwd)) {
            @
trigger_error('The provided cwd does not exist. Command is currently ran against getcwd(). This behavior is deprecated since Symfony 3.4 and will be removed in 4.0.'\E_USER_DEPRECATED);
        }

        
$this->process = @proc_open($commandline$descriptors$this->processPipes->pipes$this->cwd$envPairs$this->options);

        if (!
\is_resource($this->process)) {
            throw new 
RuntimeException('Unable to launch a new 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
     *
     * @return static
     *
     * @throws RuntimeException When process can't be launched
     * @throws RuntimeException When process is already running
     *
     * @see start()
     *
     * @final since version 3.3
     */
    
public function restart(callable $callback null/*, array $env = []*/)
    {
        if (
$this->isRunning()) {
            throw new 
RuntimeException('Process is already running.');
        }
        
$env \func_num_args() ? func_get_arg(1) : null;

        
$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 RuntimeException When process timed out
     * @throws RuntimeException When process stopped after receiving signal
     * @throws LogicException   When process is not yet started
     */
    
public function wait(callable $callback null)
    {
        
$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 enableOutput to use a callback with Process::wait.');
            }
            
$this->callback $this->buildCallback($callback);
        }

        do {
            
$this->checkTimeout();
            
$running '\\' === \DIRECTORY_SEPARATOR $this->isRunning() : $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 
RuntimeException(sprintf('The process has been signaled with signal "%s".'$this->processInformation['termsig']));
        }

        return 
$this->exitcode;
    }

    
/**
     * Returns the Pid (process identifier), if applicable.
     *
     * @return int|null The process id if running, null otherwise
     */
    
public function getPid()
    {
        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($signal)
    {
        
$this->doSignal($signaltrue);

        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()
    {
        if (
$this->isRunning()) {
            throw new 
RuntimeException('Disabling output while the process is running is not possible.');
        }
        if (
null !== $this->idleTimeout) {
            throw new 
LogicException('Output can not 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()
    {
        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.
     *
     * @return bool
     */
    
public function isOutputDisabled()
    {
        return 
$this->outputDisabled;
    }

    
/**
     * Returns the current output of the process (STDOUT).
     *
     * @return string The process output
     *
     * @throws LogicException in case the output has been disabled
     * @throws LogicException In case the process is not started
     */
    
public function getOutput()
    {
        
$this->readPipesForOutput(__FUNCTION__);

        if (
false === $ret stream_get_contents($this->stdout, -10)) {
            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.
     *
     * @return string The process 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()
    {
        
$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
     *
     * @throws LogicException in case the output has been disabled
     * @throws LogicException In case the process is not started
     *
     * @return \Generator
     */
    
public function getIterator($flags 0)
    {
        
$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()
    {
        
ftruncate($this->stdout0);
        
fseek($this->stdout0);
        
$this->incrementalOutputOffset 0;

        return 
$this;
    }

    
/**
     * Returns the current error output of the process (STDERR).
     *
     * @return string The process error output
     *
     * @throws LogicException in case the output has been disabled
     * @throws LogicException In case the process is not started
     */
    
public function getErrorOutput()
    {
        
$this->readPipesForOutput(__FUNCTION__);

        if (
false === $ret stream_get_contents($this->stderr, -10)) {
            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.
     *
     * @return string The process 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()
    {
        
$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()
    {
        
ftruncate($this->stderr0);
        
fseek($this->stderr0);
        
$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
     *
     * @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled
     */
    
public function getExitCode()
    {
        if (!
$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
            throw new 
RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.');
        }

        
$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()
    {
        if (
null === $exitcode $this->getExitCode()) {
            return 
null;
        }

        return isset(
self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error';
    }

    
/**
     * Checks if the process ended successfully.
     *
     * @return bool true if the process ended successfully, false otherwise
     */
    
public function isSuccessful()
    {
        return 
=== $this->getExitCode();
    }

    
/**
     * Returns true if the child process has been terminated by an uncaught signal.
     *
     * It always returns false on Windows.
     *
     * @return bool
     *
     * @throws RuntimeException In case --enable-sigchild is activated
     * @throws LogicException   In case the process is not terminated
     */
    
public function hasBeenSignaled()
    {
        
$this->requireProcessIsTerminated(__FUNCTION__);

        if (!
$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
            throw new 
RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
        }

        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.
     *
     * @return int
     *
     * @throws RuntimeException In case --enable-sigchild is activated
     * @throws LogicException   In case the process is not terminated
     */
    
public function getTermSignal()
    {
        
$this->requireProcessIsTerminated(__FUNCTION__);

        if (
$this->isSigchildEnabled() && (!$this->enhanceSigchildCompatibility || -=== $this->processInformation['termsig'])) {
            throw new 
RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
        }

        return 
$this->processInformation['termsig'];
    }

    
/**
     * Returns true if the child process has been stopped by a signal.
     *
     * It always returns false on Windows.
     *
     * @return bool
     *
     * @throws LogicException In case the process is not terminated
     */
    
public function hasBeenStopped()
    {
        
$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.
     *
     * @return int
     *
     * @throws LogicException In case the process is not terminated
     */
    
public function getStopSignal()
    {
        
$this->requireProcessIsTerminated(__FUNCTION__);

        return 
$this->processInformation['stopsig'];
    }

    
/**
     * Checks if the process is currently running.
     *
     * @return bool true if the process is currently running, false otherwise
     */
    
public function isRunning()
    {
        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.
     *
     * @return bool true if status is ready, false otherwise
     */
    
public function isStarted()
    {
        return 
self::STATUS_READY != $this->status;
    }

    
/**
     * Checks if the process is terminated.
     *
     * @return bool true if process is terminated, false otherwise
     */
    
public function isTerminated()
    {
        
$this->updateStatus(false);

        return 
self::STATUS_TERMINATED == $this->status;
    }

    
/**
     * Gets the process status.
     *
     * The status is one of: ready, started, terminated.
     *
     * @return string The current process status
     */
    
public function getStatus()
    {
        
$this->updateStatus(false);

        return 
$this->status;
    }

    
/**
     * Stops the process.
     *
     * @param int|float $timeout The timeout in seconds
     * @param int       $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($timeout 10$signal null)
    {
        
$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(15false);
            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 ?: 9false);
            }
        }

        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
     *
     * @param string $line The line to append
     */
    
public function addOutput($line)
    {
        
$this->lastOutputTime microtime(true);

        
fseek($this->stdout0\SEEK_END);
        
fwrite($this->stdout$line);
        
fseek($this->stdout$this->incrementalOutputOffset);
    }

    
/**
     * Adds a line to the STDERR stream.
     *
     * @internal
     *
     * @param string $line The line to append
     */
    
public function addErrorOutput($line)
    {
        
$this->lastOutputTime microtime(true);

        
fseek($this->stderr0\SEEK_END);
        
fwrite($this->stderr$line);
        
fseek($this->stderr$this->incrementalErrorOutputOffset);
    }

    
/**
     * Gets the command line to be executed.
     *
     * @return string The command to execute
     */
    
public function getCommandLine()
    {
        return 
\is_array($this->commandline) ? implode(' 'array_map([$this'escapeArgument'], $this->commandline)) : $this->commandline;
    }

    
/**
     * Sets the command line to be executed.
     *
     * @param string|array $commandline The command to execute
     *
     * @return $this
     */
    
public function setCommandLine($commandline)
    {
        
$this->commandline $commandline;

        return 
$this;
    }

    
/**
     * Gets the process timeout (max. runtime).
     *
     * @return float|null The timeout in seconds or null if it's disabled
     */
    
public function getTimeout()
    {
        return 
$this->timeout;
    }

    
/**
     * Gets the process idle timeout (max. time since last output).
     *
     * @return float|null The timeout in seconds or null if it's disabled
     */
    
public function getIdleTimeout()
    {
        return 
$this->idleTimeout;
    }

    
/**
     * Sets the process timeout (max. runtime) in seconds.
     *
     * To disable the timeout, set this value to null.
     *
     * @param int|float|null $timeout The timeout in seconds
     *
     * @return $this
     *
     * @throws InvalidArgumentException if the timeout is negative
     */
    
public function setTimeout($timeout)
    {
        
$this->timeout $this->validateTimeout($timeout);

        return 
$this;
    }

    
/**
     * Sets the process idle timeout (max. time since last output).
     *
     * To disable the timeout, set this value to null.
     *
     * @param int|float|null $timeout The timeout in seconds
     *
     * @return $this
     *
     * @throws LogicException           if the output is disabled
     * @throws InvalidArgumentException if the timeout is negative
     */
    
public function setIdleTimeout($timeout)
    {
        if (
null !== $timeout && $this->outputDisabled) {
            throw new 
LogicException('Idle timeout can not be set while the output is disabled.');
        }

        
$this->idleTimeout $this->validateTimeout($timeout);

        return 
$this;
    }

    
/**
     * Enables or disables the TTY mode.
     *
     * @param bool $tty True to enabled and false to disable
     *
     * @return $this
     *
     * @throws RuntimeException In case the TTY mode is not supported
     */
    
public function setTty($tty)
    {
        if (
'\\' === \DIRECTORY_SEPARATOR && $tty) {
            throw new 
RuntimeException('TTY mode is not supported on Windows platform.');
        }
        if (
$tty) {
            static 
$isTtySupported;

            if (
null === $isTtySupported) {
                
$isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file''/dev/tty''r'], ['file''/dev/tty''w'], ['file''/dev/tty''w']], $pipes);
            }

            if (!
$isTtySupported) {
                throw new 
RuntimeException('TTY mode requires /dev/tty to be read/writable.');
            }
        }

        
$this->tty = (bool) $tty;

        return 
$this;
    }

    
/**
     * Checks if the TTY mode is enabled.
     *
     * @return bool true if the TTY mode is enabled, false otherwise
     */
    
public function isTty()
    {
        return 
$this->tty;
    }

    
/**
     * Sets PTY mode.
     *
     * @param bool $bool
     *
     * @return $this
     */
    
public function setPty($bool)
    {
        
$this->pty = (bool) $bool;

        return 
$this;
    }

    
/**
     * Returns PTY state.
     *
     * @return bool
     */
    
public function isPty()
    {
        return 
$this->pty;
    }

    
/**
     * Gets the working directory.
     *
     * @return string|null The current working directory or null on failure
     */
    
public function getWorkingDirectory()
    {
        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.
     *
     * @param string $cwd The new working directory
     *
     * @return $this
     */
    
public function setWorkingDirectory($cwd)
    {
        
$this->cwd $cwd;

        return 
$this;
    }

    
/**
     * Gets the environment variables.
     *
     * @return array The current environment variables
     */
    
public function getEnv()
    {
        return 
$this->env;
    }

    
/**
     * Sets the environment variables.
     *
     * Each environment variable value should be a string.
     * If it is an array, the variable is ignored.
     * If it is false or null, it will be removed when
     * env vars are otherwise inherited.
     *
     * That happens in PHP when 'argv' is registered into
     * the $_ENV array for instance.
     *
     * @param array $env The new environment variables
     *
     * @return $this
     */
    
public function setEnv(array $env)
    {
        
// Process can not handle env values that are arrays
        
$env array_filter($env, function ($value) {
            return !
\is_array($value);
        });

        
$this->env $env;

        return 
$this;
    }

    
/**
     * Gets the Process input.
     *
     * @return resource|string|\Iterator|null The Process input
     */
    
public function getInput()
    {
        return 
$this->input;
    }

    
/**
     * Sets the input.
     *
     * This content will be passed to the underlying process standard input.
     *
     * @param string|int|float|bool|resource|\Traversable|null $input The content
     *
     * @return $this
     *
     * @throws LogicException In case the process is running
     */
    
public function setInput($input)
    {
        if (
$this->isRunning()) {
            throw new 
LogicException('Input can not be set while the process is running.');
        }

        
$this->input ProcessUtils::validateInput(__METHOD__$input);

        return 
$this;
    }

    
/**
     * Gets the options for proc_open.
     *
     * @return array The current options
     *
     * @deprecated since version 3.3, to be removed in 4.0.
     */
    
public function getOptions()
    {
        @
trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0.'__METHOD__), \E_USER_DEPRECATED);

        return 
$this->options;
    }

    
/**
     * Sets the options for proc_open.
     *
     * @param array $options The new options
     *
     * @return $this
     *
     * @deprecated since version 3.3, to be removed in 4.0.
     */
    
public function setOptions(array $options)
    {
        @
trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0.'__METHOD__), \E_USER_DEPRECATED);

        
$this->options $options;

        return 
$this;
    }

    
/**
     * Gets whether or not Windows compatibility is enabled.
     *
     * This is true by default.
     *
     * @return bool
     *
     * @deprecated since version 3.3, to be removed in 4.0. Enhanced Windows compatibility will always be enabled.
     */
    
public function getEnhanceWindowsCompatibility()
    {
        @
trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Enhanced Windows compatibility will always be enabled.'__METHOD__), \E_USER_DEPRECATED);

        return 
$this->enhanceWindowsCompatibility;
    }

    
/**
     * Sets whether or not Windows compatibility is enabled.
     *
     * @param bool $enhance
     *
     * @return $this
     *
     * @deprecated since version 3.3, to be removed in 4.0. Enhanced Windows compatibility will always be enabled.
     */
    
public function setEnhanceWindowsCompatibility($enhance)
    {
        @
trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Enhanced Windows compatibility will always be enabled.'__METHOD__), \E_USER_DEPRECATED);

        
$this->enhanceWindowsCompatibility = (bool) $enhance;

        return 
$this;
    }

    
/**
     * Returns whether sigchild compatibility mode is activated or not.
     *
     * @return bool
     *
     * @deprecated since version 3.3, to be removed in 4.0. Sigchild compatibility will always be enabled.
     */
    
public function getEnhanceSigchildCompatibility()
    {
        @
trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Sigchild compatibility will always be enabled.'__METHOD__), \E_USER_DEPRECATED);

        return 
$this->enhanceSigchildCompatibility;
    }

    
/**
     * Activates sigchild compatibility mode.
     *
     * Sigchild compatibility mode is required to get the exit code and
     * determine the success of a process when PHP has been compiled with
     * the --enable-sigchild option
     *
     * @param bool $enhance
     *
     * @return $this
     *
     * @deprecated since version 3.3, to be removed in 4.0.
     */
    
public function setEnhanceSigchildCompatibility($enhance)
    {
        @
trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Sigchild compatibility will always be enabled.'__METHOD__), \E_USER_DEPRECATED);

        
$this->enhanceSigchildCompatibility = (bool) $enhance;

        return 
$this;
    }

    
/**
     * Sets whether environment variables will be inherited or not.
     *
     * @param bool $inheritEnv
     *
     * @return $this
     */
    
public function inheritEnvironmentVariables($inheritEnv true)
    {
        if (!
$inheritEnv) {
            @
trigger_error('Not inheriting environment variables is deprecated since Symfony 3.3 and will always happen in 4.0. Set "Process::inheritEnvironmentVariables()" to true instead.'\E_USER_DEPRECATED);
        }

        
$this->inheritEnv = (bool) $inheritEnv;

        return 
$this;
    }

    
/**
     * Returns whether environment variables will be inherited or not.
     *
     * @return bool
     *
     * @deprecated since version 3.3, to be removed in 4.0. Environment variables will always be inherited.
     */
    
public function areEnvironmentVariablesInherited()
    {
        @
trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Environment variables will always be inherited.'__METHOD__), \E_USER_DEPRECATED);

        return 
$this->inheritEnv;
    }

    
/**
     * 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
     *
     * @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($thisProcessTimedOutException::TYPE_GENERAL);
        }

        if (
null !== $this->idleTimeout && $this->idleTimeout microtime(true) - $this->lastOutputTime) {
            
$this->stop(0);

            throw new 
ProcessTimedOutException($thisProcessTimedOutException::TYPE_IDLE);
        }
    }

    
/**
     * Returns whether PTY is supported on the current operating system.
     *
     * @return bool
     */
    
public static function isPtySupported()
    {
        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.
     *
     * @return array
     */
    
private function getDescriptors()
    {
        if (
$this->input instanceof \Iterator) {
            
$this->input->rewind();
        }
        if (
'\\' === \DIRECTORY_SEPARATOR) {
            
$this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $this->hasCallback);
        } else {
            
$this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $this->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
     *
     * @return \Closure A PHP closure
     */
    
protected function buildCallback(callable $callback null)
    {
        if (
$this->outputDisabled) {
            return function (
$type$data) use ($callback) {
                if (
null !== $callback) {
                    
\call_user_func($callback$type$data);
                }
            };
        }

        
$out self::OUT;

        return function (
$type$data) use ($callback$out) {
            if (
$out == $type) {
                
$this->addOutput($data);
            } else {
                
$this->addErrorOutput($data);
            }

            if (
null !== $callback) {
                
\call_user_func($callback$type$data);
            }
        };
    }

    
/**
     * Updates the status of the process, reads pipes.
     *
     * @param bool $blocking Whether to use a blocking read call
     */
    
protected function updateStatus($blocking)
    {
        if (
self::STATUS_STARTED !== $this->status) {
            return;
        }

        
$this->processInformation proc_get_status($this->process);
        
$running $this->processInformation['running'];

        
$this->readPipes($running && $blocking'\\' !== \DIRECTORY_SEPARATOR || !$running);

        if (
$this->fallbackStatus && $this->enhanceSigchildCompatibility && $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.
     *
     * @return bool
     */
    
protected function isSigchildEnabled()
    {
        if (
null !== self::$sigchild) {
            return 
self::$sigchild;
        }

        if (!
\function_exists('phpinfo') || \defined('HHVM_VERSION')) {
            return 
self::$sigchild false;
        }

        
ob_start();
        
phpinfo(\INFO_GENERAL);

        return 
self::$sigchild false !== strpos(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($caller$blocking false)
    {
        if (
$this->outputDisabled) {
            throw new 
LogicException('Output has been disabled.');
        }

        
$this->requireProcessIsStarted($caller);

        
$this->updateStatus($blocking);
    }

    
/**
     * Validates and returns the filtered timeout.
     *
     * @param int|float|null $timeout
     *
     * @return float|null
     *
     * @throws InvalidArgumentException if the given timeout is a negative number
     */
    
private function validateTimeout($timeout)
    {
        
$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($blocking$close)
    {
        
$result $this->processPipes->readAndWrite($blocking$close);

        
$callback $this->callback;
        foreach (
$result as $type => $data) {
            if (
!== $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()
    {
        
$this->processPipes->close();
        if (
\is_resource($this->process)) {
            
proc_close($this->process);
        }
        
$this->exitcode $this->processInformation['exitcode'];
        
$this->status self::STATUS_TERMINATED;

        if (-
=== $this->exitcode) {
            if (
$this->processInformation['signaled'] && $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->enhanceSigchildCompatibility && $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()
    {
        
$this->starttime null;
        
$this->callback null;
        
$this->exitcode null;
        
$this->fallbackStatus = [];
        
$this->processInformation null;
        
$this->stdout fopen('php://temp/maxmemory:'.(1024 1024), 'w+b');
        
$this->stderr fopen('php://temp/maxmemory:'.(1024 1024), 'w+b');
        
$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
     *
     * @return bool True if the signal was sent successfully, false otherwise
     *
     * @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($signal$throwException)
    {
        if (
null === $pid $this->getPid()) {
            if (
$throwException) {
                throw new 
LogicException('Can not 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->enhanceSigchildCompatibility || !$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), [=> ['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 = (int) $signal;
        
$this->fallbackStatus['signaled'] = true;
        
$this->fallbackStatus['exitcode'] = -1;
        
$this->fallbackStatus['termsig'] = $this->latestSignal;

        return 
true;
    }

    private function 
prepareWindowsCommandLine($cmd, array &$env)
    {
        
$uid uniqid(''true);
        
$varCount 0;
        
$varCache = [];
        
$cmd preg_replace_callback(
            
'/"(?:(
                [^"%!^]*+
                (?:
                    (?: !LF! | "(?:\^[%!^])?+" )
                    [^"%!^]*+
                )++
            ) | [^"]*+ )"/x'
,
            function (
$m) use (&$env, &$varCache, &$varCount$uid) {
                if (!isset(
$m[1])) {
                    return 
$m[0];
                }
                if (isset(
$varCache[$m[0]])) {
                    return 
$varCache[$m[0]];
                }
                if (
false !== strpos($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
        
);

        
$cmd '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.
     *
     * @param string $functionName The function name that was called
     *
     * @throws LogicException if the process has not run
     */
    
private function requireProcessIsStarted($functionName)
    {
        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`.
     *
     * @param string $functionName The function name that was called
     *
     * @throws LogicException if the process is not yet terminated
     */
    
private function requireProcessIsTerminated($functionName)
    {
        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.
     *
     * @param string $argument The argument that will be escaped
     *
     * @return string The escaped argument
     */
    
private function escapeArgument($argument)
    {
        if (
'\\' !== \DIRECTORY_SEPARATOR) {
            return 
"'".str_replace("'""'\\''"$argument)."'";
        }
        if (
'' === $argument = (string) $argument) {
            return 
'""';
        }
        if (
false !== strpos($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 
getDefaultEnv()
    {
        
$env = [];

        foreach (
$_SERVER as $k => $v) {
            if (
\is_string($v) && false !== $v getenv($k)) {
                
$env[$k] = $v;
            }
        }

        foreach (
$_ENV as $k => $v) {
            if (
\is_string($v)) {
                
$env[$k] = $v;
            }
        }

        return 
$env;
    }
}
<?
php

namespace React\Promise;

/**
 * @deprecated 2.8.0 External usage of RejectedPromise is deprecated, use `reject()` instead.
 */
class RejectedPromise implements ExtendedPromiseInterfaceCancellablePromiseInterface
{
    private 
$reason;

    public function 
__construct($reason null)
    {
        if (
$reason instanceof PromiseInterface) {
            throw new 
\InvalidArgumentException('You cannot create React\Promise\RejectedPromise with a promise. Use React\Promise\reject($promiseOrValue) instead.');
        }

        
$this->reason $reason;
    }

    public function 
then(callable $onFulfilled null, callable $onRejected null, callable $onProgress null)
    {
        if (
null === $onRejected) {
            return 
$this;
        }

        try {
            return 
resolve($onRejected($this->reason));
        } catch (
\Throwable $exception) {
            return new 
RejectedPromise($exception);
        } catch (
\Exception $exception) {
            return new 
RejectedPromise($exception);
        }
    }

    public function 
done(callable $onFulfilled null, callable $onRejected null, callable $onProgress null)
    {
        if (
null === $onRejected) {
            throw 
UnhandledRejectionException::resolve($this->reason);
        }

        
$result $onRejected($this->reason);

        if (
$result instanceof self) {
            throw 
UnhandledRejectionException::resolve($result->reason);
        }

        if (
$result instanceof ExtendedPromiseInterface) {
            
$result->done();
        }
    }

    public function 
otherwise(callable $onRejected)
    {
        if (!
_checkTypehint($onRejected$this->reason)) {
            return 
$this;
        }

        return 
$this->then(null$onRejected);
    }

    public function 
always(callable $onFulfilledOrRejected)
    {
        return 
$this->then(null, function ($reason) use ($onFulfilledOrRejected) {
            return 
resolve($onFulfilledOrRejected())->then(function () use ($reason) {
                return new 
RejectedPromise($reason);
            });
        });
    }

    public function 
progress(callable $onProgress)
    {
        return 
$this;
    }

    public function 
cancel()
    {
    }
}
<?
php

namespace React\Promise;

class 
Deferred implements PromisorInterface
{
    private 
$promise;
    private 
$resolveCallback;
    private 
$rejectCallback;
    private 
$notifyCallback;
    private 
$canceller;

    public function 
__construct(callable $canceller null)
    {
        
$this->canceller $canceller;
    }

    public function 
promise()
    {
        if (
null === $this->promise) {
            
$this->promise = new Promise(function ($resolve$reject$notify) {
                
$this->resolveCallback $resolve;
                
$this->rejectCallback  $reject;
                
$this->notifyCallback  $notify;
            }, 
$this->canceller);
            
$this->canceller null;
        }

        return 
$this->promise;
    }

    public function 
resolve($value null)
    {
        
$this->promise();

        
\call_user_func($this->resolveCallback$value);
    }

    public function 
reject($reason null)
    {
        
$this->promise();

        
\call_user_func($this->rejectCallback$reason);
    }

    
/**
     * @deprecated 2.6.0 Progress support is deprecated and should not be used anymore.
     * @param mixed $update
     */
    
public function notify($update null)
    {
        
$this->promise();

        
\call_user_func($this->notifyCallback$update);
    }

    
/**
     * @deprecated 2.2.0
     * @see Deferred::notify()
     */
    
public function progress($update null)
    {
        
$this->notify($update);
    }
}
<?
php

namespace React\Promise;

/**
 * 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.
 *
 * @param mixed $promiseOrValue
 * @return PromiseInterface
 */
function resolve($promiseOrValue null)
{
    if (
$promiseOrValue instanceof ExtendedPromiseInterface) {
        return 
$promiseOrValue;
    }

    
// Check is_object() first to avoid method_exists() triggering
    // class autoloaders if $promiseOrValue is a string.
    
if (\is_object($promiseOrValue) && \method_exists($promiseOrValue'then')) {
        
$canceller null;

        if (
\method_exists($promiseOrValue'cancel')) {
            
$canceller = [$promiseOrValue'cancel'];
        }

        return new 
Promise(function ($resolve$reject$notify) use ($promiseOrValue) {
            
$promiseOrValue->then($resolve$reject$notify);
        }, 
$canceller);
    }

    return new 
FulfilledPromise($promiseOrValue);
}

/**
 * Creates a rejected promise for the supplied `$promiseOrValue`.
 *
 * If `$promiseOrValue` is a value, it will be the rejection value of the
 * returned promise.
 *
 * If `$promiseOrValue` 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.
 *
 * @param mixed $promiseOrValue
 * @return PromiseInterface
 */
function reject($promiseOrValue null)
{
    if (
$promiseOrValue instanceof PromiseInterface) {
        return 
resolve($promiseOrValue)->then(function ($value) {
            return new 
RejectedPromise($value);
        });
    }

    return new 
RejectedPromise($promiseOrValue);
}

/**
 * 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`.
 *
 * @param array $promisesOrValues
 * @return PromiseInterface
 */
function all($promisesOrValues)
{
    return 
map($promisesOrValues, function ($val) {
        return 
$val;
    });
}

/**
 * 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.
 *
 * @param array $promisesOrValues
 * @return PromiseInterface
 */
function race($promisesOrValues)
{
    
$cancellationQueue = new CancellationQueue();
    
$cancellationQueue->enqueue($promisesOrValues);

    return new 
Promise(function ($resolve$reject$notify) use ($promisesOrValues$cancellationQueue) {
        
resolve($promisesOrValues)
            ->
done(function ($array) use ($cancellationQueue$resolve$reject$notify) {
                if (!
is_array($array) || !$array) {
                    
$resolve();
                    return;
                }

                foreach (
$array as $promiseOrValue) {
                    
$cancellationQueue->enqueue($promiseOrValue);

                    
resolve($promiseOrValue)
                        ->
done($resolve$reject$notify);
                }
            }, 
$reject$notify);
    }, 
$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.
 *
 * @param array $promisesOrValues
 * @return PromiseInterface
 */
function any($promisesOrValues)
{
    return 
some($promisesOrValues1)
        ->
then(function ($val) {
            return 
\array_shift($val);
        });
}

/**
 * Returns a promise that will resolve when `$howMany` of the supplied items in
 * `$promisesOrValues` resolve. The resolution value of the returned promise
 * will be an array of length `$howMany` containing the resolution values of the
 * triggering items.
 *
 * The returned promise will reject if it becomes impossible for `$howMany` items
 * to resolve (that is, when `(count($promisesOrValues) - $howMany) + 1` items
 * reject). The rejection value will be an array of
 * `(count($promisesOrValues) - $howMany) + 1` rejection reasons.
 *
 * The returned promise will also reject with a `React\Promise\Exception\LengthException`
 * if `$promisesOrValues` contains less items than `$howMany`.
 *
 * @param array $promisesOrValues
 * @param int $howMany
 * @return PromiseInterface
 */
function some($promisesOrValues$howMany)
{
    
$cancellationQueue = new CancellationQueue();
    
$cancellationQueue->enqueue($promisesOrValues);

    return new 
Promise(function ($resolve$reject$notify) use ($promisesOrValues$howMany$cancellationQueue) {
        
resolve($promisesOrValues)
            ->
done(function ($array) use ($howMany$cancellationQueue$resolve$reject$notify) {
                if (!
\is_array($array) || $howMany 1) {
                    
$resolve([]);
                    return;
                }

                
$len \count($array);

                if (
$len $howMany) {
                    throw new 
Exception\LengthException(
                        
\sprintf(
                            
'Input array must contain at least %d item%s but contains only %s item%s.',
                            
$howMany,
                            
=== $howMany '' 's',
                            
$len,
                            
=== $len '' 's'
                        
)
                    );
                }

                
$toResolve $howMany;
                
$toReject  = ($len $toResolve) + 1;
                
$values    = [];
                
$reasons   = [];

                foreach (
$array as $i => $promiseOrValue) {
                    
$fulfiller = function ($val) use ($i, &$values, &$toResolve$toReject$resolve) {
                        if (
$toResolve || $toReject 1) {
                            return;
                        }

                        
$values[$i] = $val;

                        if (
=== --$toResolve) {
                            
$resolve($values);
                        }
                    };

                    
$rejecter = function ($reason) use ($i, &$reasons, &$toReject$toResolve$reject) {
                        if (
$toResolve || $toReject 1) {
                            return;
                        }

                        
$reasons[$i] = $reason;

                        if (
=== --$toReject) {
                            
$reject($reasons);
                        }
                    };

                    
$cancellationQueue->enqueue($promiseOrValue);

                    
resolve($promiseOrValue)
                        ->
done($fulfiller$rejecter$notify);
                }
            }, 
$reject$notify);
    }, 
$cancellationQueue);
}

/**
 * Traditional map function, similar to `array_map()`, but allows input to contain
 * promises and/or values, and `$mapFunc` may return either a value or a promise.
 *
 * The map function receives each item as argument, where item is a fully resolved
 * value of a promise or value in `$promisesOrValues`.
 *
 * @param array $promisesOrValues
 * @param callable $mapFunc
 * @return PromiseInterface
 */
function map($promisesOrValues, callable $mapFunc)
{
    
$cancellationQueue = new CancellationQueue();
    
$cancellationQueue->enqueue($promisesOrValues);

    return new 
Promise(function ($resolve$reject$notify) use ($promisesOrValues$mapFunc$cancellationQueue) {
        
resolve($promisesOrValues)
            ->
done(function ($array) use ($mapFunc$cancellationQueue$resolve$reject$notify) {
                if (!
\is_array($array) || !$array) {
                    
$resolve([]);
                    return;
                }

                
$toResolve \count($array);
                
$values    = [];

                foreach (
$array as $i => $promiseOrValue) {
                    
$cancellationQueue->enqueue($promiseOrValue);
                    
$values[$i] = null;

                    
resolve($promiseOrValue)
                        ->
then($mapFunc)
                        ->
done(
                            function (
$mapped) use ($i, &$values, &$toResolve$resolve) {
                                
$values[$i] = $mapped;

                                if (
=== --$toResolve) {
                                    
$resolve($values);
                                }
                            },
                            
$reject,
                            
$notify
                        
);
                }
            }, 
$reject$notify);
    }, 
$cancellationQueue);
}

/**
 * Traditional reduce function, similar to `array_reduce()`, but input may contain
 * promises and/or values, and `$reduceFunc` may return either a value or a
 * promise, *and* `$initialValue` may be a promise or a value for the starting
 * value.
 *
 * @param array $promisesOrValues
 * @param callable $reduceFunc
 * @param mixed $initialValue
 * @return PromiseInterface
 */
function reduce($promisesOrValues, callable $reduceFunc$initialValue null)
{
    
$cancellationQueue = new CancellationQueue();
    
$cancellationQueue->enqueue($promisesOrValues);

    return new 
Promise(function ($resolve$reject$notify) use ($promisesOrValues$reduceFunc$initialValue$cancellationQueue) {
        
resolve($promisesOrValues)
            ->
done(function ($array) use ($reduceFunc$initialValue$cancellationQueue$resolve$reject$notify) {
                if (!
\is_array($array)) {
                    
$array = [];
                }

                
$total \count($array);
                
$i 0;

                
// Wrap the supplied $reduceFunc with one that handles promises and then
                // delegates to the supplied.
                
$wrappedReduceFunc = function ($current$val) use ($reduceFunc$cancellationQueue$total, &$i) {
                    
$cancellationQueue->enqueue($val);

                    return 
$current
                        
->then(function ($c) use ($reduceFunc$total, &$i$val) {
                            return 
resolve($val)
                                ->
then(function ($value) use ($reduceFunc$total, &$i$c) {
                                    return 
$reduceFunc($c$value$i++, $total);
                                });
                        });
                };

                
$cancellationQueue->enqueue($initialValue);

                
\array_reduce($array$wrappedReduceFuncresolve($initialValue))
                    ->
done($resolve$reject$notify);
            }, 
$reject$notify);
    }, 
$cancellationQueue);
}

/**
 * @internal
 */
function _checkTypehint(callable $callback$object)
{
    if (!
\is_object($object)) {
        return 
true;
    }

    if (
\is_array($callback)) {
        
$callbackReflection = new \ReflectionMethod($callback[0], $callback[1]);
    } elseif (
\is_object($callback) && !$callback instanceof \Closure) {
        
$callbackReflection = new \ReflectionMethod($callback'__invoke');
    } else {
        
$callbackReflection = new \ReflectionFunction($callback);
    }

    
$parameters $callbackReflection->getParameters();

    if (!isset(
$parameters[0])) {
        return 
true;
    }

    
$expectedException $parameters[0];

    if (!
$expectedException->getClass()) {
        return 
true;
    }

    return 
$expectedException->getClass()->isInstance($object);
}
<?
php

namespace React\Promise;

interface 
CancellablePromiseInterface extends 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();
}
<?
php

namespace React\Promise;

interface 
PromisorInterface
{
    
/**
     * Returns the promise of the deferred.
     *
     * @return PromiseInterface
     */
    
public function promise();
}
<?
php

namespace React\Promise;

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.
     *  * `$onProgress` (deprecated) will be invoked whenever the producer of the promise
     *     triggers progress notifications and passed a single argument (whatever it
     *     wants) to indicate progress.
     *
     * 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.
     *  3. `$onProgress` (deprecated) may be called multiple times.
     *
     * @param callable|null $onFulfilled
     * @param callable|null $onRejected
     * @param callable|null $onProgress This argument is deprecated and should not be used anymore.
     * @return PromiseInterface
     */
    
public function then(callable $onFulfilled null, callable $onRejected null, callable $onProgress null);
}
<?
php

namespace React\Promise\Exception;

class 
LengthException extends \LengthException
{
}
<?
php

namespace React\Promise;

class 
Promise implements ExtendedPromiseInterfaceCancellablePromiseInterface
{
    private 
$canceller;
    private 
$result;

    private 
$handlers = [];
    private 
$progressHandlers = [];

    private 
$requiredCancelRequests 0;
    private 
$cancelRequests 0;

    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, callable $onProgress null)
    {
        if (
null !== $this->result) {
            return 
$this->result->then($onFulfilled$onRejected$onProgress);
        }

        if (
null === $this->canceller) {
            return new static(
$this->resolver($onFulfilled$onRejected$onProgress));
        }

        
// 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$onProgress),
            static function () use (&
$parent) {
                if (++
$parent->cancelRequests >= $parent->requiredCancelRequests) {
                    
$parent->cancel();
                }

                
$parent null;
            }
        );
    }

    public function 
done(callable $onFulfilled null, callable $onRejected null, callable $onProgress null)
    {
        if (
null !== $this->result) {
            return 
$this->result->done($onFulfilled$onRejected$onProgress);
        }

        
$this->handlers[] = static function (ExtendedPromiseInterface $promise) use ($onFulfilled$onRejected) {
            
$promise
                
->done($onFulfilled$onRejected);
        };

        if (
$onProgress) {
            
$this->progressHandlers[] = $onProgress;
        }
    }

    public function 
otherwise(callable $onRejected)
    {
        return 
$this->then(null, static function ($reason) use ($onRejected) {
            if (!
_checkTypehint($onRejected$reason)) {
                return new 
RejectedPromise($reason);
            }

            return 
$onRejected($reason);
        });
    }

    public function 
always(callable $onFulfilledOrRejected)
    {
        return 
$this->then(static function ($value) use ($onFulfilledOrRejected) {
            return 
resolve($onFulfilledOrRejected())->then(function () use ($value) {
                return 
$value;
            });
        }, static function (
$reason) use ($onFulfilledOrRejected) {
            return 
resolve($onFulfilledOrRejected())->then(function () use ($reason) {
                return new 
RejectedPromise($reason);
            });
        });
    }

    public function 
progress(callable $onProgress)
    {
        return 
$this->then(nullnull$onProgress);
    }

    public function 
cancel()
    {
        if (
null === $this->canceller || null !== $this->result) {
            return;
        }

        
$canceller $this->canceller;
        
$this->canceller null;

        
$this->call($canceller);
    }

    private function 
resolver(callable $onFulfilled null, callable $onRejected null, callable $onProgress null)
    {
        return function (
$resolve$reject$notify) use ($onFulfilled$onRejected$onProgress) {
            if (
$onProgress) {
                
$progressHandler = static function ($update) use ($notify$onProgress) {
                    try {
                        
$notify($onProgress($update));
                    } catch (
\Throwable $e) {
                        
$notify($e);
                    } catch (
\Exception $e) {
                        
$notify($e);
                    }
                };
            } else {
                
$progressHandler $notify;
            }

            
$this->handlers[] = static function (ExtendedPromiseInterface $promise) use ($onFulfilled$onRejected$resolve$reject$progressHandler) {
                
$promise
                    
->then($onFulfilled$onRejected)
                    ->
done($resolve$reject$progressHandler);
            };

            
$this->progressHandlers[] = $progressHandler;
        };
    }

    private function 
reject($reason null)
    {
        if (
null !== $this->result) {
            return;
        }

        
$this->settle(reject($reason));
    }

    private function 
settle(ExtendedPromiseInterface $promise)
    {
        
$promise $this->unwrap($promise);

        if (
$promise === $this) {
            
$promise = new RejectedPromise(
                new 
\LogicException('Cannot resolve a promise with itself.')
            );
        }

        
$handlers $this->handlers;

        
$this->progressHandlers $this->handlers = [];
        
$this->result $promise;
        
$this->canceller null;

        foreach (
$handlers as $handler) {
            
$handler($promise);
        }
    }

    private function 
unwrap($promise)
    {
        
$promise $this->extract($promise);

        while (
$promise instanceof self && null !== $promise->result) {
            
$promise $this->extract($promise->result);
        }

        return 
$promise;
    }

    private function 
extract($promise)
    {
        if (
$promise instanceof LazyPromise) {
            
$promise $promise->promise();
        }

        return 
$promise;
    }

    private function 
call(callable $cb)
    {
        
// 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 {
            
$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;
                
$progressHandlers =& $this->progressHandlers;

                
$callback(
                    static function (
$value null) use (&$target) {
                        if (
$target !== null) {
                            
$target->settle(resolve($value));
                            
$target null;
                        }
                    },
                    static function (
$reason null) use (&$target) {
                        if (
$target !== null) {
                            
$target->reject($reason);
                            
$target null;
                        }
                    },
                    static function (
$update null) use (&$progressHandlers) {
                        foreach (
$progressHandlers as $handler) {
                            
$handler($update);
                        }
                    }
                );
            }
        } catch (
\Throwable $e) {
            
$target null;
            
$this->reject($e);
        } catch (
\Exception $e) {
            
$target null;
            
$this->reject($e);
        }
    }
}
<?
php

namespace React\Promise;

interface 
ExtendedPromiseInterface extends PromiseInterface
{
    
/**
     * Consumes the promise's ultimate value if the promise fulfills, or handles the
     * ultimate error.
     *
     * It will cause a fatal error if either `$onFulfilled` or
     * `$onRejected` throw or return a rejected promise.
     *
     * Since the purpose of `done()` is consumption rather than transformation,
     * `done()` always returns `null`.
     *
     * @param callable|null $onFulfilled
     * @param callable|null $onRejected
     * @param callable|null $onProgress This argument is deprecated and should not be used anymore.
     * @return void
     */
    
public function done(callable $onFulfilled null, callable $onRejected null, callable $onProgress null);

    
/**
     * 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.
     *
     * @param callable $onRejected
     * @return ExtendedPromiseInterface
     */
    
public function otherwise(callable $onRejected);

    
/**
     * 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.
     *
     * `always()` behaves similarly to the synchronous finally statement. When combined
     * with `otherwise()`, `always()` 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()
     *     ->otherwise('handleError')
     *     ->always('cleanup');
     * ```
     *
     * @param callable $onFulfilledOrRejected
     * @return ExtendedPromiseInterface
     */
    
public function always(callable $onFulfilledOrRejected);

    
/**
     * Registers a handler for progress updates from promise. It is a shortcut for:
     *
     * ```php
     * $promise->then(null, null, $onProgress);
     * ```
     *
     * @param callable $onProgress
     * @return ExtendedPromiseInterface
     * @deprecated 2.6.0 Progress support is deprecated and should not be used anymore.
     */
    
public function progress(callable $onProgress);
}
<?
php

namespace React\Promise;

/**
 * @deprecated 2.8.0 LazyPromise is deprecated and should not be used anymore.
 */
class LazyPromise implements ExtendedPromiseInterfaceCancellablePromiseInterface
{
    private 
$factory;
    private 
$promise;

    public function 
__construct(callable $factory)
    {
        
$this->factory $factory;
    }

    public function 
then(callable $onFulfilled null, callable $onRejected null, callable $onProgress null)
    {
        return 
$this->promise()->then($onFulfilled$onRejected$onProgress);
    }

    public function 
done(callable $onFulfilled null, callable $onRejected null, callable $onProgress null)
    {
        return 
$this->promise()->done($onFulfilled$onRejected$onProgress);
    }

    public function 
otherwise(callable $onRejected)
    {
        return 
$this->promise()->otherwise($onRejected);
    }

    public function 
always(callable $onFulfilledOrRejected)
    {
        return 
$this->promise()->always($onFulfilledOrRejected);
    }

    public function 
progress(callable $onProgress)
    {
        return 
$this->promise()->progress($onProgress);
    }

    public function 
cancel()
    {
        return 
$this->promise()->cancel();
    }

    
/**
     * @internal
     * @see Promise::settle()
     */
    
public function promise()
    {
        if (
null === $this->promise) {
            try {
                
$this->promise resolve(\call_user_func($this->factory));
            } catch (
\Throwable $exception) {
                
$this->promise = new RejectedPromise($exception);
            } catch (
\Exception $exception) {
                
$this->promise = new RejectedPromise($exception);
            }
        }

        return 
$this->promise;
    }
}
<?
php

namespace React\Promise;

class 
UnhandledRejectionException extends \RuntimeException
{
    private 
$reason;

    public static function 
resolve($reason)
    {
        if (
$reason instanceof \Exception || $reason instanceof \Throwable) {
            return 
$reason;
        }

        return new static(
$reason);
    }

    public function 
__construct($reason)
    {
        
$this->reason $reason;

        
$message \sprintf('Unhandled Rejection: %s'\json_encode($reason));

        
parent::__construct($message0);
    }

    public function 
getReason()
    {
        return 
$this->reason;
    }
}
<?
php

if (!\function_exists('React\Promise\resolve')) {
    require 
__DIR__.'/functions.php';
}
<?
php

namespace React\Promise;

/**
 * @deprecated 2.8.0 External usage of FulfilledPromise is deprecated, use `resolve()` instead.
 */
class FulfilledPromise implements ExtendedPromiseInterfaceCancellablePromiseInterface
{
    private 
$value;

    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;
    }

    public function 
then(callable $onFulfilled null, callable $onRejected null, callable $onProgress null)
    {
        if (
null === $onFulfilled) {
            return 
$this;
        }

        try {
            return 
resolve($onFulfilled($this->value));
        } catch (
\Throwable $exception) {
            return new 
RejectedPromise($exception);
        } catch (
\Exception $exception) {
            return new 
RejectedPromise($exception);
        }
    }

    public function 
done(callable $onFulfilled null, callable $onRejected null, callable $onProgress null)
    {
        if (
null === $onFulfilled) {
            return;
        }

        
$result $onFulfilled($this->value);

        if (
$result instanceof ExtendedPromiseInterface) {
            
$result->done();
        }
    }

    public function 
otherwise(callable $onRejected)
    {
        return 
$this;
    }

    public function 
always(callable $onFulfilledOrRejected)
    {
        return 
$this->then(function ($value) use ($onFulfilledOrRejected) {
            return 
resolve($onFulfilledOrRejected())->then(function () use ($value) {
                return 
$value;
            });
        });
    }

    public function 
progress(callable $onProgress)
    {
        return 
$this;
    }

    public function 
cancel()
    {
    }
}
<?
php

namespace React\Promise;

class 
CancellationQueue
{
    private 
$started false;
    private 
$queue = [];

    public function 
__invoke()
    {
        if (
$this->started) {
            return;
        }

        
$this->started true;
        
$this->drain();
    }

    public function 
enqueue($cancellable)
    {
        if (!
\is_object($cancellable) || !\method_exists($cancellable'then') || !\method_exists($cancellable'cancel')) {
            return;
        }

        
$length \array_push($this->queue$cancellable);

        if (
$this->started && === $length) {
            
$this->drain();
        }
    }

    private function 
drain()
    {
        for (
$i key($this->queue); isset($this->queue[$i]); $i++) {
            
$cancellable $this->queue[$i];

            
$exception null;

            try {
                
$cancellable->cancel();
            } catch (
\Throwable $exception) {
            } catch (
\Exception $exception) {
            }

            unset(
$this->queue[$i]);

            if (
$exception) {
                throw 
$exception;
            }
        }

        
$this->queue = [];
    }
}
<?
php

use WP_CLI\Formatter;
use 
WP_CLI\Utils;

/**
 * List, add, and delete items associated with a menu.
 *
 * ## EXAMPLES
 *
 *     # Add an existing post to an existing menu
 *     $ wp menu item add-post sidebar-menu 33 --title="Custom Test Post"
 *     Success: Menu item added.
 *
 *     # Create a new menu link item
 *     $ wp menu item add-custom sidebar-menu Apple http://apple.com
 *     Success: Menu item added.
 *
 *     # Delete menu item
 *     $ wp menu item delete 45
 *     Success: 1 menu item deleted.
 */
class Menu_Item_Command extends WP_CLI_Command {

    protected 
$obj_fields = [
        
'db_id',
        
'type',
        
'title',
        
'link',
        
'position',
    ];

    
/**
     * Gets a list of items associated with a menu.
     *
     * ## OPTIONS
     *
     * <menu>
     * : The name, slug, or term ID for the menu.
     *
     * [--fields=<fields>]
     * : Limit the output to specific object fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - count
     *   - ids
     *   - yaml
     * ---
     *
     * ## AVAILABLE FIELDS
     *
     * These fields will be displayed by default for each menu item:
     *
     * * db_id
     * * type
     * * title
     * * link
     * * position
     *
     * These fields are optionally available:
     *
     * * menu_item_parent
     * * object_id
     * * object
     * * type
     * * type_label
     * * target
     * * attr_title
     * * description
     * * classes
     * * xfn
     *
     * ## EXAMPLES
     *
     *     $ wp menu item list main-menu
     *     +-------+-----------+-------------+---------------------------------+----------+
     *     | db_id | type      | title       | link                            | position |
     *     +-------+-----------+-------------+---------------------------------+----------+
     *     | 5     | custom    | Home        | http://example.com              | 1        |
     *     | 6     | post_type | Sample Page | http://example.com/sample-page/ | 2        |
     *     +-------+-----------+-------------+---------------------------------+----------+
     *
     * @subcommand list
     */
    
public function list_$args$assoc_args ) {

        
$items wp_get_nav_menu_items$args[0] );
        if ( 
false === $items || is_wp_error$items ) ) {
            
WP_CLI::error'Invalid menu.' );
        }

        
// Correct position inconsistency and
        // protected `url` param in WP-CLI
        
$items array_map(
            function( 
$item ) {
                    
$item->position $item->menu_order;
                    
$item->link     $item->url;
                    return 
$item;
            },
            
$items
        
);

        if ( ! empty( 
$assoc_args['format'] ) && 'ids' === $assoc_args['format'] ) {
            
$items array_map(
                function( 
$item ) {
                        return 
$item->db_id;
                },
                
$items
            
);
        }

        
$formatter $this->get_formatter$assoc_args );
        
$formatter->display_items$items );

    }

    
/**
     * Adds a post as a menu item.
     *
     * ## OPTIONS
     *
     * <menu>
     * : The name, slug, or term ID for the menu.
     *
     * <post-id>
     * : Post ID to add to the menu.
     *
     * [--title=<title>]
     * : Set a custom title for the menu item.
     *
     * [--link=<link>]
     * : Set a custom url for the menu item.
     *
     * [--description=<description>]
     * : Set a custom description for the menu item.
     *
     * [--attr-title=<attr-title>]
     * : Set a custom title attribute for the menu item.
     *
     * [--target=<target>]
     * : Set a custom link target for the menu item.
     *
     * [--classes=<classes>]
     * : Set a custom link classes for the menu item.
     *
     * [--position=<position>]
     * : Specify the position of this menu item.
     *
     * [--parent-id=<parent-id>]
     * : Make this menu item a child of another menu item.
     *
     * [--porcelain]
     * : Output just the new menu item id.
     *
     * ## EXAMPLES
     *
     *     $ wp menu item add-post sidebar-menu 33 --title="Custom Test Post"
     *     Success: Menu item added.
     *
     * @subcommand add-post
     */
    
public function add_post$args$assoc_args ) {

        
$assoc_args['object-id'] = $args[1];
        unset( 
$args[1] );
        
$post get_post$assoc_args['object-id'] );
        if ( ! 
$post ) {
            
WP_CLI::error'Invalid post.' );
        }
        
$assoc_args['object'] = $post->post_type;

        
$this->add_or_update_item'add''post_type'$args$assoc_args );
    }

    
/**
     * Adds a taxonomy term as a menu item.
     *
     * ## OPTIONS
     *
     * <menu>
     * : The name, slug, or term ID for the menu.
     *
     * <taxonomy>
     * : Taxonomy of the term to be added.
     *
     * <term-id>
     * : Term ID of the term to be added.
     *
     * [--title=<title>]
     * : Set a custom title for the menu item.
     *
     * [--link=<link>]
     * : Set a custom url for the menu item.
     *
     * [--description=<description>]
     * : Set a custom description for the menu item.
     *
     * [--attr-title=<attr-title>]
     * : Set a custom title attribute for the menu item.
     *
     * [--target=<target>]
     * : Set a custom link target for the menu item.
     *
     * [--classes=<classes>]
     * : Set a custom link classes for the menu item.
     *
     * [--position=<position>]
     * : Specify the position of this menu item.
     *
     * [--parent-id=<parent-id>]
     * : Make this menu item a child of another menu item.
     *
     * [--porcelain]
     * : Output just the new menu item id.
     *
     * ## EXAMPLES
     *
     *     $ wp menu item add-term sidebar-menu post_tag 24
     *     Success: Menu item added.
     *
     * @subcommand add-term
     */
    
public function add_term$args$assoc_args ) {

        
$assoc_args['object'] = $args[1];
        unset( 
$args[1] );
        
$assoc_args['object-id'] = $args[2];
        unset( 
$args[2] );

        if ( ! 
get_term_by'id'$assoc_args['object-id'], $assoc_args['object'] ) ) {
            
WP_CLI::error'Invalid term.' );
        }

        
$this->add_or_update_item'add''taxonomy'$args$assoc_args );
    }

    
/**
     * Adds a custom menu item.
     *
     * ## OPTIONS
     *
     * <menu>
     * : The name, slug, or term ID for the menu.
     *
     * <title>
     * : Title for the link.
     *
     * <link>
     * : Target URL for the link.
     *
     * [--description=<description>]
     * : Set a custom description for the menu item.
     *
     * [--attr-title=<attr-title>]
     * : Set a custom title attribute for the menu item.
     *
     * [--target=<target>]
     * : Set a custom link target for the menu item.
     *
     * [--classes=<classes>]
     * : Set a custom link classes for the menu item.
     *
     * [--position=<position>]
     * : Specify the position of this menu item.
     *
     * [--parent-id=<parent-id>]
     * : Make this menu item a child of another menu item.
     *
     * [--porcelain]
     * : Output just the new menu item id.
     *
     * ## EXAMPLES
     *
     *     $ wp menu item add-custom sidebar-menu Apple http://apple.com
     *     Success: Menu item added.
     *
     * @subcommand add-custom
     */
    
public function add_custom$args$assoc_args ) {

        
$assoc_args['title'] = $args[1];
        unset( 
$args[1] );
        
$assoc_args['link'] = $args[2];
        unset( 
$args[2] );
        
$this->add_or_update_item'add''custom'$args$assoc_args );
    }

    
/**
     * Updates a menu item.
     *
     * ## OPTIONS
     *
     * <db-id>
     * : Database ID for the menu item.
     *
     * [--title=<title>]
     * : Set a custom title for the menu item.
     *
     * [--link=<link>]
     * : Set a custom url for the menu item.
     *
     * [--description=<description>]
     * : Set a custom description for the menu item.
     *
     * [--attr-title=<attr-title>]
     * : Set a custom title attribute for the menu item.
     *
     * [--target=<target>]
     * : Set a custom link target for the menu item.
     *
     * [--classes=<classes>]
     * : Set a custom link classes for the menu item.
     *
     * [--position=<position>]
     * : Specify the position of this menu item.
     *
     * [--parent-id=<parent-id>]
     * : Make this menu item a child of another menu item.
     *
     * ## EXAMPLES
     *
     *     $ wp menu item update 45 --title=WordPress --link='http://wordpress.org' --target=_blank --position=2
     *     Success: Menu item updated.
     *
     * @subcommand update
     */
    
public function update$args$assoc_args ) {

        
// Shuffle the position of these.
        
$args[1] = $args[0];
        
$terms   get_the_terms$args[1], 'nav_menu' );
        if ( 
$terms && ! is_wp_error$terms ) ) {
            
$args[0] = (int) $terms[0]->term_id;
        } else {
            
$args[0] = 0;
        }
        
$type get_post_meta$args[1], '_menu_item_type'true );
        
$this->add_or_update_item'update'$type$args$assoc_args );

    }

    
/**
     * Deletes one or more items from a menu.
     *
     * ## OPTIONS
     *
     * <db-id>...
     * : Database ID for the menu item(s).
     *
     * ## EXAMPLES
     *
     *     $ wp menu item delete 45
     *     Success: 1 menu item deleted.
     *
     * @subcommand delete
     */
    
public function delete$args$assoc_args ) {
        global 
$wpdb;

        
$count  0;
        
$errors 0;

        foreach ( 
$args as $arg ) {

            
$post           get_post$arg );
            
$menu_term      get_the_terms$arg'nav_menu' );
            
$parent_menu_id = (int) get_post_meta$arg'_menu_item_menu_item_parent'true );
            
$result         wp_delete_post$argtrue );
            if ( ! 
$result ) {
                
WP_CLI::warning"Couldn't delete menu item {$arg}." );
                
$errors++;
            } else {

                if ( 
is_array$menu_term ) && ! empty( $menu_term ) && $post ) {
                    
$this->reorder_menu_items$menu_term[0]->term_id$post->menu_order, -1);
                }

                if ( 
$parent_menu_id ) {
                    
$children $wpdb->get_results$wpdb->prepare"SELECT post_id FROM $wpdb->postmeta WHERE meta_key='_menu_item_menu_item_parent' AND meta_value=%s", (int) $arg ) );
                    if ( 
$children ) {
                        
$children_query $wpdb->prepare"UPDATE $wpdb->postmeta SET meta_value = %d WHERE meta_key = '_menu_item_menu_item_parent' AND meta_value=%s"$parent_menu_id, (int) $arg );
                        
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $children_query is already prepared above.
                        
$wpdb->query$children_query );
                        foreach ( 
$children as $child ) {
                            
clean_post_cache$child );
                        }
                    }
                }
            }

            
// phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison -- Will increase count for non existent menu.
            
if ( false != $result ) {
                
$count++;
            }
        }

        
Utils\report_batch_operation_results'menu item''delete'count$args ), $count$errors );
    }

    
/**
     * Worker method to create new items or update existing ones.
     */
    
private function add_or_update_item$method$type$args$assoc_args ) {

        
$menu            $args[0];
        
$menu_item_db_id Utils\get_flag_value$args1);

        
$menu wp_get_nav_menu_object$menu );
        if ( ! 
$menu || is_wp_error$menu ) ) {
            
WP_CLI::error'Invalid menu.' );
        }

        
// `url` is protected in WP-CLI, so we use `link` instead
        
$assoc_args['url'] = Utils\get_flag_value$assoc_args'link' );

        
// Need to persist the menu item data. See https://core.trac.wordpress.org/ticket/28138
        
if ( 'update' === $method ) {

            
$menu_item_obj get_post$menu_item_db_id );
            
$menu_item_obj wp_setup_nav_menu_item$menu_item_obj );

            
// Correct the menu position if this was the first item. See https://core.trac.wordpress.org/ticket/28140
            
$position = ( === $menu_item_obj->menu_order ) ? $menu_item_obj->menu_order;

            
$default_args = [
                
'position'    => $position,
                
'title'       => $menu_item_obj->title,
                
'url'         => $menu_item_obj->url,
                
'description' => $menu_item_obj->description,
                
'object'      => $menu_item_obj->object,
                
'object-id'   => $menu_item_obj->object_id,
                
'parent-id'   => $menu_item_obj->menu_item_parent,
                
'attr-title'  => $menu_item_obj->attr_title,
                
'target'      => $menu_item_obj->target,
                
'classes'     => implode' '$menu_item_obj->classes ), // stored in the database as array
                
'xfn'         => $menu_item_obj->xfn,
                
'status'      => $menu_item_obj->post_status,
            ];

        } else {

            
$default_args = [
                
'position'    => 0,
                
'title'       => '',
                
'url'         => '',
                
'description' => '',
                
'object'      => '',
                
'object-id'   => 0,
                
'parent-id'   => 0,
                
'attr-title'  => '',
                
'target'      => '',
                
'classes'     => '',
                
'xfn'         => '',
                
// Core oddly defaults to 'draft' for create,
                // and 'publish' for update
                // Easiest to always work with publish
                
'status'      => 'publish',
            ];

        }

        
$menu_item_args = [];
        foreach ( 
$default_args as $key => $default_value ) {
            
// wp_update_nav_menu_item() has a weird argument prefix
            
$new_key                    'menu-item-' $key;
            
$menu_item_args$new_key ] = Utils\get_flag_value$assoc_args$key$default_value );
        }

        
$menu_item_args['menu-item-type'] = $type;
        
$result                           wp_update_nav_menu_item$menu->term_id$menu_item_db_id$menu_item_args );

        if ( 
is_wp_error$result ) ) {
            
WP_CLI::error$result->get_error_message() );
        } elseif ( ! 
$result ) {
            if ( 
'add' === $method ) {
                
WP_CLI::error"Couldn't add menu item." );
            } elseif ( 
'update' === $method ) {
                
WP_CLI::error"Couldn't update menu item." );
            }
        } else {

            if ( ( 
'add' === $method ) && $menu_item_args['menu-item-position'] ) {
                
$this->reorder_menu_items$menu->term_id$menu_item_args['menu-item-position'], +1$result );
            }

            
/**
             * Set the menu
             *
             * wp_update_nav_menu_item() *should* take care of this, but
             * depends on wp_insert_post()'s "tax_input" argument, which
             * is ignored if the user can't edit the taxonomy
             *
             * @see https://core.trac.wordpress.org/ticket/27113
             */
            
if ( ! is_object_in_term$result'nav_menu', (int) $menu->term_id ) ) {
                
wp_set_object_terms$result, [ (int) $menu->term_id ], 'nav_menu' );
            }

            if ( 
'add' === $method && ! empty( $assoc_args['porcelain'] ) ) {
                
WP_CLI::line$result );
            } else {
                if ( 
'add' === $method ) {
                    
WP_CLI::success'Menu item added.' );
                } elseif ( 
'update' === $method ) {
                    
WP_CLI::success'Menu item updated.' );
                }
            }
        }

    }

    
/**
     * Move block of items in one nav_menu up or down by incrementing/decrementing their menu_order field.
     * Expects the menu items to have proper menu_orders (i.e. doesn't fix errors from previous incorrect operations).
     *
     * @param int $menu_id ID of the nav_menu
     * @param int $min_position minimal menu_order to touch
     * @param int $increment how much to change menu_order: +1 to move down, -1 to move up
     * @param int $ignore_item_id menu item that should be ignored by the change (e.g. newly created menu item)
     * @return int number of rows affected
     */
    
private function reorder_menu_items$menu_id$min_position$increment$ignore_item_id ) {
        global 
$wpdb;
        return 
$wpdb->query$wpdb->prepare"UPDATE $wpdb->posts SET `menu_order`=`menu_order`+(%d) WHERE `menu_order`>=%d AND ID IN (SELECT object_id FROM $wpdb->term_relationships WHERE term_taxonomy_id=%d) AND ID<>%d", (int) $increment, (int) $min_position, (int) $menu_id, (int) $ignore_item_id ) );
    }

    protected function 
get_formatter( &$assoc_args ) {
        return new 
Formatter$assoc_args$this->obj_fields );
    }

}
<?
php

use WP_CLI\Dispatcher\CommandNamespace;

/**
 * Perform network-wide operations.
 *
 * ## EXAMPLES
 *
 *     # Get a list of super-admins
 *     $ wp network meta get 1 site_admins
 *     array (
 *       0 => 'supervisor',
 *     )
 */
class Network_Namespace extends CommandNamespace {

}
<?
php

namespace WP_CLI;

use 
WP_CLI;
use 
WP_CLI_Command;
use 
WP_CLI\Utils;
use 
WP_Error;

/**
 * Base class for WP-CLI commands that deal with database objects.
 *
 * @package wp-cli
 */
abstract class CommandWithDBObject extends WP_CLI_Command {

    
/**
     * @var string $object_type WordPress' expected name for the object.
     */
    
protected $obj_type;

    
/**
     * @var string $obj_id_key Key representing object's PK field in db.
     */
    
protected $obj_id_key 'ID';

    
/**
     * @var array $obj_fields Default fields to display for each object.
     */
    
protected $obj_fields;

    
/**
     * Create a given database object.
     * Exits with status.
     *
     * @param array $args Arguments passed to command. Generally unused.
     * @param array $assoc_args Parameters passed to command to be passed to callback.
     * @param string $callback Function used to create object.
     */
    
protected function _create$args$assoc_args$callback ) {
        unset( 
$assoc_args$this->obj_id_key ] );

        
$obj_id $callback$assoc_args );

        if ( 
is_wp_error$obj_id ) ) {
            
WP_CLI::error$obj_id );
        }

        if ( 
Utils\get_flag_value$assoc_args'porcelain' ) ) {
            
WP_CLI::line$obj_id );
        } else {
            
WP_CLI::success"Created {$this->obj_type} {$obj_id}." );
        }
    }

    
/**
     * Update a given database object.
     * Exits with status.
     *
     * @param array $args Collection of one or more object ids to update.
     * @param array $assoc_args Fields => values to update on each object.
     * @param string $callback Function used to update object.
     */
    
protected function _update$args$assoc_args$callback ) {
        
$status 0;

        if ( empty( 
$assoc_args ) ) {
            
WP_CLI::error'Need some fields to update.' );
        }

        if ( 
Utils\get_flag_value$assoc_args'defer-term-counting' ) ) {
            
wp_defer_term_countingtrue );
        }

        foreach ( 
$args as $obj_id ) {
            
$params array_merge$assoc_args, [ $this->obj_id_key => $obj_id ] );

            
$status $this->success_or_failure(
                
$this->wp_error_to_resp(
                    
$callback$params ),
                    
"Updated {$this->obj_type} {$obj_id}."
                
)
            );
        }

        if ( 
Utils\get_flag_value$assoc_args'defer-term-counting' ) ) {
            
wp_defer_term_countingfalse );
        }

        exit( 
$status );
    }

    
/**
     * Transforms arguments with '__' from CSV into expected arrays
     *
     * @param array $assoc_args
     * @return array
     */
    
protected static function process_csv_arguments_to_arrays$assoc_args ) {
        foreach ( 
$assoc_args as $k => $v ) {
            if ( 
false !== strpos$k'__' ) ) {
                
$assoc_args$k ] = explode','$v );
            }
        }
        return 
$assoc_args;
    }

    
/**
     * Delete a given database object.
     * Exits with status.
     *
     * @param array $args Collection of one or more object ids to delete.
     * @param array $assoc_args Any arguments needed for the callback function.
     * @param callable $callback Function used to delete object.
     */
    
protected function _delete$args$assoc_args$callback ) {
        
$status 0;

        if ( 
Utils\get_flag_value$assoc_args'defer-term-counting' ) ) {
            
wp_defer_term_countingtrue );
        }

        foreach ( 
$args as $obj_id ) {
            
$result $callback$obj_id$assoc_args );
            
$status $this->success_or_failure$result );
        }

        if ( 
Utils\get_flag_value$assoc_args'defer-term-counting' ) ) {
            
wp_defer_term_countingfalse );
        }

        exit( 
$status );
    }

    
/**
     * Format callback response to consistent format.
     *
     * @param WP_Error|true $response Response from CRUD callback.
     * @param string        $success_msg
     * @return array
     */
    
protected function wp_error_to_resp$response$success_msg ) {
        return 
is_wp_error$response )
            ? [ 
'error'$response->get_error_message() ]
            : [ 
'success'$success_msg ];
    }

    
/**
     * Display success or warning based on response; return proper exit code.
     *
     * @param array $response Formatted from a CRUD callback.
     * @return int $status
     */
    
protected function success_or_failure$response ) {
        list( 
$type$msg ) = $response;

        if ( 
'success' === $type ) {
            
WP_CLI::success$msg );
            
$status 0;
        } else {
            
WP_CLI::warning$msg );
            
$status 1;
        }

        return 
$status;
    }

    
/**
     * Get Formatter object based on supplied parameters.
     *
     * @param array $assoc_args Parameters passed to command. Determines formatting.
     * @return Formatter
     */
    
protected function get_formatter( &$assoc_args ) {

        if ( ! empty( 
$assoc_args['fields'] ) ) {
            if ( 
is_string$assoc_args['fields'] ) ) {
                
$fields explode','$assoc_args['fields'] );
            } else {
                
$fields $assoc_args['fields'];
            }
        } else {
            
$fields $this->obj_fields;
        }
        return new 
Formatter$assoc_args$fields$this->obj_type );
    }

    
/**
     * Given a callback, display the URL for one or more objects.
     *
     * @param array $args One or more object references.
     * @param string $callback Function to get URL for the object.
     */
    
protected function _url$args$callback ) {
        foreach ( 
$args as $obj_id ) {
            
$object $this->fetcher->get_check$obj_id );
            
WP_CLI::line$callback$object->{$this->obj_id_key} ) );
        }
    }
}
<?
php

namespace WP_CLI\Entity;

use 
OutOfBoundsException;

class 
NonExistentKeyException extends OutOfBoundsException {
    
/** @var RecursiveDataStructureTraverser */
    
protected $traverser;

    
/**
     * @param RecursiveDataStructureTraverser $traverser
     */
    
public function set_traverser$traverser ) {
        
$this->traverser $traverser;
    }

    
/**
     * @return RecursiveDataStructureTraverser
     */
    
public function get_traverser() {
        return 
$this->traverser;
    }
}
<?
php

namespace WP_CLI\Entity;

use 
UnexpectedValueException;

class 
RecursiveDataStructureTraverser {

    
/**
     * @var mixed The data to traverse set by reference.
     */
    
protected $data;

    
/**
     * @var null|string The key the data belongs to in the parent's data.
     */
    
protected $key;

    
/**
     * @var null|static The parent instance of the traverser.
     */
    
protected $parent;

    
/**
     * RecursiveDataStructureTraverser constructor.
     *
     * @param mixed $data The data to read/manipulate by reference.
     * @param string|int $key The key/property the data belongs to.
     * @param static $parent
     */
    
public function __construct( &$data$key null$parent null ) {
        
$this->data   =& $data;
        
$this->key    $key;
        
$this->parent $parent;
    }

    
/**
     * Get the nested value at the given key path.
     *
     * @param string|int|array $key_path
     *
     * @return static
     */
    
public function get$key_path ) {
        return 
$this->traverse_to( (array) $key_path )->value();
    }

    
/**
     * Get the current data.
     *
     * @return mixed
     */
    
public function value() {
        return 
$this->data;
    }

    
/**
     * Update a nested value at the given key path.
     *
     * @param string|int|array $key_path
     * @param mixed $value
     */
    
public function update$key_path$value ) {
        
$this->traverse_to( (array) $key_path )->set_value$value );
    }

    
/**
     * Update the current data with the given value.
     *
     * This will mutate the variable which was passed into the constructor
     * as the data is set and traversed by reference.
     *
     * @param mixed $value
     */
    
public function set_value$value ) {
        
$this->data $value;
    }

    
/**
     * Unset the value at the given key path.
     *
     * @param $key_path
     */
    
public function delete$key_path ) {
        
$this->traverse_to( (array) $key_path )->unset_on_parent();
    }

    
/**
     * Define a nested value while creating keys if they do not exist.
     *
     * @param array $key_path
     * @param mixed $value
     */
    
public function insert$key_path$value ) {
        try {
            
$this->update$key_path$value );
        } catch ( 
NonExistentKeyException $exception ) {
            
$exception->get_traverser()->create_key();
            
$this->insert$key_path$value );
        }
    }

    
/**
     * Delete the key on the parent's data that references this data.
     */
    
public function unset_on_parent() {
        
$this->parent->delete_by_key$this->key );
    }

    
/**
     * Delete the given key from the data.
     *
     * @param $key
     */
    
public function delete_by_key$key ) {
        if ( 
is_array$this->data ) ) {
            unset( 
$this->data$key ] );
        } else {
            unset( 
$this->data->$key );
        }
    }

    
/**
     * Get an instance of the traverser for the given hierarchical key.
     *
     * @param array $key_path Hierarchical key path within the current data to traverse to.
     *
     * @throws NonExistentKeyException
     *
     * @return static
     */
    
public function traverse_to( array $key_path ) {
        
$current array_shift$key_path );

        if ( 
null === $current ) {
            return 
$this;
        }

        if ( ! 
$this->exists$current ) ) {
            
$exception = new NonExistentKeyException"No data exists for key \"{$current}\"" );
            
$exception->set_traverser( new static( $this->data$current$this->parent ) );
            throw 
$exception;
        }

        foreach ( 
$this->data as $key => &$key_data ) {
            if ( 
$key === $current ) {
                
$traverser = new static( $key_data$key$this );
                return 
$traverser->traverse_to$key_path );
            }
        }
    }

    
/**
     * Create the key on the current data.
     *
     * @throws UnexpectedValueException
     */
    
protected function create_key() {
        if ( 
is_array$this->data ) ) {
            
$this->data$this->key ] = null;
        } elseif ( 
is_object$this->data ) ) {
            
$this->data->{$this->key} = null;
        } else {
            
$type gettype$this->data );
            throw new 
UnexpectedValueException(
                
"Cannot create key \"{$this->key}\" on data type {$type}"
            
);
        }
    }

    
/**
     * Check if the given key exists on the current data.
     *
     * @param string $key
     *
     * @return bool
     */
    
public function exists$key ) {
        return ( 
is_array$this->data ) && array_key_exists$key$this->data ) ) ||
            ( 
is_object$this->data ) && property_exists$this->data$key ) );
    }
}
<?
php

namespace WP_CLI\Entity;

class 
Utils {

    
/**
     * Check whether any input is passed to STDIN.
     *
     * @return bool
     */
    
public static function has_stdin() {
        
$handle  fopen'php://stdin''r' );
        
$read    = array( $handle );
        
$write   null;
        
$except  null;
        
$streams stream_select$read$write$except);
        
fclose$handle );

        return 
=== $streams;
    }
}
<?
php

namespace WP_CLI;

use 
WP_CLI;
use 
WP_CLI_Command;
use 
WP_CLI\Utils;

/**
 * Base class for WP-CLI commands that deal with terms
 *
 * @package wp-cli
 */
abstract class CommandWithTerms extends WP_CLI_Command {

    
/**
     * @var string $object_type WordPress' expected name for the object.
     */
    
protected $obj_type;

    
/**
     * @var string $object_id WordPress' object id.
     */
    
protected $obj_id;

    
/**
     * @var array $obj_fields Default fields to display for each object.
     */
    
protected $obj_fields = [
        
'term_id',
        
'name',
        
'slug',
        
'taxonomy',
    ];

    
/**
     * List all terms associated with an object.
     *
     * <id>
     * : ID for the object.
     *
     * <taxonomy>...
     * : One or more taxonomies to list.
     *
     * [--field=<field>]
     * : Prints the value of a single field for each term.
     *
     * [--fields=<fields>]
     * : Limit the output to specific row fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - yaml
     *   - count
     *   - ids
     * ---
     *
     * ## AVAILABLE FIELDS
     *
     * These fields will be displayed by default for each term:
     *
     * * term_id
     * * name
     * * slug
     * * taxonomy
     *
     * These fields are optionally available:
     *
     * * term_taxonomy_id
     * * description
     * * term_group
     * * parent
     * * count
     *
     * @subcommand list
     */
    
public function list_$args$assoc_args ) {

        
$defaults   = [
            
'format' => 'table',
        ];
        
$assoc_args array_merge$defaults$assoc_args );

        
$object_id      array_shift$args );
        
$taxonomy_names $args;
        
$taxonomy_args  = [];

        
$this->set_obj_id$object_id );

        foreach ( 
$taxonomy_names as $taxonomy ) {
            
$this->taxonomy_exists$taxonomy );
        }

        if ( 
'ids' === $assoc_args['format'] ) {
            
$taxonomy_args['fields'] = 'ids';
        }

        
$items wp_get_object_terms$object_id$taxonomy_names$taxonomy_args );

        
$formatter $this->get_formatter$assoc_args );
        
$formatter->display_items$items );

    }


    
/**
     * Remove a term from an object.
     *
     * ## OPTIONS
     *
     * <id>
     * : The ID of the object.
     *
     * <taxonomy>
     * : The name of the term's taxonomy.
     *
     * [<term>...]
     * : The name of the term or terms to be removed from the object.
     *
     * [--by=<field>]
     * : Explicitly handle the term value as a slug or id.
     * ---
     * options:
     *   - slug
     *   - id
     * ---
     *
     * [--all]
     * : Remove all terms from the object.
     */
    
public function remove$args$assoc_args ) {
        
$object_id array_shift$args );
        
$taxonomy  array_shift$args );
        
$terms     $args;

        
$this->set_obj_id$object_id );

        
$this->taxonomy_exists$taxonomy );

        
$field Utils\get_flag_value$assoc_args'by' );
        if ( 
$field ) {
            
$terms $this->prepare_terms$field$terms$taxonomy );
        }

        if ( 
Utils\get_flag_value$assoc_args'all' ) ) {

            
// No need to specify terms while removing all terms.
            
if ( $terms ) {
                
WP_CLI::error'No need to specify terms while removing all terms.' );
            }

            
// Remove all set categories from post.
            
$result wp_delete_object_term_relationships$object_id$taxonomy );

            
$message 'Removed all terms.';
            if ( 
'category' === $taxonomy ) {

                
// Set default category to post.
                
$default_category = (int) get_option'default_category' );
                
$default_category = ( ! empty( $default_category ) ) ? $default_category 1;
                
$default_category wp_set_object_terms$object_id, [ $default_category ], $taxonomytrue );

                if ( 
is_wp_error$default_category ) ) {
                    
WP_CLI::error'Failed to set default term.' );
                }

                
$message 'Removed all terms and set default term.';
            }

            if ( 
is_wp_error$result ) ) {
                
WP_CLI::error'Failed to remove all terms.' );
            }

            
WP_CLI::success$message );

            return;
        }

        
// Abort if no terms are specified.
        
if ( ! $terms ) {
            
WP_CLI::error'Please specify one or more terms, or use --all.' );
        }

        
// Remove term from post.
        
$result wp_remove_object_terms$object_id$terms$taxonomy );

        
$label count$terms ) > 'terms' 'term';

        if ( 
is_wp_error$result ) ) {
            
WP_CLI::error"Failed to remove {$label}." );
        }

        
WP_CLI::success"Removed {$label}." );
    }

    
/**
     * Add a term to an object.
     *
     * Append the term to the existing set of terms on the object.
     *
     * <id>
     * : The ID of the object.
     *
     * <taxonomy>
     * : The name of the taxonomy type to be added.
     *
     * <term>...
     * : The slug of the term or terms to be added.
     *
     * [--by=<field>]
     * : Explicitly handle the term value as a slug or id.
     * ---
     * options:
     *   - slug
     *   - id
     * ---
     */
    
public function add$args$assoc_args ) {
        
$object_id array_shift$args );
        
$taxonomy  array_shift$args );
        
$terms     $args;

        
$this->set_obj_id$object_id );

        
$this->taxonomy_exists$taxonomy );

        
$field Utils\get_flag_value$assoc_args'by' );
        if ( 
$field ) {
            
$terms $this->prepare_terms$field$terms$taxonomy );
        }
        
$result wp_set_object_terms$object_id$terms$taxonomytrue );

        
$label count$terms ) > 'terms' 'term';
        if ( ! 
is_wp_error$result ) ) {
            
WP_CLI::success"Added {$label}." );
        } else {
            
WP_CLI::error"Failed to add {$label}." );
        }
    }

    
/**
     * Set object terms.
     *
     * Replaces existing terms on the object.
     *
     * <id>
     * : The ID of the object.
     *
     * <taxonomy>
     * : The name of the taxonomy type to be updated.
     *
     * <term>...
     * : The slug of the term or terms to be updated.
     *
     * [--by=<field>]
     * : Explicitly handle the term value as a slug or id.
     * ---
     * options:
     *   - slug
     *   - id
     * ---
     */
    
public function set$args$assoc_args ) {
        
$object_id array_shift$args );
        
$taxonomy  array_shift$args );
        
$terms     $args;

        
$this->set_obj_id$object_id );

        
$this->taxonomy_exists$taxonomy );

        
$field Utils\get_flag_value$assoc_args'by' );
        if ( 
$field ) {
            
$terms $this->prepare_terms$field$terms$taxonomy );
        }
        
$result wp_set_object_terms$object_id$terms$taxonomyfalse );

        
$label count$terms ) > 'terms' 'term';
        if ( ! 
is_wp_error$result ) ) {
            
WP_CLI::success"Set {$label}." );
        } else {
            
WP_CLI::error"Failed to set {$label}." );
        }
    }

    
/**
     * Check if taxonomy exists
     *
     * @param $taxonomy
     */
    
protected function taxonomy_exists$taxonomy ) {

        
$taxonomy_names get_object_taxonomies$this->get_object_type() );

        if ( ! 
in_array$taxonomy$taxonomy_namestrue ) ) {
            
WP_CLI::error"Invalid taxonomy {$taxonomy}." );
        }
    }

    
/**
     * Prepare terms if `--by=<field>` flag is used
     *
     * @param array $terms
     * @param string $field
     * @param string $taxonomy
     */
    
protected function prepare_terms$field$terms$taxonomy ) {
        if ( 
'id' === $field ) {
            
$new_terms = [];
            foreach ( 
$terms as $term_id ) {
                
$term get_term_by'term_id'$term_id$taxonomy );
                if ( 
$term ) {
                    
$new_terms[] = $term->slug;
                }
            }
            
$terms $new_terms;
        }
        return 
$terms;
    }

    
/**
     * Set obj_id Class variable
     *
     * @param string $obj_id
     */
    
protected function set_obj_id$obj_id ) {
        
$this->obj_id $obj_id;
    }

    
/**
     * Get obj_id Class variable
     *
     * @return string
     */
    
protected function get_obj_id() {
        return 
$this->obj_id;
    }


    
/**
     * Get obj_type Class variable
     *
     * @return string $obj_type
     */
    
protected function get_object_type() {
        return 
$this->obj_type;
    }

    
/**
     * Get Formatter object based on supplied parameters.
     *
     * @param array $assoc_args Parameters passed to command. Determines formatting.
     *
     * @return WP_CLI\Formatter
     */
    
protected function get_formatter( &$assoc_args ) {
        return new 
Formatter$assoc_args$this->obj_fields$this->obj_type );
    }
}

<?
php

namespace WP_CLI;

use 
Exception;
use 
WP_CLI;
use 
WP_CLI_Command;
use 
WP_CLI\Entity\RecursiveDataStructureTraverser;
use 
WP_CLI\Entity\Utils as EntityUtils;

/**
 * Base class for WP-CLI commands that deal with metadata
 *
 * @package wp-cli
 */
abstract class CommandWithMeta extends WP_CLI_Command {

    protected 
$meta_type;

    
/**
     * List all metadata associated with an object.
     *
     * ## OPTIONS
     *
     * <id>
     * : ID for the object.
     *
     * [--keys=<keys>]
     * : Limit output to metadata of specific keys.
     *
     * [--fields=<fields>]
     * : Limit the output to specific row fields. Defaults to id,meta_key,meta_value.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - yaml
     *   - count
     * ---
     *
     * [--orderby=<fields>]
     * : Set orderby which field.
     * ---
     * default: id
     * options:
     *  - id
     *  - meta_key
     *  - meta_value
     * ---
     *
     * [--order=<order>]
     * : Set ascending or descending order.
     * ---
     * default: asc
     * options:
     *  - asc
     *  - desc
     * ---
     *
     * [--unserialize]
     * : Unserialize meta_value output.
     *
     * @subcommand list
     */
    
public function list_$args$assoc_args ) {

        list( 
$object_id ) = $args;

        
$keys = ! empty( $assoc_args['keys'] ) ? explode','$assoc_args['keys'] ) : [];

        
$object_id $this->check_object_id$object_id );

        
$metadata $this->get_metadata$object_id );
        if ( ! 
$metadata ) {
            
$metadata = [];
        }

        
$items = [];
        foreach ( 
$metadata as $key => $values ) {

            
// Skip if not requested
            
if ( ! empty( $keys ) && ! in_array$key$keystrue ) ) {
                continue;
            }

            foreach ( 
$values as $item_value ) {

                if ( 
Utils\get_flag_value$assoc_args'unserialize' ) ) {
                    
$item_value maybe_unserialize$item_value );
                }

                
$items[] = (object) [
                    
"{$this->meta_type}_id" => $object_id,
                    
'meta_key'              => $key,
                    
'meta_value'            => $item_value,
                ];
            }
        }

        
$order   Utils\get_flag_value$assoc_args'order' );
        
$orderby Utils\get_flag_value$assoc_args'orderby' );

        if ( 
'id' !== $orderby ) {

            
usort(
                
$items,
                function ( 
$a$b ) use ( $orderby$order ) {
                    
// Sort array.
                    
return 'asc' === $order
                        
$a->$orderby $b->$orderby
                        
$a->$orderby $b->$orderby;
                }
            );

        } elseif ( 
'id' === $orderby && 'desc' === $order ) { // Sort by default descending.
            
krsort$items );
        }

        if ( ! empty( 
$assoc_args['fields'] ) ) {
            
$fields explode','$assoc_args['fields'] );
        } else {
            
$fields $this->get_fields();
        }

        
$formatter = new Formatter$assoc_args$fields$this->meta_type );
        
$formatter->display_items$items );

    }

    
/**
     * Get meta field value.
     *
     * ## OPTIONS
     *
     * <id>
     * : The ID of the object.
     *
     * <key>
     * : The name of the meta field to get.
     *
     * [--format=<format>]
     * : Get value in a particular format.
     * ---
     * default: var_export
     * options:
     *   - var_export
     *   - json
     *   - yaml
     * ---
     */
    
public function get$args$assoc_args ) {
        list( 
$object_id$meta_key ) = $args;

        
$object_id $this->check_object_id$object_id );

        
$value $this->get_metadata$object_id$meta_keytrue );

        if ( 
'' === $value ) {
            die( 
);
        }

        
WP_CLI::print_value$value$assoc_args );
    }

    
/**
     * Delete a meta field.
     *
     * ## OPTIONS
     *
     * <id>
     * : The ID of the object.
     *
     * [<key>]
     * : The name of the meta field to delete.
     *
     * [<value>]
     * : The value to delete. If omitted, all rows with key will deleted.
     *
     * [--all]
     * : Delete all meta for the object.
     */
    
public function delete$args$assoc_args ) {
        list( 
$object_id ) = $args;

        
$meta_key   = ! empty( $args[1] ) ? $args[1] : '';
        
$meta_value = ! empty( $args[2] ) ? $args[2] : '';

        if ( empty( 
$meta_key ) && ! Utils\get_flag_value$assoc_args'all' ) ) {
            
WP_CLI::error'Please specify a meta key, or use the --all flag.' );
        }

        
$object_id $this->check_object_id$object_id );

        if ( 
Utils\get_flag_value$assoc_args'all' ) ) {
            
$errors false;
            foreach ( 
$this->get_metadata$object_id ) as $meta_key => $values ) {
                
$success $this->delete_metadata$object_id$meta_key );
                if ( 
$success ) {
                    
WP_CLI::log"Deleted '{$meta_key}' custom field." );
                } else {
                    
WP_CLI::warning"Failed to delete '{$meta_key}' custom field." );
                    
$errors true;
                }
            }
            if ( 
$errors ) {
                
WP_CLI::error'Failed to delete all custom fields.' );
            } else {
                
WP_CLI::success'Deleted all custom fields.' );
            }
        } else {
            
$success $this->delete_metadata$object_id$meta_key$meta_value );
            if ( 
$success ) {
                
WP_CLI::success'Deleted custom field.' );
            } else {
                
WP_CLI::error'Failed to delete custom field.' );
            }
        }
    }

    
/**
     * Add a meta field.
     *
     * ## OPTIONS
     *
     * <id>
     * : The ID of the object.
     *
     * <key>
     * : The name of the meta field to create.
     *
     * [<value>]
     * : The value of the meta field. If omitted, the value is read from STDIN.
     *
     * [--format=<format>]
     * : The serialization format for the value.
     * ---
     * default: plaintext
     * options:
     *   - plaintext
     *   - json
     * ---
     */
    
public function add$args$assoc_args ) {
        list( 
$object_id$meta_key ) = $args;

        
$meta_value WP_CLI::get_value_from_arg_or_stdin$args);
        
$meta_value WP_CLI::read_value$meta_value$assoc_args );

        
$object_id $this->check_object_id$object_id );

        
$meta_value wp_slash$meta_value );
        
$success    $this->add_metadata$object_id$meta_key$meta_value );

        if ( 
$success ) {
            
WP_CLI::success'Added custom field.' );
        } else {
            
WP_CLI::error'Failed to add custom field.' );
        }
    }

    
/**
     * Update a meta field.
     *
     * ## OPTIONS
     *
     * <id>
     * : The ID of the object.
     *
     * <key>
     * : The name of the meta field to update.
     *
     * [<value>]
     * : The new value. If omitted, the value is read from STDIN.
     *
     * [--format=<format>]
     * : The serialization format for the value.
     * ---
     * default: plaintext
     * options:
     *   - plaintext
     *   - json
     * ---
     *
     * @alias set
     */
    
public function update$args$assoc_args ) {
        list( 
$object_id$meta_key ) = $args;

        
$meta_value WP_CLI::get_value_from_arg_or_stdin$args);
        
$meta_value WP_CLI::read_value$meta_value$assoc_args );

        
$object_id $this->check_object_id$object_id );

        
$meta_value sanitize_meta$meta_key$meta_value$this->meta_type );
        
$old_value  sanitize_meta$meta_key$this->get_metadata$object_id$meta_keytrue ), $this->meta_type );

        if ( 
$meta_value === $old_value ) {
            
WP_CLI::success"Value passed for custom field '{$meta_key}' is unchanged." );
        } else {
            
$meta_value wp_slash$meta_value );
            
$success    $this->update_metadata$object_id$meta_key$meta_value );

            if ( 
$success ) {
                
WP_CLI::success"Updated custom field '{$meta_key}'." );
            } else {
                
WP_CLI::error"Failed to update custom field '{$meta_key}'." );
            }
        }

    }

    
/**
     * Get a nested value from a meta field.
     *
     * ## OPTIONS
     *
     * <id>
     * : The ID of the object.
     *
     * <key>
     * : The name of the meta field to get.
     *
     * <key-path>...
     * : The name(s) of the keys within the value to locate the value to pluck.
     *
     * [--format=<format>]
     * : The output format of the value.
     * ---
     * default: plaintext
     * options:
     *   - plaintext
     *   - json
     *   - yaml
     */
    
public function pluck$args$assoc_args ) {
        list( 
$object_id$meta_key ) = $args;
        
$object_id                    $this->check_object_id$object_id );
        
$key_path                     array_map(
            function ( 
$key ) {
                if ( 
is_numeric$key ) && ( (string) intval$key ) === $key ) ) {
                    return (int) 
$key;
                }
                return 
$key;
            },
            
array_slice$args)
        );

        
$value $this->get_metadata$object_id$meta_keytrue );

        
$traverser = new RecursiveDataStructureTraverser$value );

        try {
            
$value $traverser->get$key_path );
        } catch ( 
Exception $exception ) {
            die( 
);
        }

        
WP_CLI::print_value$value$assoc_args );
    }

    
/**
     * Update a nested value for a meta field.
     *
     * ## OPTIONS
     *
     * <action>
     * : Patch action to perform.
     * ---
     * options:
     *   - insert
     *   - update
     *   - delete
     * ---
     *
     * <id>
     * : The ID of the object.
     *
     * <key>
     * : The name of the meta field to update.
     *
     * <key-path>...
     * : The name(s) of the keys within the value to locate the value to patch.
     *
     * [<value>]
     * : The new value. If omitted, the value is read from STDIN.
     *
     * [--format=<format>]
     * : The serialization format for the value.
     * ---
     * default: plaintext
     * options:
     *   - plaintext
     *   - json
     * ---
     */
    
public function patch$args$assoc_args ) {
        list( 
$action$object_id$meta_key ) = $args;
        
$object_id                             $this->check_object_id$object_id );
        
$key_path                              array_map(
            function( 
$key ) {
                if ( 
is_numeric$key ) && ( (string) intval$key ) === $key ) ) {
                    return (int) 
$key;
                }
                    return 
$key;
            },
            
array_slice$args)
        );

        if ( 
'delete' === $action ) {
            
$patch_value null;
        } else {
            
$stdin_value EntityUtils::has_stdin()
                ? 
trimWP_CLI::get_value_from_arg_or_stdin$args, -) )
                : 
null;
            
$patch_value = ! empty( $stdin_value )
                ? 
WP_CLI::read_value$stdin_value$assoc_args )
                : 
WP_CLI::read_valuearray_pop$key_path ), $assoc_args );
        }

        
/* Need to make a copy of $current_meta_value here as it is modified by reference */
        
$current_meta_value sanitize_meta$meta_key$this->get_metadata$object_id$meta_keytrue ), $this->meta_type );
        
$old_meta_value     $current_meta_value;
        if ( 
is_object$current_meta_value ) ) {
            
$old_meta_value = clone $current_meta_value;
        }

        
$traverser = new RecursiveDataStructureTraverser$current_meta_value );

        try {
            
$traverser->$action$key_path$patch_value );
        } catch ( 
Exception $exception ) {
            
WP_CLI::error$exception->getMessage() );
        }

        
$patched_meta_value sanitize_meta$meta_key$traverser->value(), $this->meta_type );

        if ( 
$patched_meta_value === $old_meta_value ) {
            
WP_CLI::success"Value passed for custom field '{$meta_key}' is unchanged." );
        } else {
            
$slashed wp_slash$patched_meta_value );
            
$success $this->update_metadata$object_id$meta_key$slashed );

            if ( 
$success ) {
                
WP_CLI::success"Updated custom field '{$meta_key}'." );
            } else {
                
WP_CLI::error"Failed to update custom field '{$meta_key}'." );
            }
        }
    }

    
/**
     * Wrapper method for add_metadata that can be overridden in sub classes.
     *
     * @param int    $object_id  ID of the object the metadata is for.
     * @param string $meta_key   Metadata key to use.
     * @param mixed  $meta_value Metadata value. Must be serializable if
     *                           non-scalar.
     * @param bool   $unique     Optional, default is false. Whether the
     *                           specified metadata key should be unique for the
     *                           object. If true, and the object already has a
     *                           value for the specified metadata key, no change
     *                           will be made.
     *
     * @return int|false The meta ID on success, false on failure.
     */
    
protected function add_metadata$object_id$meta_key$meta_value$unique false ) {
        return 
add_metadata$this->meta_type$object_id$meta_key$meta_value$unique );
    }

    
/**
     * Wrapper method for update_metadata that can be overridden in sub classes.
     *
     * @param int    $object_id  ID of the object the metadata is for.
     * @param string $meta_key   Metadata key to use.
     * @param mixed  $meta_value Metadata value. Must be serializable if
     *                           non-scalar.
     * @param mixed  $prev_value Optional. If specified, only update existing
     *                           metadata entries with the specified value.
     *                           Otherwise, update all entries.
     *
     * @return int|bool Meta ID if the key didn't exist, true on successful
     *                  update, false on failure.
     */
    
protected function update_metadata$object_id$meta_key$meta_value$prev_value '' ) {
        return 
update_metadata$this->meta_type$object_id$meta_key$meta_value$prev_value );
    }

    
/**
     * Wrapper method for get_metadata that can be overridden in sub classes.
     *
     * @param int    $object_id ID of the object the metadata is for.
     * @param string $meta_key  Optional. Metadata key. If not specified,
     *                          retrieve all metadata for the specified object.
     * @param bool   $single    Optional, default is false. If true, return only
     *                          the first value of the specified meta_key. This
     *                          parameter has no effect if meta_key is not
     *                          specified.
     *
     * @return mixed Single metadata value, or array of values.
     */
    
protected function get_metadata$object_id$meta_key ''$single false ) {
        return 
get_metadata$this->meta_type$object_id$meta_key$single );
    }

    
/**
     * Wrapper method for delete_metadata that can be overridden in sub classes.
     *
     * @param int    $object_id  ID of the object metadata is for
     * @param string $meta_key   Metadata key
     * @param mixed $meta_value  Optional. Metadata value. Must be serializable
     *                           if non-scalar. If specified, only delete
     *                           metadata entries with this value. Otherwise,
     *                           delete all entries with the specified meta_key.
     *                           Pass `null, `false`, or an empty string to skip
     *                           this check. For backward compatibility, it is
     *                           not possible to pass an empty string to delete
     *                           those entries with an empty string for a value.
     *
     * @return bool True on successful delete, false on failure.
     */
    
protected function delete_metadata$object_id$meta_key$meta_value '' ) {
        return 
delete_metadata$this->meta_type$object_id$meta_key$meta_valuefalse );
    }

    
/**
     * Get the fields for this object's meta
     *
     * @return array
     */
    
private function get_fields() {
        return [
            
"{$this->meta_type}_id",
            
'meta_key',
            
'meta_value',
        ];
    }

    
/**
     * Check that the object ID exists
     *
     * @param int
     */
    
protected function check_object_id$object_id ) {
        
// Needs to be set in subclass
        
return $object_id;
    }

}
<?
php

use WP_CLI\CommandWithMeta;
use 
WP_CLI\Fetchers\Post as PostFetcher;

/**
 * Adds, updates, deletes, and lists post custom fields.
 *
 * ## EXAMPLES
 *
 *     # Set post meta
 *     $ wp post meta set 123 _wp_page_template about.php
 *     Success: Updated custom field '_wp_page_template'.
 *
 *     # Get post meta
 *     $ wp post meta get 123 _wp_page_template
 *     about.php
 *
 *     # Update post meta
 *     $ wp post meta update 123 _wp_page_template contact.php
 *     Success: Updated custom field '_wp_page_template'.
 *
 *     # Delete post meta
 *     $ wp post meta delete 123 _wp_page_template
 *     Success: Deleted custom field.
 */
class Post_Meta_Command extends CommandWithMeta {
    protected 
$meta_type 'post';

    
/**
     * Check that the post ID exists
     *
     * @param int
     */
    
protected function check_object_id$object_id ) {
        
$fetcher = new PostFetcher();
        
$post    $fetcher->get_check$object_id );
        return 
$post->ID;
    }

    
/**
     * Wrapper method for add_metadata that can be overridden in sub classes.
     *
     * @param int    $object_id  ID of the object the metadata is for.
     * @param string $meta_key   Metadata key to use.
     * @param mixed  $meta_value Metadata value. Must be serializable if
     *                           non-scalar.
     * @param bool   $unique     Optional, default is false. Whether the
     *                           specified metadata key should be unique for the
     *                           object. If true, and the object already has a
     *                           value for the specified metadata key, no change
     *                           will be made.
     *
     * @return int|false The meta ID on success, false on failure.
     */
    
protected function add_metadata$object_id$meta_key$meta_value$unique false ) {
        return 
add_post_meta$object_id$meta_key$meta_value$unique );
    }

    
/**
     * Wrapper method for update_metadata that can be overridden in sub classes.
     *
     * @param int    $object_id  ID of the object the metadata is for.
     * @param string $meta_key   Metadata key to use.
     * @param mixed  $meta_value Metadata value. Must be serializable if
     *                           non-scalar.
     * @param mixed  $prev_value Optional. If specified, only update existing
     *                           metadata entries with the specified value.
     *                           Otherwise, update all entries.
     *
     * @return int|bool Meta ID if the key didn't exist, true on successful
     *                  update, false on failure.
     */
    
protected function update_metadata$object_id$meta_key$meta_value$prev_value '' ) {
        return 
update_post_meta$object_id$meta_key$meta_value$prev_value );
    }

    
/**
     * Wrapper method for get_metadata that can be overridden in sub classes.
     *
     * @param int    $object_id ID of the object the metadata is for.
     * @param string $meta_key  Optional. Metadata key. If not specified,
     *                          retrieve all metadata for the specified object.
     * @param bool   $single    Optional, default is false. If true, return only
     *                          the first value of the specified meta_key. This
     *                          parameter has no effect if meta_key is not
     *                          specified.
     *
     * @return mixed Single metadata value, or array of values.
     */
    
protected function get_metadata$object_id$meta_key ''$single false ) {
        return 
get_post_meta$object_id$meta_key$single );
    }

    
/**
     * Wrapper method for delete_metadata that can be overridden in sub classes.
     *
     * @param int    $object_id  ID of the object metadata is for
     * @param string $meta_key   Metadata key
     * @param mixed $meta_value  Optional. Metadata value. Must be serializable
     *                           if non-scalar. If specified, only delete
     *                           metadata entries with this value. Otherwise,
     *                           delete all entries with the specified meta_key.
     *                           Pass `null, `false`, or an empty string to skip
     *                           this check. For backward compatibility, it is
     *                           not possible to pass an empty string to delete
     *                           those entries with an empty string for a value.
     *
     * @return bool True on successful delete, false on failure.
     */
    
protected function delete_metadata$object_id$meta_key$meta_value '' ) {
        return 
delete_post_meta$object_id$meta_key$meta_value );
    }
}
<?
php

use WP_CLI\CommandWithDBObject;
use 
WP_CLI\Entity\Utils as EntityUtils;
use 
WP_CLI\Fetchers\Site as SiteFetcher;
use 
WP_CLI\Fetchers\User as UserFetcher;
use 
WP_CLI\Formatter;
use 
WP_CLI\Iterators\CSV as CsvIterator;
use 
WP_CLI\Utils;

/**
 * Manages users, along with their roles, capabilities, and meta.
 *
 * See references for [Roles and Capabilities](https://codex.wordpress.org/Roles_and_Capabilities) and [WP User class](https://codex.wordpress.org/Class_Reference/WP_User).
 *
 * ## EXAMPLES
 *
 *     # List user IDs
 *     $ wp user list --field=ID
 *     1
 *
 *     # Create a new user.
 *     $ wp user create bob [email protected] --role=author
 *     Success: Created user 3.
 *     Password: k9**&I4vNH(&
 *
 *     # Update an existing user.
 *     $ wp user update 123 --display_name=Mary --user_pass=marypass
 *     Success: Updated user 123.
 *
 *     # Delete user 123 and reassign posts to user 567
 *     $ wp user delete 123 --reassign=567
 *     Success: Removed user 123 from http://example.com
 *
 * @package wp-cli
 */
class User_Command extends CommandWithDBObject {

    protected 
$obj_type   'user';
    protected 
$obj_fields = [
        
'ID',
        
'user_login',
        
'display_name',
        
'user_email',
        
'user_registered',
        
'roles',
    ];

    private 
$cap_fields = [
        
'name',
    ];

    public function 
__construct() {
        
$this->fetcher     = new UserFetcher();
        
$this->sitefetcher = new SiteFetcher();
    }

    
/**
     * Lists users.
     *
     * Display WordPress users based on all arguments supported by
     * [WP_User_Query()](https://developer.wordpress.org/reference/classes/wp_user_query/prepare_query/).
     *
     * ## OPTIONS
     *
     * [--role=<role>]
     * : Only display users with a certain role.
     *
     * [--<field>=<value>]
     * : Control output by one or more arguments of WP_User_Query().
     *
     * [--network]
     * : List all users in the network for multisite.
     *
     * [--field=<field>]
     * : Prints the value of a single field for each user.
     *
     * [--fields=<fields>]
     * : Limit the output to specific object fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - ids
     *   - json
     *   - count
     *   - yaml
     * ---
     *
     * ## AVAILABLE FIELDS
     *
     * These fields will be displayed by default for each user:
     *
     * * ID
     * * user_login
     * * display_name
     * * user_email
     * * user_registered
     * * roles
     *
     * These fields are optionally available:
     *
     * * user_pass
     * * user_nicename
     * * user_url
     * * user_activation_key
     * * user_status
     * * spam
     * * deleted
     * * caps
     * * cap_key
     * * allcaps
     * * filter
     * * url
     *
     * ## EXAMPLES
     *
     *     # List user IDs
     *     $ wp user list --field=ID
     *     1
     *
     *     # List users with administrator role
     *     $ wp user list --role=administrator --format=csv
     *     ID,user_login,display_name,user_email,user_registered,roles
     *     1,supervisor,supervisor,[email protected],"2016-06-03 04:37:00",administrator
     *
     *     # List users with only given fields
     *     $ wp user list --fields=display_name,user_email --format=json
     *     [{"display_name":"supervisor","user_email":"[email protected]"}]
     *
     *     # List users ordered by the 'last_activity' meta value.
     *     $ wp user list --meta_key=last_activity --orderby=meta_value_num
     *
     * @subcommand list
     */
    
public function list_$args$assoc_args ) {

        if ( 
Utils\get_flag_value$assoc_args'network' ) ) {
            if ( ! 
is_multisite() ) {
                
WP_CLI::error'This is not a multisite installation.' );
            }
            
$assoc_args['blog_id'] = 0;
            if ( isset( 
$assoc_args['fields'] ) ) {
                
$fields               explode','$assoc_args['fields'] );
                
$assoc_args['fields'] = array_diff$fields, [ 'roles' ] );
            } else {
                
$assoc_args['fields'] = array_diff$this->obj_fields, [ 'roles' ] );
            }
        }

        
$formatter $this->get_formatter$assoc_args );

        if ( 
in_array$formatter->format, [ 'ids''count' ], true ) ) {
            
$assoc_args['fields'] = 'ids';
        } else {
            
$assoc_args['fields'] = 'all_with_meta';
        }

        
$assoc_args['count_total'] = false;
        
$assoc_args                self::process_csv_arguments_to_arrays$assoc_args );
        
$users                     get_users$assoc_args );

        if ( 
'ids' === $formatter->format ) {
            echo 
implode' '$users );
        } elseif ( 
'count' === $formatter->format ) {
            
$formatter->display_items$users );
        } else {
            
$iterator Utils\iterator_map(
                
$users,
                function ( 
$user ) {
                    if ( ! 
is_object$user ) ) {
                        return 
$user;
                    }

                    
$user->roles implode','$user->roles );
                    
$user->url   get_author_posts_url$user->ID$user->user_nicename );
                    return 
$user;
                }
            );

            
$formatter->display_items$iterator );
        }
    }

    
/**
     * Gets details about a user.
     *
     * ## OPTIONS
     *
     * <user>
     * : User ID, user email, or user login.
     *
     * [--field=<field>]
     * : Instead of returning the whole user, returns the value of a single field.
     *
     * [--fields=<fields>]
     * : Get a specific subset of the user's fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - yaml
     * ---
     *
     * ## EXAMPLES
     *
     *     # Get user
     *     $ wp user get 12 --field=login
     *     supervisor
     *
     *     # Get user and export to JSON file
     *     $ wp user get bob --format=json > bob.json
     */
    
public function get$args$assoc_args ) {
        
$user               $this->fetcher->get_check$args[0] );
        
$user_data          $user->to_array();
        
$user_data['roles'] = implode', '$user->roles );

        
$formatter $this->get_formatter$assoc_args );
        
$formatter->display_item$user_data );
    }

    
/**
     * Deletes one or more users from the current site.
     *
     * On multisite, `wp user delete` only removes the user from the current
     * site. Include `--network` to also remove the user from the database, but
     * make sure to reassign their posts prior to deleting the user.
     *
     * ## OPTIONS
     *
     * <user>...
     * : The user login, user email, or user ID of the user(s) to delete.
     *
     * [--network]
     * : On multisite, delete the user from the entire network.
     *
     * [--reassign=<user-id>]
     * : User ID to reassign the posts to.
     *
     * [--yes]
     * : Answer yes to any confirmation prompts.
     *
     * ## EXAMPLES
     *
     *     # Delete user 123 and reassign posts to user 567
     *     $ wp user delete 123 --reassign=567
     *     Success: Removed user 123 from http://example.com
     *
     *     # Delete all contributors and reassign their posts to user 2
     *     $ wp user delete $(wp user list --role=contributor --field=ID) --reassign=2
     *     Success: Removed user 813 from http://example.com
     *     Success: Removed user 578 from http://example.com
     */
    
public function delete$args$assoc_args ) {
        
$network  Utils\get_flag_value$assoc_args'network' ) && is_multisite();
        
$reassign Utils\get_flag_value$assoc_args'reassign' );

        if ( 
$network && $reassign ) {
            
WP_CLI::error'Reassigning content to a different user is not supported on multisite.' );
        }

        if ( ! 
$reassign ) {
            
WP_CLI::confirm'--reassign parameter not passed. All associated posts will be deleted. Proceed?'$assoc_args );
        }

        
$users $this->fetcher->get_many$args );

        
parent::_delete(
            
$users,
            
$assoc_args,
            function ( 
$user ) use ( $network$reassign ) {
                
$user_id $user->ID;

                if ( 
$network ) {
                    
$result  wpmu_delete_user$user_id );
                    
$message "Deleted user {$user_id}.";
                } else {
                    
$result  wp_delete_user$user_id$reassign );
                    
$message "Removed user {$user_id} from " home_url() . '.';
                }

                if ( ! 
$result ) {
                    
$message "Failed deleting user {$user_id}.";

                    if ( 
is_multisite() && is_super_admin$user_id ) ) {
                        
$message .= ' The user is a super admin.';
                    }

                    return [ 
'error'$message ];
                }

                return [ 
'success'$message ];
            }
        );
    }

    
/**
     * Creates a new user.
     *
     * ## OPTIONS
     *
     * <user-login>
     * : The login of the user to create.
     *
     * <user-email>
     * : The email address of the user to create.
     *
     * [--role=<role>]
     * : The role of the user to create. Default: default role. Possible values
     * include 'administrator', 'editor', 'author', 'contributor', 'subscriber'.
     *
     * [--user_pass=<password>]
     * : The user password. Default: randomly generated.
     *
     * [--user_registered=<yyyy-mm-dd-hh-ii-ss>]
     * : The date the user registered. Default: current date.
     *
     * [--display_name=<name>]
     * : The display name.
     *
     * [--user_nicename=<nice_name>]
     * : A string that contains a URL-friendly name for the user. The default is the user's username.
     *
     * [--user_url=<url>]
     * : A string containing the user's URL for the user's web site.
     *
     * [--nickname=<nickname>]
     * : The user's nickname, defaults to the user's username.
     *
     * [--first_name=<first_name>]
     * : The user's first name.
     *
     * [--last_name=<last_name>]
     * : The user's last name.
     *
     * [--description=<description>]
     * : A string containing content about the user.
     *
     * [--rich_editing=<rich_editing>]
     * : A string for whether to enable the rich editor or not. False if not empty.
     *
     * [--send-email]
     * : Send an email to the user with their new account details.
     *
     * [--porcelain]
     * : Output just the new user id.
     *
     * ## EXAMPLES
     *
     *     # Create user
     *     $ wp user create bob [email protected] --role=author
     *     Success: Created user 3.
     *     Password: k9**&I4vNH(&
     *
     *     # Create user without showing password upon success
     *     $ wp user create ann [email protected] --porcelain
     *     4
     */
    
public function create$args$assoc_args ) {
        
$user = new stdClass();

        list( 
$user->user_login$user->user_email ) = $args;

        
$assoc_args wp_slash$assoc_args );

        if ( 
username_exists$user->user_login ) ) {
            
WP_CLI::error"The '{$user->user_login}' username is already registered." );
        }

        if ( ! 
is_email$user->user_email ) ) {
            
WP_CLI::error"The '{$user->user_email}' email address is invalid." );
        }

        
$user->user_registered Utils\get_flag_value(
            
$assoc_args,
            
'user_registered',
            
date_formatdate_create(), '%F %T' )
        );

        
$user->display_name Utils\get_flag_value$assoc_args'display_name'false );

        
$user->first_name Utils\get_flag_value$assoc_args'first_name'false );

        
$user->last_name Utils\get_flag_value$assoc_args'last_name'false );

        
$user->description Utils\get_flag_value$assoc_args'description'false );

        if ( isset( 
$assoc_args['user_pass'] ) ) {
            
$user->user_pass $assoc_args['user_pass'];
        } else {
            
$user->user_pass wp_generate_password24 );
            
$generated_pass  true;
        }

        
$user->role Utils\get_flag_value$assoc_args'role'get_option'default_role' ) );
        
self::validate_role$user->role );

        if ( ! 
Utils\get_flag_value$assoc_args'send-email' ) ) {
            
add_filter'send_password_change_email''__return_false' );
            
add_filter'send_email_change_email''__return_false' );
        }

        if ( 
is_multisite() ) {
            
$result wpmu_validate_user_signup$user->user_login$user->user_email );
            if ( 
is_wp_error$result['errors'] ) && ! empty( $result['errors']->errors ) ) {
                
WP_CLI::error$result['errors'] );
            }
            
$user_id wpmu_create_user$user->user_login$user->user_pass$user->user_email );
            if ( ! 
$user_id ) {
                
WP_CLI::error'Unknown error creating new user.' );
            }
            
$user->ID $user_id;
            
$user_id  wp_update_user$user );
            if ( 
is_wp_error$user_id ) ) {
                
WP_CLI::error$user_id );
            }
        } else {
            
$user_id wp_insert_user$user );
        }

        if ( ! 
$user_id || is_wp_error$user_id ) ) {
            if ( ! 
$user_id ) {
                
$user_id 'Unknown error creating new user.';
            }
            
WP_CLI::error$user_id );
        } else {
            if ( 
false === $user->role ) {
                
delete_user_option$user_id'capabilities' );
                
delete_user_option$user_id'user_level' );
            }
        }

        if ( 
Utils\get_flag_value$assoc_args'send-email' ) ) {
            
self::wp_new_user_notification$user_id$user->user_pass );
        }

        if ( 
Utils\get_flag_value$assoc_args'porcelain' ) ) {
            
WP_CLI::line$user_id );
        } else {
            
WP_CLI::success"Created user {$user_id}." );
            if ( isset( 
$generated_pass ) ) {
                
WP_CLI::line"Password: {$user->user_pass});
            }
        }
    }

    
/**
     * Updates an existing user.
     *
     * ## OPTIONS
     *
     * <user>...
     * : The user login, user email or user ID of the user(s) to update.
     *
     * [--user_pass=<password>]
     * : A string that contains the plain text password for the user.
     *
     * [--user_nicename=<nice_name>]
     * : A string that contains a URL-friendly name for the user. The default is the user's username.
     *
     * [--user_url=<url>]
     * : A string containing the user's URL for the user's web site.
     *
     * [--user_email=<email>]
     * : A string containing the user's email address.
     *
     * [--display_name=<display_name>]
     * : A string that will be shown on the site. Defaults to user's username.
     *
     * [--nickname=<nickname>]
     * : The user's nickname, defaults to the user's username.
     *
     * [--first_name=<first_name>]
     * : The user's first name.
     *
     * [--last_name=<last_name>]
     * : The user's last name.
     *
     * [--description=<description>]
     * : A string containing content about the user.
     *
     * [--rich_editing=<rich_editing>]
     * : A string for whether to enable the rich editor or not. False if not empty.
     *
     * [--user_registered=<yyyy-mm-dd-hh-ii-ss>]
     * : The date the user registered.
     *
     * [--role=<role>]
     * : A string used to set the user's role.
     *
     * --<field>=<value>
     * : One or more fields to update. For accepted fields, see wp_update_user().
     *
     * [--skip-email]
     * : Don't send an email notification to the user.
     *
     * ## EXAMPLES
     *
     *     # Update user
     *     $ wp user update 123 --display_name=Mary --user_pass=marypass
     *     Success: Updated user 123.
     */
    
public function update$args$assoc_args ) {
        if ( isset( 
$assoc_args['user_login'] ) ) {
            
WP_CLI::warning"User logins can't be changed." );
            unset( 
$assoc_args['user_login'] );
        }

        
$user_ids = [];
        foreach ( 
$this->fetcher->get_many$args ) as $user ) {
            
$user_ids[] = $user->ID;
        }

        
$skip_email Utils\get_flag_value$assoc_args'skip-email' );
        if ( 
$skip_email ) {
            
add_filter'send_email_change_email''__return_false' );
            
add_filter'send_password_change_email''__return_false' );
        }

        
$assoc_args wp_slash$assoc_args );
        
parent::_update$user_ids$assoc_args'wp_update_user' );

        if ( 
$skip_email ) {
            
remove_filter'send_email_change_email''__return_false' );
            
remove_filter'send_password_change_email''__return_false' );
        }
    }

    
/**
     * Generates some users.
     *
     * Creates a specified number of new users with dummy data.
     *
     * ## OPTIONS
     *
     * [--count=<number>]
     * : How many users to generate?
     * ---
     * default: 100
     * ---
     *
     * [--role=<role>]
     * : The role of the generated users. Default: default role from WP
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: progress
     * options:
     *   - progress
     *   - ids
     * ---
     *
     * ## EXAMPLES
     *
     *     # Add meta to every generated users.
     *     $ wp user generate --format=ids --count=3 | xargs -d ' ' -I % wp user meta add % foo bar
     *     Success: Added custom field.
     *     Success: Added custom field.
     *     Success: Added custom field.
     */
    
public function generate$args$assoc_args ) {
        global 
$blog_id;

        
$defaults   = [
            
'count' => 100,
            
'role'  => get_option'default_role' ),
        ];
        
$assoc_args array_merge$defaults$assoc_args );

        
$role $assoc_args['role'];

        if ( ! empty( 
$role ) ) {
            
self::validate_role$role );
        }

        
$user_count count_users();
        
$total      $user_count['total_users'];
        
$limit      $assoc_args['count'] + $total;

        
$format Utils\get_flag_value$assoc_args'format''progress' );

        
$notify false;
        if ( 
'progress' === $format ) {
            
$notify Utils\make_progress_bar'Generating users'$assoc_args['count'] );
        }

        for ( 
$index $total$index $limit$index++ ) {
            
$login "user_{$blog_id}_{$index}";
            
$name  "User {$index}";

            
$user_id wp_insert_user(
                [
                    
'user_login'   => $login,
                    
'user_pass'    => $login,
                    
'nickname'     => $name,
                    
'display_name' => $name,
                    
'role'         => $role,
                ]
            );

            if ( 
false === $role ) {
                
delete_user_option$user_id'capabilities' );
                
delete_user_option$user_id'user_level' );
            }

            if ( 
'progress' === $format ) {
                
$notify->tick();
            } elseif ( 
'ids' === $format ) {
                echo 
$user_id;
                if ( 
$index $limit ) {
                    echo 
' ';
                }
            }
        }

        if ( 
'progress' === $format ) {
            
$notify->finish();
        }
    }

    
/**
     * Sets the user role.
     *
     * ## OPTIONS
     *
     * <user>
     * : User ID, user email, or user login.
     *
     * [<role>]
     * : Make the user have the specified role. If not passed, the default role is
     * used.
     *
     * ## EXAMPLES
     *
     *     $ wp user set-role 12 author
     *     Success: Added johndoe (12) to http://example.com as author.
     *
     * @subcommand set-role
     */
    
public function set_role$args$assoc_args ) {
        
$user $this->fetcher->get_check$args[0] );

        
$role Utils\get_flag_value$args1get_option'default_role' ) );

        
self::validate_role$role );

        
// Multisite
        
if ( function_exists'add_user_to_blog' ) ) {
            
add_user_to_blogget_current_blog_id(), $user->ID$role );
        } else {
            
$user->set_role$role );
        }

        
WP_CLI::success"Added {$user->user_login} ({$user->ID}) to " site_url() . " as {$role}." );
    }

    
/**
     * Adds a role for a user.
     *
     * ## OPTIONS
     *
     * <user>
     * : User ID, user email, or user login.
     *
     * <role>
     * : Add the specified role to the user.
     *
     * ## EXAMPLES
     *
     *     $ wp user add-role 12 author
     *     Success: Added 'author' role for johndoe (12).
     *
     * @subcommand add-role
     */
    
public function add_role$args$assoc_args ) {
        
$user $this->fetcher->get_check$args[0] );

        
$role $args[1];

        
self::validate_role$role );

        
$user->add_role$role );

        
WP_CLI::success"Added '{$role}' role for {$user->user_login} ({$user->ID})." );
    }

    
/**
     * Removes a user's role.
     *
     * ## OPTIONS
     *
     * <user>
     * : User ID, user email, or user login.
     *
     * [<role>]
     * : A specific role to remove.
     *
     * ## EXAMPLES
     *
     *     $ wp user remove-role 12 author
     *     Success: Removed 'author' role for johndoe (12).
     *
     * @subcommand remove-role
     */
    
public function remove_role$args$assoc_args ) {
        
$user $this->fetcher->get_check$args[0] );

        if ( isset( 
$args[1] ) ) {
            
$role $args[1];

            
self::validate_role$role );

            
$user->remove_role$role );

            
WP_CLI::success"Removed '{$role}' role for {$user->user_login} ({$user->ID})." );
        } else {
            
// Multisite
            
if ( function_exists'remove_user_from_blog' ) ) {
                
remove_user_from_blog$user->IDget_current_blog_id() );
            } else {
                
$user->remove_all_caps();
            }

            
WP_CLI::success"Removed {$user->user_login} ({$user->ID}) from " site_url() . '.' );
        }
    }

    
/**
     * Adds a capability to a user.
     *
     * ## OPTIONS
     *
     * <user>
     * : User ID, user email, or user login.
     *
     * <cap>
     * : The capability to add.
     *
     * ## EXAMPLES
     *
     *     # Add a capability for a user
     *     $ wp user add-cap john create_premium_item
     *     Success: Added 'create_premium_item' capability for john (16).
     *
     *     # Add a capability for a user
     *     $ wp user add-cap 15 edit_product
     *     Success: Added 'edit_product' capability for johndoe (15).
     *
     * @subcommand add-cap
     */
    
public function add_cap$args$assoc_args ) {
        
$user $this->fetcher->get_check$args[0] );
        if ( 
$user ) {
            
$cap $args[1];
            
$user->add_cap$cap );

            
WP_CLI::success"Added '{$cap}' capability for {$user->user_login} ({$user->ID})." );
        }
    }

    
/**
     * Removes a user's capability.
     *
     * ## OPTIONS
     *
     * <user>
     * : User ID, user email, or user login.
     *
     * <cap>
     * : The capability to be removed.
     *
     * ## EXAMPLES
     *
     *     $ wp user remove-cap 11 publish_newsletters
     *     Success: Removed 'publish_newsletters' cap for supervisor (11).
     *
     *     $ wp user remove-cap 11 publish_posts
     *     Error: The 'publish_posts' cap for supervisor (11) is inherited from a role.
     *
     *     $ wp user remove-cap 11 nonexistent_cap
     *     Error: No such 'nonexistent_cap' cap for supervisor (11).
     *
     * @subcommand remove-cap
     */
    
public function remove_cap$args$assoc_args ) {
        
$user $this->fetcher->get_check$args[0] );
        if ( 
$user ) {
            
$cap $args[1];
            if ( ! isset( 
$user->caps$cap ] ) ) {
                if ( isset( 
$user->allcaps$cap ] ) ) {
                    
WP_CLI::error"The '{$cap}' cap for {$user->user_login} ({$user->ID}) is inherited from a role." );
                }
                
WP_CLI::error"No such '{$cap}' cap for {$user->user_login} ({$user->ID})." );
            }
            
$user->remove_cap$cap );

            
WP_CLI::success"Removed '{$cap}' cap for {$user->user_login} ({$user->ID})." );
        }
    }

    
/**
     * Lists all capabilities for a user.
     *
     * ## OPTIONS
     *
     * <user>
     * : User ID, user email, or login.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: list
     * options:
     *   - list
     *   - table
     *   - csv
     *   - json
     *   - count
     *   - yaml
     * ---
     *
     * ## EXAMPLES
     *
     *     $ wp user list-caps 21
     *     edit_product
     *     create_premium_item
     *
     * @subcommand list-caps
     */
    
public function list_caps$args$assoc_args ) {
        
$user $this->fetcher->get_check$args[0] );

        if ( 
$user ) {
            
$user->get_role_caps();

            
$user_caps_list $user->allcaps;

            
$active_user_cap_list = [];

            foreach ( 
$user_caps_list as $cap => $active ) {
                if ( 
$active ) {
                    
$active_user_cap_list[] = $cap;
                }
            }

            if ( 
'list' === $assoc_args['format'] ) {
                foreach ( 
$active_user_cap_list as $cap ) {
                    
WP_CLI::line$cap );
                }
            } else {
                
$output_caps = [];
                foreach ( 
$active_user_cap_list as $cap ) {
                    
$output_cap = new stdClass();

                    
$output_cap->name $cap;

                    
$output_caps[] = $output_cap;
                }
                
$formatter = new Formatter$assoc_args$this->cap_fields );
                
$formatter->display_items$output_caps );
            }
        }
    }

    
/**
     * Imports users from a CSV file.
     *
     * If the user already exists (matching the email address or login), then
     * the user is updated unless the `--skip-update` flag is used.
     *
     * ## OPTIONS
     *
     * <file>
     * : The local or remote CSV file of users to import. If '-', then reads from STDIN.
     *
     * [--send-email]
     * : Send an email to new users with their account details.
     *
     * [--skip-update]
     * : Don't update users that already exist.
     *
     * ## EXAMPLES
     *
     *     # Import users from local CSV file
     *     $ wp user import-csv /path/to/users.csv
     *     Success: bobjones created
     *     Success: newuser1 created
     *     Success: existinguser created
     *
     *     # Import users from remote CSV file
     *     $ wp user import-csv http://example.com/users.csv
     *
     *     Sample users.csv file:
     *
     *     user_login,user_email,display_name,role
     *     bobjones,[email protected],Bob Jones,contributor
     *     newuser1,[email protected],New User,author
     *     existinguser,[email protected],Existing User,administrator
     *
     * @subcommand import-csv
     */
    
public function import_csv$args$assoc_args ) {

        
$blog_users get_users();

        
$filename $args[0];

        if ( 
=== stripos$filename'http://' ) || === stripos$filename'https://' ) ) {
            
$response      wp_remote_head$filename );
            
$response_code = (string) wp_remote_retrieve_response_code$response );
            if ( 
in_array( (int) $response_code[0], [ 4], true ) ) {
                
WP_CLI::error"Couldn't access remote CSV file (HTTP {$response_code} response)." );
            }
        } elseif ( 
'-' === $filename ) {
            if ( ! 
EntityUtils::has_stdin() ) {
                
WP_CLI::error'Unable to read content from STDIN.' );
            }
        } elseif ( ! 
file_exists$filename ) ) {
            
WP_CLI::error"Missing file: {$filename});
        }

        
// Don't send core's emails during the creation / update process
        
add_filter'send_password_change_email''__return_false' );
        
add_filter'send_email_change_email''__return_false' );

        if ( 
'-' === $filename ) {
            
$file_object = new NoRewindIterator( new SplFileObject'php://stdin' ) );
            
$file_object->setFlagsSplFileObject::READ_CSV );
            
$csv_data = [];
            
$indexes  = [];
            foreach ( 
$file_object as $line ) {
                if ( empty( 
$line[0] ) ) {
                    continue;
                }

                if ( empty( 
$indexes ) ) {
                    
$indexes $line;
                    continue;
                }

                foreach ( 
$indexes as $n => $key ) {
                    
$data$key ] = $line$n ];
                }

                
$csv_data[] = $data;
            }
        } else {
            
$csv_data = new CsvIterator$filename );
        }

        foreach ( 
$csv_data as $new_user ) {
            
$defaults = [
                
'role'            => get_option'default_role' ),
                
'user_pass'       => wp_generate_password(),
                
'user_registered' => date_formatdate_create(), '%F %T' ),
                
'display_name'    => false,
            ];
            
$new_user array_merge$defaults$new_user );

            
$secondary_roles = [];
            if ( ! empty( 
$new_user['roles'] ) ) {
                
$roles        array_map'trim'explode','$new_user['roles'] ) );
                
$invalid_role false;
                foreach ( 
$roles as $role ) {
                    if ( 
null === get_role$role ) ) {
                        
WP_CLI::warning"{$new_user['user_login']} has an invalid role." );
                        
$invalid_role true;
                        break;
                    }
                }
                if ( 
$invalid_role ) {
                    continue;
                }
                
$new_user['role'] = array_shift$roles );
                
$secondary_roles  $roles;
            } elseif ( 
'none' === $new_user['role'] ) {
                
$new_user['role'] = false;
            } elseif ( 
null === get_role$new_user['role'] ) ) {
                
WP_CLI::warning"{$new_user['user_login']} has an invalid role." );
                continue;
            }

            
// User already exists and we just need to add them to the site if they aren't already there
            
$existing_user get_user_by'email'$new_user['user_email'] );

            if ( ! 
$existing_user ) {
                
$existing_user get_user_by'login'$new_user['user_login'] );
            }

            if ( 
$existing_user && Utils\get_flag_value$assoc_args'skip-update' ) ) {

                
WP_CLI::log"{$existing_user->user_login} exists and has been skipped." );
                continue;

            }

            if ( 
$existing_user ) {
                
$new_user['ID'] = $existing_user->ID;
                
$user_id        wp_update_user$new_user );

                if ( ! 
in_array$existing_user->user_loginwp_list_pluck$blog_users'user_login' ), true ) && is_multisite() && $new_user['role'] ) {
                    
add_user_to_blogget_current_blog_id(), $existing_user->ID$new_user['role'] );
                    
WP_CLI::log"{$existing_user->user_login} added as {$new_user['role']}." );
                }

                
// Create the user
            
} else {
                unset( 
$new_user['ID'] ); // Unset else it will just return the ID

                
if ( is_multisite() ) {
                    
$result wpmu_validate_user_signup$new_user['user_login'], $new_user['user_email'] );
                    if ( 
is_wp_error$result['errors'] ) && ! empty( $result['errors']->errors ) ) {
                        
WP_CLI::warning$result['errors'] );
                        continue;
                    }
                    
$user_id wpmu_create_user$new_user['user_login'], $new_user['user_pass'], $new_user['user_email'] );
                    if ( ! 
$user_id ) {
                        
WP_CLI::warning'Unknown error creating new user.' );
                        continue;
                    }
                    
$new_user['ID'] = $user_id;
                    
$user_id        wp_update_user$new_user );
                    if ( 
is_wp_error$user_id ) ) {
                        
WP_CLI::warning$user_id );
                        continue;
                    }
                } else {
                    
$user_id wp_insert_user$new_user );
                }

                if ( 
Utils\get_flag_value$assoc_args'send-email' ) ) {
                    
self::wp_new_user_notification$user_id$new_user['user_pass'] );
                }
            }

            if ( 
is_wp_error$user_id ) ) {
                
WP_CLI::warning$user_id );
                continue;

            }

            if ( 
false === $new_user['role'] ) {
                
delete_user_option$user_id'capabilities' );
                
delete_user_option$user_id'user_level' );
            }

            
$user get_user_by'id'$user_id );
            foreach ( 
$secondary_roles as $secondary_role ) {
                
$user->add_role$secondary_role );
            }

            if ( ! empty( 
$existing_user ) ) {
                
WP_CLI::success$new_user['user_login'] . ' updated.' );
            } else {
                
WP_CLI::success$new_user['user_login'] . ' created.' );
            }
        }
    }

    
/**
     * Resets the password for one or more users.
     *
     * ## OPTIONS
     *
     * <user>...
     * : one or more user logins or IDs.
     *
     * [--skip-email]
     * : Don't send an email notification to the affected user(s).
     *
     * ## EXAMPLES
     *
     *     # Reset the password for two users and send them the change email.
     *     $ wp user reset-password admin editor
     *     Reset password for admin.
     *     Reset password for editor.
     *     Success: Passwords reset for 2 users.
     *
     * @subcommand reset-password
     */
    
public function reset_password$args$assoc_args ) {
        
$skip_email Utils\get_flag_value$assoc_args'skip-email' );
        if ( 
$skip_email ) {
            
add_filter'send_password_change_email''__return_false' );
        }
        
$fetcher = new UserFetcher();
        
$users   $fetcher->get_many$args );
        foreach ( 
$users as $user ) {
            
wp_update_user(
                [
                    
'ID'        => $user->ID,
                    
'user_pass' => wp_generate_password(),
                ]
            );
            
WP_CLI::log"Reset password for {$user->user_login}." );
        }
        if ( 
$skip_email ) {
            
remove_filter'send_password_change_email''__return_false' );
        }

        
$reset_user_count count$users );
        if ( 
=== $reset_user_count ) {
            
WP_CLI::success"Password reset for {$reset_user_count} user." );
        } elseif ( 
$reset_user_count ) {
            
WP_CLI::success"Passwords reset for {$reset_user_count} users." );
        } else {
            
WP_CLI::error'No user found to reset password.' );
        }
    }

    
/**
     * Checks whether the role is valid
     *
     * @param string
     */
    
private static function validate_role$role ) {

        if ( ! empty( 
$role ) && null === get_role$role ) ) {
            
WP_CLI::error"Role doesn't exist: {$role});
        }

    }

    
/**
     * Accommodates three different behaviors for wp_new_user_notification()
     * - 4.3.1 and above: expect second argument to be deprecated
     * - 4.3: Second argument was repurposed as $notify
     * - Below 4.3: Send the password in the notification
     *
     * @param string $user_id
     * @param string $password
     */
    
public static function wp_new_user_notification$user_id$password ) {
        if ( 
Utils\wp_version_compare'4.3.1''>=' ) ) {
            
wp_new_user_notification$user_idnull'both' );
        } elseif ( 
Utils\wp_version_compare'4.3''>=' ) ) {
            
// phpcs:ignore WordPress.WP.DeprecatedParameters.Wp_new_user_notificationParam2Found -- Only called in valid conditions.
            
wp_new_user_notification$user_id'both' );
        } else {
            
// phpcs:ignore WordPress.WP.DeprecatedParameters.Wp_new_user_notificationParam2Found -- Only called in valid conditions.
            
wp_new_user_notification$user_id$password );
        }
    }

    
/**
     * Marks one or more users as spam.
     *
     * ## OPTIONS
     *
     * <id>...
     * : One or more IDs of users to mark as spam.
     *
     * ## EXAMPLES
     *
     *     $ wp user spam 123
     *     User 123 marked as spam.
     *     Success: Spamed 1 of 1 users.
     */
    
public function spam$args ) {
        
$this->update_msuser_status$args'spam''1' );
    }

    
/**
     * Removes one or more users from spam.
     *
     * ## OPTIONS
     *
     * <id>...
     * : One or more IDs of users to remove from spam.
     *
     * ## EXAMPLES
     *
     *     $ wp user unspam 123
     *     User 123 removed from spam.
     *     Success: Unspamed 1 of 1 users.
     */
    
public function unspam$args ) {
        
$this->update_msuser_status$args'spam''0' );
    }

    
/**
     * Common command for updating user data.
     */
    
private function update_msuser_status$user_ids$pref$value ) {

        
// If site is not multisite, then stop execution.
        
if ( ! is_multisite() ) {
            
WP_CLI::error'This is not a multisite installation.' );
        }

        if ( 
'spam' === $pref ) {
            
$action = (int) $value 'marked as spam' 'removed from spam';
            
$verb   = (int) $value 'spam' 'unspam';
        }

        
$successes 0;
        
$errors    0;
        
$users     $this->fetcher->get_many$user_ids );
        if ( 
count$users ) < count$user_ids ) ) {
            
$errors count$user_ids ) - count$users );
        }

        foreach ( 
$user_ids as $user_id ) {

            
$user get_userdata$user_id );

            
// If no user found, then show warning.
            
if ( empty( $user ) ) {
                
WP_CLI::warning"User {$user_id} doesn't exist." );
                continue;
            }

            
// Super admin should not be marked as spam.
            
if ( is_super_admin$user->ID ) ) {
                
WP_CLI::warning"User cannot be modified. The user {$user->ID} is a network administrator." );
                continue;
            }

            
// Skip if user is already marked as spam and show warning.
            
if ( $value === $user->spam ) {
                
WP_CLI::warning"User {$user_id} already {$action}." );
                continue;
            }

            
// Make that user's blog as spam too.
            
$blogs = (array) get_blogs_of_user$user_idtrue );
            foreach ( 
$blogs as $details ) {
                
$site $this->sitefetcher->get_check$details->site_id );

                
// Main blog shouldn't a spam !
                
if ( $details->userblog_id !== $site->blog_id ) {
                    
update_blog_status$details->userblog_id$pref$value );
                }
            }

            if ( 
Utils\wp_version_compare'5.3''<' ) ) {
                
// phpcs:ignore WordPress.WP.DeprecatedFunctions.update_user_statusFound -- Fallback for older versions.
                
update_user_status$user_id$pref$value );
            } else {
                
wp_update_user(
                    [
                        
'ID'  => $user_id,
                        
$pref => $value,
                    ]
                );
            }

            
WP_CLI::log"User {$user_id} {$action}." );
            
$successes++;
        }

        
Utils\report_batch_operation_results'user'$verbcount$user_ids ), $successes$errors );
    }

    
/**
     * Checks if a user's password is valid or not.
     *
     * ## OPTIONS
     *
     * <user>
     * : The user login, user email or user ID of the user to check credentials for.
     *
     * <user_pass>
     * : A string that contains the plain text password for the user.
     *
     * [--escape-chars]
     * : Escape password with `wp_slash()` to mimic the same behavior as `wp-login.php`.
     *
     * ## EXAMPLES
     *
     *     # Check whether given credentials are valid; exit status 0 if valid, otherwise 1
     *     $ wp user check-password admin adminpass
     *     $ echo $?
     *     1
     *
     *     # Bash script for checking whether given credentials are valid or not
     *     if ! $(wp user check-password admin adminpass); then
     *      notify-send "Invalid Credentials";
     *     fi
     *
     * @subcommand check-password
     */
    
public function check_password$args$assoc_args ) {
        
$escape_chars Utils\get_flag_value$assoc_args'escape-chars'false );

        if ( ! 
$escape_chars && wp_slashwp_unslash$args[1] ) ) !== $args[1] ) {
            
WP_CLI::warning'Password contains characters that need to be escaped. Please escape them manually or use the `--escape-chars` option.' );
        }

        
$user      $this->fetcher->get_check$args[0] );
        
$user_pass $escape_chars wp_slash$args[1] ) : $args[1];

        if ( 
wp_check_password$user_pass$user->data->user_pass$user->ID ) ) {
            
WP_CLI::halt);
        } else {
            
WP_CLI::halt);
        }
    }

}
<?
php

use WP_CLI\CommandWithDBObject;
use 
WP_CLI\Entity\Utils as EntityUtils;
use 
WP_CLI\Fetchers\Post as PostFetcher;
use 
WP_CLI\Fetchers\User as UserFetcher;
use 
WP_CLI\Utils;

/**
 * Manages posts, content, and meta.
 *
 * ## EXAMPLES
 *
 *     # Create a new post.
 *     $ wp post create --post_type=post --post_title='A sample post'
 *     Success: Created post 123.
 *
 *     # Update an existing post.
 *     $ wp post update 123 --post_status=draft
 *     Success: Updated post 123.
 *
 *     # Delete an existing post.
 *     $ wp post delete 123
 *     Success: Trashed post 123.
 *
 * @package wp-cli
 */
class Post_Command extends CommandWithDBObject {

    protected 
$obj_type   'post';
    protected 
$obj_fields = [
        
'ID',
        
'post_title',
        
'post_name',
        
'post_date',
        
'post_status',
    ];

    public function 
__construct() {
        
$this->fetcher = new PostFetcher();
    }

    
/**
     * Creates a new post.
     *
     * ## OPTIONS
     *
     * [--post_author=<post_author>]
     * : The ID of the user who added the post. Default is the current user ID.
     *
     * [--post_date=<post_date>]
     * : The date of the post. Default is the current time.
     *
     * [--post_date_gmt=<post_date_gmt>]
     * : The date of the post in the GMT timezone. Default is the value of $post_date.
     *
     * [--post_content=<post_content>]
     * : The post content. Default empty.
     *
     * [--post_content_filtered=<post_content_filtered>]
     * : The filtered post content. Default empty.
     *
     * [--post_title=<post_title>]
     * : The post title. Default empty.
     *
     * [--post_excerpt=<post_excerpt>]
     * : The post excerpt. Default empty.
     *
     * [--post_status=<post_status>]
     * : The post status. Default 'draft'.
     *
     * [--post_type=<post_type>]
     * : The post type. Default 'post'.
     *
     * [--comment_status=<comment_status>]
     * : Whether the post can accept comments. Accepts 'open' or 'closed'. Default is the value of 'default_comment_status' option.
     *
     * [--ping_status=<ping_status>]
     * : Whether the post can accept pings. Accepts 'open' or 'closed'. Default is the value of 'default_ping_status' option.
     *
     * [--post_password=<post_password>]
     * : The password to access the post. Default empty.
     *
     * [--post_name=<post_name>]
     * : The post name. Default is the sanitized post title when creating a new post.
     *
     * [--from-post=<post_id>]
     * : Post id of a post to be duplicated.
     *
     * [--to_ping=<to_ping>]
     * : Space or carriage return-separated list of URLs to ping. Default empty.
     *
     * [--pinged=<pinged>]
     * : Space or carriage return-separated list of URLs that have been pinged. Default empty.
     *
     * [--post_modified=<post_modified>]
     * : The date when the post was last modified. Default is the current time.
     *
     * [--post_modified_gmt=<post_modified_gmt>]
     * : The date when the post was last modified in the GMT timezone. Default is the current time.
     *
     * [--post_parent=<post_parent>]
     * : Set this for the post it belongs to, if any. Default 0.
     *
     * [--menu_order=<menu_order>]
     * : The order the post should be displayed in. Default 0.
     *
     * [--post_mime_type=<post_mime_type>]
     * : The mime type of the post. Default empty.
     *
     * [--guid=<guid>]
     * : Global Unique ID for referencing the post. Default empty.
     *
     * [--post_category=<post_category>]
     * : Array of category names, slugs, or IDs. Defaults to value of the 'default_category' option.
     *
     * [--tags_input=<tags_input>]
     * : Array of tag names, slugs, or IDs. Default empty.
     *
     * [--tax_input=<tax_input>]
     * : Array of taxonomy terms keyed by their taxonomy name. Default empty.
     *
     * [--meta_input=<meta_input>]
     * : Array in JSON format of post meta values keyed by their post meta key. Default empty.
     *
     * [<file>]
     * : Read post content from <file>. If this value is present, the
     *     `--post_content` argument will be ignored.
     *
     *   Passing `-` as the filename will cause post content to
     *   be read from STDIN.
     *
     * [--<field>=<value>]
     * : Associative args for the new post. See wp_insert_post().
     *
     * [--edit]
     * : Immediately open system's editor to write or edit post content.
     *
     *   If content is read from a file, from STDIN, or from the `--post_content`
     *   argument, that text will be loaded into the editor.
     *
     * [--porcelain]
     * : Output just the new post id.
     *
     *
     * ## EXAMPLES
     *
     *     # Create post and schedule for future
     *     $ wp post create --post_type=page --post_title='A future post' --post_status=future --post_date='2020-12-01 07:00:00'
     *     Success: Created post 1921.
     *
     *     # Create post with content from given file
     *     $ wp post create ./post-content.txt --post_category=201,345 --post_title='Post from file'
     *     Success: Created post 1922.
     *
     *     # Create a post with multiple meta values.
     *     $ wp post create --post_title='A post' --post_content='Just a small post.' --meta_input='{"key1":"value1","key2":"value2"}'
     *     Success: Created post 1923.
     *
     *     # Create a duplicate post from existing posts.
     *     $ wp post create --from-post=123 --post_title='Different Title'
     *     Success: Created post 2350.
     */
    
public function create$args$assoc_args ) {
        if ( ! empty( 
$args[0] ) ) {
            
$assoc_args['post_content'] = $this->read_from_file_or_stdin$args[0] );
        }

        if ( 
Utils\get_flag_value$assoc_args'edit' ) ) {
            
$input Utils\get_flag_value$assoc_args'post_content''' );

            
$output $this->_edit$input'WP-CLI: New Post' );
            if ( 
$output ) {
                
$assoc_args['post_content'] = $output;
            } else {
                
$assoc_args['post_content'] = $input;
            }
        }

        if ( isset( 
$assoc_args['post_category'] ) ) {
            
$assoc_args['post_category'] = $this->get_category_ids$assoc_args['post_category'] );
        }

        if ( isset( 
$assoc_args['meta_input'] ) && Utils\wp_version_compare'4.4''<' ) ) {
            
WP_CLI::warning"The 'meta_input' field was only introduced in WordPress 4.4 so will have no effect." );
        }

        
$array_arguments = [ 'meta_input' ];
        
$assoc_args      Utils\parse_shell_arrays$assoc_args$array_arguments );

        if ( isset( 
$assoc_args['from-post'] ) ) {
            
$post     $this->fetcher->get_check$assoc_args['from-post'] );
            
$post_arr get_object_vars$post );
            
$post_id  $post_arr['ID'];
            unset( 
$post_arr['post_date'] );
            unset( 
$post_arr['post_date_gmt'] );
            unset( 
$post_arr['guid'] );
            unset( 
$post_arr['ID'] );

            if ( empty( 
$assoc_args['meta_input'] ) ) {
                
$assoc_args['meta_input'] = $this->get_metadata$post_id );
            }
            if ( empty( 
$assoc_args['post_category'] ) ) {
                
$post_arr['post_category'] = $this->get_category$post_id );
            }
            if ( empty( 
$assoc_args['tags_input'] ) ) {
                
$post_arr['tags_input'] = $this->get_tags$post_id );
            }
            
$assoc_args array_merge$post_arr$assoc_args );
        }

        
$assoc_args wp_slash$assoc_args );
        
parent::_create(
            
$args,
            
$assoc_args,
            function ( 
$params ) {
                return 
wp_insert_post$paramstrue );
            }
        );
    }

    
/**
     * Updates one or more existing posts.
     *
     * ## OPTIONS
     *
     * <id>...
     * : One or more IDs of posts to update.
     *
     * [--post_author=<post_author>]
     * : The ID of the user who added the post. Default is the current user ID.
     *
     * [--post_date=<post_date>]
     * : The date of the post. Default is the current time.
     *
     * [--post_date_gmt=<post_date_gmt>]
     * : The date of the post in the GMT timezone. Default is the value of $post_date.
     *
     * [--post_content=<post_content>]
     * : The post content. Default empty.
     *
     * [--post_content_filtered=<post_content_filtered>]
     * : The filtered post content. Default empty.
     *
     * [--post_title=<post_title>]
     * : The post title. Default empty.
     *
     * [--post_excerpt=<post_excerpt>]
     * : The post excerpt. Default empty.
     *
     * [--post_status=<post_status>]
     * : The post status. Default 'draft'.
     *
     * [--post_type=<post_type>]
     * : The post type. Default 'post'.
     *
     * [--comment_status=<comment_status>]
     * : Whether the post can accept comments. Accepts 'open' or 'closed'. Default is the value of 'default_comment_status' option.
     *
     * [--ping_status=<ping_status>]
     * : Whether the post can accept pings. Accepts 'open' or 'closed'. Default is the value of 'default_ping_status' option.
     *
     * [--post_password=<post_password>]
     * : The password to access the post. Default empty.
     *
     * [--post_name=<post_name>]
     * : The post name. Default is the sanitized post title when creating a new post.
     *
     * [--to_ping=<to_ping>]
     * : Space or carriage return-separated list of URLs to ping. Default empty.
     *
     * [--pinged=<pinged>]
     * : Space or carriage return-separated list of URLs that have been pinged. Default empty.
     *
     * [--post_modified=<post_modified>]
     * : The date when the post was last modified. Default is the current time.
     *
     * [--post_modified_gmt=<post_modified_gmt>]
     * : The date when the post was last modified in the GMT timezone. Default is the current time.
     *
     * [--post_parent=<post_parent>]
     * : Set this for the post it belongs to, if any. Default 0.
     *
     * [--menu_order=<menu_order>]
     * : The order the post should be displayed in. Default 0.
     *
     * [--post_mime_type=<post_mime_type>]
     * : The mime type of the post. Default empty.
     *
     * [--guid=<guid>]
     * : Global Unique ID for referencing the post. Default empty.
     *
     * [--post_category=<post_category>]
     * : Array of category names, slugs, or IDs. Defaults to value of the 'default_category' option.
     *
     * [--tags_input=<tags_input>]
     * : Array of tag names, slugs, or IDs. Default empty.
     *
     * [--tax_input=<tax_input>]
     * : Array of taxonomy terms keyed by their taxonomy name. Default empty.
     *
     * [--meta_input=<meta_input>]
     * : Array in JSON format of post meta values keyed by their post meta key. Default empty.
     *
     * [<file>]
     * : Read post content from <file>. If this value is present, the
     *     `--post_content` argument will be ignored.
     *
     *   Passing `-` as the filename will cause post content to
     *   be read from STDIN.
     *
     * --<field>=<value>
     * : One or more fields to update. See wp_insert_post().
     *
     * [--defer-term-counting]
     * : Recalculate term count in batch, for a performance boost.
     *
     * ## EXAMPLES
     *
     *     $ wp post update 123 --post_name=something --post_status=draft
     *     Success: Updated post 123.
     *
     *     # Update a post with multiple meta values.
     *     $ wp post update 123 --meta_input='{"key1":"value1","key2":"value2"}'
     *     Success: Updated post 123.
     *
     *     # Update multiple posts at once.
     *     $ wp post update 123 456 --post_author=789
     *     Success: Updated post 123.
     *     Success: Updated post 456.
     *
     *     # Update all posts of a given post type at once.
     *     $ wp post update $(wp post list --post_type=page --format=ids) --post_author=123
     *     Success: Updated post 123.
     *     Success: Updated post 456.
     */
    
public function update$args$assoc_args ) {

        foreach ( 
$args as $key => $arg ) {
            if ( 
is_numeric$arg ) ) {
                continue;
            }

            
$assoc_args['post_content'] = $this->read_from_file_or_stdin$arg );
            unset( 
$args$key ] );
            break;
        }

        if ( isset( 
$assoc_args['post_category'] ) ) {
            
$assoc_args['post_category'] = $this->get_category_ids$assoc_args['post_category'] );
        }

        if ( isset( 
$assoc_args['meta_input'] ) && Utils\wp_version_compare'4.4''<' ) ) {
            
WP_CLI::warning"The 'meta_input' field was only introduced in WordPress 4.4 so will have no effect." );
        }

        
$array_arguments = [ 'meta_input' ];
        
$assoc_args      Utils\parse_shell_arrays$assoc_args$array_arguments );

        
$assoc_args wp_slash$assoc_args );
        
parent::_update(
            
$args,
            
$assoc_args,
            function ( 
$params ) {
                return 
wp_update_post$paramstrue );
            }
        );
    }

    
/**
     * Launches system editor to edit post content.
     *
     * ## OPTIONS
     *
     * <id>
     * : The ID of the post to edit.
     *
     * ## EXAMPLES
     *
     *     # Launch system editor to edit post
     *     $ wp post edit 123
     */
    
public function edit$args$assoc_args ) {
        
$post $this->fetcher->get_check$args[0] );

        
$result $this->_edit$post->post_content"WP-CLI post {$post->ID});

        if ( 
false === $result ) {
            
WP_CLI::warning'No change made to post content.''Aborted' );
        } else {
            
$this->update$args, [ 'post_content' => $result ] );
        }
    }

    
// phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore -- Whitelisting to provide backward compatibility to classes possibly extending this class.
    
protected function _edit$content$title ) {
        
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Calling native WordPress hook.
        
$content apply_filters'the_editor_content'$content );
        
$output  Utils\launch_editor_for_input$content$title );
        
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Calling native WordPress hook.
        
return ( is_string$output ) ) ? apply_filters'content_save_pre'$output ) : $output;
    }

    
/**
     * Gets details about a post.
     *
     * ## OPTIONS
     *
     * <id>
     * : The ID of the post to get.
     *
     * [--field=<field>]
     * : Instead of returning the whole post, returns the value of a single field.
     *
     * [--fields=<fields>]
     * : Limit the output to specific fields. Defaults to all fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - yaml
     * ---
     *
     * ## EXAMPLES
     *
     *     # Save the post content to a file
     *     $ wp post get 123 --field=content > file.txt
     */
    
public function get$args$assoc_args ) {
        
$post $this->fetcher->get_check$args[0] );

        
$post_arr get_object_vars$post );
        unset( 
$post_arr['filter'] );

        if ( empty( 
$assoc_args['fields'] ) ) {
            
$assoc_args['fields'] = array_keys$post_arr );
        }

        
$formatter $this->get_formatter$assoc_args );
        
$formatter->display_item$post_arr );
    }

    
/**
     * Deletes an existing post.
     *
     * ## OPTIONS
     *
     * <id>...
     * : One or more IDs of posts to delete.
     *
     * [--force]
     * : Skip the trash bin.
     *
     * [--defer-term-counting]
     * : Recalculate term count in batch, for a performance boost.
     *
     * ## EXAMPLES
     *
     *     # Delete post skipping trash
     *     $ wp post delete 123 --force
     *     Success: Deleted post 123.
     *
     *     # Delete all pages
     *     $ wp post delete $(wp post list --post_type='page' --format=ids)
     *     Success: Trashed post 1164.
     *     Success: Trashed post 1186.
     *
     *     # Delete all posts in the trash
     *     $ wp post delete $(wp post list --post_status=trash --format=ids)
     *     Success: Deleted post 1268.
     *     Success: Deleted post 1294.
     */
    
public function delete$args$assoc_args ) {
        
$defaults   = [ 'force' => false ];
        
$assoc_args array_merge$defaults$assoc_args );

        
parent::_delete$args$assoc_args, [ $this'delete_callback' ] );
    }

    
/**
     * Callback used to delete a post.
     *
     * @param $post_id
     * @param $assoc_args
     * @return array
     */
    
protected function delete_callback$post_id$assoc_args ) {
        
$status    get_post_status$post_id );
        
$post_type get_post_type$post_id );

        if ( ! 
$assoc_args['force']
            && ( 
'post' !== $post_type && 'page' !== $post_type ) ) {
            return [
                
'error',
                
"Posts of type '{$post_type}' do not support being sent to trash.\n"
                
'Please use the --force flag to skip trash and delete them permanently.',
            ];
        }

        if ( ! 
wp_delete_post$post_id$assoc_args['force'] ) ) {
            return [ 
'error'"Failed deleting post {$post_id}." ];
        }

        
$action $assoc_args['force'] || 'trash' === $status || 'revision' === $post_type 'Deleted' 'Trashed';

        return [ 
'success'"{$action} post {$post_id}." ];
    }

    
/**
     * Gets a list of posts.
     *
     * Display posts based on all arguments supported by [WP_Query()](https://developer.wordpress.org/reference/classes/wp_query/).
     * Only shows post types marked as post by default.
     *
     * ## OPTIONS
     *
     * [--<field>=<value>]
     * : One or more args to pass to WP_Query.
     *
     * [--field=<field>]
     * : Prints the value of a single field for each post.
     *
     * [--fields=<fields>]
     * : Limit the output to specific object fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - ids
     *   - json
     *   - count
     *   - yaml
     * ---
     *
     * ## AVAILABLE FIELDS
     *
     * These fields will be displayed by default for each post:
     *
     * * ID
     * * post_title
     * * post_name
     * * post_date
     * * post_status
     *
     * These fields are optionally available:
     *
     * * post_author
     * * post_date_gmt
     * * post_content
     * * post_excerpt
     * * comment_status
     * * ping_status
     * * post_password
     * * to_ping
     * * pinged
     * * post_modified
     * * post_modified_gmt
     * * post_content_filtered
     * * post_parent
     * * guid
     * * menu_order
     * * post_type
     * * post_mime_type
     * * comment_count
     * * filter
     * * url
     *
     * ## EXAMPLES
     *
     *     # List post
     *     $ wp post list --field=ID
     *     568
     *     829
     *     1329
     *     1695
     *
     *     # List posts in JSON
     *     $ wp post list --post_type=post --posts_per_page=5 --format=json
     *     [{"ID":1,"post_title":"Hello world!","post_name":"hello-world","post_date":"2015-06-20 09:00:10","post_status":"publish"},{"ID":1178,"post_title":"Markup: HTML Tags and Formatting","post_name":"markup-html-tags-and-formatting","post_date":"2013-01-11 20:22:19","post_status":"draft"}]
     *
     *     # List all pages
     *     $ wp post list --post_type=page --fields=post_title,post_status
     *     +-------------+-------------+
     *     | post_title  | post_status |
     *     +-------------+-------------+
     *     | Sample Page | publish     |
     *     +-------------+-------------+
     *
     *     # List ids of all pages and posts
     *     $ wp post list --post_type=page,post --format=ids
     *     15 25 34 37 198
     *
     *     # List given posts
     *     $ wp post list --post__in=1,3
     *     +----+--------------+-------------+---------------------+-------------+
     *     | ID | post_title   | post_name   | post_date           | post_status |
     *     +----+--------------+-------------+---------------------+-------------+
     *     | 3  | Lorem Ipsum  | lorem-ipsum | 2016-06-01 14:34:36 | publish     |
     *     | 1  | Hello world! | hello-world | 2016-06-01 14:31:12 | publish     |
     *     +----+--------------+-------------+---------------------+-------------+
     *
     *     # List given post by a specific author
     *     $ wp post list --author=2
     *     +----+-------------------+-------------------+---------------------+-------------+
     *     | ID | post_title        | post_name         | post_date           | post_status |
     *     +----+-------------------+-------------------+---------------------+-------------+
     *     | 14 | New documentation | new-documentation | 2021-06-18 21:05:11 | publish     |
     *     +----+-------------------+-------------------+---------------------+-------------+
     *
     * @subcommand list
     */
    
public function list_$args$assoc_args ) {
        
$formatter $this->get_formatter$assoc_args );

        
$defaults   = [
            
'posts_per_page' => -1,
            
'post_status'    => 'any',
        ];
        
$query_args array_merge$defaults$assoc_args );
        
$query_args self::process_csv_arguments_to_arrays$query_args );
        if ( isset( 
$query_args['post_type'] ) && 'any' !== $query_args['post_type'] ) {
            
$query_args['post_type'] = explode','$query_args['post_type'] );
        }

        if ( 
'ids' === $formatter->format ) {
            
$query_args['fields'] = 'ids';
            
$query                = new WP_Query$query_args );
            echo 
implode' '$query->posts );
        } elseif ( 
'count' === $formatter->format ) {
            
$query_args['fields'] = 'ids';
            
$query                = new WP_Query$query_args );
            
$formatter->display_items$query->posts );
        } else {
            
$query = new WP_Query$query_args );
            
$posts array_map(
                function( 
$post ) {
                        
$post->url get_permalink$post->ID );
                        return 
$post;
                },
                
$query->posts
            
);
            
$formatter->display_items$posts );
        }
    }

    
/**
     * Generates some posts.
     *
     * Creates a specified number of new posts with dummy data.
     *
     * ## OPTIONS
     *
     * [--count=<number>]
     * : How many posts to generate?
     * ---
     * default: 100
     * ---
     *
     * [--post_type=<type>]
     * : The type of the generated posts.
     * ---
     * default: post
     * ---
     *
     * [--post_status=<status>]
     * : The status of the generated posts.
     * ---
     * default: publish
     * ---
     *
     * [--post_title=<post_title>]
     * : The post title.
     * ---
     * default:
     * ---
     *
     * [--post_author=<login>]
     * : The author of the generated posts.
     * ---
     * default:
     * ---
     *
     * [--post_date=<yyyy-mm-dd-hh-ii-ss>]
     * : The date of the generated posts. Default: current date
     *
     * [--post_date_gmt=<yyyy-mm-dd-hh-ii-ss>]
     * : The GMT date of the generated posts. Default: value of post_date (or current date if it's not set)
     *
     * [--post_content]
     * : If set, the command reads the post_content from STDIN.
     *
     * [--max_depth=<number>]
     * : For hierarchical post types, generate child posts down to a certain depth.
     * ---
     * default: 1
     * ---
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: progress
     * options:
     *   - progress
     *   - ids
     * ---
     *
     * ## EXAMPLES
     *
     *     # Generate posts.
     *     $ wp post generate --count=10 --post_type=page --post_date=1999-01-04
     *     Generating posts  100% [================================================] 0:01 / 0:04
     *
     *     # Generate posts with fetched content.
     *     $ curl -N http://loripsum.net/api/5 | wp post generate --post_content --count=10
     *       % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
     *                                      Dload  Upload   Total   Spent    Left  Speed
     *     100  2509  100  2509    0     0    616      0  0:00:04  0:00:04 --:--:--   616
     *     Generating posts  100% [================================================] 0:01 / 0:04
     *
     *     # Add meta to every generated posts.
     *     $ wp post generate --format=ids | xargs -d ' ' -I % wp post meta add % foo bar
     *     Success: Added custom field.
     *     Success: Added custom field.
     *     Success: Added custom field.
     */
    
public function generate$args$assoc_args ) {
        global 
$wpdb;

        
$defaults = [
            
'count'         => 100,
            
'max_depth'     => 1,
            
'post_type'     => 'post',
            
'post_status'   => 'publish',
            
'post_author'   => false,
            
'post_date'     => false,
            
'post_date_gmt' => false,
            
'post_content'  => '',
            
'post_title'    => '',
        ];

        
$post_data array_merge$defaults$assoc_args );

        
$call_time current_time'mysql' );

        if ( 
false === $post_data['post_date_gmt'] ) {
            
$post_data['post_date_gmt'] = $post_data['post_date'] ?: $call_time;
        }

        if ( 
false === $post_data['post_date'] ) {
            
$post_data['post_date'] = $post_data['post_date_gmt'] ?: $call_time;
        }

        if ( ! 
post_type_exists$post_data['post_type'] ) ) {
            
WP_CLI::error"'{$post_data['post_type']}' is not a registered post type." );
        }

        if ( 
$post_data['post_author'] ) {
            
$user_fetcher             = new UserFetcher();
            
$post_data['post_author'] = $user_fetcher->get_check$post_data['post_author'] )->ID;
        }

        if ( 
Utils\get_flag_value$assoc_args'post_content' ) ) {
            if ( ! 
EntityUtils::has_stdin() ) {
                
WP_CLI::error'The parameter `post_content` reads from STDIN.' );
            }

            
$post_data['post_content'] = file_get_contents'php://stdin' );
        }

        
// Get the total number of posts.
        
$total $wpdb->get_var$wpdb->prepare"SELECT COUNT(*) FROM $wpdb->posts WHERE post_type = %s"$post_data['post_type'] ) );

        
$label = ! empty( $post_data['post_title'] )
            ? 
$post_data['post_title']
            : 
get_post_type_object$post_data['post_type'] )->labels->singular_name;

        
$hierarchical get_post_type_object$post_data['post_type'] )->hierarchical;

        
$limit $post_data['count'] + $total;

        
$format Utils\get_flag_value$assoc_args'format''progress' );

        
$notify false;
        if ( 
'progress' === $format ) {
            
$notify Utils\make_progress_bar'Generating posts'$post_data['count'] );
        }

        
$previous_post_id 0;
        
$current_depth    1;
        
$current_parent   0;

        for ( 
$index $total$index $limit$index++ ) {

            if ( 
$hierarchical ) {

                if ( 
$this->maybe_make_child() && $current_depth $post_data['max_depth'] ) {

                    
$current_parent $previous_post_id;
                    
$current_depth++;

                } elseif ( 
$this->maybe_reset_depth() ) {

                    
$current_depth  1;
                    
$current_parent 0;

                }
            }

            
$args = [
                
'post_type'     => $post_data['post_type'],
                
'post_title'    => ( ! empty( $post_data['post_title'] ) && $index === $total )
                    ? 
$label
                    
"{$label} {$index}",
                
'post_status'   => $post_data['post_status'],
                
'post_author'   => $post_data['post_author'],
                
'post_parent'   => $current_parent,
                
'post_name'     => ! empty( $post_data['post_title'] )
                    ? 
sanitize_title$post_data['post_title'] . ( $index === $total '' "-{$index}) )
                    : 
"post-{$index}",
                
'post_date'     => $post_data['post_date'],
                
'post_date_gmt' => $post_data['post_date_gmt'],
                
'post_content'  => $post_data['post_content'],
            ];

            
$post_id wp_insert_post$argstrue );
            if ( 
is_wp_error$post_id ) ) {
                
WP_CLI::warning$post_id );
            } else {
                
$previous_post_id $post_id;
                if ( 
'ids' === $format ) {
                    echo 
$post_id;
                    if ( 
$index $limit ) {
                        echo 
' ';
                    }
                }
            }

            if ( 
'progress' === $format ) {
                
$notify->tick();
            }
        }
        if ( 
'progress' === $format ) {
            
$notify->finish();
        }
    }

    private function 
maybe_make_child() {
        
// 50% chance of making child post.
        
return ( wp_rand1) === );
    }

    private function 
maybe_reset_depth() {
        
// 10% chance of reseting to root depth,
        
return ( wp_rand110 ) === );
    }

    
/**
     * Read post content from file or STDIN
     *
     * @param string $arg Supplied argument
     * @return string
     */
    
private function read_from_file_or_stdin$arg ) {
        if ( 
'-' !== $arg ) {
            
$readfile $arg;
            if ( ! 
file_exists$readfile ) || ! is_file$readfile ) ) {
                
WP_CLI::error"Unable to read content from '{$readfile}'." );
            }
        } else {
            
$readfile 'php://stdin';
        }
        return 
file_get_contents$readfile );
    }

    
/**
     * Resolves post_category arg into an array of category ids.
     *
     * @param string $arg Supplied argument.
     * @return array
     */
    
private function get_category_ids$arg ) {

        
$categories   explode','$arg );
        
$category_ids = [];
        foreach ( 
$categories as $post_category ) {
            if ( 
trim$post_category ) ) {
                if ( 
is_numeric$post_category ) && (int) $post_category ) {
                    
$category_id category_exists( (int) $post_category );
                } else {
                    
$category_id category_exists$post_category );
                }
                if ( ! 
$category_id ) {
                    
WP_CLI::error"No such post category '{$post_category}'." );
                }
                
$category_ids[] = $category_id;
            }
        }
        
// If no category ids found, return exploded array for compat with previous WP-CLI versions.
        
return $category_ids ?: $categories;
    }

    
/**
     * Get post metadata.
     *
     * @param $post_id ID of the post.
     *
     * @return array
     */
    
private function get_metadata$post_id ) {
        
$metadata get_metadata'post'$post_id );
        
$items    = [];
        foreach ( 
$metadata as $key => $values ) {
            foreach ( 
$values as $item_value ) {
                
$item_value    maybe_unserialize$item_value );
                
$items$key ] = $item_value;
            }
        }

        return 
$items;
    }

    
/**
     * Get Categories of a post.
     *
     * @param $post_id ID of the post.
     *
     * @return array
     */
    
private function get_category$post_id ) {
        
$category_data get_the_category$post_id );
        
$category_arr  = [];
        foreach ( 
$category_data as $cat ) {
            
array_push$category_arr$cat->term_id );
        }

        return 
$category_arr;
    }

    
/**
     * Get Tags of a post.
     *
     * @param $post_id ID of the post.
     *
     * @return array
     */
    
private function get_tags$post_id ) {
        
$tag_data get_the_tags$post_id );
        
$tag_arr  = [];
        if ( 
$tag_data ) {
            foreach ( 
$tag_data as $tag ) {
                
array_push$tag_arr$tag->slug );
            }
        }

        return 
$tag_arr;
    }

    
/**
     * Verifies whether a post exists.
     *
     * Displays a success message if the post does exist.
     *
     * ## OPTIONS
     *
     * <id>
     * : The ID of the post to check.
     *
     * ## EXAMPLES
     *
     *     # The post exists.
     *     $ wp post exists 1337
     *     Success: Post with ID 1337 exists.
     *     $ echo $?
     *     0
     *
     *     # The post does not exist.
     *     $ wp post exists 10000
     *     $ echo $?
     *     1
     */
    
public function exists$args ) {
        if ( 
$this->fetcher->get$args[0] ) ) {
            
WP_CLI::success"Post with ID {$args[0]} exists." );
        } else {
            
WP_CLI::halt);
        }
    }
}
<?
php

use WP_CLI\CommandWithMeta;
use 
WP_CLI\Fetchers\Comment as CommentFetcher;

/**
 * Adds, updates, deletes, and lists comment custom fields.
 *
 * ## EXAMPLES
 *
 *     # Set comment meta
 *     $ wp comment meta set 123 description "Mary is a WordPress developer."
 *     Success: Updated custom field 'description'.
 *
 *     # Get comment meta
 *     $ wp comment meta get 123 description
 *     Mary is a WordPress developer.
 *
 *     # Update comment meta
 *     $ wp comment meta update 123 description "Mary is an awesome WordPress developer."
 *     Success: Updated custom field 'description'.
 *
 *     # Delete comment meta
 *     $ wp comment meta delete 123 description
 *     Success: Deleted custom field.
 */
class Comment_Meta_Command extends CommandWithMeta {
    protected 
$meta_type 'comment';

    
/**
     * Wrapper method for add_metadata that can be overridden in sub classes.
     *
     * @param int    $object_id  ID of the object the metadata is for.
     * @param string $meta_key   Metadata key to use.
     * @param mixed  $meta_value Metadata value. Must be serializable if
     *                           non-scalar.
     * @param bool   $unique     Optional, default is false. Whether the
     *                           specified metadata key should be unique for the
     *                           object. If true, and the object already has a
     *                           value for the specified metadata key, no change
     *                           will be made.
     *
     * @return int|false The meta ID on success, false on failure.
     */
    
protected function add_metadata$object_id$meta_key$meta_value$unique false ) {
        return 
add_comment_meta$object_id$meta_key$meta_value$unique );
    }

    
/**
     * Wrapper method for update_metadata that can be overridden in sub classes.
     *
     * @param int    $object_id  ID of the object the metadata is for.
     * @param string $meta_key   Metadata key to use.
     * @param mixed  $meta_value Metadata value. Must be serializable if
     *                           non-scalar.
     * @param mixed  $prev_value Optional. If specified, only update existing
     *                           metadata entries with the specified value.
     *                           Otherwise, update all entries.
     *
     * @return int|bool Meta ID if the key didn't exist, true on successful
     *                  update, false on failure.
     */
    
protected function update_metadata$object_id$meta_key$meta_value$prev_value '' ) {
        return 
update_comment_meta$object_id$meta_key$meta_value$prev_value );
    }

    
/**
     * Wrapper method for get_metadata that can be overridden in sub classes.
     *
     * @param int    $object_id ID of the object the metadata is for.
     * @param string $meta_key  Optional. Metadata key. If not specified,
     *                          retrieve all metadata for the specified object.
     * @param bool   $single    Optional, default is false. If true, return only
     *                          the first value of the specified meta_key. This
     *                          parameter has no effect if meta_key is not
     *                          specified.
     *
     * @return mixed Single metadata value, or array of values.
     */
    
protected function get_metadata$object_id$meta_key ''$single false ) {
        return 
get_comment_meta$object_id$meta_key$single );
    }

    
/**
     * Wrapper method for delete_metadata that can be overridden in sub classes.
     *
     * @param int    $object_id  ID of the object metadata is for
     * @param string $meta_key   Metadata key
     * @param mixed $meta_value  Optional. Metadata value. Must be serializable
     *                           if non-scalar. If specified, only delete
     *                           metadata entries with this value. Otherwise,
     *                           delete all entries with the specified meta_key.
     *                           Pass `null, `false`, or an empty string to skip
     *                           this check. For backward compatibility, it is
     *                           not possible to pass an empty string to delete
     *                           those entries with an empty string for a value.
     *
     * @return bool True on successful delete, false on failure.
     */
    
protected function delete_metadata$object_id$meta_key$meta_value '' ) {
        return 
delete_comment_meta$object_id$meta_key$meta_value );
    }

    
/**
     * Check that the comment ID exists
     *
     * @param int
     */
    
protected function check_object_id$object_id ) {
        
$fetcher = new CommentFetcher();
        
$comment $fetcher->get_check$object_id );
        return 
$comment->comment_ID;
    }
}
<?
php

use WP_CLI\CommandWithDBObject;
use 
WP_CLI\Fetchers\Comment as CommentFetcher;
use 
WP_CLI\Utils;

/**
 * Creates, updates, deletes, and moderates comments.
 *
 * ## EXAMPLES
 *
 *     # Create a new comment.
 *     $ wp comment create --comment_post_ID=15 --comment_content="hello blog" --comment_author="wp-cli"
 *     Success: Created comment 932.
 *
 *     # Update an existing comment.
 *     $ wp comment update 123 --comment_author='That Guy'
 *     Success: Updated comment 123.
 *
 *     # Delete an existing comment.
 *     $ wp comment delete 1337 --force
 *     Success: Deleted comment 1337.
 *
 *     # Delete all spam comments.
 *     $ wp comment delete $(wp comment list --status=spam --format=ids)
 *     Success: Deleted comment 264.
 *     Success: Deleted comment 262.
 *
 * @package wp-cli
 */
class Comment_Command extends CommandWithDBObject {

    protected 
$obj_type   'comment';
    protected 
$obj_id_key 'comment_ID';
    protected 
$obj_fields = [
        
'comment_ID',
        
'comment_post_ID',
        
'comment_date',
        
'comment_approved',
        
'comment_author',
        
'comment_author_email',
    ];

    public function 
__construct() {
        
$this->fetcher = new CommentFetcher();
    }

    
/**
     * Creates a new comment.
     *
     * ## OPTIONS
     *
     * [--<field>=<value>]
     * : Associative args for the new comment. See wp_insert_comment().
     *
     * [--porcelain]
     * : Output just the new comment id.
     *
     * ## EXAMPLES
     *
     *     # Create comment.
     *     $ wp comment create --comment_post_ID=15 --comment_content="hello blog" --comment_author="wp-cli"
     *     Success: Created comment 932.
     */
    
public function create$args$assoc_args ) {
        
$assoc_args wp_slash$assoc_args );
        
parent::_create(
            
$args,
            
$assoc_args,
            function ( 
$params ) {
                if ( isset( 
$params['comment_post_ID'] ) ) {
                    
$post_id $params['comment_post_ID'];
                    
$post    get_post$post_id );
                    if ( ! 
$post ) {
                        return new 
WP_Error'no_post'"Can't find post {$post_id}." );
                    }
                } else {
                    
// Make sure it's set for older WP versions else get undefined PHP notice.
                    
$params['comment_post_ID'] = 0;
                }

                
// We use wp_insert_comment() instead of wp_new_comment() to stay at a low level and
                // avoid wp_die() formatted messages or notifications
                
$comment_id wp_insert_comment$params );

                if ( ! 
$comment_id ) {
                    return new 
WP_Error'db_error''Could not create comment.' );
                }

                return 
$comment_id;
            }
        );
    }

    
/**
     * Updates one or more comments.
     *
     * ## OPTIONS
     *
     * <id>...
     * : One or more IDs of comments to update.
     *
     * --<field>=<value>
     * : One or more fields to update. See wp_update_comment().
     *
     * ## EXAMPLES
     *
     *     # Update comment.
     *     $ wp comment update 123 --comment_author='That Guy'
     *     Success: Updated comment 123.
     */
    
public function update$args$assoc_args ) {
        
$assoc_args wp_slash$assoc_args );
        
parent::_update(
            
$args,
            
$assoc_args,
            function ( 
$params ) {
                if ( ! 
wp_update_comment$params ) ) {
                    return new 
WP_Error'Could not update comment.' );
                }

                return 
true;
            }
        );
    }

    
/**
     * Generates some number of new dummy comments.
     *
     * Creates a specified number of new comments with dummy data.
     *
     * ## OPTIONS
     *
     * [--count=<number>]
     * : How many comments to generate?
     * ---
     * default: 100
     * ---
     *
     * [--post_id=<post-id>]
     * : Assign comments to a specific post.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: progress
     * options:
     *   - progress
     *   - ids
     * ---
     *
     * ## EXAMPLES
     *
     *     # Generate comments for the given post.
     *     $ wp comment generate --format=ids --count=3 --post_id=123
     *     138 139 140
     *
     *     # Add meta to every generated comment.
     *     $ wp comment generate --format=ids --count=3 | xargs -d ' ' -I % wp comment meta add % foo bar
     *     Success: Added custom field.
     *     Success: Added custom field.
     *     Success: Added custom field.
     */
    
public function generate$args$assoc_args ) {

        
$defaults   = [
            
'count'   => 100,
            
'post_id' => 0,
        ];
        
$assoc_args array_merge$defaults$assoc_args );

        
$format Utils\get_flag_value$assoc_args'format''progress' );

        
$notify false;
        if ( 
'progress' === $format ) {
            
$notify Utils\make_progress_bar'Generating comments'$assoc_args['count'] );
        }

        
$comment_count wp_count_comments();
        
$total         = (int) $comment_count->total_comments;
        
$limit         $total $assoc_args['count'];

        for ( 
$index $total$index $limit$index++ ) {
            
$comment_id wp_insert_comment(
                [
                    
'comment_content' => "Comment {$index}",
                    
'comment_post_ID' => $assoc_args['post_id'],
                ]
            );
            if ( 
'progress' === $format ) {
                
$notify->tick();
            } elseif ( 
'ids' === $format ) {
                echo 
$comment_id;
                if ( 
$index $limit ) {
                    echo 
' ';
                }
            }
        }

        if ( 
'progress' === $format ) {
            
$notify->finish();
        }

    }

    
/**
     * Gets the data of a single comment.
     *
     * ## OPTIONS
     *
     * <id>
     * : The comment to get.
     *
     * [--field=<field>]
     * : Instead of returning the whole comment, returns the value of a single field.
     *
     * [--fields=<fields>]
     * : Limit the output to specific fields. Defaults to all fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - yaml
     * ---
     *
     * ## EXAMPLES
     *
     *     # Get comment.
     *     $ wp comment get 21 --field=content
     *     Thanks for all the comments, everyone!
     */
    
public function get$args$assoc_args ) {
        
$comment_id = (int) $args[0];
        
$comment    get_comment$comment_id );
        if ( empty( 
$comment ) ) {
            
WP_CLI::error'Invalid comment ID.' );
        }

        if ( empty( 
$assoc_args['fields'] ) ) {
            
$comment_array        get_object_vars$comment );
            
$assoc_args['fields'] = array_keys$comment_array );
        }

        
$formatter $this->get_formatter$assoc_args );
        
$formatter->display_item$comment );
    }

    
/**
     * Gets a list of comments.
     *
     * Display comments based on all arguments supported by
     * [WP_Comment_Query()](https://developer.wordpress.org/reference/classes/WP_Comment_Query/__construct/).
     *
     * ## OPTIONS
     *
     * [--<field>=<value>]
     * : One or more args to pass to WP_Comment_Query.
     *
     * [--field=<field>]
     * : Prints the value of a single field for each comment.
     *
     * [--fields=<fields>]
     * : Limit the output to specific object fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - ids
     *   - csv
     *   - json
     *   - count
     *   - yaml
     * ---
     *
     * ## AVAILABLE FIELDS
     *
     * These fields will be displayed by default for each comment:
     *
     * * comment_ID
     * * comment_post_ID
     * * comment_date
     * * comment_approved
     * * comment_author
     * * comment_author_email
     *
     * These fields are optionally available:
     *
     * * comment_author_url
     * * comment_author_IP
     * * comment_date_gmt
     * * comment_content
     * * comment_karma
     * * comment_agent
     * * comment_type
     * * comment_parent
     * * user_id
     * * url
     *
     * ## EXAMPLES
     *
     *     # List comment IDs.
     *     $ wp comment list --field=ID
     *     22
     *     23
     *     24
     *
     *     # List comments of a post.
     *     $ wp comment list --post_id=1 --fields=ID,comment_date,comment_author
     *     +------------+---------------------+----------------+
     *     | comment_ID | comment_date        | comment_author |
     *     +------------+---------------------+----------------+
     *     | 1          | 2015-06-20 09:00:10 | Mr WordPress   |
     *     +------------+---------------------+----------------+
     *
     *     # List approved comments.
     *     $ wp comment list --number=3 --status=approve --fields=ID,comment_date,comment_author
     *     +------------+---------------------+----------------+
     *     | comment_ID | comment_date        | comment_author |
     *     +------------+---------------------+----------------+
     *     | 1          | 2015-06-20 09:00:10 | Mr WordPress   |
     *     | 30         | 2013-03-14 12:35:07 | John Doe       |
     *     | 29         | 2013-03-14 11:56:08 | Jane Doe       |
     *     +------------+---------------------+----------------+
     *
     * @subcommand list
     */
    
public function list_$args$assoc_args ) {
        
$formatter $this->get_formatter$assoc_args );

        if ( 
'ids' === $formatter->format ) {
            
$assoc_args['fields'] = 'comment_ID';
        }

        
$assoc_args self::process_csv_arguments_to_arrays$assoc_args );

        if ( 
'count' === $formatter->format ) {
            
$assoc_args['count'] = true;
        }

        if ( ! empty( 
$assoc_args['comment__in'] )
            && ! empty( 
$assoc_args['orderby'] )
            && 
'comment__in' === $assoc_args['orderby']
            && 
Utils\wp_version_compare'4.4''<' ) ) {
            
$comments = [];
            foreach ( 
$assoc_args['comment__in'] as $comment_id ) {
                
$comment get_comment$comment_id );
                if ( 
$comment ) {
                    
$comments[] = $comment;
                } else {
                    
WP_CLI::warning"Invalid comment {$comment_id}." );
                }
            }
        } else {
            
$query    = new WP_Comment_Query();
            
$comments $query->query$assoc_args );
        }

        if ( 
'count' === $formatter->format ) {
            echo 
$comments;
        } else {
            if ( 
'ids' === $formatter->format ) {
                
$comments wp_list_pluck$comments'comment_ID' );
            } elseif ( 
is_array$comments ) ) {
                
$comments array_map(
                    function( 
$comment ) {
                            
$comment->url get_comment_link$comment->comment_ID );
                            return 
$comment;
                    },
                    
$comments
                
);
            }
            
$formatter->display_items$comments );
        }
    }

    
/**
     * Deletes a comment.
     *
     * ## OPTIONS
     *
     * <id>...
     * : One or more IDs of comments to delete.
     *
     * [--force]
     * : Skip the trash bin.
     *
     * ## EXAMPLES
     *
     *     # Delete comment.
     *     $ wp comment delete 1337 --force
     *     Success: Deleted comment 1337.
     *
     *     # Delete multiple comments.
     *     $ wp comment delete 1337 2341 --force
     *     Success: Deleted comment 1337.
     *     Success: Deleted comment 2341.
     */
    
public function delete$args$assoc_args ) {
        
parent::_delete(
            
$args,
            
$assoc_args,
            function ( 
$comment_id$assoc_args ) {
                
$force Utils\get_flag_value$assoc_args'force' );

                
$status wp_get_comment_status$comment_id );
                
$result wp_delete_comment$comment_id$force );

                if ( ! 
$result ) {
                    return [ 
'error'"Failed deleting comment {$comment_id}." ];
                }

                
$verb = ( $force || 'trash' === $status ) ? 'Deleted' 'Trashed';
                return [ 
'success'"{$verb} comment {$comment_id}." ];
            }
        );
    }

    private function 
call$args$status$success$failure ) {
        
$comment_id absint$args );

        
$func "wp_{$status}_comment";

        if ( ! 
$func$comment_id ) ) {
            
WP_CLI::errorsprintf$failure"comment {$comment_id}) );
        }
        
WP_CLI::successsprintf$success"comment {$comment_id}) );
    }

    private function 
set_status$args$status$success ) {
        
$comment $this->fetcher->get_check$args );

        
$result wp_set_comment_status$comment->comment_ID$statustrue );

        if ( 
is_wp_error$result ) ) {
            
WP_CLI::error$result );
        }

        
WP_CLI::success"{$success} comment {$comment->comment_ID}." );
    }

    
/**
     * Warns if `$_SERVER['SERVER_NAME']` not set as used in email from-address sent to post author in `wp_notify_postauthor()`.
     */
    
private function check_server_name() {
        if ( empty( 
$_SERVER['SERVER_NAME'] ) ) {
            
WP_CLI::warning'Site url not set - defaulting to \'example.com\'. Any notification emails sent to post author may appear to come from \'example.com\'.' );
            
$_SERVER['SERVER_NAME'] = 'example.com';
        }
    }

    
/**
     * Trashes a comment.
     *
     * ## OPTIONS
     *
     * <id>...
     * : The IDs of the comments to trash.
     *
     * ## EXAMPLES
     *
     *     # Trash comment.
     *     $ wp comment trash 1337
     *     Success: Trashed comment 1337.
     */
    
public function trash$args$assoc_args ) {
        foreach ( 
$args as $id ) {
            
$this->call$id__FUNCTION__'Trashed %s.''Failed trashing %s.' );
        }
    }

    
/**
     * Untrashes a comment.
     *
     * ## OPTIONS
     *
     * <id>...
     * : The IDs of the comments to untrash.
     *
     * ## EXAMPLES
     *
     *     # Untrash comment.
     *     $ wp comment untrash 1337
     *     Success: Untrashed comment 1337.
     */
    
public function untrash$args$assoc_args ) {
        
$this->check_server_name();
        foreach ( 
$args as $id ) {
            
$this->call$id__FUNCTION__'Untrashed %s.''Failed untrashing %s.' );
        }
    }

    
/**
     * Marks a comment as spam.
     *
     * ## OPTIONS
     *
     * <id>...
     * : The IDs of the comments to mark as spam.
     *
     * ## EXAMPLES
     *
     *     # Spam comment.
     *     $ wp comment spam 1337
     *     Success: Marked as spam comment 1337.
     */
    
public function spam$args$assoc_args ) {
        foreach ( 
$args as $id ) {
            
$this->call$id__FUNCTION__'Marked %s as spam.''Failed marking %s as spam.' );
        }
    }

    
/**
     * Unmarks a comment as spam.
     *
     * ## OPTIONS
     *
     * <id>...
     * : The IDs of the comments to unmark as spam.
     *
     * ## EXAMPLES
     *
     *     # Unspam comment.
     *     $ wp comment unspam 1337
     *     Success: Unspammed comment 1337.
     */
    
public function unspam$args$assoc_args ) {
        
$this->check_server_name();
        foreach ( 
$args as $id ) {
            
$this->call$id__FUNCTION__'Unspammed %s.''Failed unspamming %s.' );
        }
    }

    
/**
     * Approves a comment.
     *
     * ## OPTIONS
     *
     * <id>...
     * : The IDs of the comments to approve.
     *
     * ## EXAMPLES
     *
     *     # Approve comment.
     *     $ wp comment approve 1337
     *     Success: Approved comment 1337.
     */
    
public function approve$args$assoc_args ) {
        
$this->check_server_name();
        foreach ( 
$args as $id ) {
            
$this->set_status$id'approve''Approved' );
        }
    }

    
/**
     * Unapproves a comment.
     *
     * ## OPTIONS
     *
     * <id>...
     * : The IDs of the comments to unapprove.
     *
     * ## EXAMPLES
     *
     *     # Unapprove comment.
     *     $ wp comment unapprove 1337
     *     Success: Unapproved comment 1337.
     */
    
public function unapprove$args$assoc_args ) {
        
$this->check_server_name();
        foreach ( 
$args as $id ) {
            
$this->set_status$id'hold''Unapproved' );
        }
    }

    
/**
     * Counts comments, on whole blog or on a given post.
     *
     * ## OPTIONS
     *
     * [<post-id>]
     * : The ID of the post to count comments in.
     *
     * ## EXAMPLES
     *
     *     # Count comments on whole blog.
     *     $ wp comment count
     *     approved:        33
     *     spam:            3
     *     trash:           1
     *     post-trashed:    0
     *     all:             34
     *     moderated:       1
     *     total_comments:  37
     *
     *     # Count comments in a post.
     *     $ wp comment count 42
     *     approved:        19
     *     spam:            0
     *     trash:           0
     *     post-trashed:    0
     *     all:             19
     *     moderated:       0
     *     total_comments:  19
     */
    
public function count$args$assoc_args ) {
        
$post_id Utils\get_flag_value$args0);

        
$count wp_count_comments$post_id );

        
// Move total_comments to the end of the object
        
$total $count->total_comments;
        unset( 
$count->total_comments );
        
$count->total_comments $total;

        foreach ( 
$count as $status => $count ) {
            
WP_CLI::linestr_pad"$status:"17 ) . $count );
        }
    }

    
/**
     * Recalculates the comment_count value for one or more posts.
     *
     * ## OPTIONS
     *
     * <id>...
     * : IDs for one or more posts to update.
     *
     * ## EXAMPLES
     *
     *     # Recount comment for the post.
     *     $ wp comment recount 123
     *     Updated post 123 comment count to 67.
     */
    
public function recount$args ) {
        foreach ( 
$args as $id ) {
            
wp_update_comment_count$id );
            
$post get_post$id );
            if ( 
$post ) {
                
WP_CLI::log"Updated post {$post->ID} comment count to {$post->comment_count}." );
            } else {
                
WP_CLI::warning"Post {$post->ID} doesn't exist." );
            }
        }
    }

    
/**
     * Gets the status of a comment.
     *
     * ## OPTIONS
     *
     * <id>
     * : The ID of the comment to check.
     *
     * ## EXAMPLES
     *
     *     # Get status of comment.
     *     $ wp comment status 1337
     *     approved
     */
    
public function status$args$assoc_args ) {
        list( 
$comment_id ) = $args;

        
$status wp_get_comment_status$comment_id );

        if ( 
false === $status ) {
            
WP_CLI::error"Could not check status of comment {$comment_id}." );
        } else {
            
WP_CLI::line$status );
        }
    }

    
/**
     * Verifies whether a comment exists.
     *
     * Displays a success message if the comment does exist.
     *
     * ## OPTIONS
     *
     * <id>
     * : The ID of the comment to check.
     *
     * ## EXAMPLES
     *
     *     # Check whether comment exists.
     *     $ wp comment exists 1337
     *     Success: Comment with ID 1337 exists.
     */
    
public function exists$args ) {
        if ( 
$this->fetcher->get$args[0] ) ) {
            
WP_CLI::success"Comment with ID {$args[0]} exists." );
        }
    }
}
<?
php

use WP_CLI\CommandWithDBObject;
use 
WP_CLI\Fetchers\Site as SiteFetcher;
use 
WP_CLI\Iterators\Query as QueryIterator;
use 
WP_CLI\Iterators\Table as TableIterator;
use 
WP_CLI\Utils;
use 
WP_CLI\Formatter;

/**
 * Creates, deletes, empties, moderates, and lists one or more sites on a multisite installation.
 *
 * ## EXAMPLES
 *
 *     # Create site
 *     $ wp site create --slug=example
 *     Success: Site 3 created: www.example.com/example/
 *
 *     # Output a simple list of site URLs
 *     $ wp site list --field=url
 *     http://www.example.com/
 *     http://www.example.com/subdir/
 *
 *     # Delete site
 *     $ wp site delete 123
 *     Are you sure you want to delete the 'http://www.example.com/example' site? [y/n] y
 *     Success: The site at 'http://www.example.com/example' was deleted.
 *
 * @package wp-cli
 */
class Site_Command extends CommandWithDBObject {

    protected 
$obj_type   'site';
    protected 
$obj_id_key 'blog_id';

    public function 
__construct() {
        
$this->fetcher = new SiteFetcher();
    }

    
/**
     * Delete comments.
     */
    
private function empty_comments() {
        global 
$wpdb;

        
// Empty comments and comment cache
        
$comment_ids $wpdb->get_col"SELECT comment_ID FROM $wpdb->comments);
        foreach ( 
$comment_ids as $comment_id ) {
            
wp_cache_delete$comment_id'comment' );
            
wp_cache_delete$comment_id'comment_meta' );
        }
        
$wpdb->query"TRUNCATE $wpdb->comments);
        
$wpdb->query"TRUNCATE $wpdb->commentmeta);
    }

    
/**
     * Delete all posts.
     */
    
private function empty_posts() {
        global 
$wpdb;

        
// Empty posts and post cache
        
$posts_query "SELECT ID FROM $wpdb->posts";
        
$posts       = new QueryIterator$posts_query10000 );

        
$taxonomies get_taxonomies();

        while ( 
$posts->valid() ) {
            
$post_id $posts->current()->ID;

            
wp_cache_delete$post_id'posts' );
            
wp_cache_delete$post_id'post_meta' );
            foreach ( 
$taxonomies as $taxonomy ) {
                
wp_cache_delete$post_id"{$taxonomy}_relationships" );
            }
            
wp_cache_delete$wpdb->blogid '-' $post_id'global-posts' );

            
$posts->next();
        }
        
$wpdb->query"TRUNCATE $wpdb->posts);
        
$wpdb->query"TRUNCATE $wpdb->postmeta);
    }

    
/**
     * Delete terms, taxonomies, and tax relationships.
     */
    
private function empty_taxonomies() {
        global 
$wpdb;

        
// Empty taxonomies and terms
        
$terms      $wpdb->get_results"SELECT term_id, taxonomy FROM $wpdb->term_taxonomy);
        
$taxonomies = [];
        foreach ( (array) 
$terms as $term ) {
            
$taxonomies[] = $term->taxonomy;
            
wp_cache_delete$term->term_id$term->taxonomy );
        }

        
$taxonomies array_unique$taxonomies );
        
$cleaned    = [];
        foreach ( 
$taxonomies as $taxonomy ) {
            if ( isset( 
$cleaned$taxonomy ] ) ) {
                continue;
            }
            
$cleaned$taxonomy ] = true;

            
wp_cache_delete'all_ids'$taxonomy );
            
wp_cache_delete'get'$taxonomy );
            
delete_option"{$taxonomy}_children" );
        }
        
$wpdb->query"TRUNCATE $wpdb->terms);
        
$wpdb->query"TRUNCATE $wpdb->term_taxonomy);
        
$wpdb->query"TRUNCATE $wpdb->term_relationships);
        if ( ! empty( 
$wpdb->termmeta ) ) {
            
$wpdb->query"TRUNCATE $wpdb->termmeta);
        }
    }

    
/**
     * Delete all links, link_category terms, and related cache.
     */
    
private function empty_links() {
        global 
$wpdb;

        
// Remove links and related cached data.
        
$links_query "SELECT link_id FROM {$wpdb->links}";
        
$links       = new QueryIterator$links_query10000 );

        
// Remove bookmarks cache group.
        
wp_cache_delete'get_bookmarks''bookmark' );

        while ( 
$links->valid() ) {
            
$link_id $links->current()->link_id;

            
// Remove cache for the link.
            
wp_delete_object_term_relationships$link_id'link_category' );
            
wp_cache_delete$link_id'bookmark' );
            
clean_object_term_cache$link_id'link' );

            
$links->next();
        }

        
// Empty the table once link related cache and term is removed.
        
$wpdb->query"TRUNCATE {$wpdb->links});
    }

    
/**
     * Insert default terms.
     */
    
private function insert_default_terms() {
        global 
$wpdb;

        
// Default category
        
$cat_name __'Uncategorized' );

        
/* translators: Default category slug */
        
$cat_slug sanitize_title_x'Uncategorized''Default category slug' ) );

        if ( 
global_terms_enabled() ) {
            
$cat_id $wpdb->get_var$wpdb->prepare"SELECT cat_ID FROM {$wpdb->sitecategories} WHERE category_nicename = %s"$cat_slug ) );
            if ( 
null === $cat_id ) {
                
$wpdb->insert(
                    
$wpdb->sitecategories,
                    [
                        
'cat_ID'            => 0,
                        
'cat_name'          => $cat_name,
                        
'category_nicename' => $cat_slug,
                        
'last_updated'      => current_time(
                            
'mysql',
                            
true
                        
),
                    ]
                );
                
$cat_id $wpdb->insert_id;
            }
            
update_option'default_category'$cat_id );
        } else {
            
$cat_id 1;
        }

        
$wpdb->insert(
            
$wpdb->terms,
            [
                
'term_id'    => $cat_id,
                
'name'       => $cat_name,
                
'slug'       => $cat_slug,
                
'term_group' => 0,
            ]
        );
        
$wpdb->insert(
            
$wpdb->term_taxonomy,
            [
                
'term_id'     => $cat_id,
                
'taxonomy'    => 'category',
                
'description' => '',
                
'parent'      => 0,
                
'count'       => 0,
            ]
        );
    }

    
/**
     * Reset option values to default.
     */
    
private function reset_options() {
        
// Reset Privacy Policy value to prevent error.
        
update_option'wp_page_for_privacy_policy');

        
// Reset sticky posts option.
        
update_option'sticky_posts', [] );
    }

    
/**
     * Empties a site of its content (posts, comments, terms, and meta).
     *
     * Truncates posts, comments, and terms tables to empty a site of its
     * content. Doesn't affect site configuration (options) or users.
     *
     * If running a persistent object cache, make sure to flush the cache
     * after emptying the site, as the cache values will be invalid otherwise.
     *
     * To also empty custom database tables, you'll need to hook into command
     * execution:
     *
     * ```
     * WP_CLI::add_hook( 'after_invoke:site empty', function(){
     *     global $wpdb;
     *     foreach( array( 'p2p', 'p2pmeta' ) as $table ) {
     *         $table = $wpdb->$table;
     *         $wpdb->query( "TRUNCATE $table" );
     *     }
     * });
     * ```
     *
     * ## OPTIONS
     *
     * [--uploads]
     * : Also delete *all* files in the site's uploads directory.
     *
     * [--yes]
     * : Proceed to empty the site without a confirmation prompt.
     *
     * ## EXAMPLES
     *
     *     $ wp site empty
     *     Are you sure you want to empty the site at http://www.example.com of all posts, links, comments, and terms? [y/n] y
     *     Success: The site at 'http://www.example.com' was emptied.
     *
     * @subcommand empty
     */
    
public function empty_$args$assoc_args ) {

        
$upload_message '';
        if ( 
Utils\get_flag_value$assoc_args'uploads' ) ) {
            
$upload_message ', and delete its uploads directory';
        }

        
WP_CLI::confirm"Are you sure you want to empty the site at '" site_url() . "' of all posts, links, comments, and terms" $upload_message '?'$assoc_args );

        
$this->empty_posts();
        
$this->empty_links();
        
$this->empty_comments();
        
$this->empty_taxonomies();
        
$this->insert_default_terms();
        
$this->reset_options();

        if ( ! empty( 
$upload_message ) ) {
            
$upload_dir wp_upload_dir();
            
$files      = new RecursiveIteratorIterator(
                new 
RecursiveDirectoryIterator$upload_dir['basedir'], RecursiveDirectoryIterator::SKIP_DOTS ),
                
RecursiveIteratorIterator::CHILD_FIRST
            
);

            
$files_to_unlink       = [];
            
$directories_to_delete = [];
            
$is_main_site          is_main_site();
            foreach ( 
$files as $fileinfo ) {
                
$realpath $fileinfo->getRealPath();
                
// Don't clobber subsites when operating on the main site
                
if ( $is_main_site && false !== stripos$realpath'/sites/' ) ) {
                    continue;
                }
                if ( 
$fileinfo->isDir() ) {
                    
$directories_to_delete[] = $realpath;
                } else {
                    
$files_to_unlink[] = $realpath;
                }
            }
            foreach ( 
$files_to_unlink as $file ) {
                
unlink$file );
            }
            foreach ( 
$directories_to_delete as $directory ) {
                
// Directory could be main sites directory '/sites' which may be non-empty.
                
@rmdir$directory );
            }
            
// May be non-empty if '/sites' still around.
            
@rmdir$upload_dir['basedir'] );
        }

        
WP_CLI::success"The site at '" site_url() . "' was emptied." );
    }

    
/**
     * Deletes a site in a multisite installation.
     *
     * ## OPTIONS
     *
     * [<site-id>]
     * : The id of the site to delete. If not provided, you must set the --slug parameter.
     *
     * [--slug=<slug>]
     * : Path of the blog to be deleted. Subdomain on subdomain installs, directory on subdirectory installs.
     *
     * [--yes]
     * : Answer yes to the confirmation message.
     *
     * [--keep-tables]
     * : Delete the blog from the list, but don't drop it's tables.
     *
     * ## EXAMPLES
     *
     *     $ wp site delete 123
     *     Are you sure you want to delete the http://www.example.com/example site? [y/n] y
     *     Success: The site at 'http://www.example.com/example' was deleted.
     */
    
public function delete$args$assoc_args ) {
        if ( ! 
is_multisite() ) {
            
WP_CLI::error'This is not a multisite installation.' );
        }

        if ( isset( 
$assoc_args['slug'] ) ) {
            
$blog get_blog_detailstrim$assoc_args['slug'], '/' ) );
        } else {
            if ( empty( 
$args ) ) {
                
WP_CLI::error'Need to specify a blog id.' );
            }

            
$blog_id $args[0];

            if ( 
is_main_site$blog_id ) ) {
                
WP_CLI::error'You cannot delete the root site.' );
            }

            
$blog get_blog_details$blog_id );
        }

        if ( ! 
$blog ) {
            
WP_CLI::error'Site not found.' );
        }

        
$site_url trailingslashit$blog->siteurl );

        
WP_CLI::confirm"Are you sure you want to delete the '{$site_url}' site?"$assoc_args );

        
wpmu_delete_blog$blog->blog_id, ! Utils\get_flag_value$assoc_args'keep-tables' ) );

        
WP_CLI::success"The site at '{$site_url}' was deleted." );
    }

    
/**
     * Creates a site in a multisite installation.
     *
     * ## OPTIONS
     *
     * --slug=<slug>
     * : Path for the new site. Subdomain on subdomain installs, directory on subdirectory installs.
     *
     * [--title=<title>]
     * : Title of the new site. Default: prettified slug.
     *
     * [--email=<email>]
     * : Email for admin user. User will be created if none exists. Assignment to super admin if not included.
     *
     * [--network_id=<network-id>]
     * : Network to associate new site with. Defaults to current network (typically 1).
     *
     * [--private]
     * : If set, the new site will be non-public (not indexed)
     *
     * [--porcelain]
     * : If set, only the site id will be output on success.
     *
     * ## EXAMPLES
     *
     *     $ wp site create --slug=example
     *     Success: Site 3 created: http://www.example.com/example/
     */
    
public function create$args$assoc_args ) {
        if ( ! 
is_multisite() ) {
            
WP_CLI::error'This is not a multisite installation.' );
        }

        global 
$wpdb$current_site;

        
$base  $assoc_args['slug'];
        
$title Utils\get_flag_value$assoc_args'title'ucfirst$base ) );

        
$email = empty( $assoc_args['email'] ) ? '' $assoc_args['email'];

        
// Network
        
if ( ! empty( $assoc_args['network_id'] ) ) {
            
$network $this->get_network$assoc_args['network_id'] );
            if ( 
false === $network ) {
                
WP_CLI::error"Network with id {$assoc_args['network_id']} does not exist." );
            }
        } else {
            
$network $current_site;
        }

        
$public = ! Utils\get_flag_value$assoc_args'private' );

        
// Sanitize
        
if ( preg_match'|^([a-zA-Z0-9-])+$|'$base ) ) {
            
$base strtolower$base );
        }

        
// If not a subdomain install, make sure the domain isn't a reserved word
        
if ( ! is_subdomain_install() ) {
            
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Calling WordPress native hook.
            
$subdirectory_reserved_names apply_filters'subdirectory_reserved_names', [ 'page''comments''blog''files''feed' ] );
            if ( 
in_array$base$subdirectory_reserved_namestrue ) ) {
                
WP_CLI::error'The following words are reserved and cannot be used as blog names: ' implode', '$subdirectory_reserved_names ) );
            }
        }

        
// Check for valid email, if not, use the first super admin found
        // Probably a more efficient way to do this so we dont query for the
        // User twice if super admin
        
$email sanitize_email$email );
        if ( empty( 
$email ) || ! is_email$email ) ) {
            
$super_admins get_super_admins();
            
$email        '';
            if ( ! empty( 
$super_admins ) && is_array$super_admins ) ) {
                
// Just get the first one
                
$super_login $super_admins[0];
                
$super_user  get_user_by'login'$super_login );
                if ( 
$super_user ) {
                    
$email $super_user->user_email;
                }
            }
        }

        if ( 
is_subdomain_install() ) {
            
$newdomain $base '.' preg_replace'|^www\.|'''$current_site->domain );
            
$path      $current_site->path;
        } else {
            
$newdomain $current_site->domain;
            
$path      $current_site->path $base '/';
        }

        
$user_id email_exists$email );
        if ( ! 
$user_id ) { // Create a new user with a random password
            
$password wp_generate_password12false );
            
$user_id  wpmu_create_user$base$password$email );
            if ( 
false === $user_id ) {
                
WP_CLI::error"Can't create user." );
            } else {
                
User_Command::wp_new_user_notification$user_id$password );
            }
        }

        
$wpdb->hide_errors();
        
$title wp_slash$title );
        
$id    wpmu_create_blog$newdomain$path$title$user_id, [ 'public' => $public ], $network->id );
        
$wpdb->show_errors();
        if ( ! 
is_wp_error$id ) ) {
            if ( ! 
is_super_admin$user_id ) && ! get_user_option'primary_blog'$user_id ) ) {
                
update_user_option$user_id'primary_blog'$idtrue );
            }
        } else {
            
WP_CLI::error$id->get_error_message() );
        }

        if ( 
Utils\get_flag_value$assoc_args'porcelain' ) ) {
            
WP_CLI::line$id );
        } else {
            
$site_url trailingslashitget_site_url$id ) );
            
WP_CLI::success"Site {$id} created: {$site_url});
        }
    }

    
/**
     * Gets network data for a given id.
     *
     * @param int     $network_id
     * @return bool|array False if no network found with given id, array otherwise
     */
    
private function get_network$network_id ) {
        global 
$wpdb;

        
// Load network data
        
$networks $wpdb->get_results(
            
$wpdb->prepare(
                
"SELECT * FROM $wpdb->site WHERE id = %d",
                
$network_id
            
)
        );

        if ( ! empty( 
$networks ) ) {
            
// Only care about domain and path which are set here
            
return $networks[0];
        }

        return 
false;
    }

    
/**
     * Lists all sites in a multisite installation.
     *
     * ## OPTIONS
     *
     * [--network=<id>]
     * : The network to which the sites belong.
     *
     * [--<field>=<value>]
     * : Filter by one or more fields (see "Available Fields" section). However,
     * 'url' isn't an available filter, because it's created from domain + path.
     *
     * [--site__in=<value>]
     * : Only list the sites with these blog_id values (comma-separated).
     *
     * [--field=<field>]
     * : Prints the value of a single field for each site.
     *
     * [--fields=<fields>]
     * : Comma-separated list of fields to show.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - count
     *   - ids
     *   - json
     *   - yaml
     * ---
     *
     * ## AVAILABLE FIELDS
     *
     * These fields will be displayed by default for each site:
     *
     * * blog_id
     * * url
     * * last_updated
     * * registered
     *
     * These fields are optionally available:
     *
     * * site_id
     * * domain
     * * path
     * * public
     * * archived
     * * mature
     * * spam
     * * deleted
     * * lang_id
     *
     * ## EXAMPLES
     *
     *     # Output a simple list of site URLs
     *     $ wp site list --field=url
     *     http://www.example.com/
     *     http://www.example.com/subdir/
     *
     * @subcommand list
     */
    
public function list_$args$assoc_args ) {
        if ( ! 
is_multisite() ) {
            
WP_CLI::error'This is not a multisite installation.' );
        }

        global 
$wpdb;

        if ( isset( 
$assoc_args['fields'] ) ) {
            
$assoc_args['fields'] = preg_split'/,[ \t]*/'$assoc_args['fields'] );
        }

        
$defaults   = [
            
'format' => 'table',
            
'fields' => [ 'blog_id''url''last_updated''registered' ],
        ];
        
$assoc_args array_merge$defaults$assoc_args );

        
$where  = [];
        
$append '';

        
$site_cols = [ 'blog_id''last_updated''registered''site_id''domain''path''public''archived''mature''spam''deleted''lang_id' ];
        foreach ( 
$site_cols as $col ) {
            if ( isset( 
$assoc_args$col ] ) ) {
                
$where$col ] = $assoc_args$col ];
            }
        }

        if ( isset( 
$assoc_args['site__in'] ) ) {
            
$where['blog_id'] = explode','$assoc_args['site__in'] );
            
$append           'ORDER BY FIELD( blog_id, ' implode','array_map'intval'$where['blog_id'] ) ) . ' )';
        }

        if ( isset( 
$assoc_args['network'] ) ) {
            
$where['site_id'] = $assoc_args['network'];
        }

        
$iterator_args = [
            
'table'  => $wpdb->blogs,
            
'where'  => $where,
            
'append' => $append,
        ];

        
$iterator = new TableIterator$iterator_args );

        
$iterator Utils\iterator_map(
            
$iterator,
            function( 
$blog ) {
                
$blog->url trailingslashitget_home_url$blog->blog_id ) );
                return 
$blog;
            }
        );

        if ( ! empty( 
$assoc_args['format'] ) && 'ids' === $assoc_args['format'] ) {
            
$sites     iterator_to_array$iterator );
            
$ids       wp_list_pluck$sites'blog_id' );
            
$formatter = new Formatter$assoc_argsnull'site' );
            
$formatter->display_items$ids );
        } else {
            
$formatter = new Formatter$assoc_argsnull'site' );
            
$formatter->display_items$iterator );
        }
    }

    
/**
     * Archives one or more sites.
     *
     * ## OPTIONS
     *
     * <id>...
     * : One or more IDs of sites to archive.
     *
     * ## EXAMPLES
     *
     *     $ wp site archive 123
     *     Success: Site 123 archived.
     */
    
public function archive$args ) {
        
$this->update_site_status$args'archived');
    }

    
/**
     * Unarchives one or more sites.
     *
     * ## OPTIONS
     *
     * <id>...
     * : One or more IDs of sites to unarchive.
     *
     * ## EXAMPLES
     *
     *     $ wp site unarchive 123
     *     Success: Site 123 unarchived.
     */
    
public function unarchive$args ) {
        
$this->update_site_status$args'archived');
    }

    
/**
     * Activates one or more sites.
     *
     * ## OPTIONS
     *
     * <id>...
     * : One or more IDs of sites to activate.
     *
     * ## EXAMPLES
     *
     *     $ wp site activate 123
     *     Success: Site 123 activated.
     */
    
public function activate$args ) {
        
$this->update_site_status$args'deleted');
    }

    
/**
     * Deactivates one or more sites.
     *
     * ## OPTIONS
     *
     * <id>...
     * : One or more IDs of sites to deactivate.
     *
     * ## EXAMPLES
     *
     *     $ wp site deactivate 123
     *     Success: Site 123 deactivated.
     */
    
public function deactivate$args ) {
        
$this->update_site_status$args'deleted');
    }

    
/**
     * Marks one or more sites as spam.
     *
     * ## OPTIONS
     *
     * <id>...
     * : One or more IDs of sites to be marked as spam.
     *
     * ## EXAMPLES
     *
     *     $ wp site spam 123
     *     Success: Site 123 marked as spam.
     */
    
public function spam$args ) {
        
$this->update_site_status$args'spam');
    }

    
/**
     * Removes one or more sites from spam.
     *
     * ## OPTIONS
     *
     * <id>...
     * : One or more IDs of sites to remove from spam.
     *
     * ## EXAMPLES
     *
     *     $ wp site unspam 123
     *     Success: Site 123 removed from spam.
     *
     * @subcommand unspam
     */
    
public function unspam$args ) {
        
$this->update_site_status$args'spam');
    }

    
/**
     * Sets one or more sites as mature.
     *
     * ## OPTIONS
     *
     * <id>...
     * : One or more IDs of sites to set as mature.
     *
     * ## EXAMPLES
     *
     *     $ wp site mature 123
     *     Success: Site 123 marked as mature.
     */
    
public function mature$args ) {
        
$this->update_site_status$args'mature');
    }

    
/**
     * Sets one or more sites as immature.
     *
     * ## OPTIONS
     *
     * <id>...
     * : One or more IDs of sites to set as unmature.
     *
     * ## EXAMPLES
     *
     *     $ wp site general 123
     *     Success: Site 123 marked as unmature.
     */
    
public function unmature$args ) {
        
$this->update_site_status$args'mature');
    }

    
/**
     * Sets one or more sites as public.
     *
     * ## OPTIONS
     *
     * <id>...
     * : One or more IDs of sites to set as public.
     *
     * ## EXAMPLES
     *
     *     $ wp site public 123
     *     Success: Site 123 marked as public.
     *
     * @subcommand public
     */
    
public function set_public$args ) {
        
$this->update_site_status$args'public');
    }

    
/**
     * Sets one or more sites as private.
     *
     * ## OPTIONS
     *
     * <id>...
     * : One or more IDs of sites to set as private.
     *
     * ## EXAMPLES
     *
     *     $ wp site private 123
     *     Success: Site 123 marked as private.
     *
     * @subcommand private
     */
    
public function set_private$args ) {
        
$this->update_site_status$args'public');
    }

    private function 
update_site_status$ids$pref$value ) {
        
$value = (int) $value;

        switch ( 
$pref ) {
            case 
'archived':
                
$action $value 'archived' 'unarchived';
                break;
            case 
'deleted':
                
$action $value 'deactivated' 'activated';
                break;
            case 
'mature':
                
$action $value 'marked as mature' 'marked as unmature';
                break;
            case 
'public':
                
$action $value 'marked as public' 'marked as private';
                break;
            case 
'spam':
                
$action $value 'marked as spam' 'removed from spam';
                break;
        }

        foreach ( 
$ids as $site_id ) {
            
$site $this->fetcher->get_check$site_id );

            if ( 
is_main_site$site->blog_id ) ) {
                
WP_CLI::warning'You are not allowed to change the main site.' );
                continue;
            }

            
$old_value = (int) get_blog_status$site->blog_id$pref );

            if ( 
$value === $old_value ) {
                
WP_CLI::warning"Site {$site->blog_id} already {$action}." );
                continue;
            }

            
update_blog_status$site->blog_id$pref$value );
            
WP_CLI::success"Site {$site->blog_id} {$action}." );
        }
    }
}
<?
php

use WP_CLI\ExitException;
use 
WP_CLI\Fetchers\User as UserFetcher;
use 
WP_CLI\Formatter;
use 
WP_CLI\Utils;

/**
 * Creates, updates, deletes, lists and retrieves application passwords.
 *
 * ## EXAMPLES
 *
 *     # List user application passwords and only show app name and password hash
 *     $ wp user application-passwords list 123 --fields=name,password
 *     +--------+------------------------------------+
 *     | name   | password                           |
 *     +--------+------------------------------------+
 *     | myapp  | $P$BVGeou1CUot114YohIemgpwxQCzb8O/ |
 *     +--------+------------------------------------+
 *
 *     # Get a specific application password and only show app name and created timestamp
 *     $ wp user application-passwords get 123 6633824d-c1d7-4f79-9dd5-4586f734d69e --fields=name,created
 *     +--------+------------+
 *     | name   | created    |
 *     +--------+------------+
 *     | myapp  | 1638395611 |
 *     +--------+------------+
 *
 *     # Create user application password
 *     $ wp user application-passwords create 123 myapp
 *     Success: Created application password.
 *     Password: ZG1bxdxdzjTwhsY8vK8l1C65
 *
 *     # Only print the password without any chrome
 *     $ wp user application-passwords create 123 myapp --porcelain
 *     ZG1bxdxdzjTwhsY8vK8l1C65
 *
 *     # Update an existing application password
 *     $ wp user application-passwords update 123 6633824d-c1d7-4f79-9dd5-4586f734d69e --name=newappname
 *     Success: Updated application password.
 *
 *     # Check if an application password for a given application exists
 *     $ wp user application-passwords exists 123 myapp
 *     $ echo $?
 *     1
 *
 *     # Bash script for checking whether an application password exists and creating one if not
 *     if ! wp user application-password exists 123 myapp; then
 *         PASSWORD=$(wp user application-password create 123 myapp --porcelain)
 *     fi
 */
final class User_Application_Password_Command {

    
/**
     * List of application password fields.
     *
     * @var array<string>
     */
    
const APPLICATION_PASSWORD_FIELDS = [
        
'uuid',
        
'app_id',
        
'name',
        
'password',
        
'created',
        
'last_used',
        
'last_ip',
    ];

    
/**
     * Lists all application passwords associated with a user.
     *
     * ## OPTIONS
     *
     * <user>
     * : The user login, user email, or user ID of the user to get application passwords for.
     *
     * [--<field>=<value>]
     * : Filter the list by a specific field.
     *
     * [--field=<field>]
     * : Prints the value of a single field for each application password.
     *
     * [--fields=<fields>]
     * : Limit the output to specific fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - count
     *   - yaml
     * ---
     *
     * [--orderby=<fields>]
     * : Set orderby which field.
     * ---
     * default: created
     * options:
     *  - uuid
     *  - app_id
     *  - name
     *  - password
     *  - created
     *  - last_used
     *  - last_ip
     * ---
     *
     * [--order=<order>]
     * : Set ascending or descending order.
     * ---
     * default: desc
     * options:
     *  - asc
     *  - desc
     * ---
     *
     * ## EXAMPLES
     *
     *     # List user application passwords and only show app name and password hash
     *     $ wp user application-passwords list 123 --fields=name,password
     *     +--------+------------------------------------+
     *     | name   | password                           |
     *     +--------+------------------------------------+
     *     | myapp  | $P$BVGeou1CUot114YohIemgpwxQCzb8O/ |
     *     +--------+------------------------------------+
     *
     * @subcommand list
     *
     * @param array $args       Indexed array of positional arguments.
     * @param array $assoc_args Associative array of associative arguments.
     * @throws ExitException If the user could not be found/parsed.
     * @throws ExitException If the application passwords could not be retrieved.
     */
    
public function list_$args$assoc_args ) {
        
$args $this->replace_login_with_user_id$args );

        list( 
$user_id ) = $args;

        
$application_passwords WP_Application_Passwords::get_user_application_passwords$user_id );

        if ( 
$application_passwords instanceof WP_Error ) {
            
WP_CLI::error$application_passwords );
        }

        if ( empty( 
$application_passwords ) ) {
            
$application_passwords = [];
        }

        
$order   Utils\get_flag_value$assoc_args'order' );
        
$orderby Utils\get_flag_value$assoc_args'orderby' );

        
usort(
            
$application_passwords,
            static function ( 
$a$b ) use ( $orderby$order ) {
                
// Sort array.
                
return 'asc' === $order
                    
$a$orderby ] > $b$orderby ]
                    : 
$a$orderby ] < $b$orderby ];
            }
        );

        
$fields self::APPLICATION_PASSWORD_FIELDS;

        
// Avoid confusion regarding the dash/underscore usage.
        
foreach ( [ 'app-id''last-used''last-ip' ] as $flag ) {
            if ( 
array_key_exists$flag$assoc_args ) ) {
                
$underscored_flag                str_replace'-''_'$flag );
                
$assoc_args$underscored_flag ] = $assoc_args$flag ];
                unset( 
$assoc_args$flag ] );
            }
        }

        foreach ( 
$fields as $field ) {
            if ( ! 
array_key_exists$field$assoc_args ) ) {
                continue;
            }

            
$value Utils\get_flag_value$assoc_args$field );

            
$application_passwords array_filter(
                
$application_passwords,
                static function ( 
$application_password ) use ( $field$value ) {
                    return 
$application_password$field ] === $value;
                }
            );
        }

        if ( ! empty( 
$assoc_args['fields'] ) ) {
            
$fields explode','$assoc_args['fields'] );
        }

        
$formatter = new Formatter$assoc_args$fields );
        
$formatter->display_items$application_passwords );
    }

    
/**
     * Gets a specific application password.
     *
     * ## OPTIONS
     *
     * <user>
     * : The user login, user email, or user ID of the user to get the application password for.
     *
     * <uuid>
     * : The universally unique ID of the application password.
     *
     * [--field=<field>]
     * : Prints the value of a single field for the application password.
     *
     * [--fields=<fields>]
     * : Limit the output to specific fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - yaml
     * ---
     *
     * ## EXAMPLES
     *
     *     # Get a specific application password and only show app name and created timestamp
     *     $ wp user application-passwords get 123 6633824d-c1d7-4f79-9dd5-4586f734d69e --fields=name,created
     *     +--------+------------+
     *     | name   | created    |
     *     +--------+------------+
     *     | myapp  | 1638395611 |
     *     +--------+------------+
     *
     * @param array $args       Indexed array of positional arguments.
     * @param array $assoc_args Associative array of associative arguments.
     * @throws ExitException If the application passwords could not be retrieved.
     */
    
public function get$args$assoc_args ) {
        
$args $this->replace_login_with_user_id$args );

        list( 
$user_id$uuid ) = $args;

        
$application_password WP_Application_Passwords::get_user_application_password$user_id$uuid );

        if ( 
$application_password instanceof WP_Error ) {
            
WP_CLI::error$application_password );
        }

        if ( 
null === $application_password ) {
            
WP_CLI::error'No application password found for this user ID and UUID.' );
        }

        
$fields self::APPLICATION_PASSWORD_FIELDS;

        if ( ! empty( 
$assoc_args['fields'] ) ) {
            
$fields explode','$assoc_args['fields'] );
        }

        
$formatter = new Formatter$assoc_args$fields );
        
$formatter->display_items( [ $application_password ] );
    }

    
/**
     * Creates a new application password.
     *
     * ## OPTIONS
     *
     * <user>
     * : The user login, user email, or user ID of the user to create a new application password for.
     *
     * <app-name>
     * : Unique name of the application to create an application password for.
     *
     * [--app-id=<app-id>]
     * : Application ID to attribute to the application password.
     *
     * [--porcelain]
     * : Output just the new password.
     *
     * ## EXAMPLES
     *
     *     # Create user application password
     *     $ wp user application-passwords create 123 myapp
     *     Success: Created application password.
     *     Password: ZG1bxdxdzjTwhsY8vK8l1C65
     *
     *     # Only print the password without any chrome
     *     $ wp user application-passwords create 123 myapp --porcelain
     *     ZG1bxdxdzjTwhsY8vK8l1C65
     *
     *     # Create user application with a custom application ID for internal tracking
     *     $ wp user application-passwords create 123 myapp --app-id=42 --porcelain
     *     ZG1bxdxdzjTwhsY8vK8l1C65
     *
     * @param array $args       Indexed array of positional arguments.
     * @param array $assoc_args Associative array of associative arguments.
     * @throws ExitException If the application password could not be created.
     */
    
public function create$args$assoc_args ) {
        
$args $this->replace_login_with_user_id$args );

        list( 
$user_id$app_name ) = $args;

        
$app_id Utils\get_flag_value$assoc_args'app-id''' );

        
$arguments = [
            
'name'   => $app_name,
            
'app_id' => $app_id,
        ];

        
$result WP_Application_Passwords::create_new_application_password$user_id$arguments );

        if ( 
$result instanceof WP_Error ) {
            
WP_CLI::error$result );
        }

        if ( 
Utils\get_flag_value$assoc_args'porcelain'false ) ) {
            
WP_CLI::line$result[0] );
            
WP_CLI::halt);
        }

        
WP_CLI::success'Created application password.' );
        
WP_CLI::line"Password: {$result[0]});
    }

    
/**
     * Updates an existing application password.
     *
     * ## OPTIONS
     *
     * <user>
     * : The user login, user email, or user ID of the user to update the application password for.
     *
     * <uuid>
     * : The universally unique ID of the application password.
     *
     * [--<field>=<value>]
     * : Update the <field> with a new <value>. Currently supported fields: name.
     *
     * ## EXAMPLES
     *
     *     # Update an existing application password
     *     $ wp user application-passwords update 123 6633824d-c1d7-4f79-9dd5-4586f734d69e --name=newappname
     *     Success: Updated application password.
     *
     * @param array $args       Indexed array of positional arguments.
     * @param array $assoc_args Associative array of associative arguments.
     * @throws ExitException If the application password could not be created.
     */
    
public function update$args$assoc_args ) {
        
$args $this->replace_login_with_user_id$args );

        list( 
$user_id$uuid ) = $args;

        
$result WP_Application_Passwords::update_application_password$user_id$uuid$assoc_args );

        if ( 
$result instanceof WP_Error ) {
            
WP_CLI::error$result );
        }

        
WP_CLI::success'Updated application password.' );
    }

    
/**
     * Record usage of an application password.
     *
     * ## OPTIONS
     *
     * <user>
     * : The user login, user email, or user ID of the user to update the application password for.
     *
     * <uuid>
     * : The universally unique ID of the application password.
     *
     * ## EXAMPLES
     *
     *     # Record usage of an application password
     *     $ wp user application-passwords record-usage 123 6633824d-c1d7-4f79-9dd5-4586f734d69e
     *     Success: Recorded application password usage.
     *
     * @subcommand record-usage
     *
     * @param array $args       Indexed array of positional arguments.
     * @param array $assoc_args Associative array of associative arguments.
     * @throws ExitException If the application password could not be created.
     */
    
public function record_usage$args$assoc_args ) {
        
$args $this->replace_login_with_user_id$args );

        list( 
$user_id$uuid ) = $args;

        
$result WP_Application_Passwords::record_application_password_usage$user_id$uuid );

        if ( 
$result instanceof WP_Error ) {
            
WP_CLI::error$result );
        }

        
WP_CLI::success'Recorded application password usage.' );
    }

    
/**
     * Delete an existing application password.
     *
     * ## OPTIONS
     *
     * <user>
     * : The user login, user email, or user ID of the user to delete the application password for.
     *
     * [<uuid>...]
     * : Comma-separated list of UUIDs of the application passwords to delete.
     *
     * [--all]
     * : Delete all of the user's application password.
     *
     * ## EXAMPLES
     *
     *     # Record usage of an application password
     *     $ wp user application-passwords record-usage 123 6633824d-c1d7-4f79-9dd5-4586f734d69e
     *     Success: Recorded application password usage.
     *
     * @param array $args       Indexed array of positional arguments.
     * @param array $assoc_args Associative array of associative arguments.
     * @throws ExitException If the application password could not be created.
     */
    
public function delete$args$assoc_args ) {
        
$args $this->replace_login_with_user_id$args );

        
$user_id array_shift$args );
        
$all     Utils\get_flag_value$assoc_args'all'false );
        
$count   count$args );

        if ( ( 
$count && true === $all ) || ( === $count && true !== $all ) ) {
            
WP_CLI::error'You need to specify either one or more UUIDS or provide the --all flag' );
        }

        if ( 
true === $all ) {
            
$result WP_Application_Passwords::delete_all_application_passwords$user_id );

            if ( 
$result instanceof WP_Error ) {
                
WP_CLI::error$result );
            }

            
WP_CLI::success'Deleted all application passwords.' );
            
WP_CLI::halt);
        }

        
$errors 0;
        foreach ( 
$args as $uuid ) {
            
$result WP_Application_Passwords::delete_application_password$user_id$uuid );

            if ( 
$result instanceof WP_Error ) {
                
WP_CLI::warning"Failed to delete UUID {$uuid}: " $result->get_error_message() );
                
$errors++;
            }
        }

        
WP_CLI::success(
            
sprintf(
                
'Deleted %d of %d application %s.',
                
$count $errors,
                
$count,
                
Utils\pluralize'password'$count )
            )
        );

        
WP_CLI::halt=== $errors );
    }

    
/**
     * Checks whether an application password for a given application exists.
     *
     * ## OPTIONS
     *
     * <user>
     * : The user login, user email, or user ID of the user to check the existence of an application password for.
     *
     * <app-name>
     * : Name of the application to check the existence of an application password for.
     *
     * ## EXAMPLES
     *
     *     # Check if an application password for a given application exists
     *     $ wp user application-passwords exists 123 myapp
     *     $ echo $?
     *     1
     *
     *     # Bash script for checking whether an application password exists and creating one if not
     *     if ! wp user application-password exists 123 myapp; then
     *         PASSWORD=$(wp user application-password create 123 myapp --porcelain)
     *     fi
     *
     * @param array $args       Indexed array of positional arguments.
     * @param array $assoc_args Associative array of associative arguments.
     * @throws ExitException If the application password could not be created.
     */
    
public function exists$args$assoc_args ) {
        
$args $this->replace_login_with_user_id$args );

        list( 
$user_id$app_name ) = $args;

        
$result $this->application_name_exists_for_user$user_id$app_name );

        if ( 
$result instanceof WP_Error ) {
            
WP_CLI::error$result );
        }

        
WP_CLI::halt$result );
    }

    
/**
     * Replaces user_login value with user ID.
     *
     * @param array $args Associative array of arguments.
     * @return array Associative array of arguments with the user login replaced with an ID.
     * @throws ExitException If the user is not found.
     */
    
private function replace_login_with_user_id$args ) {
        
$user    = ( new UserFetcher() )->get_check$args[0] );
        
$args[0] = $user->ID;

        return 
$args;
    }

    
/**
     * Checks if application name exists for the given user.
     *
     * This is a polyfill for WP_Application_Passwords::get_user_application_passwords(), which was only added for
     * WordPress 5.7+, but we're already supporting application passwords for WordPress 5.6+.
     *
     * @param int    $user_id  User ID to check the application passwords for.
     * @param string $app_name Application name to look for.
     * @return bool
     */
    
private function application_name_exists_for_user$user_id$app_name ) {
        if ( 
Utils\wp_version_compare'5.7''<' ) ) {
            
$passwords WP_Application_Passwords::get_user_application_passwords$user_id );

            foreach ( 
$passwords as $password ) {
                if ( 
strtolower$password['name'] ) === strtolower$name ) ) {
                    return 
true;
                }
            }

            return 
false;
        }

        return 
WP_Application_Passwords::application_name_exists_for_user$user_id$app_name );
    }
}
<?
php

use WP_CLI\CommandWithTerms;
use 
WP_CLI\Fetchers\Post as PostFetcher;

/**
 * Adds, updates, removes, and lists post terms.
 *
 * ## EXAMPLES
 *
 *     # Set post terms
 *     $ wp post term set 123 test category
 *     Success: Set terms.
 */
class Post_Term_Command extends CommandWithTerms {
    protected 
$obj_type 'post';

    public function 
__construct() {
        
$this->fetcher = new PostFetcher();
    }

    protected function 
get_object_type() {
        
$post $this->fetcher->get_check$this->get_obj_id() );

        return 
$post->post_type;
    }
}
<?
php

use WP_CLI\Fetchers\User as UserFetcher;
use 
WP_CLI\Formatter;
use 
WP_CLI\Utils;

/**
 * Destroys and lists a user's sessions.
 *
 * ## EXAMPLES
 *
 *     # List a user's sessions.
 *     $ wp user session list [email protected] --format=csv
 *     login_time,expiration_time,ip,ua
 *     "2016-01-01 12:34:56","2016-02-01 12:34:56",127.0.0.1,"Mozilla/5.0..."
 *
 *     # Destroy the most recent session of the given user.
 *     $ wp user session destroy admin
 *     Success: Destroyed session. 3 sessions remaining.
 *
 * @package wp-cli
 */
class User_Session_Command extends WP_CLI_Command {

    private 
$fields = [
        
'token',
        
'login_time',
        
'expiration_time',
        
'ip',
        
'ua',
    ];

    public function 
__construct() {
        
$this->fetcher = new UserFetcher();
    }

    
/**
     * Destroy a session for the given user.
     *
     * ## OPTIONS
     *
     * <user>
     * : User ID, user email, or user login.
     *
     * [<token>]
     * : The token of the session to destroy. Defaults to the most recently created session.
     *
     * [--all]
     * : Destroy all of the user's sessions.
     *
     * ## EXAMPLES
     *
     *     # Destroy the most recent session of the given user.
     *     $ wp user session destroy admin
     *     Success: Destroyed session. 3 sessions remaining.
     *
     *     # Destroy a specific session of the given user.
     *     $ wp user session destroy admin e073ad8540a9c2...
     *     Success: Destroyed session. 2 sessions remaining.
     *
     *     # Destroy all the sessions of the given user.
     *     $ wp user session destroy admin --all
     *     Success: Destroyed all sessions.
     *
     *     # Destroy all sessions for all users.
     *     $ wp user list --field=ID | xargs -n 1 wp user session destroy --all
     *     Success: Destroyed all sessions.
     *     Success: Destroyed all sessions.
     */
    
public function destroy$args$assoc_args ) {
        
$user    $this->fetcher->get_check$args[0] );
        
$token   Utils\get_flag_value$args1null );
        
$all     Utils\get_flag_value$assoc_args'all'false );
        
$manager WP_Session_Tokens::get_instance$user->ID );

        if ( 
$token && $all ) {
            
WP_CLI::error'The --all flag cannot be specified along with a session token.' );
        }

        if ( 
$all ) {
            
$manager->destroy_all();
            
WP_CLI::success'Destroyed all sessions.' );
            return;
        }

        
$sessions $this->get_all_sessions$manager );

        if ( ! 
$token ) {
            if ( empty( 
$sessions ) ) {
                
WP_CLI::success'No sessions to destroy.' );
            }
            
$last  end$sessions );
            
$token $last['token'];
        }

        if ( ! isset( 
$sessions$token ] ) ) {
            
WP_CLI::error'Session not found.' );
        }

        
$this->destroy_session$manager$token );
        
$remaining count$manager->get_all() );

        
WP_CLI::success"Destroyed session. {$remaining} remaining." );
    }

    
/**
     * List sessions for the given user.
     *
     * Note: The `token` field does not return the actual token, but a hash of
     * it. The real token is not persisted and can only be found in the
     * corresponding cookies on the client side.
     *
     * ## OPTIONS
     *
     * <user>
     * : User ID, user email, or user login.
     *
     * [--fields=<fields>]
     * : Limit the output to specific fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - yaml
     *   - count
     *   - ids
     * ---
     *
     * ## AVAILABLE FIELDS
     *
     * These fields will be displayed by default for each session:
     *
     * * token
     * * login_time
     * * expiration_time
     * * ip
     * * ua
     *
     * These fields are optionally available:
     *
     * * expiration
     * * login
     *
     * ## EXAMPLES
     *
     *     # List a user's sessions.
     *     $ wp user session list [email protected] --format=csv
     *     login_time,expiration_time,ip,ua
     *     "2016-01-01 12:34:56","2016-02-01 12:34:56",127.0.0.1,"Mozilla/5.0..."
     *
     * @subcommand list
     */
    
public function list_$args$assoc_args ) {
        
$user      $this->fetcher->get_check$args[0] );
        
$formatter $this->get_formatter$assoc_args );
        
$manager   WP_Session_Tokens::get_instance$user->ID );
        
$sessions  $this->get_all_sessions$manager );

        if ( 
'ids' === $formatter->format ) {
            echo 
implode' 'array_keys$sessions ) );
        } else {
            
$formatter->display_items$sessions );
        }
    }

    protected function 
get_all_sessionsWP_Session_Tokens $manager ) {
        
// Make the private session data accessible to WP-CLI
        
$get_sessions = new ReflectionMethod$manager'get_sessions' );
        
$get_sessions->setAccessibletrue );
        
$sessions $get_sessions->invoke$manager );

        
array_walk(
            
$sessions,
            function( &
$session$token ) {
                
$session['token']           = $token;
                
$session['login_time']      = date'Y-m-d H:i:s'$session['login'] ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
                
$session['expiration_time'] = date'Y-m-d H:i:s'$session['expiration'] ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
            
}
        );

        return 
$sessions;
    }

    protected function 
destroy_sessionWP_Session_Tokens $manager$token ) {
        
$update_session = new ReflectionMethod$manager'update_session' );
        
$update_session->setAccessibletrue );
        return 
$update_session->invoke$manager$tokennull );
    }

    private function 
get_formatter( &$assoc_args ) {
        return new 
Formatter$assoc_args$this->fields );
    }

}
<?
php

use WP_CLI\Entity\RecursiveDataStructureTraverser;
use 
WP_CLI\Formatter;
use 
WP_CLI\Utils;
use 
WP_CLI\Entity\Utils as EntityUtils;

/**
 * Adds, updates, deletes, and lists site options in a multisite installation.
 *
 * ## EXAMPLES
 *
 *     # Get site registration
 *     $ wp site option get registration
 *     none
 *
 *     # Add site option
 *     $ wp site option add my_option foobar
 *     Success: Added 'my_option' site option.
 *
 *     # Update site option
 *     $ wp site option update my_option '{"foo": "bar"}' --format=json
 *     Success: Updated 'my_option' site option.
 *
 *     # Delete site option
 *     $ wp site option delete my_option
 *     Success: Deleted 'my_option' site option.
 */
class Site_Option_Command extends WP_CLI_Command {

    
/**
     * Gets a site option.
     *
     * ## OPTIONS
     *
     * <key>
     * : Key for the site option.
     *
     * [--format=<format>]
     * : Get value in a particular format.
     * ---
     * default: var_export
     * options:
     *   - var_export
     *   - json
     *   - yaml
     * ---
     *
     * ## EXAMPLES
     *
     *     # Get site upload filetypes
     *     $ wp site option get upload_filetypes
     *     jpg jpeg png gif mov avi mpg
     */
    
public function get$args$assoc_args ) {
        list( 
$key ) = $args;

        
$value get_site_option$key );

        if ( 
false === $value ) {
            
WP_CLI::halt);
        }

        
WP_CLI::print_value$value$assoc_args );
    }

    
/**
     * Adds a site option.
     *
     * ## OPTIONS
     *
     * <key>
     * : The name of the site option to add.
     *
     * [<value>]
     * : The value of the site option to add. If ommited, the value is read from STDIN.
     *
     * [--format=<format>]
     * : The serialization format for the value.
     * ---
     * default: plaintext
     * options:
     *   - plaintext
     *   - json
     * ---
     *
     * ## EXAMPLES
     *
     *     # Create a site option by reading a JSON file
     *     $ wp site option add my_option --format=json < config.json
     *     Success: Added 'my_option' site option.
     */
    
public function add$args$assoc_args ) {
        
$key $args[0];

        
$value WP_CLI::get_value_from_arg_or_stdin$args);
        
$value WP_CLI::read_value$value$assoc_args );

        if ( ! 
add_site_option$key$value ) ) {
            
WP_CLI::error"Could not add site option '{$key}'. Does it already exist?" );
        } else {
            
WP_CLI::success"Added '{$key}' site option." );
        }
    }

    
/**
     * Lists site options.
     *
     * ## OPTIONS
     *
     * [--search=<pattern>]
     * : Use wildcards ( * and ? ) to match option name.
     *
     * [--site_id=<id>]
     * : Limit options to those of a particular site id.
     *
     * [--field=<field>]
     * : Prints the value of a single field.
     *
     * [--fields=<fields>]
     * : Limit the output to specific object fields.
     *
     * [--format=<format>]
     * : The serialization format for the value. total_bytes displays the total size of matching options in bytes.
     * ---
     * default: table
     * options:
     *   - table
     *   - json
     *   - csv
     *   - count
     *   - yaml
     *   - total_bytes
     * ---
     *
     * ## AVAILABLE FIELDS
     *
     * This field will be displayed by default for each matching option:
     *
     * * meta_key
     * * meta_value
     *
     * These fields are optionally available:
     *
     * * meta_id
     * * site_id
     * * size_bytes
     *
     * ## EXAMPLES
     *
     *     # List all site options begining with "i2f_"
     *     $ wp site option list --search="i2f_*"
     *     +-------------+--------------+
     *     | meta_key    | meta_value   |
     *     +-------------+--------------+
     *     | i2f_version | 0.1.0        |
     *     +-------------+--------------+
     *
     * @subcommand list
     */
    
public function list_$args$assoc_args ) {

        global 
$wpdb;
        
$pattern    '%';
        
$fields     = [ 'meta_key''meta_value' ];
        
$size_query ',LENGTH(meta_value) AS `size_bytes`';

        if ( isset( 
$assoc_args['search'] ) ) {
            
$pattern self::esc_like$assoc_args['search'] );
            
// substitute wildcards
            
$pattern str_replace'*''%'$pattern );
            
$pattern str_replace'?''_'$pattern );
        }

        if ( isset( 
$assoc_args['fields'] ) ) {
            
$fields explode','$assoc_args['fields'] );
        }

        if ( 
Utils\get_flag_value$assoc_args'format' ) === 'total_bytes' ) {
            
$fields     = [ 'size_bytes' ];
            
$size_query ',SUM(LENGTH(meta_value)) AS `size_bytes`';
        }

        
$query $wpdb->prepare(
            
'SELECT `meta_id`, `site_id`, `meta_key`,`meta_value`'
                
$size_query // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Hard-coded partial query without user input.
                
" FROM `$wpdb->sitemeta` WHERE `meta_key` LIKE %s",
            
$pattern
        
);

        
$site_id Utils\get_flag_value$assoc_args'site_id' );
        if ( 
$site_id ) {
            
$query .= $wpdb->prepare' AND site_id=%d'$site_id );
        }
        
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $query is already prepared above.
        
$results $wpdb->get_results$query );

        if ( 
Utils\get_flag_value$assoc_args'format' ) === 'total_bytes' ) {
            
WP_CLI::line$results[0]->size_bytes );
        } else {
            
$formatter = new Formatter(
                
$assoc_args,
                
$fields
            
);
            
$formatter->display_items$results );
        }
    }

    
/**
     * Updates a site option.
     *
     * ## OPTIONS
     *
     * <key>
     * : The name of the site option to update.
     *
     * [<value>]
     * : The new value. If ommited, the value is read from STDIN.
     *
     * [--format=<format>]
     * : The serialization format for the value.
     * ---
     * default: plaintext
     * options:
     *   - plaintext
     *   - json
     * ---
     *
     * ## EXAMPLES
     *
     *     # Update a site option by reading from a file
     *     $ wp site option update my_option < value.txt
     *     Success: Updated 'my_option' site option.
     *
     * @alias set
     */
    
public function update$args$assoc_args ) {
        
$key $args[0];

        
$value WP_CLI::get_value_from_arg_or_stdin$args);
        
$value WP_CLI::read_value$value$assoc_args );

        
$value     sanitize_option$key$value );
        
$old_value sanitize_option$keyget_site_option$key ) );

        if ( 
$value === $old_value ) {
            
WP_CLI::success"Value passed for '{$key}' site option is unchanged." );
        } else {
            if ( 
update_site_option$key$value ) ) {
                
WP_CLI::success"Updated '{$key}' site option." );
            } else {
                
WP_CLI::error"Could not update site option '{$key}'." );
            }
        }
    }

    
/**
     * Deletes a site option.
     *
     * ## OPTIONS
     *
     * <key>
     * : Key for the site option.
     *
     * ## EXAMPLES
     *
     *     $ wp site option delete my_option
     *     Success: Deleted 'my_option' site option.
     */
    
public function delete$args ) {
        list( 
$key ) = $args;

        if ( ! 
delete_site_option$key ) ) {
            
WP_CLI::error"Could not delete '{$key}' site option. Does it exist?" );
        } else {
            
WP_CLI::success"Deleted '{$key}' site option." );
        }
    }

    
/**
     * Gets a nested value from an option.
     *
     * ## OPTIONS
     *
     * <key>
     * : The option name.
     *
     * <key-path>...
     * : The name(s) of the keys within the value to locate the value to pluck.
     *
     * [--format=<format>]
     * : The output format of the value.
     * ---
     * default: plaintext
     * options:
     *   - plaintext
     *   - json
     *   - yaml
     */
    
public function pluck$args$assoc_args ) {
        list( 
$key ) = $args;

        
$value get_site_option$key );

        if ( 
false === $value ) {
            
WP_CLI::halt);
        }

        
$key_path array_map(
            function( 
$key ) {
                if ( 
is_numeric$key ) && ( (string) intval$key ) === $key ) ) {
                    return (int) 
$key;
                }
                    return 
$key;
            },
            
array_slice$args)
        );

        
$traverser = new RecursiveDataStructureTraverser$value );

        try {
            
$value $traverser->get$key_path );
        } catch ( 
Exception $exception ) {
            die( 
);
        }

        
WP_CLI::print_value$value$assoc_args );
    }

    
/**
     * Updates a nested value in an option.
     *
     * ## OPTIONS
     *
     * <action>
     * : Patch action to perform.
     * ---
     * options:
     *   - insert
     *   - update
     *   - delete
     * ---
     *
     * <key>
     * : The option name.
     *
     * <key-path>...
     * : The name(s) of the keys within the value to locate the value to patch.
     *
     * [<value>]
     * : The new value. If omitted, the value is read from STDIN.
     *
     * [--format=<format>]
     * : The serialization format for the value.
     * ---
     * default: plaintext
     * options:
     *   - plaintext
     *   - json
     * ---
     */
    
public function patch$args$assoc_args ) {
        list( 
$action$key ) = $args;
        
$key_path             array_map(
            function( 
$key ) {
                if ( 
is_numeric$key ) && ( (string) intval$key ) === $key ) ) {
                    return (int) 
$key;
                }
                    return 
$key;
            },
            
array_slice$args)
        );

        if ( 
'delete' === $action ) {
            
$patch_value null;
        } else {
            
$stdin_value EntityUtils::has_stdin()
                ? 
trimWP_CLI::get_value_from_arg_or_stdin$args, -) )
                : 
null;
            
$patch_value = ! empty( $stdin_value )
                ? 
WP_CLI::read_value$stdin_value$assoc_args )
                : 
WP_CLI::read_valuearray_pop$key_path ), $assoc_args );
        }

        
/* Need to make a copy of $current_value here as it is modified by reference */
        
$old_value     sanitize_option$keyget_site_option$key ) );
        
$current_value $old_value;
        if ( 
is_object$current_value ) ) {
            
$old_value = clone $current_value;
        }

        
$traverser = new RecursiveDataStructureTraverser$current_value );

        try {
            
$traverser->$action$key_path$patch_value );
        } catch ( 
Exception $exception ) {
            
WP_CLI::error$exception->getMessage() );
        }

        
$patched_value sanitize_option$key$traverser->value() );

        if ( 
$patched_value === $old_value ) {
            
WP_CLI::success"Value passed for '{$key}' site option is unchanged." );
        } else {
            if ( 
update_site_option$key$patched_value ) ) {
                
WP_CLI::success"Updated '{$key}' site option." );
            } else {
                
WP_CLI::error"Could not update site option '{$key}'." );
            }
        }
    }

    private static function 
esc_like$old ) {
        global 
$wpdb;

        
// Remove notices in 4.0 and support backwards compatibility
        
if ( method_exists$wpdb'esc_like' ) ) {
            
// 4.0
            
$old $wpdb->esc_like$old );
        } else {
            
// phpcs:ignore WordPress.WP.DeprecatedFunctions.like_escapeFound -- called in WordPress 3.9 or less.
            
$old like_escapeesc_sql$old ) );
        }

        return 
$old;
    }
}
<?
php

use WP_CLI\CommandWithMeta;
use 
WP_CLI\Fetchers\User as UserFetcher;

/**
 * Adds, updates, deletes, and lists user custom fields.
 *
 * ## EXAMPLES
 *
 *     # Add user meta
 *     $ wp user meta add 123 bio "Mary is an WordPress developer."
 *     Success: Added custom field.
 *
 *     # List user meta
 *     $ wp user meta list 123 --keys=nickname,description,wp_capabilities
 *     +---------+-----------------+--------------------------------+
 *     | user_id | meta_key        | meta_value                     |
 *     +---------+-----------------+--------------------------------+
 *     | 123     | nickname        | supervisor                     |
 *     | 123     | description     | Mary is a WordPress developer. |
 *     | 123     | wp_capabilities | {"administrator":true}         |
 *     +---------+-----------------+--------------------------------+
 *
 *     # Update user meta
 *     $ wp user meta update 123 bio "Mary is an awesome WordPress developer."
 *     Success: Updated custom field 'bio'.
 *
 *     # Delete user meta
 *     $ wp user meta delete 123 bio
 *     Success: Deleted custom field.
 */
class User_Meta_Command extends CommandWithMeta {
    protected 
$meta_type 'user';

    public function 
__construct() {
        
$this->fetcher = new UserFetcher();
    }

    
/**
     * Lists all metadata associated with a user.
     *
     * ## OPTIONS
     *
     * <user>
     * : The user login, user email, or user ID of the user to get metadata for.
     *
     * [--keys=<keys>]
     * : Limit output to metadata of specific keys.
     *
     * [--fields=<fields>]
     * : Limit the output to specific row fields. Defaults to id,meta_key,meta_value.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - count
     *   - yaml
     * ---
     *
     * [--orderby=<fields>]
     * : Set orderby which field.
     * ---
     * default: id
     * options:
     *  - id
     *  - meta_key
     *  - meta_value
     * ---
     *
     * [--order=<order>]
     * : Set ascending or descending order.
     * ---
     * default: asc
     * options:
     *  - asc
     *  - desc
     * ---
     *
     * [--unserialize]
     * : Unserialize meta_value output.
     *
     * ## EXAMPLES
     *
     *     # List user meta
     *     $ wp user meta list 123 --keys=nickname,description,wp_capabilities
     *     +---------+-----------------+--------------------------------+
     *     | user_id | meta_key        | meta_value                     |
     *     +---------+-----------------+--------------------------------+
     *     | 123     | nickname        | supervisor                     |
     *     | 123     | description     | Mary is a WordPress developer. |
     *     | 123     | wp_capabilities | {"administrator":true}         |
     *     +---------+-----------------+--------------------------------+
     *
     * @subcommand list
     */
    
public function list_$args$assoc_args ) {
        
$args $this->replace_login_with_user_id$args );
        
parent::list_$args$assoc_args );
    }

    
/**
     * Gets meta field value.
     *
     * ## OPTIONS
     *
     * <user>
     * : The user login, user email, or user ID of the user to get metadata for.
     *
     * <key>
     * : The metadata key.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - yaml
     * ---
     *
     * ## EXAMPLES
     *
     *     # Get user meta
     *     $ wp user meta get 123 bio
     *     Mary is an WordPress developer.
     */
    
public function get$args$assoc_args ) {
        
$args $this->replace_login_with_user_id$args );
        
parent::get$args$assoc_args );
    }

    
/**
     * Deletes a meta field.
     *
     * ## OPTIONS
     *
     * <user>
     * : The user login, user email, or user ID of the user to delete metadata from.
     *
     * <key>
     * : The metadata key.
     *
     * [<value>]
     * : The value to delete. If omitted, all rows with key will deleted.
     *
     * ## EXAMPLES
     *
     *     # Delete user meta
     *     $ wp user meta delete 123 bio
     *     Success: Deleted custom field.
     */
    
public function delete$args$assoc_args ) {
        
$args $this->replace_login_with_user_id$args );
        
parent::delete$args$assoc_args );
    }

    
/**
     * Adds a meta field.
     *
     * ## OPTIONS
     *
     * <user>
     * : The user login, user email, or user ID of the user to add metadata for.
     *
     * <key>
     * : The metadata key.
     *
     * <value>
     * : The new metadata value.
     *
     * [--format=<format>]
     * : The serialization format for the value.
     * ---
     * default: plaintext
     * options:
     *   - plaintext
     *   - json
     * ---
     *
     * ## EXAMPLES
     *
     *     # Add user meta
     *     $ wp user meta add 123 bio "Mary is an WordPress developer."
     *     Success: Added custom field.
     */
    
public function add$args$assoc_args ) {
        
$args $this->replace_login_with_user_id$args );
        
parent::add$args$assoc_args );
    }

    
/**
     * Updates a meta field.
     *
     * ## OPTIONS
     *
     * <user>
     * : The user login, user email, or user ID of the user to update metadata for.
     *
     * <key>
     * : The metadata key.
     *
     * <value>
     * : The new metadata value.
     *
     * [--format=<format>]
     * : The serialization format for the value.
     * ---
     * default: plaintext
     * options:
     *   - plaintext
     *   - json
     * ---
     *
     * ## EXAMPLES
     *
     *     # Update user meta
     *     $ wp user meta update 123 bio "Mary is an awesome WordPress developer."
     *     Success: Updated custom field 'bio'.
     *
     * @alias set
     */
    
public function update$args$assoc_args ) {
        
$args $this->replace_login_with_user_id$args );
        
parent::update$args$assoc_args );
    }

    
/**
     * Wrapper method for add_metadata that can be overridden in sub classes.
     *
     * @param int    $object_id  ID of the object the metadata is for.
     * @param string $meta_key   Metadata key to use.
     * @param mixed  $meta_value Metadata value. Must be serializable if
     *                           non-scalar.
     * @param bool   $unique     Optional, default is false. Whether the
     *                           specified metadata key should be unique for the
     *                           object. If true, and the object already has a
     *                           value for the specified metadata key, no change
     *                           will be made.
     *
     * @return int|false The meta ID on success, false on failure.
     */
    
protected function add_metadata$object_id$meta_key$meta_value$unique false ) {
        return 
add_user_meta$object_id$meta_key$meta_value$unique );
    }

    
/**
     * Wrapper method for update_metadata that can be overridden in sub classes.
     *
     * @param int    $object_id  ID of the object the metadata is for.
     * @param string $meta_key   Metadata key to use.
     * @param mixed  $meta_value Metadata value. Must be serializable if
     *                           non-scalar.
     * @param mixed  $prev_value Optional. If specified, only update existing
     *                           metadata entries with the specified value.
     *                           Otherwise, update all entries.
     *
     * @return int|bool Meta ID if the key didn't exist, true on successful
     *                  update, false on failure.
     */
    
protected function update_metadata$object_id$meta_key$meta_value$prev_value '' ) {
        return 
update_user_meta$object_id$meta_key$meta_value$prev_value );
    }

    
/**
     * Wrapper method for get_metadata that can be overridden in sub classes.
     *
     * @param int    $object_id ID of the object the metadata is for.
     * @param string $meta_key  Optional. Metadata key. If not specified,
     *                          retrieve all metadata for the specified object.
     * @param bool   $single    Optional, default is false. If true, return only
     *                          the first value of the specified meta_key. This
     *                          parameter has no effect if meta_key is not
     *                          specified.
     *
     * @return mixed Single metadata value, or array of values.
     */
    
protected function get_metadata$object_id$meta_key ''$single false ) {
        return 
get_user_meta$object_id$meta_key$single );
    }

    
/**
     * Wrapper method for delete_metadata that can be overridden in sub classes.
     *
     * @param int    $object_id  ID of the object metadata is for
     * @param string $meta_key   Metadata key
     * @param mixed $meta_value  Optional. Metadata value. Must be serializable
     *                           if non-scalar. If specified, only delete
     *                           metadata entries with this value. Otherwise,
     *                           delete all entries with the specified meta_key.
     *                           Pass `null, `false`, or an empty string to skip
     *                           this check. For backward compatibility, it is
     *                           not possible to pass an empty string to delete
     *                           those entries with an empty string for a value.
     *
     * @return bool True on successful delete, false on failure.
     */
    
protected function delete_metadata$object_id$meta_key$meta_value '' ) {
        return 
delete_user_meta$object_id$meta_key$meta_value );
    }

    
/**
     * Replaces user_login value with user ID
     * user meta is a special case that also supports user_login
     *
     * @param array
     * @return array
     */
    
private function replace_login_with_user_id$args ) {
        
$user    $this->fetcher->get_check$args[0] );
        
$args[0] = $user->ID;
        return 
$args;
    }

}
<?
php

use WP_CLI\Formatter;
use 
WP_CLI\Utils;

/**
 * Lists, creates, assigns, and deletes the active theme's navigation menus.
 *
 * See the [Navigation Menus](https://developer.wordpress.org/themes/functionality/navigation-menus/) reference in the Theme Handbook.
 *
 * ## EXAMPLES
 *
 *     # Create a new menu
 *     $ wp menu create "My Menu"
 *     Success: Created menu 200.
 *
 *     # List existing menus
 *     $ wp menu list
 *     +---------+----------+----------+-----------+-------+
 *     | term_id | name     | slug     | locations | count |
 *     +---------+----------+----------+-----------+-------+
 *     | 200     | My Menu  | my-menu  |           | 0     |
 *     | 177     | Top Menu | top-menu | primary   | 7     |
 *     +---------+----------+----------+-----------+-------+
 *
 *     # Create a new menu link item
 *     $ wp menu item add-custom my-menu Apple http://apple.com --porcelain
 *     1922
 *
 *     # Assign the 'my-menu' menu to the 'primary' location
 *     $ wp menu location assign my-menu primary
 *     Success: Assigned location to menu.
 *
 * @package wp-cli
 */
class Menu_Command extends WP_CLI_Command {

    protected 
$obj_type   'nav_menu';
    protected 
$obj_fields = [
        
'term_id',
        
'name',
        
'slug',
        
'locations',
        
'count',
    ];

    
/**
     * Creates a new menu.
     *
     * ## OPTIONS
     *
     * <menu-name>
     * : A descriptive name for the menu.
     *
     * [--porcelain]
     * : Output just the new menu id.
     *
     * ## EXAMPLES
     *
     *     $ wp menu create "My Menu"
     *     Success: Created menu 200.
     */
    
public function create$args$assoc_args ) {

        
$menu_id wp_create_nav_menu$args[0] );

        if ( 
is_wp_error$menu_id ) ) {

            
WP_CLI::error$menu_id->get_error_message() );

        } else {

            if ( 
Utils\get_flag_value$assoc_args'porcelain' ) ) {
                
WP_CLI::line$menu_id );
            } else {
                
WP_CLI::success"Created menu {$menu_id}." );
            }
        }
    }

    
/**
     * Deletes one or more menus.
     *
     * ## OPTIONS
     *
     * <menu>...
     * : The name, slug, or term ID for the menu(s).
     *
     * ## EXAMPLES
     *
     *     $ wp menu delete "My Menu"
     *     Success: 1 menu deleted.
     */
    
public function delete$args$assoc_args ) {

        
$count  0;
        
$errors 0;
        foreach ( 
$args as $arg ) {
            
$ret wp_delete_nav_menu$arg );
            if ( ! 
$ret || is_wp_error$ret ) ) {
                
WP_CLI::warning"Couldn't delete menu '{$arg}'." );
                
$errors++;
            } else {
                
WP_CLI::log"Deleted menu '{$arg}'." );
                
$count++;
            }
        }

        
Utils\report_batch_operation_results'menu''delete'count$args ), $count$errors );
    }

    
/**
     * Gets a list of menus.
     *
     * ## OPTIONS
     *
     * [--fields=<fields>]
     * : Limit the output to specific object fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - count
     *   - ids
     *   - yaml
     * ---
     *
     * ## AVAILABLE FIELDS
     *
     * These fields will be displayed by default for each menu:
     *
     * * term_id
     * * name
     * * slug
     * * count
     *
     * These fields are optionally available:
     *
     * * term_group
     * * term_taxonomy_id
     * * taxonomy
     * * description
     * * parent
     * * locations
     *
     * ## EXAMPLES
     *
     *     $ wp menu list
     *     +---------+----------+----------+-----------+-------+
     *     | term_id | name     | slug     | locations | count |
     *     +---------+----------+----------+-----------+-------+
     *     | 200     | My Menu  | my-menu  |           | 0     |
     *     | 177     | Top Menu | top-menu | primary   | 7     |
     *     +---------+----------+----------+-----------+-------+
     *
     * @subcommand list
     */
    
public function list_$args$assoc_args ) {

        
$menus wp_get_nav_menus();

        
$menu_locations get_nav_menu_locations();
        foreach ( 
$menus as &$menu ) {

            
$menu->locations = [];
            foreach ( 
$menu_locations as $location => $term_id ) {

                if ( 
$term_id === $menu->term_id ) {
                    
$menu->locations[] = $location;
                }
            }

            
// Normalize the data for some output formats.
            
if ( ! isset( $assoc_args['format'] ) || in_array$assoc_args['format'], [ 'csv''table' ], true ) ) {
                
$menu->locations implode','$menu->locations );
            }
        }

        
$formatter $this->get_formatter$assoc_args );

        if ( 
'ids' === $formatter->format ) {
            
$ids array_map(
                function( 
$o ) {
                    return 
$o->term_id;
                },
                
$menus
            
);
            
$formatter->display_items$ids );
        } else {
            
$formatter->display_items$menus );
        }
    }

    protected function 
get_formatter( &$assoc_args ) {
        return new 
Formatter$assoc_args$this->obj_fields$this->obj_type );
    }

}
<?
php

use WP_CLI\CommandWithMeta;

/**
 * Adds, updates, deletes, and lists term custom fields.
 *
 * ## EXAMPLES
 *
 *     # Set term meta
 *     $ wp term meta set 123 bio "Mary is a WordPress developer."
 *     Success: Updated custom field 'bio'.
 *
 *     # Get term meta
 *     $ wp term meta get 123 bio
 *     Mary is a WordPress developer.
 *
 *     # Update term meta
 *     $ wp term meta update 123 bio "Mary is an awesome WordPress developer."
 *     Success: Updated custom field 'bio'.
 *
 *     # Delete term meta
 *     $ wp term meta delete 123 bio
 *     Success: Deleted custom field.
 */
class Term_Meta_Command extends CommandWithMeta {
    protected 
$meta_type 'term';

    
/**
     * Check that the term ID exists
     *
     * @param int
     */
    
protected function check_object_id$object_id ) {
        
$term get_term$object_id );
        if ( ! 
$term ) {
            
WP_CLI::error"Could not find the term with ID {$object_id}." );
        }
        return 
$term->term_id;
    }

    
/**
     * Wrapper method for add_metadata that can be overridden in sub classes.
     *
     * @param int    $object_id  ID of the object the metadata is for.
     * @param string $meta_key   Metadata key to use.
     * @param mixed  $meta_value Metadata value. Must be serializable if
     *                           non-scalar.
     * @param bool   $unique     Optional, default is false. Whether the
     *                           specified metadata key should be unique for the
     *                           object. If true, and the object already has a
     *                           value for the specified metadata key, no change
     *                           will be made.
     *
     * @return int|false The meta ID on success, false on failure.
     */
    
protected function add_metadata$object_id$meta_key$meta_value$unique false ) {
        return 
add_term_meta$object_id$meta_key$meta_value$unique );
    }

    
/**
     * Wrapper method for update_metadata that can be overridden in sub classes.
     *
     * @param int    $object_id  ID of the object the metadata is for.
     * @param string $meta_key   Metadata key to use.
     * @param mixed  $meta_value Metadata value. Must be serializable if
     *                           non-scalar.
     * @param mixed  $prev_value Optional. If specified, only update existing
     *                           metadata entries with the specified value.
     *                           Otherwise, update all entries.
     *
     * @return int|bool Meta ID if the key didn't exist, true on successful
     *                  update, false on failure.
     */
    
protected function update_metadata$object_id$meta_key$meta_value$prev_value '' ) {
        return 
update_term_meta$object_id$meta_key$meta_value$prev_value );
    }

    
/**
     * Wrapper method for get_metadata that can be overridden in sub classes.
     *
     * @param int    $object_id ID of the object the metadata is for.
     * @param string $meta_key  Optional. Metadata key. If not specified,
     *                          retrieve all metadata for the specified object.
     * @param bool   $single    Optional, default is false. If true, return only
     *                          the first value of the specified meta_key. This
     *                          parameter has no effect if meta_key is not
     *                          specified.
     *
     * @return mixed Single metadata value, or array of values.
     */
    
protected function get_metadata$object_id$meta_key ''$single false ) {
        return 
get_term_meta$object_id$meta_key$single );
    }

    
/**
     * Wrapper method for delete_metadata that can be overridden in sub classes.
     *
     * @param int    $object_id  ID of the object metadata is for
     * @param string $meta_key   Metadata key
     * @param mixed $meta_value  Optional. Metadata value. Must be serializable
     *                           if non-scalar. If specified, only delete
     *                           metadata entries with this value. Otherwise,
     *                           delete all entries with the specified meta_key.
     *                           Pass `null, `false`, or an empty string to skip
     *                           this check. For backward compatibility, it is
     *                           not possible to pass an empty string to delete
     *                           those entries with an empty string for a value.
     *
     * @return bool True on successful delete, false on failure.
     */
    
protected function delete_metadata$object_id$meta_key$meta_value '' ) {
        return 
delete_term_meta$object_id$meta_key$meta_value );
    }
}
<?
php

use WP_CLI\Formatter;
use 
WP_CLI\Utils;

/**
 * Assigns, removes, and lists a menu's locations.
 *
 * ## EXAMPLES
 *
 *     # List available menu locations
 *     $ wp menu location list
 *     +----------+-------------------+
 *     | location | description       |
 *     +----------+-------------------+
 *     | primary  | Primary Menu      |
 *     | social   | Social Links Menu |
 *     +----------+-------------------+
 *
 *     # Assign the 'primary-menu' menu to the 'primary' location
 *     $ wp menu location assign primary-menu primary
 *     Success: Assigned location to menu.
 *
 *     # Remove the 'primary-menu' menu from the 'primary' location
 *     $ wp menu location remove primary-menu primary
 *     Success: Removed location from menu.
 */
class Menu_Location_Command extends WP_CLI_Command {

    
/**
     * Lists locations for the current theme.
     *
     * ## OPTIONS
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - count
     *   - yaml
     *   - ids
     * ---
     *
     * ## AVAILABLE FIELDS
     *
     * These fields will be displayed by default for each location:
     *
     * * name
     * * description
     *
     * ## EXAMPLES
     *
     *     $ wp menu location list
     *     +----------+-------------------+
     *     | location | description       |
     *     +----------+-------------------+
     *     | primary  | Primary Menu      |
     *     | social   | Social Links Menu |
     *     +----------+-------------------+
     *
     * @subcommand list
     */
    
public function list_$args$assoc_args ) {

        
$locations     get_registered_nav_menus();
        
$location_objs = [];
        foreach ( 
$locations as $location => $description ) {
            
$location_obj              = new stdClass();
            
$location_obj->location    $location;
            
$location_obj->description $description;
            
$location_objs[]           = $location_obj;
        }

        
$formatter = new Formatter$assoc_args, [ 'location''description' ] );

        if ( 
'ids' === $formatter->format ) {
            
$ids array_map(
                function( 
$o ) {
                    return 
$o->location;
                },
                
$location_objs
            
);
            
$formatter->display_items$ids );
        } else {
            
$formatter->display_items$location_objs );
        }
    }

    
/**
     * Assigns a location to a menu.
     *
     * ## OPTIONS
     *
     * <menu>
     * : The name, slug, or term ID for the menu.
     *
     * <location>
     * : Location's slug.
     *
     * ## EXAMPLES
     *
     *     $ wp menu location assign primary-menu primary
     *     Success: Assigned location primary to menu primary-menu.
     *
     * @subcommand assign
     */
    
public function assign$args$assoc_args ) {

        list( 
$menu$location ) = $args;

        
$menu_obj wp_get_nav_menu_object$menu );
        if ( ! 
$menu_obj ) {
            
WP_CLI::error"Invalid menu {$menu}." );
        }

        
$locations get_registered_nav_menus();
        if ( ! 
array_key_exists$location$locations ) ) {
            
WP_CLI::error"Invalid location {$location}." );
        }

        
$locations              get_nav_menu_locations();
        
$locations$location ] = $menu_obj->term_id;

        
set_theme_mod'nav_menu_locations'$locations );

        
WP_CLI::success"Assigned location {$location} to menu {$menu}." );
    }

    
/**
     * Removes a location from a menu.
     *
     * ## OPTIONS
     *
     * <menu>
     * : The name, slug, or term ID for the menu.
     *
     * <location>
     * : Location's slug.
     *
     * ## EXAMPLES
     *
     *     $ wp menu location remove primary-menu primary
     *     Success: Removed location from menu.
     *
     * @subcommand remove
     */
    
public function remove$args$assoc_args ) {

        list( 
$menu$location ) = $args;

        
$menu wp_get_nav_menu_object$menu );
        if ( ! 
$menu || is_wp_error$menu ) ) {
            
WP_CLI::error'Invalid menu.' );
        }

        
$locations get_nav_menu_locations();
        if ( 
Utils\get_flag_value$locations$location ) !== $menu->term_id ) {
            
WP_CLI::error"Menu isn't assigned to location." );
        }

        
$locations$location ] = 0;
        
set_theme_mod'nav_menu_locations'$locations );

        
WP_CLI::success'Removed location from menu.' );

    }

}
<?
php

use WP_CLI\Formatter;
use 
WP_CLI\Utils;

/**
 * Manages taxonomy terms and term meta, with create, delete, and list commands.
 *
 * See reference for [taxonomies and their terms](https://codex.wordpress.org/Taxonomies).
 *
 * ## EXAMPLES
 *
 *     # Create a new term.
 *     $ wp term create category Apple --description="A type of fruit"
 *     Success: Created category 199.
 *
 *     # Get details about a term.
 *     $ wp term get category 199 --format=json --fields=term_id,name,slug,count
 *     {"term_id":199,"name":"Apple","slug":"apple","count":1}
 *
 *     # Update an existing term.
 *     $ wp term update category 15 --name=Apple
 *     Success: Term updated.
 *
 *     # Get the term's URL.
 *     $ wp term list post_tag --include=123 --field=url
 *     http://example.com/tag/tips-and-tricks
 *
 *     # Delete post category
 *     $ wp term delete category 15
 *     Success: Deleted category 15.
 *
 *     # Recount posts assigned to each categories and tags
 *     $ wp term recount category post_tag
 *     Success: Updated category term count
 *     Success: Updated post_tag term count
 *
 * @package wp-cli
 */
class Term_Command extends WP_CLI_Command {

    private 
$fields = [
        
'term_id',
        
'term_taxonomy_id',
        
'name',
        
'slug',
        
'description',
        
'parent',
        
'count',
    ];

    
/**
     * Lists terms in a taxonomy.
     *
     * ## OPTIONS
     *
     * <taxonomy>...
     * : List terms of one or more taxonomies
     *
     * [--<field>=<value>]
     * : Filter by one or more fields (see get_terms() $args parameter for a list of fields).
     *
     * [--field=<field>]
     * : Prints the value of a single field for each term.
     *
     * [--fields=<fields>]
     * : Limit the output to specific object fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - ids
     *   - json
     *   - count
     *   - yaml
     * ---
     *
     * ## AVAILABLE FIELDS
     *
     * These fields will be displayed by default for each term:
     *
     * * term_id
     * * term_taxonomy_id
     * * name
     * * slug
     * * description
     * * parent
     * * count
     *
     * These fields are optionally available:
     *
     * * url
     *
     * ## EXAMPLES
     *
     *     # List post categories
     *     $ wp term list category --format=csv
     *     term_id,term_taxonomy_id,name,slug,description,parent,count
     *     2,2,aciform,aciform,,0,1
     *     3,3,antiquarianism,antiquarianism,,0,1
     *     4,4,arrangement,arrangement,,0,1
     *     5,5,asmodeus,asmodeus,,0,1
     *
     *     # List post tags
     *     $ wp term list post_tag --fields=name,slug
     *     +-----------+-------------+
     *     | name      | slug        |
     *     +-----------+-------------+
     *     | 8BIT      | 8bit        |
     *     | alignment | alignment-2 |
     *     | Articles  | articles    |
     *     | aside     | aside       |
     *     +-----------+-------------+
     *
     * @subcommand list
     */
    
public function list_$args$assoc_args ) {
        foreach ( 
$args as $taxonomy ) {
            if ( ! 
taxonomy_exists$taxonomy ) ) {
                
WP_CLI::error"Taxonomy {$taxonomy} doesn't exist." );
            }
        }

        
$formatter $this->get_formatter$assoc_args );

        
$defaults   = [
            
'hide_empty' => false,
        ];
        
$assoc_args array_merge$defaults$assoc_args );

        if ( ! empty( 
$assoc_args['term_id'] ) ) {
            
$term  get_term_by'id'$assoc_args['term_id'], $args[0] );
            
$terms = [ $term ];
        } elseif ( ! empty( 
$assoc_args['include'] )
            && ! empty( 
$assoc_args['orderby'] )
            && 
'include' === $assoc_args['orderby']
            && 
Utils\wp_version_compare'4.7''<' ) ) {
            
$terms    = [];
            
$term_ids explode','$assoc_args['include'] );
            foreach ( 
$term_ids as $term_id ) {
                
$term get_term_by'id'$term_id$args[0] );
                if ( 
$term && ! is_wp_error$term ) ) {
                    
$terms[] = $term;
                } else {
                    
WP_CLI::warning"Invalid term {$term_id}." );
                }
            }
        } else {
            
$terms get_terms$args$assoc_args );
        }

        
$terms array_map(
            function( 
$term ) {
                    
$term->count  = (int) $term->count;
                    
$term->parent = (int) $term->parent;
                    
$term->url    get_term_link$term );
                    return 
$term;
            },
            
$terms
        
);

        if ( 
'ids' === $formatter->format ) {
            
$terms wp_list_pluck$terms'term_id' );
            echo 
implode' '$terms );
        } else {
            
$formatter->display_items$terms );
        }
    }

    
/**
     * Creates a new term.
     *
     * ## OPTIONS
     *
     * <taxonomy>
     * : Taxonomy for the new term.
     *
     * <term>
     * : A name for the new term.
     *
     * [--slug=<slug>]
     * : A unique slug for the new term. Defaults to sanitized version of name.
     *
     * [--description=<description>]
     * : A description for the new term.
     *
     * [--parent=<term-id>]
     * : A parent for the new term.
     *
     * [--porcelain]
     * : Output just the new term id.
     *
     * ## EXAMPLES
     *
     *     # Create a new category "Apple" with a description.
     *     $ wp term create category Apple --description="A type of fruit"
     *     Success: Created category 199.
     */
    
public function create$args$assoc_args ) {

        list( 
$taxonomy$term ) = $args;

        
$defaults   = [
            
'slug'        => sanitize_title$term ),
            
'description' => '',
            
'parent'      => 0,
        ];
        
$assoc_args wp_parse_args$assoc_args$defaults );

        
$porcelain Utils\get_flag_value$assoc_args'porcelain' );
        unset( 
$assoc_args['porcelain'] );

        
// Compatibility for < WP 4.0
        
if ( $assoc_args['parent'] > && ! term_exists( (int) $assoc_args['parent'] ) ) {
            
WP_CLI::error'Parent term does not exist.' );
        }

        
$assoc_args wp_slash$assoc_args );
        
$term       wp_slash$term );
        
$result     wp_insert_term$term$taxonomy$assoc_args );

        if ( 
is_wp_error$result ) ) {
            
WP_CLI::error$result->get_error_message() );
        } else {
            if ( 
$porcelain ) {
                
WP_CLI::line$result['term_id'] );
            } else {
                
WP_CLI::success"Created {$taxonomy} {$result['term_id']}." );
            }
        }
    }

    
/**
     * Gets details about a term.
     *
     * ## OPTIONS
     *
     * <taxonomy>
     * : Taxonomy of the term to get
     *
     * <term>
     * : ID or slug of the term to get
     *
     * [--by=<field>]
     * : Explicitly handle the term value as a slug or id.
     * ---
     * default: id
     * options:
     *   - slug
     *   - id
     * ---
     *
     * [--field=<field>]
     * : Instead of returning the whole term, returns the value of a single field.
     *
     * [--fields=<fields>]
     * : Limit the output to specific fields. Defaults to all fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - yaml
     * ---
     *
     * ## EXAMPLES
     *
     *     # Get details about a category with id 199.
     *     $ wp term get category 199 --format=json
     *     {"term_id":199,"name":"Apple","slug":"apple","term_group":0,"term_taxonomy_id":199,"taxonomy":"category","description":"A type of fruit","parent":0,"count":0,"filter":"raw"}
     *
     *     # Get details about a category with slug apple.
     *     $ wp term get category apple --by=slug --format=json
     *     {"term_id":199,"name":"Apple","slug":"apple","term_group":0,"term_taxonomy_id":199,"taxonomy":"category","description":"A type of fruit","parent":0,"count":0,"filter":"raw"}
     */
    
public function get$args$assoc_args ) {

        list( 
$taxonomy$term ) = $args;

        
$term get_term_byUtils\get_flag_value$assoc_args'by' ), $term$taxonomy );

        if ( ! 
$term ) {
            
WP_CLI::error"Term doesn't exist." );
        }

        if ( empty( 
$assoc_args['fields'] ) ) {
            
$term_array           get_object_vars$term );
            
$assoc_args['fields'] = array_keys$term_array );
        }

        
$term->count  = (int) $term->count;
        
$term->parent = (int) $term->parent;

        
$formatter $this->get_formatter$assoc_args );
        
$formatter->display_item$term );
    }

    
/**
     * Updates an existing term.
     *
     * ## OPTIONS
     *
     * <taxonomy>
     * : Taxonomy of the term to update.
     *
     * <term>
     * : ID or slug for the term to update.
     *
     * [--by=<field>]
     * : Explicitly handle the term value as a slug or id.
     * ---
     * default: id
     * options:
     *   - slug
     *   - id
     * ---
     *
     * [--name=<name>]
     * : A new name for the term.
     *
     * [--slug=<slug>]
     * : A new slug for the term.
     *
     * [--description=<description>]
     * : A new description for the term.
     *
     * [--parent=<term-id>]
     * : A new parent for the term.
     *
     * ## EXAMPLES
     *
     *     # Change category with id 15 to use the name "Apple"
     *     $ wp term update category 15 --name=Apple
     *     Success: Term updated.
     *
     *     # Change category with slug apple to use the name "Apple"
     *     $ wp term update category apple --by=slug --name=Apple
     *     Success: Term updated.
     */
    
public function update$args$assoc_args ) {

        list( 
$taxonomy$term ) = $args;

        
$defaults   = [
            
'name'        => null,
            
'slug'        => null,
            
'description' => null,
            
'parent'      => null,
        ];
        
$assoc_args wp_parse_args$assoc_args$defaults );

        foreach ( 
$assoc_args as $key => $value ) {
            if ( 
null === $value ) {
                unset( 
$assoc_args$key ] );
            }
        }

        
$assoc_args wp_slash$assoc_args );

        
$term get_term_byUtils\get_flag_value$assoc_args'by' ), $term$taxonomy );

        if ( ! 
$term ) {
            
WP_CLI::error"Term doesn't exist." );
        }

        
// Update term.
        
$result wp_update_term$term->term_id$taxonomy$assoc_args );

        if ( 
is_wp_error$result ) ) {
            
WP_CLI::error$result->get_error_message() );
        } else {
            
WP_CLI::success'Term updated.' );
        }
    }

    
/**
     * Deletes an existing term.
     *
     * Errors if the term doesn't exist, or there was a problem in deleting it.
     *
     * ## OPTIONS
     *
     * <taxonomy>
     * : Taxonomy of the term to delete.
     *
     * <term>...
     * : One or more IDs or slugs of terms to delete.
     *
     * [--by=<field>]
     * : Explicitly handle the term value as a slug or id.
     * ---
     * default: id
     * options:
     *   - slug
     *   - id
     * ---
     *
     * ## EXAMPLES
     *
     *     # Delete post category by id
     *     $ wp term delete category 15
     *     Deleted category 15.
     *     Success: Deleted 1 of 1 terms.
     *
     *     # Delete post category by slug
     *     $ wp term delete category apple --by=slug
     *     Deleted category 15.
     *     Success: Deleted 1 of 1 terms.
     *
     *     # Delete all post tags
     *     $ wp term list post_tag --field=term_id | xargs wp term delete post_tag
     *     Deleted post_tag 159.
     *     Deleted post_tag 160.
     *     Deleted post_tag 161.
     *     Success: Deleted 3 of 3 terms.
     */
    
public function delete$args$assoc_args ) {
        
$taxonomy array_shift$args );

        
$successes 0;
        
$errors    0;
        foreach ( 
$args as $term_id ) {

            
$term $term_id;

            
// Get term by specified argument otherwise get term by id.
            
$field Utils\get_flag_value$assoc_args'by' );
            if ( 
'slug' === $field ) {

                
// Get term by slug.
                
$term get_term_by$field$term$taxonomy );

                
// If term not found, then show error message and skip the iteration.
                
if ( ! $term ) {
                    
WP_CLI::warning"{$taxonomy} {$term_id} doesn't exist." );
                    continue;
                }

                
// Get the term id;
                
$term_id $term->term_id;

            }

            
$result wp_delete_term$term_id$taxonomy );

            if ( 
is_wp_error$result ) ) {
                
WP_CLI::warning$result );
                
$errors++;
            } elseif ( 
$result ) {
                
WP_CLI::log"Deleted {$taxonomy} {$term_id}." );
                
$successes++;
            } else {
                
WP_CLI::warning"{$taxonomy} {$term_id} doesn't exist." );
            }
        }
        
Utils\report_batch_operation_results'term''delete'count$args ), $successes$errors );
    }

    
/**
     * Generates some terms.
     *
     * Creates a specified number of new terms with dummy data.
     *
     * ## OPTIONS
     *
     * <taxonomy>
     * : The taxonomy for the generated terms.
     *
     * [--count=<number>]
     * : How many terms to generate?
     * ---
     * default: 100
     * ---
     *
     * [--max_depth=<number>]
     * : Generate child terms down to a certain depth.
     * ---
     * default: 1
     * ---
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: progress
     * options:
     *   - progress
     *   - ids
     * ---
     *
     * ## EXAMPLES
     *
     *     # Generate post categories.
     *     $ wp term generate category --count=10
     *     Generating terms  100% [=========] 0:02 / 0:02
     *
     *     # Add meta to every generated term.
     *     $ wp term generate category --format=ids --count=3 | xargs -d ' ' -I % wp term meta add % foo bar
     *     Success: Added custom field.
     *     Success: Added custom field.
     *     Success: Added custom field.
     */
    
public function generate$args$assoc_args ) {
        global 
$wpdb;

        list ( 
$taxonomy ) = $args;

        
$defaults = [
            
'count'     => 100,
            
'max_depth' => 1,
        ];

        
$final_args array_merge$defaults$assoc_args );
        
$count      $final_args['count'];
        
$max_depth  $final_args['max_depth'];

        if ( ! 
taxonomy_exists$taxonomy ) ) {
            
WP_CLI::error"'{$taxonomy}' is not a registered taxonomy." );
        }

        
$label get_taxonomy$taxonomy )->labels->singular_name;
        
$slug  sanitize_title_with_dashes$label );

        
$hierarchical get_taxonomy$taxonomy )->hierarchical;

        
$format Utils\get_flag_value$assoc_args'format''progress' );

        
$notify false;
        if ( 
'progress' === $format ) {
            
$notify Utils\make_progress_bar'Generating terms'$count );
        }

        
$previous_term_id 0;
        
$current_parent   0;
        
$current_depth    1;

        
$max_id = (int) $wpdb->get_var"SELECT term_taxonomy_id FROM {$wpdb->term_taxonomy} ORDER BY term_taxonomy_id DESC LIMIT 1" );

        
$suspend_cache_invalidation wp_suspend_cache_invalidationtrue );
        
$created                    = [];

        for ( 
$index $max_id 1$index <= $max_id $count$index++ ) {

            if ( 
$hierarchical ) {

                if ( 
$previous_term_id && $this->maybe_make_child() && $current_depth $max_depth ) {

                    
$current_parent $previous_term_id;
                    
$current_depth++;

                } elseif ( 
$this->maybe_reset_depth() ) {

                    
$current_parent 0;
                    
$current_depth  1;

                }
            }

            
$args = [
                
'parent' => $current_parent,
                
'slug'   => $slug "-{$index}",
            ];

            
$name "{$label} {$index}";
            
$term wp_insert_term$name$taxonomy$args );
            if ( 
is_wp_error$term ) ) {
                
WP_CLI::warning$term );
            } else {
                
$created[]        = $term['term_id'];
                
$previous_term_id $term['term_id'];
                if ( 
'ids' === $format ) {
                    echo 
$term['term_id'];
                    if ( 
$index $max_id $count ) {
                        echo 
' ';
                    }
                }
            }

            if ( 
'progress' === $format ) {
                
$notify->tick();
            }
        }

        
wp_suspend_cache_invalidation$suspend_cache_invalidation );
        
clean_term_cache$created$taxonomy );

        if ( 
'progress' === $format ) {
            
$notify->finish();
        }
    }

    
/**
     * Recalculates number of posts assigned to each term.
     *
     * In instances where manual updates are made to the terms assigned to
     * posts in the database, the number of posts associated with a term
     * can become out-of-sync with the actual number of posts.
     *
     * This command runs wp_update_term_count() on the taxonomy's terms
     * to bring the count back to the correct value.
     *
     * ## OPTIONS
     *
     * <taxonomy>...
     * : One or more taxonomies to recalculate.
     *
     * ## EXAMPLES
     *
     *     # Recount posts assigned to each categories and tags
     *     $ wp term recount category post_tag
     *     Success: Updated category term count.
     *     Success: Updated post_tag term count.
     *
     *     # Recount all listed taxonomies
     *     $ wp taxonomy list --field=name | xargs wp term recount
     *     Success: Updated category term count.
     *     Success: Updated post_tag term count.
     *     Success: Updated nav_menu term count.
     *     Success: Updated link_category term count.
     *     Success: Updated post_format term count.
     */
    
public function recount$args ) {
        foreach ( 
$args as $taxonomy ) {

            if ( ! 
taxonomy_exists$taxonomy ) ) {
                
WP_CLI::warning"Taxonomy {$taxonomy} does not exist." );
            } else {

                
$terms             get_terms$taxonomy, [ 'hide_empty' => false ] );
                
$term_taxonomy_ids wp_list_pluck$terms'term_taxonomy_id' );

                
wp_update_term_count$term_taxonomy_ids$taxonomy );

                
WP_CLI::success"Updated {$taxonomy} term count." );
            }
        }
    }

    
/**
     * Migrate a term of a taxonomy to another taxonomy.
     *
     * ## OPTIONS
     *
     * <term>
     * : Slug or ID of the term to migrate.
     *
     * [--by=<field>]
     * : Explicitly handle the term value as a slug or id.
     * ---
     * default: id
     * options:
     *   - slug
     *   - id
     * ---
     *
     * [--from=<taxonomy>]
     * : Taxonomy slug of the term to migrate.
     *
     * [--to=<taxonomy>]
     * : Taxonomy slug to migrate to.
     *
     * ## EXAMPLES
     *
     *     # Migrate a category's term (video) to tag taxonomy.
     *     $ wp term migrate 9190 --from=category --to=post_tag
     *     Term '9190' migrated!
     *     Old instance of term '9190' removed from its original taxonomy.
     *     Success: Migrated the term '9190' from taxonomy 'category' to taxonomy 'post_tag' for 1 posts
     */
    
public function migrate$args$assoc_args ) {
        
$term_reference       $args[0];
        
$original_taxonomy    Utils\get_flag_value$assoc_args'from' );
        
$destination_taxonomy Utils\get_flag_value$assoc_args'to' );

        
$term get_term_byUtils\get_flag_value$assoc_args'by' ), $term_reference$original_taxonomy );

        if ( ! 
$term ) {
            
WP_CLI::error"Taxonomy term '{$term_reference}' for taxonomy '{$original_taxonomy}' doesn't exist." );
        }

        
$original_taxonomy get_taxonomy$original_taxonomy );

        
$id wp_insert_term(
            
$term->name,
            
$destination_taxonomy,
            [
                
'slug'        => $term->slug,
                
'parent'      => 0,
                
'description' => $term->description,
            ]
        );

        if ( 
is_wp_error$id ) ) {
            
WP_CLI::error$id->get_error_message() );
        }

        
$post_ids get_objects_in_term$term->term_id$original_taxonomy->name );

        foreach ( 
$post_ids as $post_id ) {
            
$type get_post_type$post_id );
            if ( 
in_array$type$original_taxonomy->object_typetrue ) ) {
                
$term_taxonomy_id wp_set_object_terms$post_id$id['term_id'], $destination_taxonomytrue );

                if ( 
is_wp_error$term_taxonomy_id ) ) {
                    
WP_CLI::error"Failed to assign the term '{$term->slug}' to the post {$post_id}. Reason: " $term_taxonomy_id->get_error_message() );
                }

                
WP_CLI::log"Term '{$term->slug}' assigned to post {$post_id}." );
            }

            
clean_post_cache$post_id );
        }

        
clean_term_cache$term->term_id );

        
WP_CLI::log"Term '{$term->slug}' migrated." );

        
$del wp_delete_term$term->term_id$original_taxonomy->name );

        if ( 
is_wp_error$del ) ) {
            
WP_CLI::error"Failed to delete the term '{$term->slug}'. Reason: " $del->get_error_message() );
        }

        
WP_CLI::log"Old instance of term '{$term->slug}' removed from its original taxonomy." );
        
$post_count  count$post_ids );
        
$post_plural Utils\pluralize'post'$post_count );
        
WP_CLI::success"Migrated the term '{$term->slug}' from taxonomy '{$original_taxonomy->name}' to taxonomy '{$destination_taxonomy}' for {$post_count} {$post_plural}." );
    }

    private function 
maybe_make_child() {
        
// 50% chance of making child term.
        
return ( wp_rand1) === );
    }

    private function 
maybe_reset_depth() {
        
// 10% chance of reseting to root depth.
        
return ( wp_rand110 ) === );
    }

    private function 
get_formatter( &$assoc_args ) {
        return new 
Formatter$assoc_args$this->fields'term' );
    }
}
<?
php

use WP_CLI\Entity\RecursiveDataStructureTraverser;
use 
WP_CLI\Formatter;
use 
WP_CLI\Utils;
use 
WP_CLI\Entity\Utils as EntityUtils;

/**
 * Retrieves and sets site options, including plugin and WordPress settings.
 *
 * See the [Plugin Settings API](https://developer.wordpress.org/plugins/settings/settings-api/) and the [Theme Options](https://developer.wordpress.org/themes/customize-api/) for more information on adding customized options.
 *
 * ## EXAMPLES
 *
 *     # Get site URL.
 *     $ wp option get siteurl
 *     http://example.com
 *
 *     # Add option.
 *     $ wp option add my_option foobar
 *     Success: Added 'my_option' option.
 *
 *     # Update option.
 *     $ wp option update my_option '{"foo": "bar"}' --format=json
 *     Success: Updated 'my_option' option.
 *
 *     # Delete option.
 *     $ wp option delete my_option
 *     Success: Deleted 'my_option' option.
 *
 * @package wp-cli
 */
class Option_Command extends WP_CLI_Command {

    
/**
     * Gets the value for an option.
     *
     * ## OPTIONS
     *
     * <key>
     * : Key for the option.
     *
     * [--format=<format>]
     * : Get value in a particular format.
     * ---
     * default: var_export
     * options:
     *   - var_export
     *   - json
     *   - yaml
     * ---
     *
     * ## EXAMPLES
     *
     *     # Get option.
     *     $ wp option get home
     *     http://example.com
     *
     *     # Get blog description.
     *     $ wp option get blogdescription
     *     A random blog description
     *
     *     # Get blog name
     *     $ wp option get blogname
     *     A random blog name
     *
     *     # Get admin email.
     *     $ wp option get admin_email
     *     [email protected]
     *
     *     # Get option in JSON format.
     *     $ wp option get active_plugins --format=json
     *     {"0":"dynamically-dynamic-sidebar\/dynamically-dynamic-sidebar.php","1":"monster-widget\/monster-widget.php","2":"show-current-template\/show-current-template.php","3":"theme-check\/theme-check.php","5":"wordpress-importer\/wordpress-importer.php"}
     */
    
public function get$args$assoc_args ) {
        list( 
$key ) = $args;

        
$value get_option$key );

        if ( 
false === $value ) {
            
WP_CLI::error"Could not get '{$key}' option. Does it exist?" );
        }

        
WP_CLI::print_value$value$assoc_args );
    }

    
/**
     * Adds a new option value.
     *
     * Errors if the option already exists.
     *
     * ## OPTIONS
     *
     * <key>
     * : The name of the option to add.
     *
     * [<value>]
     * : The value of the option to add. If ommited, the value is read from STDIN.
     *
     * [--format=<format>]
     * : The serialization format for the value.
     * ---
     * default: plaintext
     * options:
     *   - plaintext
     *   - json
     * ---
     *
     * [--autoload=<autoload>]
     * : Should this option be automatically loaded.
     * ---
     * options:
     *   - 'yes'
     *   - 'no'
     * ---
     *
     * ## EXAMPLES
     *
     *     # Create an option by reading a JSON file.
     *     $ wp option add my_option --format=json < config.json
     *     Success: Added 'my_option' option.
     */
    
public function add$args$assoc_args ) {
        
$key $args[0];

        
$value WP_CLI::get_value_from_arg_or_stdin$args);
        
$value WP_CLI::read_value$value$assoc_args );

        if ( 
Utils\get_flag_value$assoc_args'autoload' ) === 'no' ) {
            
$autoload 'no';
        } else {
            
$autoload 'yes';
        }

        if ( ! 
add_option$key$value''$autoload ) ) {
            
WP_CLI::error"Could not add option '{$key}'. Does it already exist?" );
        } else {
            
WP_CLI::success"Added '{$key}' option." );
        }
    }

    
/**
     * Lists options and their values.
     *
     * ## OPTIONS
     *
     * [--search=<pattern>]
     * : Use wildcards ( * and ? ) to match option name.
     *
     * [--exclude=<pattern>]
     * : Pattern to exclude. Use wildcards ( * and ? ) to match option name.
     *
     * [--autoload=<value>]
     * : Match only autoload options when value is on, and only not-autoload option when off.
     *
     * [--transients]
     * : List only transients. Use `--no-transients` to ignore all transients.
     *
     * [--unserialize]
     * : Unserialize option values in output.
     *
     * [--field=<field>]
     * : Prints the value of a single field.
     *
     * [--fields=<fields>]
     * : Limit the output to specific object fields.
     *
     * [--format=<format>]
     * : The serialization format for the value. total_bytes displays the total size of matching options in bytes.
     * ---
     * default: table
     * options:
     *   - table
     *   - json
     *   - csv
     *   - count
     *   - yaml
     *   - total_bytes
     * ---
     *
     * [--orderby=<fields>]
     * : Set orderby which field.
     * ---
     * default: option_id
     * options:
     *  - option_id
     *  - option_name
     *  - option_value
     * ---
     *
     * [--order=<order>]
     * : Set ascending or descending order.
     * ---
     * default: asc
     * options:
     *  - asc
     *  - desc
     * ---
     *
     * ## AVAILABLE FIELDS
     *
     * This field will be displayed by default for each matching option:
     *
     * * option_name
     * * option_value
     *
     * These fields are optionally available:
     *
     * * autoload
     * * size_bytes
     *
     * ## EXAMPLES
     *
     *     # Get the total size of all autoload options.
     *     $ wp option list --autoload=on --format=total_bytes
     *     33198
     *
     *     # Find biggest transients.
     *     $ wp option list --search="*_transient_*" --fields=option_name,size_bytes | sort -n -k 2 | tail
     *     option_name size_bytes
     *     _site_transient_timeout_theme_roots 10
     *     _site_transient_theme_roots 76
     *     _site_transient_update_themes   181
     *     _site_transient_update_core 808
     *     _site_transient_update_plugins  6645
     *
     *     # List all options beginning with "i2f_".
     *     $ wp option list --search="i2f_*"
     *     +-------------+--------------+
     *     | option_name | option_value |
     *     +-------------+--------------+
     *     | i2f_version | 0.1.0        |
     *     +-------------+--------------+
     *
     *     # Delete all options beginning with "theme_mods_".
     *     $ wp option list --search="theme_mods_*" --field=option_name | xargs -I % wp option delete %
     *     Success: Deleted 'theme_mods_twentysixteen' option.
     *     Success: Deleted 'theme_mods_twentyfifteen' option.
     *     Success: Deleted 'theme_mods_twentyfourteen' option.
     *
     * @subcommand list
     */
    
public function list_$args$assoc_args ) {

        global 
$wpdb;
        
$pattern        '%';
        
$exclude        '';
        
$fields         = array( 'option_name''option_value' );
        
$size_query     ',LENGTH(option_value) AS `size_bytes`';
        
$autoload_query '';

        if ( isset( 
$assoc_args['search'] ) ) {
            
$pattern self::esc_like$assoc_args['search'] );
            
// substitute wildcards
            
$pattern str_replace'*''%'$pattern );
            
$pattern str_replace'?''_'$pattern );
        }

        if ( isset( 
$assoc_args['exclude'] ) ) {
            
$exclude self::esc_like$assoc_args['exclude'] );
            
$exclude str_replace'*''%'$exclude );
            
$exclude str_replace'?''_'$exclude );
        }

        if ( isset( 
$assoc_args['fields'] ) ) {
            
$fields explode','$assoc_args['fields'] );
        }

        if ( 
Utils\get_flag_value$assoc_args'format' ) === 'total_bytes' ) {
            
$fields     = array( 'size_bytes' );
            
$size_query ',SUM(LENGTH(option_value)) AS `size_bytes`';
        }

        if ( isset( 
$assoc_args['autoload'] ) ) {
            if ( 
'on' === $assoc_args['autoload'] ) {
                
$autoload_query " AND autoload='yes'";
            } elseif ( 
'off' === $assoc_args['autoload'] ) {
                
$autoload_query " AND autoload='no'";
            } else {
                
WP_CLI::error"Value of '--autoload' should be on or off." );
            }
        }

        
// By default we don't want to display transients.
        
$show_transients Utils\get_flag_value$assoc_args'transients'false );

        if ( 
$show_transients ) {
            
$transients_query " AND option_name LIKE '\_transient\_%'
            OR option_name LIKE '\_site\_transient\_%'"
;
        } else {
            
$transients_query " AND option_name NOT LIKE '\_transient\_%'
            AND option_name NOT LIKE '\_site\_transient\_%'"
;
        }

        
$where '';
        if ( 
$pattern ) {
            
$where .= $wpdb->prepare'WHERE `option_name` LIKE %s'$pattern );
        }

        if ( 
$exclude ) {
            
$where .= $wpdb->prepare' AND `option_name` NOT LIKE %s'$exclude );
        }
        
$where .= $autoload_query $transients_query;

        
// phpcs:disable WordPress.DB.PreparedSQL -- Hardcoded query parts without user input.
        
$results $wpdb->get_results(
            
'SELECT `option_name`,`option_value`,`autoload`' $size_query
            
" FROM `$wpdb->options{$where}"
        
);
        
// phpcs:enable

        
$orderby Utils\get_flag_value$assoc_args'orderby' );
        
$order   Utils\get_flag_value$assoc_args'order' );

        
// Sort result.
        
if ( 'option_id' !== $orderby ) {
            
usort(
                
$results,
                function ( 
$a$b ) use ( $orderby$order ) {
                    
// Sort array.
                    
return 'asc' === $order
                        
$a->$orderby $b->$orderby
                        
$a->$orderby $b->$orderby;
                }
            );
        } elseif ( 
'option_id' === $orderby && 'desc' === $order ) { // Sort by default descending.
            
krsort$results );
        }

        if ( 
true === Utils\get_flag_value$assoc_args'unserialize'null ) ) {
            foreach ( 
$results as $k => &$v ) {
                if ( ! empty( 
$v->option_value ) ) {
                    
$v->option_value maybe_unserialize$v->option_value );
                }
            }
        }

        if ( 
Utils\get_flag_value$assoc_args'format' ) === 'total_bytes' ) {
            
WP_CLI::line$results[0]->size_bytes );
        } else {
            
$formatter = new Formatter(
                
$assoc_args,
                
$fields
            
);
            
$formatter->display_items$results );
        }
    }

    
/**
     * Updates an option value.
     *
     * ## OPTIONS
     *
     * <key>
     * : The name of the option to update.
     *
     * [<value>]
     * : The new value. If ommited, the value is read from STDIN.
     *
     * [--autoload=<autoload>]
     * : Requires WP 4.2. Should this option be automatically loaded.
     * ---
     * options:
     *   - 'yes'
     *   - 'no'
     * ---
     *
     * [--format=<format>]
     * : The serialization format for the value.
     * ---
     * default: plaintext
     * options:
     *   - plaintext
     *   - json
     * ---
     *
     * ## EXAMPLES
     *
     *     # Update an option by reading from a file.
     *     $ wp option update my_option < value.txt
     *     Success: Updated 'my_option' option.
     *
     *     # Update one option on multiple sites using xargs.
     *     $ wp site list --field=url | xargs -n1 -I {} sh -c 'wp --url={} option update my_option my_value'
     *     Success: Updated 'my_option' option.
     *     Success: Updated 'my_option' option.
     *
     *     # Update site blog name.
     *     $ wp option update blogname "Random blog name"
     *     Success: Updated 'blogname' option.
     *
     *     # Update site blog description.
     *     $ wp option update blogdescription "Some random blog description"
     *     Success: Updated 'blogdescription' option.
     *
     *     # Update admin email address.
     *     $ wp option update admin_email [email protected]
     *     Success: Updated 'admin_email' option.
     *
     *     # Set the default role.
     *     $ wp option update default_role author
     *     Success: Updated 'default_role' option.
     *
     *     # Set the timezone string.
     *     $ wp option update timezone_string "America/New_York"
     *     Success: Updated 'timezone_string' option.
     *
     * @alias set
     */
    
public function update$args$assoc_args ) {
        
$key $args[0];

        
$value WP_CLI::get_value_from_arg_or_stdin$args);
        
$value WP_CLI::read_value$value$assoc_args );

        
$autoload Utils\get_flag_value$assoc_args'autoload' );
        if ( ! 
in_array$autoload, [ 'yes''no' ], true ) ) {
            
$autoload null;
        }

        
$value sanitize_option$key$value );
        
// Sanitization WordPress normally performs when getting an option
        
if ( in_array$key, [ 'siteurl''home''category_base''tag_base' ], true ) ) {
            
$value untrailingslashit$value );
        }
        
$old_value sanitize_option$keyget_option$key ) );

        if ( 
$value === $old_value && null === $autoload ) {
            
WP_CLI::success"Value passed for '{$key}' option is unchanged." );
        } else {
            if ( 
update_option$key$value$autoload ) ) {
                
WP_CLI::success"Updated '{$key}' option." );
            } else {
                
WP_CLI::error"Could not update option '{$key}'." );
            }
        }
    }

    
/**
     * Deletes an option.
     *
     * ## OPTIONS
     *
     * <key>...
     * : Key for the option.
     *
     * ## EXAMPLES
     *
     *     # Delete an option.
     *     $ wp option delete my_option
     *     Success: Deleted 'my_option' option.
     *
     *     # Delete multiple options.
     *     $ wp option delete option_one option_two option_three
     *     Success: Deleted 'option_one' option.
     *     Success: Deleted 'option_two' option.
     *     Warning: Could not delete 'option_three' option. Does it exist?
     */
    
public function delete$args ) {
        foreach ( 
$args as $arg ) {
            if ( ! 
delete_option$arg ) ) {
                
WP_CLI::warning"Could not delete '{$arg}' option. Does it exist?" );
            } else {
                
WP_CLI::success"Deleted '{$arg}' option." );
            }
        }
    }

    
/**
     * Gets a nested value from an option.
     *
     * ## OPTIONS
     *
     * <key>
     * : The option name.
     *
     * <key-path>...
     * : The name(s) of the keys within the value to locate the value to pluck.
     *
     * [--format=<format>]
     * : The output format of the value.
     * ---
     * default: plaintext
     * options:
     *   - plaintext
     *   - json
     *   - yaml
     * ---
     */
    
public function pluck$args$assoc_args ) {
        list( 
$key ) = $args;

        
$value get_option$key );

        if ( 
false === $value ) {
            
WP_CLI::halt);
        }

        
$key_path array_map(
            function( 
$key ) {
                if ( 
is_numeric$key ) && ( (string) intval$key ) === $key ) ) {
                    return (int) 
$key;
                }
                    return 
$key;
            },
            
array_slice$args)
        );

        
$traverser = new RecursiveDataStructureTraverser$value );

        try {
            
$value $traverser->get$key_path );
        } catch ( 
Exception $exception ) {
            die( 
);
        }

        
WP_CLI::print_value$value$assoc_args );
    }

    
/**
     * Updates a nested value in an option.
     *
     * ## OPTIONS
     *
     * <action>
     * : Patch action to perform.
     * ---
     * options:
     *   - insert
     *   - update
     *   - delete
     * ---
     *
     * <key>
     * : The option name.
     *
     * <key-path>...
     * : The name(s) of the keys within the value to locate the value to patch.
     *
     * [<value>]
     * : The new value. If omitted, the value is read from STDIN.
     *
     * [--format=<format>]
     * : The serialization format for the value.
     * ---
     * default: plaintext
     * options:
     *   - plaintext
     *   - json
     * ---
     */
    
public function patch$args$assoc_args ) {
        list( 
$action$key ) = $args;
        
$key_path             array_map(
            function( 
$key ) {
                if ( 
is_numeric$key ) && ( (string) intval$key ) === $key ) ) {
                    return (int) 
$key;
                }
                    return 
$key;
            },
            
array_slice$args)
        );

        if ( 
'delete' === $action ) {
            
$patch_value null;
        } else {
            
$stdin_value EntityUtils::has_stdin()
                ? 
trimWP_CLI::get_value_from_arg_or_stdin$args, -) )
                : 
null;
            
$patch_value = ! empty( $stdin_value )
                ? 
WP_CLI::read_value$stdin_value$assoc_args )
                : 
WP_CLI::read_valuearray_pop$key_path ), $assoc_args );
        }

        
/* Need to make a copy of $current_value here as it is modified by reference */
        
$old_value     sanitize_option$keyget_option$key ) );
        
$current_value $old_value;
        if ( 
is_object$current_value ) ) {
            
$old_value = clone $current_value;
        }

        
$traverser = new RecursiveDataStructureTraverser$current_value );

        try {
            
$traverser->$action$key_path$patch_value );
        } catch ( 
Exception $exception ) {
            
WP_CLI::error$exception->getMessage() );
        }

        
$patched_value sanitize_option$key$traverser->value() );

        if ( 
$patched_value === $old_value ) {
            
WP_CLI::success"Value passed for '{$key}' option is unchanged." );
        } else {
            if ( 
update_option$key$patched_value ) ) {
                
WP_CLI::success"Updated '{$key}' option." );
            } else {
                
WP_CLI::error"Could not update option '{$key}'." );
            }
        }
    }

    private static function 
esc_like$old ) {
        global 
$wpdb;

        
// Remove notices in 4.0 and support backwards compatibility
        
if ( method_exists$wpdb'esc_like' ) ) {
            
// 4.0
            
$old $wpdb->esc_like$old );
        } else {
            
// phpcs:ignore WordPress.WP.DeprecatedFunctions.like_escapeFound -- called in WordPress 3.9 or less.
            
$old like_escapeesc_sql$old ) );
        }

        return 
$old;
    }
}
<?
php

use WP_CLI\CommandWithMeta;

/**
 * Gets, adds, updates, deletes, and lists network custom fields.
 *
 * ## EXAMPLES
 *
 *     # Get a list of super-admins
 *     $ wp network meta get 1 site_admins
 *     array (
 *       0 => 'supervisor',
 *     )
 */
class Network_Meta_Command extends CommandWithMeta {
    protected 
$meta_type 'site';
}
<?
php

use WP_CLI\Formatter;
use 
WP_CLI\Utils;

/**
 * Retrieves information about registered taxonomies.
 *
 * See references for [built-in taxonomies](https://developer.wordpress.org/themes/basics/categories-tags-custom-taxonomies/) and [custom taxonomies](https://developer.wordpress.org/plugins/taxonomies/working-with-custom-taxonomies/).
 *
 * ## EXAMPLES
 *
 *     # List all taxonomies with 'post' object type.
 *     $ wp taxonomy list --object_type=post --fields=name,public
 *     +-------------+--------+
 *     | name        | public |
 *     +-------------+--------+
 *     | category    | 1      |
 *     | post_tag    | 1      |
 *     | post_format | 1      |
 *     +-------------+--------+
 *
 *     # Get capabilities of 'post_tag' taxonomy.
 *     $ wp taxonomy get post_tag --field=cap
 *     {"manage_terms":"manage_categories","edit_terms":"manage_categories","delete_terms":"manage_categories","assign_terms":"edit_posts"}
 *
 * @package wp-cli
 */
class Taxonomy_Command extends WP_CLI_Command {

    private 
$fields = array(
        
'name',
        
'label',
        
'description',
        
'object_type',
        
'show_tagcloud',
        
'hierarchical',
        
'public',
    );

    public function 
__construct() {

        if ( 
Utils\wp_version_compare3.7'<' ) ) {
            
// remove description for wp <= 3.7
            
$this->fields array_valuesarray_diff$this->fields, array( 'description' ) ) );
        }

        
parent::__construct();
    }

    
/**
     * Gets the term counts for each supplied taxonomy.
     *
     * @param array $taxonomies Taxonomies to fetch counts for.
     * @return array Associative array of term counts keyed by taxonomy.
     */
    
protected function get_counts$taxonomies ) {
        global 
$wpdb;

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

        
$query $wpdb->prepare(
            
"SELECT `taxonomy`, COUNT(*) AS `count`
            FROM 
$wpdb->term_taxonomy
            WHERE `taxonomy` IN (" 
implode','array_fill0count$taxonomies ), '%s' ) ) . ')
            GROUP BY `taxonomy`'
,
            
$taxonomies
        
);
        
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $query is already prepared above.
        
$counts $wpdb->get_results$query );

        
// Make sure there's a count for every item.
        
$counts array_merge(
            
array_fill_keys$taxonomies),
            
wp_list_pluck$counts'count''taxonomy' )
        );

        return 
$counts;
    }

    
/**
     * Lists registered taxonomies.
     *
     * ## OPTIONS
     *
     * [--<field>=<value>]
     * : Filter by one or more fields (see get_taxonomies() first parameter for a list of available fields).
     *
     * [--field=<field>]
     * : Prints the value of a single field for each taxonomy.
     *
     * [--fields=<fields>]
     * : Limit the output to specific taxonomy fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - count
     *   - yaml
     * ---
     *
     * ## AVAILABLE FIELDS
     *
     * These fields will be displayed by default for each term:
     *
     * * name
     * * label
     * * description
     * * object_type
     * * show_tagcloud
     * * hierarchical
     * * public
     *
     * These fields are optionally available:
     *
     * * count
     *
     * ## EXAMPLES
     *
     *     # List all taxonomies.
     *     $ wp taxonomy list --format=csv
     *     name,label,description,object_type,show_tagcloud,hierarchical,public
     *     category,Categories,,post,1,1,1
     *     post_tag,Tags,,post,1,,1
     *     nav_menu,"Navigation Menus",,nav_menu_item,,,
     *     link_category,"Link Categories",,link,1,,
     *     post_format,Format,,post,,,1
     *
     *     # List all taxonomies with 'post' object type.
     *     $ wp taxonomy list --object_type=post --fields=name,public
     *     +-------------+--------+
     *     | name        | public |
     *     +-------------+--------+
     *     | category    | 1      |
     *     | post_tag    | 1      |
     *     | post_format | 1      |
     *     +-------------+--------+
     *
     * @subcommand list
     */
    
public function list_$args$assoc_args ) {
        
$formatter $this->get_formatter$assoc_args );

        
// Check if it's strict mode or not.
        
$strict Utils\get_flag_value$assoc_args'strict'false );

        unset( 
$assoc_args['strict'] );

        if ( isset( 
$assoc_args['object_type'] ) ) {
            
$assoc_args['object_type'] = array( $assoc_args['object_type'] );
            
$taxonomy_object           $assoc_args['object_type'];
        } else {
            
$taxonomy_object get_post_types();
        }

        
$fields     $formatter->fields;
        
$taxonomies = ( isset( $taxonomy_object ) && ! $strict )
            ? 
get_object_taxonomies$taxonomy_object'objects' )
            : 
get_taxonomies$assoc_args'objects' );

        
$counts = [];

        if ( 
count$taxonomies ) > && in_array'count'$fieldstrue ) ) {
            
$counts $this->get_countswp_list_pluck$taxonomies'name' ) );
        }

        
$taxonomies array_map(
            function( 
$taxonomy ) use ( $counts ) {
                    
$taxonomy->object_type implode', '$taxonomy->object_type );
                    
$taxonomy->count       = isset( $counts$taxonomy->name ] ) ? $counts$taxonomy->name ] : 0;
                    return 
$taxonomy;
            },
            
$taxonomies
        
);

        
$formatter->display_items$taxonomies );
    }

    
/**
     * Gets details about a registered taxonomy.
     *
     * ## OPTIONS
     *
     * <taxonomy>
     * : Taxonomy slug.
     *
     * [--field=<field>]
     * : Instead of returning the whole taxonomy, returns the value of a single field.
     *
     * [--fields=<fields>]
     * : Limit the output to specific fields. Defaults to all fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - yaml
     * ---
     *
     * ## AVAILABLE FIELDS
     *
     * These fields will be displayed by default for the specified taxonomy:
     *
     * * name
     * * label
     * * description
     * * object_type
     * * show_tagcloud
     * * hierarchical
     * * public
     * * labels
     * * cap
     *
     * These fields are optionally available:
     *
     * * count
     *
     * ## EXAMPLES
     *
     *     # Get details of `category` taxonomy.
     *     $ wp taxonomy get category --fields=name,label,object_type
     *     +-------------+------------+
     *     | Field       | Value      |
     *     +-------------+------------+
     *     | name        | category   |
     *     | label       | Categories |
     *     | object_type | ["post"]   |
     *     +-------------+------------+
     *
     *     # Get capabilities of 'post_tag' taxonomy.
     *     $ wp taxonomy get post_tag --field=cap
     *     {"manage_terms":"manage_categories","edit_terms":"manage_categories","delete_terms":"manage_categories","assign_terms":"edit_posts"}
     */
    
public function get$args$assoc_args ) {
        
$taxonomy get_taxonomy$args[0] );

        if ( ! 
$taxonomy ) {
            
WP_CLI::error"Taxonomy {$args[0]} doesn't exist." );
        }

        if ( empty( 
$assoc_args['fields'] ) ) {
            
$default_fields array_merge(
                
$this->fields,
                array(
                    
'labels',
                    
'cap',
                )
            );

            
$assoc_args['fields'] = $default_fields;
        }

        
$formatter $this->get_formatter$assoc_args );
        
$fields    $formatter->fields;
        
$count     0;

        if ( 
in_array'count'$fieldstrue ) ) {
            
$count $this->get_counts( [ $taxonomy->name ] );
            
$count $count$taxonomy->name ];
        }

        
$data = array(
            
'name'          => $taxonomy->name,
            
'label'         => $taxonomy->label,
            
'description'   => $taxonomy->description,
            
'object_type'   => $taxonomy->object_type,
            
'show_tagcloud' => $taxonomy->show_tagcloud,
            
'hierarchical'  => $taxonomy->hierarchical,
            
'public'        => $taxonomy->public,
            
'labels'        => $taxonomy->labels,
            
'cap'           => $taxonomy->cap,
            
'count'         => $count,
        );
        
$formatter->display_item$data );
    }

    private function 
get_formatter( &$assoc_args ) {
        return new 
Formatter$assoc_args$this->fields'taxonomy' );
    }
}
<?
php

use WP_CLI\CommandWithTerms;

/**
 * Adds, updates, removes, and lists user terms.
 *
 * ## EXAMPLES
 *
 *     # Set user terms
 *     $ wp user term set 123 test category
 *     Success: Set terms.
 */
class User_Term_Command extends CommandWithTerms {
    protected 
$obj_type 'user';
}
<?
php

use WP_CLI\CommandWithMeta;
use 
WP_CLI\Fetchers\Site as SiteFetcher;

/**
 * Adds, updates, deletes, and lists site custom fields.
 *
 * ## EXAMPLES
 *
 *     # Set site meta
 *     $ wp site meta set 123 bio "Mary is a WordPress developer."
 *     Success: Updated custom field 'bio'.
 *
 *     # Get site meta
 *     $ wp site meta get 123 bio
 *     Mary is a WordPress developer.
 *
 *     # Update site meta
 *     $ wp site meta update 123 bio "Mary is an awesome WordPress developer."
 *     Success: Updated custom field 'bio'.
 *
 *     # Delete site meta
 *     $ wp site meta delete 123 bio
 *     Success: Deleted custom field.
 */
class Site_Meta_Command extends CommandWithMeta {
    protected 
$meta_type 'blog';

    
/**
     * Check that the site ID exists
     *
     * @param int
     */
    
protected function check_object_id$object_id ) {
        
$fetcher = new SiteFetcher();
        
$site    $fetcher->get_check$object_id );
        return 
$site->blog_id;
    }

    
/**
     * Wrapper method for add_metadata that can be overridden in sub classes.
     *
     * @param int    $object_id  ID of the object the metadata is for.
     * @param string $meta_key   Metadata key to use.
     * @param mixed  $meta_value Metadata value. Must be serializable if
     *                           non-scalar.
     * @param bool   $unique     Optional, default is false. Whether the
     *                           specified metadata key should be unique for the
     *                           object. If true, and the object already has a
     *                           value for the specified metadata key, no change
     *                           will be made.
     *
     * @return int|false The meta ID on success, false on failure.
     */
    
protected function add_metadata$object_id$meta_key$meta_value$unique false ) {
        return 
add_site_meta$object_id$meta_key$meta_value$unique );
    }

    
/**
     * Wrapper method for update_metadata that can be overridden in sub classes.
     *
     * @param int    $object_id  ID of the object the metadata is for.
     * @param string $meta_key   Metadata key to use.
     * @param mixed  $meta_value Metadata value. Must be serializable if
     *                           non-scalar.
     * @param mixed  $prev_value Optional. If specified, only update existing
     *                           metadata entries with the specified value.
     *                           Otherwise, update all entries.
     *
     * @return int|bool Meta ID if the key didn't exist, true on successful
     *                  update, false on failure.
     */
    
protected function update_metadata$object_id$meta_key$meta_value$prev_value '' ) {
        return 
update_site_meta$object_id$meta_key$meta_value$prev_value );
    }

    
/**
     * Wrapper method for get_metadata that can be overridden in sub classes.
     *
     * @param int    $object_id ID of the object the metadata is for.
     * @param string $meta_key  Optional. Metadata key. If not specified,
     *                          retrieve all metadata for the specified object.
     * @param bool   $single    Optional, default is false. If true, return only
     *                          the first value of the specified meta_key. This
     *                          parameter has no effect if meta_key is not
     *                          specified.
     *
     * @return mixed Single metadata value, or array of values.
     */
    
protected function get_metadata$object_id$meta_key ''$single false ) {
        return 
get_site_meta$object_id$meta_key$single );
    }

    
/**
     * Wrapper method for delete_metadata that can be overridden in sub classes.
     *
     * @param int    $object_id  ID of the object metadata is for
     * @param string $meta_key   Metadata key
     * @param mixed $meta_value  Optional. Metadata value. Must be serializable
     *                           if non-scalar. If specified, only delete
     *                           metadata entries with this value. Otherwise,
     *                           delete all entries with the specified meta_key.
     *                           Pass `null, `false`, or an empty string to skip
     *                           this check. For backward compatibility, it is
     *                           not possible to pass an empty string to delete
     *                           those entries with an empty string for a value.
     *
     * @return bool True on successful delete, false on failure.
     */
    
protected function delete_metadata$object_id$meta_key$meta_value '' ) {
        return 
delete_site_meta$object_id$meta_key$meta_value );
    }

}
<?
php

use WP_CLI\Formatter;

/**
 * Retrieves details on the site's registered post types.
 *
 * Get information on WordPress' built-in and the site's [custom post types](https://developer.wordpress.org/plugins/post-types/).
 *
 * ## EXAMPLES
 *
 *     # Get details about a post type
 *     $ wp post-type get page --fields=name,label,hierarchical --format=json
 *     {"name":"page","label":"Pages","hierarchical":true}
 *
 *     # List post types with 'post' capability type
 *     $ wp post-type list --capability_type=post --fields=name,public
 *     +---------------+--------+
 *     | name          | public |
 *     +---------------+--------+
 *     | post          | 1      |
 *     | attachment    | 1      |
 *     | revision      |        |
 *     | nav_menu_item |        |
 *     +---------------+--------+
 *
 * @package wp-cli
 */
class Post_Type_Command extends WP_CLI_Command {

    private 
$fields = array(
        
'name',
        
'label',
        
'description',
        
'hierarchical',
        
'public',
        
'capability_type',
    );

    
/**
     * Gets the post counts for each supplied post type.
     *
     * @param array $post_types Post types to fetch counts for.
     * @return array Associative array of post counts keyed by post type.
     */
    
protected function get_counts$post_types ) {
        global 
$wpdb;

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

        
$query $wpdb->prepare(
            
"SELECT `post_type`, COUNT(*) AS `count`
            FROM 
$wpdb->posts
            WHERE `post_type` IN (" 
implode','array_fill0count$post_types ), '%s' ) ) . ')
            GROUP BY `post_type`'
,
            
$post_types
        
);
        
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $query is already prepared above.
        
$counts $wpdb->get_results$query );

        
// Make sure there's a count for every item.
        
$counts array_merge(
            
array_fill_keys$post_types),
            
wp_list_pluck$counts'count''post_type' )
        );

        return 
$counts;
    }

    
/**
     * Lists registered post types.
     *
     * ## OPTIONS
     *
     * [--<field>=<value>]
     * : Filter by one or more fields (see get_post_types() first parameter for a list of available fields).
     *
     * [--field=<field>]
     * : Prints the value of a single field for each post type.
     *
     * [--fields=<fields>]
     * : Limit the output to specific post type fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - count
     *   - yaml
     * ---
     *
     * ## AVAILABLE FIELDS
     *
     * These fields will be displayed by default for each post type:
     *
     * * name
     * * label
     * * description
     * * hierarchical
     * * public
     * * capability_type
     *
     * These fields are optionally available:
     *
     * * count
     *
     * ## EXAMPLES
     *
     *     # List registered post types
     *     $ wp post-type list --format=csv
     *     name,label,description,hierarchical,public,capability_type
     *     post,Posts,,,1,post
     *     page,Pages,,1,1,page
     *     attachment,Media,,,1,post
     *     revision,Revisions,,,,post
     *     nav_menu_item,"Navigation Menu Items",,,,post
     *
     *     # List post types with 'post' capability type
     *     $ wp post-type list --capability_type=post --fields=name,public
     *     +---------------+--------+
     *     | name          | public |
     *     +---------------+--------+
     *     | post          | 1      |
     *     | attachment    | 1      |
     *     | revision      |        |
     *     | nav_menu_item |        |
     *     +---------------+--------+
     *
     * @subcommand list
     */
    
public function list_$args$assoc_args ) {
        
$formatter $this->get_formatter$assoc_args );

        
$fields $formatter->fields;
        
$types  get_post_types$assoc_args'objects' );
        
$counts = [];

        if ( 
count$types ) > && in_array'count'$fieldstrue ) ) {
            
$counts $this->get_countswp_list_pluck$types'name' ) );
        }

        
$types array_map(
            function( 
$type ) use ( $counts ) {
                    
$type->count = isset( $counts$type->name ] ) ? $counts$type->name ] : 0;
                    return 
$type;
            },
            
$types
        
);

        
$formatter->display_items$types );
    }

    
/**
     * Gets details about a registered post type.
     *
     * ## OPTIONS
     *
     * <post-type>
     * : Post type slug
     *
     * [--field=<field>]
     * : Instead of returning the whole taxonomy, returns the value of a single field.
     *
     * [--fields=<fields>]
     * : Limit the output to specific fields. Defaults to all fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - yaml
     * ---
     *
     * ## AVAILABLE FIELDS
     *
     * These fields will be displayed by default for the specified post type:
     *
     * * name
     * * label
     * * description
     * * hierarchical
     * * public
     * * capability_type
     * * labels
     * * cap
     * * supports
     *
     * These fields are optionally available:
     *
     * * count
     *
     * ## EXAMPLES
     *
     *     # Get details about the 'page' post type.
     *     $ wp post-type get page --fields=name,label,hierarchical --format=json
     *     {"name":"page","label":"Pages","hierarchical":true}
     */
    
public function get$args$assoc_args ) {
        
$post_type get_post_type_object$args[0] );

        if ( ! 
$post_type ) {
            
WP_CLI::error"Post type {$args[0]} doesn't exist." );
        }

        if ( empty( 
$assoc_args['fields'] ) ) {
            
$default_fields array_merge(
                
$this->fields,
                array(
                    
'labels',
                    
'cap',
                    
'supports',
                )
            );

            
$assoc_args['fields'] = $default_fields;
        }

        
$formatter $this->get_formatter$assoc_args );
        
$fields    $formatter->fields;
        
$count     0;

        if ( 
in_array'count'$fieldstrue ) ) {
            
$count $this->get_counts( [ $post_type->name ] );
            
$count $count$post_type->name ];
        }

        
$data = array(
            
'name'            => $post_type->name,
            
'label'           => $post_type->label,
            
'description'     => $post_type->description,
            
'hierarchical'    => $post_type->hierarchical,
            
'public'          => $post_type->public,
            
'capability_type' => $post_type->capability_type,
            
'labels'          => $post_type->labels,
            
'cap'             => $post_type->cap,
            
'supports'        => get_all_post_type_supports$post_type->name ),
            
'count'           => $count,
        );
        
$formatter->display_item$data );
    }

    private function 
get_formatter( &$assoc_args ) {
        return new 
Formatter$assoc_args$this->fields'post-type' );
    }
}
<?
php

use WP_CLI\Utils;

if ( ! 
class_exists'WP_CLI' ) ) {
    return;
}

$wpcli_entity_autoloader dirname__FILE__ ) . '/vendor/autoload.php';
if ( 
file_exists$wpcli_entity_autoloader ) ) {
    require_once 
$wpcli_entity_autoloader;
}

WP_CLI::add_command'comment''Comment_Command' );
WP_CLI::add_command'comment meta''Comment_Meta_Command' );
WP_CLI::add_command'menu''Menu_Command' );
WP_CLI::add_command'menu item''Menu_Item_Command' );
WP_CLI::add_command'menu location''Menu_Location_Command' );
WP_CLI::add_command(
    
'network meta',
    
'Network_Meta_Command',
    array(
        
'before_invoke' => function () {
            if ( ! 
is_multisite() ) {
                
WP_CLI::error'This is not a multisite installation.' );
            }
        },
    )
);
WP_CLI::add_command'option''Option_Command' );
WP_CLI::add_command'post''Post_Command' );
WP_CLI::add_command'post meta''Post_Meta_Command' );
WP_CLI::add_command'post term''Post_Term_Command' );
WP_CLI::add_command'post-type''Post_Type_Command' );
WP_CLI::add_command'site''Site_Command' );
WP_CLI::add_command(
    
'site meta',
    
'Site_Meta_Command',
    array(
        
'before_invoke' => function() {
            if ( ! 
is_multisite() ) {
                
WP_CLI::error'This is not a multisite installation.' );
            }
            if ( ! 
function_exists'is_site_meta_supported' ) || ! is_site_meta_supported() ) {
                
WP_CLI::errorsprintf'The %s table is not installed. Please run the network database upgrade.'$GLOBALS['wpdb']->blogmeta ) );
            }
        },
    )
);
WP_CLI::add_command(
    
'site option',
    
'Site_Option_Command',
    array(
        
'before_invoke' => function() {
            if ( ! 
is_multisite() ) {
                
WP_CLI::error'This is not a multisite installation.' );
            }
        },
    )
);
WP_CLI::add_command'taxonomy''Taxonomy_Command' );
WP_CLI::add_command'term''Term_Command' );
WP_CLI::add_command(
    
'term meta',
    
'Term_Meta_Command',
    array(
        
'before_invoke' => function() {
            if ( 
Utils\wp_version_compare'4.4''<' ) ) {
                
WP_CLI::error'Requires WordPress 4.4 or greater.' );
            }
        },
    )
);
WP_CLI::add_command'user''User_Command' );
WP_CLI::add_command(
    
'user application-password',
    
'User_Application_Password_Command',
    array(
        
'before_invoke' => function() {
            if ( 
Utils\wp_version_compare'5.6''<' ) ) {
                
WP_CLI::error'Requires WordPress 5.6 or greater.' );
            }
        },
    )
);
WP_CLI::add_command'user meta''User_Meta_Command' );
WP_CLI::add_command(
    
'user session',
    
'User_Session_Command',
    array(
        
'before_invoke' => function() {
            if ( 
Utils\wp_version_compare'4.0''<' ) ) {
                
WP_CLI::error'Requires WordPress 4.0 or greater.' );
            }
        },
    )
);
WP_CLI::add_command'user term''User_Term_Command' );

if ( 
class_exists'WP_CLI\Dispatcher\CommandNamespace' ) ) {
    
WP_CLI::add_command'network''Network_Namespace' );
}
<?
php

namespace WP_CLI\I18n;

use 
Exception;
use 
Gettext\Extractors\PhpCode;
use 
Gettext\Translations;
use 
WP_CLI;

final class 
PhpCodeExtractor extends PhpCode {
    use 
IterableCodeExtractor;

    public static 
$options = [
        
'extractComments' => [ 'translators''Translators' ],
        
'constants'       => [],
        
'functions'       => [
            
'__'              => 'text_domain',
            
'esc_attr__'      => 'text_domain',
            
'esc_html__'      => 'text_domain',
            
'esc_xml__'       => 'text_domain',
            
'_e'              => 'text_domain',
            
'esc_attr_e'      => 'text_domain',
            
'esc_html_e'      => 'text_domain',
            
'esc_xml_e'       => 'text_domain',
            
'_x'              => 'text_context_domain',
            
'_ex'             => 'text_context_domain',
            
'esc_attr_x'      => 'text_context_domain',
            
'esc_html_x'      => 'text_context_domain',
            
'esc_xml_x'       => 'text_context_domain',
            
'_n'              => 'single_plural_number_domain',
            
'_nx'             => 'single_plural_number_context_domain',
            
'_n_noop'         => 'single_plural_domain',
            
'_nx_noop'        => 'single_plural_context_domain',

            
// Compat.
            
'_'               => 'gettext'// Same as 'text_domain'.

            // Deprecated.
            
'_c'              => 'text_domain',
            
'_nc'             => 'single_plural_number_domain',
            
'__ngettext'      => 'single_plural_number_domain',
            
'__ngettext_noop' => 'single_plural_domain',
        ],
    ];

    protected static 
$functionsScannerClass 'WP_CLI\I18n\PhpFunctionsScanner';

    
/**
     * {@inheritdoc}
     */
    
public static function fromString$stringTranslations $translations, array $options = [] ) {
        
WP_CLI::debug"Parsing file {$options['file']}"'make-pot' );

        try {
            static::
fromStringMultiple$string, [ $translations ], $options );
        } catch ( 
Exception $exception ) {
            
WP_CLI::debug(
                
sprintf(
                    
'Could not parse file %1$s: %2$s',
                    
$options['file'],
                    
$exception->getMessage()
                ),
                
'make-pot'
            
);
        }
    }
}
<?
php

namespace WP_CLI\I18n;

use 
Gettext\Extractors\Extractor;
use 
Gettext\Extractors\ExtractorInterface;
use 
Gettext\Translations;
use 
WP_CLI;

final class 
BlockExtractor extends Extractor implements ExtractorInterface {
    use 
IterableCodeExtractor;

    
/**
     * @inheritdoc
     */
    
public static function fromString$stringTranslations $translations, array $options = [] ) {
        
$file $options['file'];
        
WP_CLI::debug"Parsing file {$file}"'make-pot' );

        
$file_data json_decode$stringtrue );

        if ( 
null === $file_data ) {
            
WP_CLI::debug(
                
sprintf(
                    
'Could not parse file %1$s: error code %2$s',
                    
$file,
                    
json_last_error()
                ),
                
'make-pot'
            
);

            return;
        }

        
$domain = isset( $file_data['textdomain'] ) ? $file_data['textdomain'] : null;

        
// Always allow missing domain or when --ignore-domain is used, but skip if domains don't match.
        
if ( null !== $translations->getDomain() && null !== $domain && $domain !== $translations->getDomain() ) {
            return;
        }

        
$add_reference = ! empty( $options['addReferences'] );

        foreach ( 
$file_data as $key => $original ) {
            switch ( 
$key ) {
                case 
'title':
                case 
'description':
                    
$translation $translations->insertsprintf'block %s'$key ), $original );
                    if ( 
$add_reference ) {
                        
$translation->addReference$file );
                    }
                    break;
                case 
'keywords':
                    if ( ! 
is_array$original ) ) {
                        continue 
2;
                    }

                    foreach ( 
$original as $msg ) {
                        
$translation $translations->insert'block keyword'$msg );
                        if ( 
$add_reference ) {
                            
$translation->addReference$file );
                        }
                    }

                    break;
                case 
'styles':
                    if ( ! 
is_array$original ) ) {
                        continue 
2;
                    }

                    foreach ( 
$original as $msg ) {
                        
$translation $translations->insert'block style label'$msg['label'] );
                        if ( 
$add_reference ) {
                            
$translation->addReference$file );
                        }
                    }
            }
        }
    }
}
<?
php

namespace WP_CLI\I18n;

use 
Gettext\Extractors\JsCode;
use 
Gettext\Translations;
use 
Peast\Syntax\Exception as PeastException;
use 
WP_CLI;

final class 
MapCodeExtractor extends JsCode {
    use 
IterableCodeExtractor;

    public static 
$options = [
        
'extractComments' => [ 'translators''Translators' ],
        
'constants'       => [],
        
'functions'       => [
            
'__'  => 'text_domain',
            
'_x'  => 'text_context_domain',
            
'_n'  => 'single_plural_number_domain',
            
'_nx' => 'single_plural_number_context_domain',
        ],
    ];

    
/**
     * {@inheritdoc}
     */
    
public static function fromString$stringTranslations $translations, array $options = [] ) {
        if ( ! 
array_key_exists'file'$options ) || substr$options['file'], -) !== '.js.map' ) {
            return;
        }
        
$options['file'] = substr$options['file'], 0, -) . '.js';

        try {
            
$options += static::$options;

            
$map_object json_decode$string );

            if ( ! isset( 
$map_object->sourcesContent ) || ! is_array$map_object->sourcesContent ) ) {
                return;
            }

            
$string implode"\n"$map_object->sourcesContent );

            
WP_CLI::debug"Parsing file {$options['file']}"'make-pot' );

            
$functions = new JsFunctionsScanner$string );

            
$functions->enableCommentsExtraction$options['extractComments'] );
            
$functions->saveGettextFunctions$translations$options );
        } catch ( 
PeastException $e ) {
            
WP_CLI::debug(
                
sprintf(
                    
'Could not parse file %1$s.map: %2$s (line %3$d, column %4$d in the concatenated sourcesContent)',
                    
$options['file'],
                    
$e->getMessage(),
                    
$e->getPosition()->getLine(),
                    
$e->getPosition()->getColumn()
                ),
                
'make-pot'
            
);
        }
    }
}
<?
php

namespace WP_CLI\I18n;

use 
Gettext\Translation;
use 
Gettext\Translations;
use 
RecursiveCallbackFilterIterator;
use 
RecursiveDirectoryIterator;
use 
RecursiveIteratorIterator;
use 
SplFileInfo;
use 
WP_CLI;
use 
WP_CLI\Utils;

trait 
IterableCodeExtractor {

    private static 
$dir '';

    
/**
     * Extract the translations from a file.
     *
     * @param array|string $file_or_files A path of a file or files
     * @param Translations $translations  The translations instance to append the new translations.
     * @param array        $options      {
     *     Optional. An array of options passed down to static::fromString()
     *
     *     @type bool  $wpExtractTemplates Extract 'Template Name' headers in theme files. Default 'false'.
     *     @type array $restrictFileNames  Skip all files which are not included in this array.
     * }
     * @return null
     */
    
public static function fromFile$file_or_filesTranslations $translations, array $options = [] ) {
        foreach ( static::
getFiles$file_or_files ) as $file ) {
            if ( ! empty( 
$options['restrictFileNames'] ) ) {
                
$basename Utils\basename$file );
                if ( ! 
in_array$basename$options['restrictFileNames'], true ) ) {
                    continue;
                }
            }

            
// Make sure a relative file path is added as a comment.
            
$options['file'] = ltrimstr_replace( static::$dir''Utils\normalize_path$file ) ), '/' );

            
$string file_get_contents$file );

            if ( ! 
$string ) {
                
WP_CLI::debug(
                    
sprintf(
                        
'Could not load file %1s',
                        
$file
                    
),
                    
'make-pot'
                
);

                continue;
            }

            if ( ! empty( 
$options['wpExtractTemplates'] ) ) {
                
$headers MakePotCommand::get_file_data_from_string$string, [ 'Template Name' => 'Template Name' ] );

                if ( ! empty( 
$headers['Template Name'] ) ) {
                    
$translation = new Translation''$headers['Template Name'] );
                    
$translation->addExtractedComment'Template Name of the theme' );

                    
$translations[] = $translation;
                }
            }

            static::
fromString$string$translations$options );
        }
    }

    
/**
     * Extract the translations from a file.
     *
     * @param string $dir                Root path to start the recursive traversal in.
     * @param Translations $translations The translations instance to append the new translations.
     * @param array        $options      {
     *     Optional. An array of options passed down to static::fromString()
     *
     *     @type bool $wpExtractTemplates Extract 'Template Name' headers in theme files. Default 'false'.
     *     @type array $exclude           A list of path to exclude. Default [].
     *     @type array $extensions        A list of extensions to process. Default [].
     * }
     * @return null
     */
    
public static function fromDirectory$dirTranslations $translations, array $options = [] ) {
        
$dir Utils\normalize_path$dir );

        static::
$dir $dir;

        
$include = isset( $options['include'] ) ? $options['include'] : [];
        
$exclude = isset( $options['exclude'] ) ? $options['exclude'] : [];

        
$files = static::getFilesFromDirectory$dir$include$exclude$options['extensions'] );

        if ( ! empty( 
$files ) ) {
            static::
fromFile$files$translations$options );
        }

        static::
$dir '';
    }

    
/**
     * Determines whether a file is valid based on given matchers.
     *
     * @param SplFileInfo $file     File or directory.
     * @param array       $matchers List of files and directories to match.
     * @return int How strongly the file is matched.
     */
    
protected static function calculateMatchScoreSplFileInfo $file, array $matchers = [] ) {
        if ( empty( 
$matchers ) ) {
            return 
0;
        }

        if ( 
in_array$file->getBasename(), $matcherstrue ) ) {
            return 
10;
        }

        
// Check for more complex paths, e.g. /some/sub/folder.
        
$root_relative_path str_replace( static::$dir''$file->getPathname() );

        foreach ( 
$matchers as $path_or_file ) {
            
$pattern preg_quotestr_replace'*''__wildcard__'$path_or_file ), '/' );
            
$pattern '(^|/)' str_replace'__wildcard__''(.+)'$pattern );

            
// Base score is the amount of nested directories, discounting wildcards.
            
$base_score count(
                
array_filter(
                    
explode'/'$path_or_file ),
                    static function ( 
$component ) {
                        return 
'*' !== $component;
                    }
                )
            );
            if ( 
=== $base_score ) {
                
// If the matcher is simply * it gets a score above the implicit score but below 1.
                
$base_score 0.2;
            }

            
// If the matcher contains no wildcards and matches the end of the path.
            
if (
                
false === strpos$path_or_file'*' ) &&
                
false !== mb_ereg$pattern '$'$root_relative_path )
            ) {
                return 
$base_score 10;
            }

            
// If the matcher matches the end of the path or a full directory contained.
            
if ( false !== mb_ereg$pattern '(/|$)'$root_relative_path ) ) {
                return 
$base_score;
            }
        }

        return 
0;
    }

    
/**
     * Determines whether or not a directory has children that may be matched.
     *
     * @param SplFileInfo $dir      Directory.
     * @param array       $matchers List of files and directories to match.
     * @return bool Whether or not there are any matchers for children of this directory.
     */
    
protected static function containsMatchingChildrenSplFileInfo $dir, array $matchers = [] ) {
        if ( empty( 
$matchers ) ) {
            return 
false;
        }

        
/** @var string $root_relative_path */
        
$root_relative_path str_replace( static::$dir''$dir->getPathname() );

        foreach ( 
$matchers as $path_or_file ) {
            
// If the matcher contains no wildcards and the path matches the start of the matcher.
            
if (
                
'' !== $root_relative_path &&
                
false === strpos$path_or_file'*' ) &&
                
=== strpos$path_or_file '/'$root_relative_path )
            ) {
                return 
true;
            }

            
$base currentexplode'*'$path_or_file ) );

            
// If start of the path matches the start of the matcher until the first wildcard.
            // Or the start of the matcher until the first wildcard matches the start of the path.
            
if (
                ( 
'' !== $root_relative_path && === strpos$base$root_relative_path ) ) ||
                ( 
'' !== $base && === strpos$root_relative_path$base ) )
            ) {
                return 
true;
            }
        }

        return 
false;
    }

    
/**
     * Recursively gets all PHP files within a directory.
     *
     * @param string $dir A path of a directory.
     * @param array $include List of files and directories to include.
     * @param array $exclude List of files and directories to skip.
     * @param array $extensions List of filename extensions to process.
     *
     * @return array File list.
     */
    
public static function getFilesFromDirectory$dir, array $include = [], array $exclude = [], $extensions = [] ) {
        
$filtered_files = [];

        
$files = new RecursiveIteratorIterator(
            new 
RecursiveCallbackFilterIterator(
                new 
RecursiveDirectoryIterator$dirRecursiveDirectoryIterator::SKIP_DOTS RecursiveDirectoryIterator::UNIX_PATHS RecursiveDirectoryIterator::FOLLOW_SYMLINKS ),
                static function ( 
$file$key$iterator ) use ( $include$exclude$extensions ) {
                    
/** @var RecursiveCallbackFilterIterator $iterator */
                    /** @var SplFileInfo $file */

                    // Normalize include and exclude paths.
                    
$include array_map'static::trim_leading_slash'$include );
                    
$exclude array_map'static::trim_leading_slash'$exclude );

                    
// If no $include is passed everything gets the weakest possible matching score.
                    
$inclusion_score = empty( $include ) ? 0.1 : static::calculateMatchScore$file$include );
                    
$exclusion_score = static::calculateMatchScore$file$exclude );

                    
// Always include directories that aren't excluded.
                    
if ( === $exclusion_score && $iterator->hasChildren() ) {
                        return 
true;
                    }

                    if ( ( 
=== $inclusion_score || $exclusion_score $inclusion_score ) && $iterator->hasChildren() ) {
                        
// Always include directories that may have matching children even if they are excluded.
                        
return static::containsMatchingChildren$file$include );
                    }

                    if ( ! 
$file->isFile() || ! in_array$file->getExtension(), $extensionstrue ) ) {
                        return 
false;
                    }

                    return 
$inclusion_score $exclusion_score;
                }
            ),
            
RecursiveIteratorIterator::CHILD_FIRST
        
);

        foreach ( 
$files as $file ) {
            
/** @var SplFileInfo $file */
            
if ( ! $file->isFile() || ! in_array$file->getExtension(), $extensionstrue ) ) {
                continue;
            }

            
$filtered_files[] = Utils\normalize_path$file->getPathname() );
        }

        
sort$filtered_filesSORT_NATURAL SORT_FLAG_CASE );

        return 
$filtered_files;
    }

    
/**
     * Trim leading slash from a path.
     *
     * @param string $path Path to trim.
     * @return string Trimmed path.
     */
    
private static function trim_leading_slash$path ) {
        return 
ltrim$path'/' );
    }
}
<?
php

namespace WP_CLI\I18n;

use 
Gettext\Utils\JsFunctionsScanner as GettextJsFunctionsScanner;
use 
Gettext\Utils\ParsedComment;
use 
Peast\Peast;
use 
Peast\Syntax\Node;
use 
Peast\Traverser;

final class 
JsFunctionsScanner extends GettextJsFunctionsScanner {

    
/**
     * If not false, comments will be extracted.
     *
     * @var string|false|array
     */
    
private $extract_comments false;

    
/**
     * Holds a list of source code comments already added to a string.
     *
     * Prevents associating the same comment to multiple strings.
     *
     * @var Node\Comment[] $comments_cache
     */
    
private $comments_cache = [];

    
/**
     * Enable extracting comments that start with a tag (if $tag is empty all the comments will be extracted).
     *
     * @param mixed $tag
     */
    
public function enableCommentsExtraction$tag '' ) {
        
$this->extract_comments $tag;
    }

    
/**
     * Disable comments extraction.
     */
    
public function disableCommentsExtraction() {
        
$this->extract_comments false;
    }

    
/**
     * {@inheritdoc}
     */
    
public function saveGettextFunctions$translations, array $options ) {
        
// Ignore multiple translations for now.
        // @todo Add proper support for multiple translations.
        
if ( is_array$translations ) ) {
            
$translations $translations[0];
        }

        
$peast_options = [
            
'sourceType' => Peast::SOURCE_TYPE_MODULE,
            
'comments'   => false !== $this->extract_comments,
            
'jsx'        => true,
        ];
        
$ast           Peast::latest$this->code$peast_options )->parse();

        
$traverser = new Traverser();

        
$all_comments = [];

        
/**
         * Traverse through JS code to find and extract gettext functions.
         *
         * Make sure translator comments in front of variable declarations
         * and inside nested call expressions are available when parsing the function call.
         */
        
$traverser->addFunction(
            function ( 
$node ) use ( &$translations$options, &$all_comments ) {
                
$functions     $options['functions'];
                
$file          $options['file'];
                
$add_reference = ! empty( $options['addReferences'] );

                
/** @var Node\Node $node */
                
foreach ( $node->getLeadingComments() as $comment ) {
                    
$all_comments[] = $comment;
                }

                
/** @var Node\CallExpression $node */
                
if ( 'CallExpression' !== $node->getType() ) {
                    return;
                }

                
$callee $this->resolveExpressionCallee$node );

                if ( ! 
$callee || ! isset( $functions$callee['name'] ] ) ) {
                    return;
                }

                
/** @var Node\CallExpression $node */
                
foreach ( $node->getArguments() as $argument ) {
                    
// Support nested function calls.
                    
$argument->setLeadingComments$argument->getLeadingComments() + $node->getLeadingComments() );
                }

                foreach ( 
$callee['comments'] as $comment ) {
                    
$all_comments[] = $comment;
                }

                
$domain   null;
                
$original null;
                
$context  null;
                
$plural   null;
                
$args     = [];

                
/** @var Node\Node $argument */
                
foreach ( $node->getArguments() as $argument ) {
                    foreach ( 
$argument->getLeadingComments() as $comment ) {
                        
$all_comments[] = $comment;
                    }

                    if (
                        
'Identifier' === $argument->getType() ||
                        
'Expression' === substr$argument->getType(), -strlen'Expression' ) )
                    ) {
                        
$args[] = ''// The value doesn't matter as it's unused.
                        
continue;
                    }

                    if ( 
'Literal' === $argument->getType() ) {
                        
/** @var Node\Literal $argument */
                        
$args[] = $argument->getValue();
                        continue;
                    }

                    if ( 
'TemplateLiteral' === $argument->getType() && === count$argument->getExpressions() ) ) {
                        
/** @var Node\TemplateLiteral $argument */
                        /** @var Node\TemplateElement[] $parts */

                        // Since there are no expressions within the TemplateLiteral, there is only one TemplateElement.
                        
$parts  $argument->getParts();
                        
$args[] = $parts[0]->getValue();
                        continue;
                    }

                    
// If we reach this, an unsupported argument type has been encountered.
                    // Do not try to parse this function call at all.
                    
return;
                }

                switch ( 
$functions$callee['name'] ] ) {
                    case 
'text_domain':
                    case 
'gettext':
                        list( 
$original$domain ) = array_pad$args2null );
                        break;

                    case 
'text_context_domain':
                        list( 
$original$context$domain ) = array_pad$args3null );
                        break;

                    case 
'single_plural_number_domain':
                        list( 
$original$plural$number$domain ) = array_pad$args4null );
                        break;

                    case 
'single_plural_number_context_domain':
                        list( 
$original$plural$number$context$domain ) = array_pad$args5null );
                        break;
                }

                if ( 
'' === (string) $original ) {
                    return;
                }

                if ( 
$domain !== $translations->getDomain() && null !== $translations->getDomain() ) {
                    return;
                }

                if ( isset( 
$options['line'] ) ) {
                    
$line $options['line'];
                } else {
                    
$line $node->getLocation()->getStart()->getLine();
                }

                
$translation $translations->insert$context$original$plural );
                if ( 
$add_reference ) {
                    
$translation->addReference$file$line );
                }

                
/** @var Node\Comment $comment */
                
foreach ( $all_comments as $comment ) {
                    
// Comments should be before the translation.
                    
if ( ! $this->commentPrecedesNode$comment$node ) ) {
                        continue;
                    }

                    if ( 
in_array$comment$this->comments_cachetrue ) ) {
                        continue;
                    }

                    
$parsed_comment ParsedComment::create$comment->getRawText(), $comment->getLocation()->getStart()->getLine() );
                    
$prefixes       array_filter( (array) $this->extract_comments );

                    if ( 
$parsed_comment->checkPrefixes$prefixes ) ) {
                        
$translation->addExtractedComment$parsed_comment->getComment() );

                        
$this->comments_cache[] = $comment;
                    }
                }

                if ( isset( 
$parsed_comment ) ) {
                    
$all_comments = [];
                }
            }
        );

        
/**
         * Traverse through JS code contained within eval() to find and extract gettext functions.
         */
        
$scanner $this;
        
$traverser->addFunction(
            function ( 
$node ) use ( &$translations$options$scanner ) {
                
/** @var Node\CallExpression $node */
                
if ( 'CallExpression' !== $node->getType() ) {
                    return;
                }

                
$callee $this->resolveExpressionCallee$node );

                if ( ! 
$callee || 'eval' !== $callee['name'] ) {
                    return;
                }

                
$eval_contents '';
                
/** @var Node\Node $argument */
                
foreach ( $node->getArguments() as $argument ) {
                    if ( 
'Literal' === $argument->getType() ) {
                        
/** @var Node\Literal $argument */
                        
$eval_contents $argument->getValue();
                        break;
                    }
                }

                if ( ! 
$eval_contents ) {
                    return;
                }

                
// Override the line location to be that of the eval().
                
$options['line'] = $node->getLocation()->getStart()->getLine();

                
$class get_class$scanner );
                
$evals = new $class$eval_contents );
                
$evals->enableCommentsExtraction$options['extractComments'] );
                
$evals->saveGettextFunctions$translations$options );
            }
        );

        
$traverser->traverse$ast );
    }

    
/**
     * Resolve the callee of a call expression using known formats.
     *
     * @param Node\CallExpression $node The call expression whose callee to resolve.
     *
     * @return array|bool Array containing the name and comments of the identifier if resolved. False if not.
     */
    
private function resolveExpressionCalleeNode\CallExpression $node ) {
        
$callee $node->getCallee();

        
// If the callee is a simple identifier it can simply be returned.
        // For example: __( "translation" ).
        
if ( 'Identifier' === $callee->getType() ) {
            return [
                
'name'     => $callee->getName(),
                
'comments' => $callee->getLeadingComments(),
            ];
        }

        
// If the callee is a member expression resolve it to the property.
        // For example: wp.i18n.__( "translation" ) or u.__( "translation" ).
        
if (
            
'MemberExpression' === $callee->getType() &&
            
'Identifier' === $callee->getProperty()->getType()
        ) {
            
// Make sure to unpack wp.i18n which is a nested MemberExpression.
            
$comments 'MemberExpression' === $callee->getObject()->getType()
                ? 
$callee->getObject()->getObject()->getLeadingComments()
                : 
$callee->getObject()->getLeadingComments();

            return [
                
'name'     => $callee->getProperty()->getName(),
                
'comments' => $comments,
            ];
        }

        
// If the callee is a call expression as created by Webpack resolve it.
        // For example: Object(u.__)( "translation" ).
        
if (
            
'CallExpression' === $callee->getType() &&
            
'Identifier' === $callee->getCallee()->getType() &&
            
'Object' === $callee->getCallee()->getName() &&
            [] !== 
$callee->getArguments() &&
            
'MemberExpression' === $callee->getArguments()[0]->getType()
        ) {
            
$property $callee->getArguments()[0]->getProperty();

            
// Matches minified webpack statements: Object(u.__)( "translation" ).
            
if ( 'Identifier' === $property->getType() ) {
                return [
                    
'name'     => $property->getName(),
                    
'comments' => $callee->getCallee()->getLeadingComments(),
                ];
            }

            
// Matches unminified webpack statements:
            // Object(_wordpress_i18n__WEBPACK_IMPORTED_MODULE_7__["__"])( "translation" );
            
if ( 'Literal' === $property->getType() ) {
                
$name $property->getValue();

                
// Matches mangled webpack statement:
                // Object(_wordpress_i18n__WEBPACK_IMPORTED_MODULE_7__[/* __ */ "a"])( "translation" );
                
$leading_property_comments $property->getLeadingComments();
                if ( 
count$leading_property_comments ) === && $leading_property_comments[0]->getKind() === 'multiline' ) {
                    
$name trim$leading_property_comments[0]->getText() );
                }

                return [
                    
'name'     => $name,
                    
'comments' => $callee->getCallee()->getLeadingComments(),
                ];
            }
        }

        
// If the callee is an indirect function call as created by babel, resolve it.
        // For example: `(0, u.__)( "translation" )`.
        
if (
            
'ParenthesizedExpression' === $callee->getType()
            && 
'SequenceExpression' === $callee->getExpression()->getType()
            && 
=== count$callee->getExpression()->getExpressions() )
            && 
'Literal' === $callee->getExpression()->getExpressions()[0]->getType()
            && [] !== 
$node->getArguments()
        ) {
            
// Matches any general indirect function call: `(0, __)( "translation" )`.
            
if ( 'Identifier' === $callee->getExpression()->getExpressions()[1]->getType() ) {
                return [
                    
'name'     => $callee->getExpression()->getExpressions()[1]->getName(),
                    
'comments' => $callee->getLeadingComments(),
                ];
            }

            
// Matches indirect function calls used by babel for module imports: `(0, _i18n.__)( "translation" )`.
            
if ( 'MemberExpression' === $callee->getExpression()->getExpressions()[1]->getType() ) {
                
$property $callee->getExpression()->getExpressions()[1]->getProperty();

                if ( 
'Identifier' === $property->getType() ) {
                    return [
                        
'name'     => $property->getName(),
                        
'comments' => $callee->getLeadingComments(),
                    ];
                }
            }
        }

        
// Unknown format.
        
return false;
    }

    
/**
     * Returns wether or not a comment precedes a node.
     * The comment must be before the node and on the same line or the one before.
     *
     * @param Node\Comment $comment The comment.
     * @param Node\Node    $node    The node.
     *
     * @return bool Whether or not the comment precedes the node.
     */
    
private function commentPrecedesNodeNode\Comment $commentNode\Node $node ) {
        
// Comments should be on the same or an earlier line than the translation.
        
if ( $node->getLocation()->getStart()->getLine() - $comment->getLocation()->getEnd()->getLine() > ) {
            return 
false;
        }

        
// Comments on the same line should be before the translation.
        
if (
            
$node->getLocation()->getStart()->getLine() === $comment->getLocation()->getEnd()->getLine() &&
            
$node->getLocation()->getStart()->getColumn() < $comment->getLocation()->getStart()->getColumn()
        ) {
            return 
false;
        }

        return 
true;
    }
}
<?
php

namespace WP_CLI\I18n;

use 
Exception;
use 
Gettext\Extractors\JsCode;
use 
Gettext\Translations;
use 
Peast\Syntax\Exception as PeastException;
use 
WP_CLI;

final class 
JsCodeExtractor extends JsCode {
    use 
IterableCodeExtractor;

    public static 
$options = [
        
'extractComments' => [ 'translators''Translators' ],
        
'constants'       => [],
        
'functions'       => [
            
'__'  => 'text_domain',
            
'_x'  => 'text_context_domain',
            
'_n'  => 'single_plural_number_domain',
            
'_nx' => 'single_plural_number_context_domain',
        ],
    ];

    protected static 
$functionsScannerClass 'WP_CLI\I18n\JsFunctionsScanner';

    
/**
     * @inheritdoc
     */
    
public static function fromString$stringTranslations $translations, array $options = [] ) {
        
WP_CLI::debug"Parsing file {$options['file']}"'make-pot' );

        try {
            static::
fromStringMultiple$string, [ $translations ], $options );
        } catch ( 
PeastException $exception ) {
            
WP_CLI::debug(
                
sprintf(
                    
'Could not parse file %1$s: %2$s (line %3$d, column %4$d)',
                    
$options['file'],
                    
$exception->getMessage(),
                    
$exception->getPosition()->getLine(),
                    
$exception->getPosition()->getColumn()
                ),
                
'make-pot'
            
);
        } catch ( 
Exception $exception ) {
            
WP_CLI::debug(
                
sprintf(
                    
'Could not parse file %1$s: %2$s',
                    
$options['file'],
                    
$exception->getMessage()
                ),
                
'make-pot'
            
);
        }
    }

    
/**
     * @inheritDoc
     */
    
public static function fromStringMultiple$string, array $translations, array $options = [] ) {
        
$options += static::$options;

        
/** @var JsFunctionsScanner $functions */
        
$functions = new static::$functionsScannerClass$string );
        
$functions->enableCommentsExtraction$options['extractComments'] );
        
$functions->saveGettextFunctions$translations$options );
    }
}
<?
php

namespace WP_CLI\I18n;

use 
WP_CLI\Dispatcher\CommandNamespace as BaseCommandNamespace;

/**
 * Provides internationalization tools for WordPress projects.
 *
 * ## EXAMPLES
 *
 *     # Create a POT file for the WordPress plugin/theme in the current directory
 *     $ wp i18n make-pot . languages/my-plugin.pot
 *
 * @when before_wp_load
 */
class CommandNamespace extends BaseCommandNamespace {
}
<?
php

namespace WP_CLI\I18n;

use 
Gettext\Utils\PhpFunctionsScanner as GettextPhpFunctionsScanner;

class 
PhpFunctionsScanner extends GettextPhpFunctionsScanner {

    
/**
     * {@inheritdoc}
     */
    
public function saveGettextFunctions$translations, array $options ) {
        
// Ignore multiple translations for now.
        // @todo Add proper support for multiple translations.
        
if ( is_array$translations ) ) {
            
$translations $translations[0];
        }

        
$functions     $options['functions'];
        
$file          $options['file'];
        
$add_reference = ! empty( $options['addReferences'] );

        foreach ( 
$this->getFunctions$options['constants'] ) as $function ) {
            list( 
$name$line$args ) = $function;

            if ( ! isset( 
$functions$name ] ) ) {
                continue;
            }

            
$original null;
            
$domain   null;
            
$context  null;
            
$plural   null;

            switch ( 
$functions$name ] ) {
                case 
'text_domain':
                case 
'gettext':
                    list( 
$original$domain ) = array_pad$args2null );
                    break;

                case 
'text_context_domain':
                    list( 
$original$context$domain ) = array_pad$args3null );
                    break;

                case 
'single_plural_number_domain':
                    list( 
$original$plural$number$domain ) = array_pad$args4null );
                    break;

                case 
'single_plural_number_context_domain':
                    list( 
$original$plural$number$context$domain ) = array_pad$args5null );
                    break;

                case 
'single_plural_domain':
                    list( 
$original$plural$domain ) = array_pad$args3null );
                    break;

                case 
'single_plural_context_domain':
                    list( 
$original$plural$context$domain ) = array_pad$args4null );
                    break;

                default:
                    
// Should never happen.
                    
\WP_CLI::errorsprintf"Internal error: unknown function map '%s' for '%s'."$functions$name ], $name ) );
            }

            if ( 
'' === (string) $original ) {
                continue;
            }

            if ( 
$domain !== $translations->getDomain() && null !== $translations->getDomain() ) {
                continue;
            }

            
$translation $translations->insert$context$original$plural );
            if ( 
$add_reference ) {
                
$translation $translation->addReference$file$line );
            }

            if ( isset( 
$function[3] ) ) {
                foreach ( 
$function[3] as $extracted_comment ) {
                    
$translation $translation->addExtractedComment$extracted_comment );
                }
            }
        }
    }
}
<?
php

namespace WP_CLI\I18n;

use 
Gettext\Extractors\Extractor;
use 
Gettext\Extractors\ExtractorInterface;
use 
Gettext\Translations;
use 
WP_CLI;
use 
WP_CLI\Utils;

final class 
ThemeJsonExtractor extends Extractor implements ExtractorInterface {
    use 
IterableCodeExtractor;

    
/**
     * Source URL from which to download the latest theme-i18n.json file.
     *
     * @var string
     */
    
const THEME_JSON_SOURCE 'https://develop.svn.wordpress.org/trunk/src/wp-includes/theme-i18n.json';

    
/**
     * @inheritdoc
     */
    
public static function fromString$stringTranslations $translations, array $options = [] ) {
        
$file $options['file'];
        
WP_CLI::debug"Parsing file {$file}"'make-pot' );

        
$theme_json json_decode$stringtrue );

        if ( 
null === $theme_json ) {
            
WP_CLI::debug(
                
sprintf(
                    
'Could not parse file %1$s: error code %2$s',
                    
$file,
                    
json_last_error()
                ),
                
'make-pot'
            
);

            return;
        }

        
$fields self::get_fields_to_translate();
        foreach ( 
$fields as $field ) {
            
$path    $field['path'];
            
$key     $field['key'];
            
$context $field['context'];

            
/*
             * We need to process the paths that include '*' separately.
             * One example of such a path would be:
             * [ 'settings', 'blocks', '*', 'color', 'palette' ]
             */
            
$nodes_to_iterate array_keys$path'*'true );
            if ( ! empty( 
$nodes_to_iterate ) ) {
                
/*
                 * At the moment, we only need to support one '*' in the path, so take it directly.
                 * - base will be [ 'settings', 'blocks' ]
                 * - data will be [ 'color', 'palette' ]
                 */
                
$base_path array_slice$path0$nodes_to_iterate[0] );
                
$data_path array_slice$path$nodes_to_iterate[0] + );
                
$base_tree self::array_get$theme_json$base_path, [] );
                foreach ( 
$base_tree as $node_data ) {
                    
$array_to_translate self::array_get$node_data$data_path );
                    if ( 
null === $array_to_translate ) {
                        continue;
                    }

                    foreach ( 
$array_to_translate as $item_to_translate ) {
                        if ( empty( 
$item_to_translate$key ] ) ) {
                            continue;
                        }

                        
$translation $translations->insert$context$item_to_translate$key ] );
                        
$translation->addReference$file );
                    }
                }
            } else {
                
$array_to_translate self::array_get$theme_json$path );
                if ( 
null === $array_to_translate ) {
                    continue;
                }

                foreach ( 
$array_to_translate as $item_to_translate ) {
                    if ( empty( 
$item_to_translate$key ] ) ) {
                        continue;
                    }

                    
$translation $translations->insert$context$item_to_translate$key ] );
                    
$translation->addReference$file );
                }
            }
        }
    }

    
/**
     * Given a remote URL, fetches it remotely and returns its content.
     *
     * Returns an empty string in case of error.
     *
     * @param string $url URL of the file to fetch.
     *
     * @return string Contents of the file.
     */
    
private static function remote_get$url ) {
        if ( ! 
$url ) {
            return 
'';
        }

        
$headers  = [ 'Content-type: application/json' ];
        
$options  = [ 'halt_on_error' => false ];
        
$response Utils\http_request'GET'$urlnull$headers$options );

        if (
            ! 
$response->success
            
|| 200 > (int) $response->status_code
            
|| 300 <= $response->status_code
        
) {
            
WP_CLI::debug"Failed to download from URL {$url}"'make-pot' );
            return 
'';
        }

        return 
trim$response->body );
    }

    
/**
     * Returns a data structure to help setting up translations for theme.json data.
     *
     * [
     *     [
     *         'path'    => [ 'settings', 'color', 'palette' ],
     *         'key'     => 'key-that-stores-the-string-to-translate',
     *         'context' => 'translation-context',
     *     ],
     *     [
     *         'path'    => 'etc',
     *         'key'     => 'etc',
     *         'context' => 'etc',
     *     ],
     * ]
     *
     * Ported from the core class `WP_Theme_JSON_Resolver`.
     *
     * @return array An array of theme.json fields that are translatable and the keys that are translatable.
     */
    
private static function get_fields_to_translate() {
        
$json self::remote_getself::THEME_JSON_SOURCE );

        if ( empty( 
$json ) ) {
            
WP_CLI::debug'Remote file could not be accessed, will use local file as fallback''make-pot' );
            
$json file_get_contents__DIR__ '/../assets/theme-i18n.json' );
        }

        
$file_structure json_decode$jsontrue );

        if ( 
JSON_ERROR_NONE !== json_last_error() ) {
            
WP_CLI::debug'Error when decoding theme-i18n.json file''make-pot' );
            return [];
        }

        if ( ! 
is_array$file_structure ) ) {
            return [];
        }

        return 
self::extract_paths_to_translate$file_structure );
    }

    
/**
     * Converts a tree as in theme-i18.json file into a linear array
     * containing metadata to translate a theme.json file.
     *
     * For example, given this input:
     *
     *     {
     *       "settings": {
     *         "*": {
     *           "typography": {
     *             "fontSizes": [ { "name": "Font size name" } ],
     *             "fontStyles": [ { "name": "Font size name" } ]
     *           }
     *         }
     *       }
     *     }
     *
     * will return this output:
     *
     *     [
     *       0 => [
     *         'path'    => [ 'settings', '*', 'typography', 'fontSizes' ],
     *         'key'     => 'name',
     *         'context' => 'Font size name'
     *       ],
     *       1 => [
     *         'path'    => [ 'settings', '*', 'typography', 'fontStyles' ],
     *         'key'     => 'name',
     *         'context' => 'Font style name'
     *       ]
     *     ]
     *
     * Ported from the core class `WP_Theme_JSON_Resolver`.
     *
     * @param array $i18n_partial A tree that follows the format of theme-i18n.json.
     * @param array $current_path Keeps track of the path as we walk down the given tree.
     * @return array A linear array containing the paths to translate.
     */
    
private static function extract_paths_to_translate$i18n_partial$current_path = [] ) {
        
$result = [];
        foreach ( 
$i18n_partial as $property => $partial_child ) {
            if ( 
is_numeric$property ) ) {
                foreach ( 
$partial_child as $key => $context ) {
                    
$result[] = [
                        
'path'    => $current_path,
                        
'key'     => $key,
                        
'context' => $context,
                    ];
                }
                return 
$result;
            }
            
$result array_merge(
                
$result,
                
self::extract_paths_to_translate$partial_childarray_merge$current_path, [ $property ] ) )
            );
        }
        return 
$result;
    }

    
/**
     * Accesses an array in depth based on a path of keys.
     *
     * It is the PHP equivalent of JavaScript's `lodash.get()` and mirroring it may help other components
     * retain some symmetry between client and server implementations.
     *
     * Example usage:
     *
     *     $array = [
     *         'a' => [
     *             'b' => [
     *                 'c' => 1,
     *             ],
     *         ],
     *     ];
     *     array_get( $array, [ 'a', 'b', 'c' ] );
     *
     * @param array $array   An array from which we want to retrieve some information.
     * @param array $path    An array of keys describing the path with which to retrieve information.
     * @param mixed $default The return value if the path does not exist within the array,
     *                       or if `$array` or `$path` are not arrays.
     * @return mixed The value from the path specified.
     */
    
private static function array_get$array$path$default null ) {
        
// Confirm $path is valid.
        
if ( ! is_array$path ) || === count$path ) ) {
            return 
$default;
        }

        foreach ( 
$path as $path_element ) {
            if (
                ! 
is_array$array ) ||
                ( ! 
is_string$path_element ) && ! is_integer$path_element ) && null !== $path_element ) ||
                ! 
array_key_exists$path_element$array )
            ) {
                return 
$default;
            }
            
$array $array$path_element ];
        }

        return 
$array;
    }

}
<?
php

namespace WP_CLI\I18n;

use 
Gettext\Extractors\Po;
use 
Gettext\Merge;
use 
Gettext\Translation;
use 
Gettext\Translations;
use 
Gettext\Utils\ParsedComment;
use 
WP_CLI;
use 
WP_CLI_Command;
use 
WP_CLI\Utils;
use 
DirectoryIterator;
use 
IteratorIterator;

class 
MakePotCommand extends WP_CLI_Command {
    
/**
     * @var string
     */
    
protected $source;

    
/**
     * @var string
     */
    
protected $destination;

    
/**
     * @var array
     */
    
protected $merge = [];

    
/**
     * @var Translations[]
     */
    
protected $exceptions = [];

    
/**
     * @var bool
     */
    
protected $subtract_and_merge;

    
/**
     * @var array
     */
    
protected $include = [];

    
/**
     * @var array
     */
    
protected $exclude = [ 'node_modules''.git''.svn''.CVS''.hg''vendor''Gruntfile.js''webpack.config.js''*.min.js' ];

    
/**
     * @var string
     */
    
protected $slug;

    
/**
     * @var array
     */
    
protected $main_file_data = [];

    
/**
     * @var bool
     */
    
protected $skip_js false;

    
/**
     * @var bool
     */
    
protected $skip_php false;

    
/**
     * @var bool
     */
    
protected $skip_block_json false;

    
/**
     * @var bool
     */
    
protected $skip_theme_json false;

    
/**
     * @var bool
     */
    
protected $skip_audit false;

    
/**
     * @var bool
     */
    
protected $location true;

    
/**
     * @var array
     */
    
protected $headers = [];

    
/**
     * @var string
     */
    
protected $domain;

    
/**
     * @var string
     */
    
protected $package_name;

    
/**
     * @var string
     */
    
protected $file_comment;

    
/**
     * @var string
     */
    
protected $project_type 'generic';

    
/**
     * These Regexes copied from http://php.net/manual/en/function.sprintf.php#93552
     * and adjusted for better precision and updated specs.
     */
    
const SPRINTF_PLACEHOLDER_REGEX '/(?:
        (?<!%)                     # Don\'t match a literal % (%%).
        (
            %                          # Start of placeholder.
            (?:[0-9]+\$)?              # Optional ordering of the placeholders.
            [+-]?                      # Optional sign specifier.
            (?:
                (?:0|\'.)?                 # Optional padding specifier - excluding the space.
                -?                         # Optional alignment specifier.
                [0-9]*                     # Optional width specifier.
                (?:\.(?:[ 0]|\'.)?[0-9]+)? # Optional precision specifier with optional padding character.
                |                      # Only recognize the space as padding in combination with a width specifier.
                (?:[ ])?                   # Optional space padding specifier.
                -?                         # Optional alignment specifier.
                [0-9]+                     # Width specifier.
                (?:\.(?:[ 0]|\'.)?[0-9]+)? # Optional precision specifier with optional padding character.
            )
            [bcdeEfFgGosuxX]           # Type specifier.
        )
    )/x'
;

    
/**
     * "Unordered" means there's no position specifier: '%s', not '%2$s'.
     */
    
const UNORDERED_SPRINTF_PLACEHOLDER_REGEX '/(?:
        (?<!%)                     # Don\'t match a literal % (%%).
        %                          # Start of placeholder.
        [+-]?                      # Optional sign specifier.
        (?:
            (?:0|\'.)?                 # Optional padding specifier - excluding the space.
            -?                         # Optional alignment specifier.
            [0-9]*                     # Optional width specifier.
            (?:\.(?:[ 0]|\'.)?[0-9]+)? # Optional precision specifier with optional padding character.
            |                      # Only recognize the space as padding in combination with a width specifier.
            (?:[ ])?                   # Optional space padding specifier.
            -?                         # Optional alignment specifier.
            [0-9]+                     # Width specifier.
            (?:\.(?:[ 0]|\'.)?[0-9]+)? # Optional precision specifier with optional padding character.
        )
        [bcdeEfFgGosuxX]           # Type specifier.
    )/x'
;

    
/**
     * Create a POT file for a WordPress project.
     *
     * Scans PHP and JavaScript files for translatable strings, as well as theme stylesheets and plugin files
     * if the source directory is detected as either a plugin or theme.
     *
     * ## OPTIONS
     *
     * <source>
     * : Directory to scan for string extraction.
     *
     * [<destination>]
     * : Name of the resulting POT file.
     *
     * [--slug=<slug>]
     * : Plugin or theme slug. Defaults to the source directory's basename.
     *
     * [--domain=<domain>]
     * : Text domain to look for in the source code, unless the `--ignore-domain` option is used.
     * By default, the "Text Domain" header of the plugin or theme is used.
     * If none is provided, it falls back to the project slug.
     *
     * [--ignore-domain]
     * : Ignore the text domain completely and extract strings with any text domain.
     *
     * [--merge[=<paths>]]
     * : Comma-separated list of POT files whose contents should be merged with the extracted strings.
     * If left empty, defaults to the destination POT file. POT file headers will be ignored.
     *
     * [--subtract=<paths>]
     * : Comma-separated list of POT files whose contents should act as some sort of denylist for string extraction.
     * Any string which is found on that denylist will not be extracted.
     * This can be useful when you want to create multiple POT files from the same source directory with slightly
     * different content and no duplicate strings between them.
     *
     * [--subtract-and-merge]
     * : Whether source code references and comments from the generated POT file should be instead added to the POT file
     * used for subtraction. Warning: this modifies the files passed to `--subtract`!
     *
     * [--include=<paths>]
     * : Comma-separated list of files and paths that should be used for string extraction.
     * If provided, only these files and folders will be taken into account for string extraction.
     * For example, `--include="src,my-file.php` will ignore anything besides `my-file.php` and files in the `src`
     * directory. Simple glob patterns can be used, i.e. `--include=foo-*.php` includes any PHP file with the `foo-`
     * prefix. Leading and trailing slashes are ignored, i.e. `/my/directory/` is the same as `my/directory`.
     *
     * [--exclude=<paths>]
     * : Comma-separated list of files and paths that should be skipped for string extraction.
     * For example, `--exclude=".github,myfile.php` would ignore any strings found within `myfile.php` or the `.github`
     * folder. Simple glob patterns can be used, i.e. `--exclude=foo-*.php` excludes any PHP file with the `foo-`
     * prefix. Leading and trailing slashes are ignored, i.e. `/my/directory/` is the same as `my/directory`. The
     * following files and folders are always excluded: node_modules, .git, .svn, .CVS, .hg, vendor, *.min.js.
     *
     * [--headers=<headers>]
     * : Array in JSON format of custom headers which will be added to the POT file. Defaults to empty array.
     *
     * [--location]
     * : Whether to write `#: filename:line` lines.
     * Defaults to true, use `--no-location` to skip the removal.
     * Note that disabling this option makes it harder for technically skilled translators to understand each message’s context.
     *
     * [--skip-js]
     * : Skips JavaScript string extraction. Useful when this is done in another build step, e.g. through Babel.
     *
     * [--skip-php]
     * : Skips PHP string extraction.
     *
     * [--skip-block-json]
     * : Skips string extraction from block.json files.
     *
     * [--skip-theme-json]
     * : Skips string extraction from theme.json files.
     *
     * [--skip-audit]
     * : Skips string audit where it tries to find possible mistakes in translatable strings. Useful when running in an
     * automated environment.
     *
     * [--file-comment=<file-comment>]
     * : String that should be added as a comment to the top of the resulting POT file.
     * By default, a copyright comment is added for WordPress plugins and themes in the following manner:
     *
     *      ```
     *      Copyright (C) 2018 Example Plugin Author
     *      This file is distributed under the same license as the Example Plugin package.
     *      ```
     *
     *      If a plugin or theme specifies a license in their main plugin file or stylesheet, the comment looks like
     *      this:
     *
     *      ```
     *      Copyright (C) 2018 Example Plugin Author
     *      This file is distributed under the GPLv2.
     *      ```
     *
     * [--package-name=<name>]
     * : Name to use for package name in the resulting POT file's `Project-Id-Version` header.
     * Overrides plugin or theme name, if applicable.
     *
     * ## EXAMPLES
     *
     *     # Create a POT file for the WordPress plugin/theme in the current directory
     *     $ wp i18n make-pot . languages/my-plugin.pot
     *
     *     # Create a POT file for the continents and cities list in WordPress core.
     *     $ wp i18n make-pot . continents-and-cities.pot --include="wp-admin/includes/continents-cities.php"
     *     --ignore-domain
     *
     * @when before_wp_load
     *
     * @throws WP_CLI\ExitException
     */
    
public function __invoke$args$assoc_args ) {
        
$this->handle_arguments$args$assoc_args );

        
$translations $this->extract_strings();

        if ( ! 
$translations ) {
            
WP_CLI::warning'No strings found' );
        }

        
$translations_count count$translations );

        if ( 
=== $translations_count ) {
            
WP_CLI::debugsprintf'Extracted %d string'$translations_count ), 'make-pot' );
        } else {
            
WP_CLI::debugsprintf'Extracted %d strings'$translations_count ), 'make-pot' );
        }

        if ( ! 
PotGenerator::toFile$translations$this->destination ) ) {
            
WP_CLI::error'Could not generate a POT file!' );
        }

        
WP_CLI::success'POT file successfully generated!' );
    }

    
/**
     * Process arguments from command-line in a reusable way.
     *
     * @throws WP_CLI\ExitException
     *
     * @param array $args       Command arguments.
     * @param array $assoc_args Associative arguments.
     */
    
public function handle_arguments$args$assoc_args ) {
        
$array_arguments = array( 'headers' );
        
$assoc_args      Utils\parse_shell_arrays$assoc_args$array_arguments );

        
$this->source          realpath$args[0] );
        
$this->slug            Utils\get_flag_value$assoc_args'slug'Utils\basename$this->source ) );
        
$this->skip_js         Utils\get_flag_value$assoc_args'skip-js'$this->skip_js );
        
$this->skip_php        Utils\get_flag_value$assoc_args'skip-php'$this->skip_php );
        
$this->skip_block_json Utils\get_flag_value$assoc_args'skip-block-json'$this->skip_block_json );
        
$this->skip_theme_json Utils\get_flag_value$assoc_args'skip-theme-json'$this->skip_theme_json );
        
$this->skip_audit      Utils\get_flag_value$assoc_args'skip-audit'$this->skip_audit );
        
$this->headers         Utils\get_flag_value$assoc_args'headers'$this->headers );
        
$this->file_comment    Utils\get_flag_value$assoc_args'file-comment' );
        
$this->package_name    Utils\get_flag_value$assoc_args'package-name' );
        
$this->location        Utils\get_flag_value$assoc_args'location'true );

        
$ignore_domain Utils\get_flag_value$assoc_args'ignore-domain'false );

        if ( ! 
$this->source || ! is_dir$this->source ) ) {
            
WP_CLI::error'Not a valid source directory!' );
        }

        
$this->main_file_data $this->get_main_file_data();

        if ( 
$ignore_domain ) {
            
WP_CLI::debug'Extracting all strings regardless of text domain''make-pot' );
        }

        if ( ! 
$ignore_domain ) {
            
$this->domain $this->slug;

            if ( ! empty( 
$this->main_file_data['Text Domain'] ) ) {
                
$this->domain $this->main_file_data['Text Domain'];
            }

            
$this->domain Utils\get_flag_value$assoc_args'domain'$this->domain );

            
WP_CLI::debugsprintf'Extracting all strings with text domain "%s"'$this->domain ), 'make-pot' );
        }

        
// Determine destination.
        
$this->destination "{$this->source}/{$this->slug}.pot";

        if ( ! empty( 
$this->main_file_data['Domain Path'] ) ) {
            
// Domain Path inside source folder.
            
$this->destination sprintf(
                
'%s/%s/%s.pot',
                
$this->source,
                
$this->unslashit$this->main_file_data['Domain Path'] ),
                
$this->slug
            
);
        }

        if ( isset( 
$args[1] ) ) {
            
$this->destination $args[1];
        }

        
WP_CLI::debugsprintf'Destination: %s'$this->destination ), 'make-pot' );

        
// Two is_dir() checks in case of a race condition.
        
if ( ! is_dirdirname$this->destination ) )
            && ! 
mkdirdirname$this->destination ), 0777true )
            && ! 
is_dirdirname$this->destination ) )
        ) {
            
WP_CLI::error'Could not create destination directory!' );
        }

        if ( isset( 
$assoc_args['merge'] ) ) {
            if ( 
true === $assoc_args['merge'] ) {
                
$this->merge = [ $this->destination ];
            } elseif ( ! empty( 
$assoc_args['merge'] ) ) {
                
$this->merge explode','$assoc_args['merge'] );
            }

            
$this->merge array_filter(
                
$this->merge,
                function ( 
$file ) {
                    if ( ! 
file_exists$file ) ) {
                        
WP_CLI::warningsprintf'Invalid file provided to --merge: %s'$file ) );

                        return 
false;
                    }

                    return 
true;
                }
            );

            if ( ! empty( 
$this->merge ) ) {
                
WP_CLI::debug(
                    
sprintf(
                        
'Merging with existing POT %s: %s',
                        
WP_CLI\Utils\pluralize'file'count$this->merge ) ),
                        
implode','$this->merge )
                    ),
                    
'make-pot'
                
);
            }
        }

        if ( isset( 
$assoc_args['subtract'] ) ) {
            
$this->subtract_and_merge Utils\get_flag_value$assoc_args'subtract-and-merge'false );

            
$files explode','$assoc_args['subtract'] );

            foreach ( 
$files as $file ) {
                if ( ! 
file_exists$file ) ) {
                    
WP_CLI::warningsprintf'Invalid file provided to --subtract: %s'$file ) );
                    continue;
                }

                
WP_CLI::debugsprintf'Ignoring any string already existing in: %s'$file ), 'make-pot' );

                
$this->exceptions$file ] = new Translations();
                
Po::fromFile$file$this->exceptions$file ] );
            }
        }

        if ( isset( 
$assoc_args['include'] ) ) {
            
$this->include array_filterexplode','$assoc_args['include'] ) );
            
$this->include array_map( [ $this'unslashit' ], $this->include );
            
$this->include array_unique$this->include );

            
WP_CLI::debugsprintf'Only including the following files: %s'implode','$this->include ) ), 'make-pot' );
        }

        if ( isset( 
$assoc_args['exclude'] ) ) {
            
$this->exclude array_filterarray_merge$this->excludeexplode','$assoc_args['exclude'] ) ) );
            
$this->exclude array_map( [ $this'unslashit' ], $this->exclude );
            
$this->exclude array_unique$this->exclude );
        }

        
WP_CLI::debugsprintf'Excluding the following files: %s'implode','$this->exclude ) ), 'make-pot' );
    }

    
/**
     * Removes leading and trailing slashes of a string.
     *
     * @param string $string What to add and remove slashes from.
     * @return string String without leading and trailing slashes.
     */
    
protected function unslashit$string ) {
        return 
ltrimrtrimtrim$string ), '/\\' ), '/\\' );
    }

    
/**
     * Retrieves the main file data of the plugin or theme.
     *
     * @return array
     */
    
protected function get_main_file_data() {
        
$files = new IteratorIterator( new DirectoryIterator$this->source ) );

        
/** @var DirectoryIterator $file */
        
foreach ( $files as $file ) {
            
// wp-content/themes/my-theme/style.css
            
if ( $file->isFile() && 'style' === $file->getBasename'.css' ) && $file->isReadable() ) {
                
$theme_data = static::get_file_data$file->getRealPath(), array_combine$this->get_file_headers'theme' ), $this->get_file_headers'theme' ) ) );

                
// Stop when it contains a valid Theme Name header.
                
if ( ! empty( $theme_data['Theme Name'] ) ) {
                    
WP_CLI::log'Theme stylesheet detected.' );
                    
WP_CLI::debugsprintf'Theme stylesheet: %s'$file->getRealPath() ), 'make-pot' );

                    
$this->project_type 'theme';

                    return 
$theme_data;
                }
            }

            
// wp-content/themes/my-themes/theme-a/style.css
            
if ( $file->isDir() && ! $file->isDot() && is_readable$file->getRealPath() . '/style.css' ) ) {
                
$theme_data = static::get_file_data$file->getRealPath() . '/style.css'array_combine$this->get_file_headers'theme' ), $this->get_file_headers'theme' ) ) );

                
// Stop when it contains a valid Theme Name header.
                
if ( ! empty( $theme_data['Theme Name'] ) ) {
                    
WP_CLI::log'Theme stylesheet detected.' );
                    
WP_CLI::debugsprintf'Theme stylesheet: %s'$file->getRealPath() . '/style.css' ), 'make-pot' );

                    
$this->project_type 'theme';

                    return 
$theme_data;
                }
            }

            
// wp-content/plugins/my-plugin/my-plugin.php
            
if ( $file->isFile() && $file->isReadable() && 'php' === $file->getExtension() ) {
                
$plugin_data = static::get_file_data$file->getRealPath(), array_combine$this->get_file_headers'plugin' ), $this->get_file_headers'plugin' ) ) );

                
// Stop when we find a file with a valid Plugin Name header.
                
if ( ! empty( $plugin_data['Plugin Name'] ) ) {
                    
WP_CLI::log'Plugin file detected.' );
                    
WP_CLI::debugsprintf'Plugin file: %s'$file->getRealPath() ), 'make-pot' );

                    
$this->project_type 'plugin';

                    return 
$plugin_data;
                }
            }
        }

        
WP_CLI::debug'No valid theme stylesheet or plugin file found, treating as a regular project.''make-pot' );

        return [];
    }

    
/**
     * Returns the file headers for themes and plugins.
     *
     * @param string $type Source type, either theme or plugin.
     *
     * @return array List of file headers.
     */
    
protected function get_file_headers$type ) {
        switch ( 
$type ) {
            case 
'plugin':
                return [
                    
'Plugin Name',
                    
'Plugin URI',
                    
'Description',
                    
'Author',
                    
'Author URI',
                    
'Version',
                    
'License',
                    
'Domain Path',
                    
'Text Domain',
                ];
            case 
'theme':
                return [
                    
'Theme Name',
                    
'Theme URI',
                    
'Description',
                    
'Author',
                    
'Author URI',
                    
'Version',
                    
'License',
                    
'Domain Path',
                    
'Text Domain',
                ];
            default:
                return [];
        }
    }

    
/**
     * Creates a POT file and stores it on disk.
     *
     * @throws WP_CLI\ExitException
     *
     * @return Translations A Translation set.
     */
    
protected function extract_strings() {
        
$translations = new Translations();

        
// Add existing strings first but don't keep headers.
        
if ( ! empty( $this->merge ) ) {
            
$existing_translations = new Translations();
            
Po::fromFile$this->merge$existing_translations );
            
$translations->mergeWith$existing_translationsMerge::ADD Merge::REMOVE );
        }

        
PotGenerator::setCommentBeforeHeaders$this->get_file_comment() );

        
$this->set_default_headers$translations );

        
// POT files have no Language header.
        
$translations->deleteHeaderTranslations::HEADER_LANGUAGE );

        
// Only relevant for PO files, not POT files.
        
$translations->setHeader'PO-Revision-Date''YEAR-MO-DA HO:MI+ZONE' );

        if ( 
$this->domain ) {
            
$translations->setDomain$this->domain );
        }

        unset( 
$this->main_file_data['Version'], $this->main_file_data['License'], $this->main_file_data['Domain Path'], $this->main_file_data['Text Domain'] );

        
// Set entries from main file data.
        
foreach ( $this->main_file_data as $header => $data ) {
            if ( empty( 
$data ) ) {
                continue;
            }

            
$translation = new Translation''$data );

            if ( isset( 
$this->main_file_data['Theme Name'] ) ) {
                
$translation->addExtractedCommentsprintf'%s of the theme'$header ) );
            } else {
                
$translation->addExtractedCommentsprintf'%s of the plugin'$header ) );
            }

            
$translations[] = $translation;
        }

        try {
            if ( ! 
$this->skip_php ) {
                
$options = [
                    
// Extract 'Template Name' headers in theme files.
                    
'wpExtractTemplates' => isset( $this->main_file_data['Theme Name'] ),
                    
'include'            => $this->include,
                    
'exclude'            => $this->exclude,
                    
'extensions'         => [ 'php' ],
                    
'addReferences'      => $this->location,
                ];
                
PhpCodeExtractor::fromDirectory$this->source$translations$options );
            }

            if ( ! 
$this->skip_js ) {
                
JsCodeExtractor::fromDirectory(
                    
$this->source,
                    
$translations,
                    [
                        
'include'       => $this->include,
                        
'exclude'       => $this->exclude,
                        
'extensions'    => [ 'js''jsx' ],
                        
'addReferences' => $this->location,
                    ]
                );

                
MapCodeExtractor::fromDirectory(
                    
$this->source,
                    
$translations,
                    [
                        
'include'       => $this->include,
                        
'exclude'       => $this->exclude,
                        
'extensions'    => [ 'map' ],
                        
'addReferences' => $this->location,
                    ]
                );
            }

            if ( ! 
$this->skip_block_json ) {
                
BlockExtractor::fromDirectory(
                    
$this->source,
                    
$translations,
                    [
                        
// Only look for block.json files, nothing else.
                        
'restrictFileNames' => [ 'block.json' ],
                        
'include'           => $this->include,
                        
'exclude'           => $this->exclude,
                        
'extensions'        => [ 'json' ],
                        
'addReferences'     => $this->location,
                    ]
                );
            }

            if ( ! 
$this->skip_theme_json ) {
                
ThemeJsonExtractor::fromDirectory(
                    
$this->source,
                    
$translations,
                    [
                        
// Only look for theme.json files, nothing else.
                        
'restrictFileNames' => [ 'theme.json' ],
                        
'include'           => $this->include,
                        
'exclude'           => $this->exclude,
                        
'extensions'        => [ 'json' ],
                    ]
                );
            }
        } catch ( 
\Exception $e ) {
            
WP_CLI::error$e->getMessage() );
        }

        foreach ( 
$this->exceptions as $file => $exception_translations ) {
            
/** @var Translation $exception_translation */
            
foreach ( $exception_translations as $exception_translation ) {
                if ( ! 
$translations->find$exception_translation ) ) {
                    continue;
                }

                if ( 
$this->subtract_and_merge ) {
                    
$translation $translations$exception_translation->getId() ];
                    
$exception_translation->mergeWith$translation );
                }

                unset( 
$translations$exception_translation->getId() ] );
            }

            if ( 
$this->subtract_and_merge ) {
                
PotGenerator::toFile$exception_translations$file );
            }
        }

        if ( ! 
$this->skip_audit ) {
            
$this->audit_strings$translations );
        }

        return 
$translations;
    }

    
/**
     * Audits strings.
     *
     * Goes through all extracted strings to find possible mistakes.
     *
     * @param Translations $translations Translations object.
     */
    
protected function audit_strings$translations ) {
        foreach ( 
$translations as $translation ) {
            
/** @var Translation $translation */

            
$references $translation->getReferences();

            
// File headers don't have any file references.
            
$location $translation->hasReferences() ? '(' implode':'array_shift$references ) ) . ')' '';

            
// Check 1: Flag strings with placeholders that should have translator comments.
            
if (
                ! 
$translation->hasExtractedComments() &&
                
preg_matchself::SPRINTF_PLACEHOLDER_REGEX$translation->getOriginal(), $placeholders ) >= 1
            
) {
                
$message sprintf(
                    
'The string "%1$s" contains placeholders but has no "translators:" comment to clarify their meaning. %2$s',
                    
$translation->getOriginal(),
                    
$location
                
);
                
WP_CLI::warning$message );
            }

            
// Check 2: Flag strings with different translator comments.
            
if ( $translation->hasExtractedComments() ) {
                
$comments $translation->getExtractedComments();

                
// Remove plugin header information from comments.
                
$comments array_filter(
                    
$comments,
                    function ( 
$comment ) {
                        
/** @var ParsedComment|string $comment */
                        /** @var string $file_header */
                        
foreach ( $this->get_file_headers$this->project_type ) as $file_header ) {
                            if ( 
=== strpos( ( $comment instanceof ParsedComment $comment->getComment() : $comment ), $file_header ) ) {
                                return 
null;
                            }
                        }

                        return 
$comment;
                    }
                );

                
$unique_comments = array();

                
// Remove duplicate comments.
                
$comments array_filter(
                    
$comments,
                    function ( 
$comment ) use ( &$unique_comments ) {
                        
/** @var ParsedComment|string $comment */
                        
if ( in_array( ( $comment instanceof ParsedComment $comment->getComment() : $comment ), $unique_commentstrue ) ) {
                            return 
null;
                        }

                        
$unique_comments[] = ( $comment instanceof ParsedComment $comment->getComment() : $comment );

                        return 
$comment;
                    }
                );

                
$comments_count count$comments );

                if ( 
$comments_count ) {
                    
$message sprintf(
                        
'The string "%1$s" has %2$d different translator comments. %3$s',
                        
$translation->getOriginal(),
                        
$comments_count,
                        
$location
                    
);
                    
WP_CLI::warning$message );
                }
            }

            
$non_placeholder_content trimpreg_replace'`^([\'"])(.*)\1$`Ds''$2'$translation->getOriginal() ) );
            
$non_placeholder_content preg_replaceself::SPRINTF_PLACEHOLDER_REGEX''$non_placeholder_content );

            
// Check 3: Flag empty strings without any translatable content.
            
if ( '' === $non_placeholder_content ) {
                
$message sprintf(
                    
'Found string without translatable content. %s',
                    
$location
                
);
                
WP_CLI::warning$message );
            }

            
// Check 4: Flag strings with multiple unordered placeholders (%s %s %s vs. %1$s %2$s %3$s).
            
$unordered_matches_count preg_match_allself::UNORDERED_SPRINTF_PLACEHOLDER_REGEX$translation->getOriginal(), $unordered_matches );
            
$unordered_matches       $unordered_matches[0];

            if ( 
$unordered_matches_count >= ) {
                
$message sprintf(
                    
'Multiple placeholders should be ordered. %s',
                    
$location
                
);
                
WP_CLI::warning$message );
            }

            if ( 
$translation->hasPlural() ) {
                
preg_match_allself::SPRINTF_PLACEHOLDER_REGEX$translation->getOriginal(), $single_placeholders );
                
$single_placeholders $single_placeholders[0];

                
preg_match_allself::SPRINTF_PLACEHOLDER_REGEX$translation->getPlural(), $plural_placeholders );
                
$plural_placeholders $plural_placeholders[0];

                
// see https://developer.wordpress.org/plugins/internationalization/how-to-internationalize-your-plugin/#plurals
                
if ( count$single_placeholders ) < count$plural_placeholders ) ) {
                    
// Check 5: Flag things like _n( 'One comment', '%s Comments' )
                    
$message sprintf(
                        
'Missing singular placeholder, needed for some languages. See https://developer.wordpress.org/plugins/internationalization/how-to-internationalize-your-plugin/#plurals %s',
                        
$location
                    
);
                    
WP_CLI::warning$message );
                } else {
                    
// Reordering is fine, but mismatched placeholders is probably wrong.
                    
sort$single_placeholders );
                    
sort$plural_placeholders );

                    
// Check 6: Flag things like _n( '%s Comment (%d)', '%s Comments (%s)' )
                    
if ( $single_placeholders !== $plural_placeholders ) {
                        
$message sprintf(
                            
'Mismatched placeholders for singular and plural string. %s',
                            
$location
                        
);
                        
WP_CLI::warning$message );
                    }
                }
            }
        }
    }

    
/**
     * Returns the copyright comment for the given package.
     *
     * @return string File comment.
     */
    
protected function get_file_comment() {
        if ( 
'' === $this->file_comment ) {
            return 
'';
        }

        if ( isset( 
$this->file_comment ) ) {
            return 
implode"\n"explode'\n'$this->file_comment ) );
        }

        if ( isset( 
$this->main_file_data['Theme Name'] ) ) {
            if ( isset( 
$this->main_file_data['License'] ) ) {
                return 
sprintf(
                    
"Copyright (C) %1\$s %2\$s\nThis file is distributed under the %3\$s.",
                    
date'Y' ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
                    
$this->main_file_data['Author'],
                    
$this->main_file_data['License']
                );
            }

            return 
sprintf(
                
"Copyright (C) %1\$s %2\$s\nThis file is distributed under the same license as the %3\$s theme.",
                
date'Y' ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
                
$this->main_file_data['Author'],
                
$this->main_file_data['Theme Name']
            );
        }

        if ( isset( 
$this->main_file_data['Plugin Name'] ) ) {
            if ( isset( 
$this->main_file_data['License'] ) && ! empty( $this->main_file_data['License'] ) ) {
                return 
sprintf(
                    
"Copyright (C) %1\$s %2\$s\nThis file is distributed under the %3\$s.",
                    
date'Y' ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
                    
$this->main_file_data['Author'],
                    
$this->main_file_data['License']
                );
            }

            return 
sprintf(
                
"Copyright (C) %1\$s %2\$s\nThis file is distributed under the same license as the %3\$s plugin.",
                
date'Y' ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
                
$this->main_file_data['Author'],
                
$this->main_file_data['Plugin Name']
            );
        }

        return 
'';
    }

    
/**
     * Sets default POT file headers for the project.
     *
     * @param Translations $translations Translations object.
     */
    
protected function set_default_headers$translations ) {
        
$name         null;
        
$version      $this->get_wp_version();
        
$bugs_address null;

        if ( ! 
$version && isset( $this->main_file_data['Version'] ) ) {
            
$version $this->main_file_data['Version'];
        }

        if ( isset( 
$this->main_file_data['Theme Name'] ) ) {
            
$name         $this->main_file_data['Theme Name'];
            
$bugs_address sprintf'https://wordpress.org/support/theme/%s'$this->slug );
        } elseif ( isset( 
$this->main_file_data['Plugin Name'] ) ) {
            
$name         $this->main_file_data['Plugin Name'];
            
$bugs_address sprintf'https://wordpress.org/support/plugin/%s'$this->slug );
        }

        if ( 
null !== $this->package_name ) {
            
$name $this->package_name;
        }

        if ( 
null !== $name ) {
            
$translations->setHeader'Project-Id-Version'$name . ( $version ' ' $version '' ) );
        }

        if ( 
null !== $bugs_address ) {
            
$translations->setHeader'Report-Msgid-Bugs-To'$bugs_address );
        }

        
$translations->setHeader'Last-Translator''FULL NAME <EMAIL@ADDRESS>' );
        
$translations->setHeader'Language-Team''LANGUAGE <[email protected]>' );
        
$translations->setHeader'X-Generator''WP-CLI ' WP_CLI_VERSION );

        foreach ( 
$this->headers as $key => $value ) {
            
$translations->setHeader$key$value );
        }
    }

    
/**
     * Extracts the WordPress version number from wp-includes/version.php.
     *
     * @return string|false Version number on success, false otherwise.
     */
    
protected function get_wp_version() {
        
$version_php $this->source '/wp-includes/version.php';
        if ( ! 
file_exists$version_php ) || ! is_readable$version_php ) ) {
            return 
false;
        }

        return 
preg_match'/\$wp_version\s*=\s*\'(.*?)\';/'file_get_contents$version_php ), $matches ) ? $matches[1] : false;
    }

    
/**
     * Retrieves metadata from a file.
     *
     * Searches for metadata in the first 8kiB of a file, such as a plugin or theme.
     * Each piece of metadata must be on its own line. Fields can not span multiple
     * lines, the value will get cut at the end of the first line.
     *
     * If the file data is not within that first 8kiB, then the author should correct
     * their plugin file and move the data headers to the top.
     *
     * @see get_file_data()
     *
     * @param string $file Path to the file.
     * @param array $headers List of headers, in the format array('HeaderKey' => 'Header Name').
     *
     * @return array Array of file headers in `HeaderKey => Header Value` format.
     */
    
protected static function get_file_data$file$headers ) {
        
// We don't need to write to the file, so just open for reading.
        
$fp fopen$file'rb' );

        
// Pull only the first 8kiB of the file in.
        
$file_data fread$fp8192 );

        
// PHP will close file handle, but we are good citizens.
        
fclose$fp );

        
// Make sure we catch CR-only line endings.
        
$file_data str_replace"\r""\n"$file_data );

        return static::
get_file_data_from_string$file_data$headers );
    }

    
/**
     * Retrieves metadata from a string.
     *
     * @param string $string String to look for metadata in.
     * @param array $headers List of headers.
     *
     * @return array Array of file headers in `HeaderKey => Header Value` format.
     */
    
public static function get_file_data_from_string$string$headers ) {
        foreach ( 
$headers as $field => $regex ) {
            if ( 
preg_match'/^[ \t\/*#@]*' preg_quote$regex'/' ) . ':(.*)$/mi'$string$match ) && $match[1] ) {
                
$headers$field ] = static::_cleanup_header_comment$match[1] );
            } else {
                
$headers$field ] = '';
            }
        }

        return 
$headers;
    }

    
/**
     * Strip close comment and close php tags from file headers used by WP.
     *
     * @see _cleanup_header_comment()
     *
     * @param string $str Header comment to clean up.
     *
     * @return string
     */
    
protected static function _cleanup_header_comment$str ) { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore -- Not changing because third-party commands might use/extend.
        
return trimpreg_replace'/\s*(?:\*\/|\?>).*/'''$str ) );
    }
}
<?
php

namespace WP_CLI\I18n;

use 
Gettext\Generators\Po as PoGenerator;
use 
Gettext\Translations;
use 
Gettext\Utils\ParsedComment;

/**
 * POT file generator.
 *
 * The only difference to the existing PO file generator is that this
 * adds some comments at the very beginning of the file.
 */
class PotGenerator extends PoGenerator {
    protected static 
$comments_before_headers = [];

    
/**
     * Text to include as a comment before the start of the PO contents
     *
     * Doesn't need to include # in the beginning of lines, these are added automatically.
     *
     * @param string $comment File comment.
     */
    
public static function setCommentBeforeHeaders$comment ) {
        
$comments explode"\n"$comment );

        foreach ( 
$comments as $line ) {
            if ( 
'' !== trim$line ) ) {
                static::
$comments_before_headers[] = '# ' $line;
            }
        }
    }

    
/**
     * {@parentDoc}.
     */
    
public static function toStringTranslations $translations, array $options = [] ) {
        
$lines   = static::$comments_before_headers;
        
$lines[] = 'msgid ""';
        
$lines[] = 'msgstr ""';

        
$plural_form $translations->getPluralForms();
        
$plural_size is_array$plural_form ) ? ( $plural_form[0] - ) : 1;

        foreach ( 
$translations->getHeaders() as $name => $value ) {
            
$lines[] = sprintf'"%s: %s\\n"'$name$value );
        }

        
$lines[] = '';

        foreach ( 
$translations as $translation ) {
            
/** @var \Gettext\Translation $translation */
            
if ( $translation->hasComments() ) {
                foreach ( 
$translation->getComments() as $comment ) {
                    
$lines[] = '# ' $comment;
                }
            }

            if ( 
$translation->hasExtractedComments() ) {
                
$unique_comments = array();

                
/** @var ParsedComment|string $comment */
                
foreach ( $translation->getExtractedComments() as $comment ) {
                    
$comment = ( $comment instanceof ParsedComment $comment->getComment() : $comment );
                    if ( ! 
in_array$comment$unique_commentstrue ) ) {
                        
$lines[]           = '#. ' $comment;
                        
$unique_comments[] = $comment;
                    }
                }
            }

            foreach ( 
$translation->getReferences() as $reference ) {
                
$lines[] = '#: ' $reference[0] . ( null !== $reference[1] ? ':' $reference[1] : '' );
            }

            if ( 
$translation->hasFlags() ) {
                
$lines[] = '#, ' implode','$translation->getFlags() );
            }

            
$prefix $translation->isDisabled() ? '#~ ' '';

            if ( 
$translation->hasContext() ) {
                
$lines[] = $prefix 'msgctxt ' self::convertString$translation->getContext() );
            }

            
self::addLines$lines$prefix 'msgid'$translation->getOriginal() );

            if ( 
$translation->hasPlural() ) {
                
self::addLines$lines$prefix 'msgid_plural'$translation->getPlural() );

                for ( 
$i 0$i <= $plural_size$i ++ ) {
                    
self::addLines$lines$prefix 'msgstr[' $i ']''' );
                }
            } else {
                
self::addLines$lines$prefix 'msgstr'$translation->getTranslation() );
            }

            
$lines[] = '';
        }

        return 
implode"\n"$lines );
    }

    
/**
     * Escapes and adds double quotes to a string.
     *
     * @param string $string Multiline string.
     *
     * @return string[]
     */
    
protected static function multilineQuote$string ) {
        
$lines explode"\n"$string );
        
$last  count$lines ) - 1;

        foreach ( 
$lines as $k => $line ) {
            if ( 
$k === $last ) {
                
$lines$k ] = self::convertString$line );
            } else {
                
$lines$k ] = self::convertString$line "\n" );
            }
        }

        return 
$lines;
    }

    
/**
     * Add one or more lines depending whether the string is multiline or not.
     *
     * @param array  &$lines Array lines should be added to.
     * @param string $name   Name of the line, e.g. msgstr or msgid_plural.
     * @param string $value  The line to add.
     */
    
protected static function addLines( array &$lines$name$value ) {
        
$newlines self::multilineQuote$value );

        if ( 
count$newlines ) === ) {
            
$lines[] = $name ' ' $newlines[0];
        } else {
            
$lines[] = $name ' ""';

            foreach ( 
$newlines as $line ) {
                
$lines[] = $line;
            }
        }
    }
}
<?
php

namespace WP_CLI\I18n;

use 
Gettext\Extractors\Po as PoExtractor;
use 
Gettext\Generators\Po as PoGenerator;
use 
Gettext\Translation;
use 
Gettext\Translations;
use 
WP_CLI;
use 
WP_CLI_Command;
use 
WP_CLI\Utils;
use 
DirectoryIterator;
use 
IteratorIterator;
use 
SplFileInfo;

class 
MakeJsonCommand extends WP_CLI_Command {
    
/**
     * Options passed to json_encode().
     *
     * @var int JSON options.
     */
    
protected $json_options 0;

    
/**
     * Extract JavaScript strings from PO files and add them to individual JSON files.
     *
     * For JavaScript internationalization purposes, WordPress requires translations to be split up into
     * one Jed-formatted JSON file per JavaScript source file.
     *
     * See https://make.wordpress.org/core/2018/11/09/new-javascript-i18n-support-in-wordpress/ to learn more
     * about WordPress JavaScript internationalization.
     *
     * ## OPTIONS
     *
     * <source>
     * : Path to an existing PO file or a directory containing multiple PO files.
     *
     * [<destination>]
     * : Path to the destination directory for the resulting JSON files. Defaults to the source directory.
     *
     * [--purge]
     * : Whether to purge the strings that were extracted from the original source file. Defaults to true, use `--no-purge` to skip the removal.
     *
     * [--update-mo-files]
     * : Whether MO files should be updated as well after updating PO files.
     * Only has an effect when used in combination with `--purge`.
     *
     * [--pretty-print]
     * : Pretty-print resulting JSON files.
     *
     * [--use-map=<paths_or_maps>]
     * : Whether to use a mapping file for the strings, as a JSON value, array to specify multiple.
     * Each element can either be a string (file path) or object (map).
     *
     * ## EXAMPLES
     *
     *     # Create JSON files for all PO files in the languages directory
     *     $ wp i18n make-json languages
     *
     *     # Create JSON files for my-plugin-de_DE.po and leave the PO file untouched.
     *     $ wp i18n make-json my-plugin-de_DE.po /tmp --no-purge
     *
     *     # Create JSON files with mapping
     *     $ wp i18n make-json languages --use-map=build/map.json
     *
     *     # Create JSON files with multiple mappings
     *     $ wp i18n make-json languages '--use-map=["build/map.json","build/map2.json"]'
     *
     *     # Create JSON files with object mapping
     *     $ wp i18n make-json languages '--use-map={"source/index.js":"build/index.js"}'
     *
     * @when before_wp_load
     *
     * @throws WP_CLI\ExitException
     */
    
public function __invoke$args$assoc_args ) {
        
$assoc_args      Utils\parse_shell_arrays$assoc_args, array( 'use-map' ) );
        
$purge           Utils\get_flag_value$assoc_args'purge'true );
        
$update_mo_files Utils\get_flag_value$assoc_args'update-mo-files'true );
        
$map_paths       Utils\get_flag_value$assoc_args'use-map'false );

        if ( 
Utils\get_flag_value$assoc_args'pretty-print'false ) ) {
            
$this->json_options |= JSON_PRETTY_PRINT;
        }

        
$source realpath$args[0] );

        if ( ! 
$source || ( ! is_file$source ) && ! is_dir$source ) ) ) {
            
WP_CLI::error'Source file or directory does not exist!' );
        }

        
$destination is_file$source ) ? dirname$source ) : $source;

        if ( isset( 
$args[1] ) ) {
            
$destination $args[1];
        }

        
$map $this->build_map$map_paths );
        if ( 
is_array$map ) && empty( $map ) ) {
            
WP_CLI::error'No valid keys found. No file was created.' );
        }

        
// Two is_dir() checks in case of a race condition.
        
if ( ! is_dir$destination )
            && ! 
mkdir$destination0777true )
            && ! 
is_dir$destination )
        ) {
            
WP_CLI::error'Could not create destination directory!' );
        }

        
$result_count 0;

        if ( 
is_file$source ) ) {
            
$files = [ new SplFileInfo$source ) ];
        } else {
            
$files = new IteratorIterator( new DirectoryIterator$source ) );
        }

        
/** @var DirectoryIterator $file */
        
foreach ( $files as $file ) {
            if ( 
$file->isFile() && $file->isReadable() && 'po' === $file->getExtension() ) {
                
$result        $this->make_json$file->getRealPath(), $destination$map );
                
$result_count += count$result );

                if ( 
$purge ) {
                    
$removed $this->remove_js_strings_from_po_file$file->getRealPath() );

                    if ( ! 
$removed ) {
                        
WP_CLI::warningsprintf'Could not update file %s'basename$source ) ) );
                        continue;
                    }

                    if ( 
$update_mo_files ) {
                        
$file_basename    basename$file->getFilename(), '.po' );
                        
$destination_file "{$destination}/{$file_basename}.mo";

                        
$translations Translations::fromPoFile$file->getPathname() );
                        if ( ! 
$translations->toMoFile$destination_file ) ) {
                            
WP_CLI::warning"Could not create file {$destination_file});
                            continue;
                        }
                    }
                }
            }
        }

        
WP_CLI::successsprintf'Created %d %s.'$result_countUtils\pluralize'file'$result_count ) ) );
    }

    
/**
     * Collect maps from paths, normalize and merge
     *
     * @param string|array|bool $paths_or_maps argument. False to do nothing.
     * @return array|null       Mapping array. Null if no maps specified.
     */
    
protected function build_map$paths_or_maps ) {
        if ( 
false === $paths_or_maps ) {
            return 
null;
        }

        
$map = [];

        
// not an array: single value could also be object (associative array)
        
if ( ! is_array$paths_or_maps ) || empty( array_filterarray_keys$paths_or_maps ), 'is_int' ) ) ) {
            
$paths_or_maps = [ $paths_or_maps ];
        }
        
$paths array_filter$paths_or_maps'is_string' );
        
WP_CLI::debugsprintf'Using %d map files: %s'count$paths ), implode', '$paths ) ), 'make-json' );
        
$maps array_filter$paths_or_maps'is_array' );
        
WP_CLI::debugsprintf'Using %d inline map objects'count$maps ) ), 'make-json' );
        
WP_CLI::debugsprintf'Dropping %d invalid values from map argument'count$paths_or_maps ) - count$paths ) - count$maps ) ), 'make-json' );

        
$to_transform array_map(
            function ( 
$value$index ) {
                return [ 
$valuesprintf'inline object %d'$index ) ];
            },
            
$maps,
            
array_keys$maps )
        );

        foreach ( 
$paths as $path ) {
            if ( ! 
file_exists$path ) || is_dir$path ) ) {
                
WP_CLI::warningsprintf'Map file %s does not exist'$path ) );
                continue;
            }

            
$json json_decodefile_get_contents$path ), true );
            if ( ! 
is_array$json ) ) {
                
WP_CLI::warningsprintf'Map file %s invalid'$path ) );
                continue;
            }

            
$to_transform[] = [ $json$path ];
        }

        foreach ( 
$to_transform as $transform ) {
            list( 
$json$file ) = $transform;
            
$key_num             count$json );
            
// normalize contents to string[]
            
$json array_map(
                function ( 
$value ) {
                    if ( 
is_array$value ) ) {
                        
$value array_valuesarray_filter$value'is_string' ) );
                        if ( ! empty( 
$value ) ) {
                            return 
$value;
                        }
                    }

                    if ( 
is_string$value ) ) {
                        return [ 
$value ];
                    }

                    return 
null;
                },
                
$json
            
);
            
WP_CLI::debugsprintf'Dropped %d keys from %s'count$json ) - $key_num$file ), 'make-json' );

            
$map array_merge_recursive$map$json );
        }

        return 
$map;
    }

    
/**
     * Splits a single PO file into multiple JSON files.
     *
     * @param string     $source_file Path to the source file.
     * @param string     $destination Path to the destination directory.
     * @param array|null $map               Source to build file mapping.
     * @return array     List of created JSON files.
     */
    
protected function make_json$source_file$destination$map ) {
        
/** @var Translations[] $mapping */
        
$mapping      = [];
        
$translations = new Translations();
        
$result       = [];

        
PoExtractor::fromFile$source_file$translations );

        
$base_file_name basename$source_file'.po' );

        foreach ( 
$translations as $index => $translation ) {
            
/** @var Translation $translation */

            // Find all unique sources this translation originates from.
            
$sources array_map(
                function ( 
$reference ) {
                    
$file $reference[0];

                    if ( 
substr$file, - ) === '.min.js' ) {
                        return 
substr$file0, - ) . '.js';
                    }

                    if ( 
substr$file, - ) === '.js' ) {
                        return 
$file;
                    }

                    return 
null;
                },
                
$this->reference_map$translation->getReferences(), $map )
            );

            
$sources array_uniquearray_filter$sources ) );

            foreach ( 
$sources as $source ) {
                if ( ! isset( 
$mapping$source ] ) ) {
                    
$mapping$source ] = new Translations();

                    
// phpcs:ignore Squiz.PHP.CommentedOutCode.Found -- Provide code that is meant to be used once the bug is fixed.
                    // See https://core.trac.wordpress.org/ticket/45441
                    // $mapping[ $source ]->setDomain( $translations->getDomain() );

                    
$mapping$source ]->setHeader'Language'$translations->getLanguage() );
                    
$mapping$source ]->setHeader'PO-Revision-Date'$translations->getHeader'PO-Revision-Date' ) );

                    
$plural_forms $translations->getPluralForms();

                    if ( 
$plural_forms ) {
                        list( 
$count$rule ) = $plural_forms;
                        
$mapping$source ]->setPluralForms$count$rule );
                    }
                }

                
$mapping$source ][] = $translation;
            }
        }

        
$result += $this->build_json_files$mapping$base_file_name$destination );

        return 
$result;
    }

    
/**
     * Takes the references and applies map, if given
     *
     * @param array      $references translation references
     * @param array|null $map file mapping
     * @return array     mapped references
     */
    
protected function reference_map$references$map ) {
        if ( 
is_null$map ) ) {
            return 
$references;
        }

        
// translate using map
        
$temp array_map(
            function ( 
$reference ) use ( &$map ) {
                
$file $reference[0];

                if ( 
array_key_exists$file$map ) ) {
                    return 
$map$file ];
                }

                return 
null;
            },
            
$references
        
);
        
// this is now an array of arrays of sources, translate to array of sources
        
$references = [];
        foreach ( 
$temp as $sources ) {
            if ( 
is_null$sources ) ) {
                continue;
            }
            
$references array_merge$references$sources );
        }
        
// and wrap to array
        
$references array_map(
            function ( 
$value ) {
                return [ 
$value ];
            },
            
$references
        
);
        return 
$references;
    }

    
/**
     * Builds a mapping of JS file names to translation entries.
     *
     * Exports translations for each JS file to a separate translation file.
     *
     * @param array  $mapping        A mapping of files to translation entries.
     * @param string $base_file_name Base file name for JSON files.
     * @param string $destination    Path to the destination directory.
     *
     * @return array List of created JSON files.
     */
    
protected function build_json_files$mapping$base_file_name$destination ) {
        
$result = [];

        foreach ( 
$mapping as $file => $translations ) {
            
/** @var Translations $translations */

            
$hash             md5$file );
            
$destination_file "${destination}/{$base_file_name}-{$hash}.json";

            
$success JedGenerator::toFile(
                
$translations,
                
$destination_file,
                [
                    
'json'   => $this->json_options,
                    
'source' => $file,
                ]
            );

            if ( ! 
$success ) {
                
WP_CLI::warningsprintf'Could not create file %s'basename$destination_file'.json' ) ) );

                continue;
            }

            
$result[] = $destination_file;
        }

        return 
$result;
    }

    
/**
     * Removes strings from PO file that only occur in JavaScript file.
     *
     * @param string $source_file Path to the PO file.
     * @return bool True on success, false otherwise.
     */
    
protected function remove_js_strings_from_po_file$source_file ) {
        
/** @var Translations[] $mapping */
        
$translations = new Translations();

        
PoExtractor::fromFile$source_file$translations );

        foreach ( 
$translations->getArrayCopy() as $translation ) {
            
/** @var Translation $translation */

            
if ( ! $translation->hasReferences() ) {
                continue;
            }

            foreach ( 
$translation->getReferences() as $reference ) {
                
$file $reference[0];

                if ( 
substr$file, - ) !== '.js' ) {
                    continue 
2;
                }
            }

            unset( 
$translations$translation->getId() ] );
        }

        return 
PoGenerator::toFile$translations$source_file );
    }
}
<?
php

namespace WP_CLI\I18n;

use 
Gettext\Generators\Jed;
use 
Gettext\Translation;
use 
Gettext\Translations;

/**
 * Jed file generator.
 *
 * Adds some more meta data to JED translation files than the default generator.
 */
class JedGenerator extends Jed {
    
/**
     * {@parentDoc}.
     */
    
public static function toStringTranslations $translations, array $options = [] ) {
        
$options += static::$options;
        
$domain   $translations->getDomain() ?: 'messages';
        
$messages = static::buildMessages$translations );

        
$configuration = [
            
'' => [
                
'domain'       => $domain,
                
'lang'         => $translations->getLanguage() ?: 'en',
                
'plural-forms' => $translations->getHeader'Plural-Forms' ) ?: 'nplurals=2; plural=(n != 1);',
            ],
        ];

        
$data = [
            
'translation-revision-date' => $translations->getHeader'PO-Revision-Date' ),
            
'generator'                 => 'WP-CLI/' WP_CLI_VERSION,
            
'source'                    => $options['source'],
            
'domain'                    => $domain,
            
'locale_data'               => [
                
$domain => $configuration $messages,
            ],
        ];

        return 
json_encode$data$options['json'] );
    }

    
/**
     * Generates an array with all translations.
     *
     * @param Translations $translations
     *
     * @return array
     */
    
public static function buildMessagesTranslations $translations ) {
        
$plural_forms      $translations->getPluralForms();
        
$number_of_plurals is_array$plural_forms ) ? ( $plural_forms[0] - ) : null;
        
$messages          = [];
        
$context_glue      chr);

        foreach ( 
$translations as $translation ) {
            
/** @var Translation $translation */

            
if ( $translation->isDisabled() ) {
                continue;
            }

            
$key $translation->getOriginal();

            if ( 
$translation->hasContext() ) {
                
$key $translation->getContext() . $context_glue $key;
            }

            if ( 
$translation->hasPluralTranslationstrue ) ) {
                
$message $translation->getPluralTranslations$number_of_plurals );
                
array_unshift$message$translation->getTranslation() );
            } else {
                
$message = [ $translation->getTranslation() ];
            }

            
$messages$key ] = $message;
        }

        return 
$messages;
    }
}
<?
php

namespace WP_CLI\I18n;

use 
DirectoryIterator;
use 
Gettext\Translations;
use 
IteratorIterator;
use 
SplFileInfo;
use 
WP_CLI;
use 
WP_CLI\Utils;
use 
WP_CLI_Command;

class 
MakeMoCommand extends WP_CLI_Command {
    
/**
     * Create MO files from PO files.
     *
     * ## OPTIONS
     *
     * <source>
     * : Path to an existing PO file or a directory containing multiple PO files.
     *
     * [<destination>]
     * : Path to the destination directory for the resulting MO files. Defaults to the source directory.
     *
     * @when before_wp_load
     *
     * @throws WP_CLI\ExitException
     */
    
public function __invoke$args$assoc_args ) {
        
$source realpath$args[0] );
        if ( ! 
$source || ( ! is_file$source ) && ! is_dir$source ) ) ) {
            
WP_CLI::error'Source file or directory does not exist!' );
        }

        
$destination is_file$source ) ? dirname$source ) : $source;
        if ( isset( 
$args[1] ) ) {
            
$destination $args[1];
        }

        
// Two is_dir() checks in case of a race condition.
        
if ( ! is_dir$destination )
            && ! 
mkdir$destination0777true )
            && ! 
is_dir$destination )
        ) {
            
WP_CLI::error'Could not create destination directory!' );
        }

        if ( 
is_file$source ) ) {
            
$files = [ new SplFileInfo$source ) ];
        } else {
            
$files = new IteratorIterator( new DirectoryIterator$source ) );
        }

        
$result_count 0;
        
/** @var DirectoryIterator $file */
        
foreach ( $files as $file ) {
            if ( 
'po' !== $file->getExtension() ) {
                continue;
            }

            if ( ! 
$file->isFile() || ! $file->isReadable() ) {
                
WP_CLI::warningsprintf'Could not read file %s'$file->getFilename() ) );
                continue;
            }

            
$file_basename    basename$file->getFilename(), '.po' );
            
$destination_file "{$destination}/{$file_basename}.mo";

            
$translations Translations::fromPoFile$file->getPathname() );
            if ( ! 
$translations->toMoFile$destination_file ) ) {
                
WP_CLI::warningsprintf'Could not create file %s'$destination_file ) );
                continue;
            }

            
$result_count++;
        }

        
WP_CLI::successsprintf'Created %d %s.'$result_countUtils\pluralize'file'$result_count ) ) );
    }
}
<?
php

if ( ! class_exists'WP_CLI' ) ) {
    return;
}

$wpcli_i18n_autoloader __DIR__ '/vendor/autoload.php';

if ( 
file_exists$wpcli_i18n_autoloader ) ) {
    require_once 
$wpcli_i18n_autoloader;
}

if ( 
class_exists'WP_CLI\Dispatcher\CommandNamespace' ) ) {
    
WP_CLI::add_command'i18n''\WP_CLI\I18n\CommandNamespace' );
}

WP_CLI::add_command(
    
'i18n make-pot',
    
'\WP_CLI\I18n\MakePotCommand',
    array(
        
'before_invoke' => static function() {
            if ( ! 
function_exists'mb_ereg' ) ) {
                
WP_CLI::error'The mbstring extension is required for string extraction to work reliably.' );
            }
        },
    )
);

WP_CLI::add_command'i18n make-json''\WP_CLI\I18n\MakeJsonCommand' );

WP_CLI::add_command'i18n make-mo''\WP_CLI\I18n\MakeMoCommand' );
<?
php

use WP_CLI\ExitException;
use 
WP_CLI\Utils;
use 
WP_CLI\WpOrgApi;

/**
 * Generates and reads the wp-config.php file.
 */
class Config_Command extends WP_CLI_Command {

    
/**
     * List of characters that are valid for a key name.
     *
     * @var string
     */
    
const VALID_KEY_CHARACTERS 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_ []{}<>~`+=,.;:/?|';

    
/**
     * List of default constants that are generated by WordPress Core.
     *
     * @string
     */
    
const DEFAULT_SALT_CONSTANTS = [
        
'AUTH_KEY',
        
'SECURE_AUTH_KEY',
        
'LOGGED_IN_KEY',
        
'NONCE_KEY',
        
'AUTH_SALT',
        
'SECURE_AUTH_SALT',
        
'LOGGED_IN_SALT',
        
'NONCE_SALT',
    ];

    
/**
     * Retrieve the initial locale from the WordPress version file.
     *
     * @return string Initial locale if present, or an empty string if not.
     */
    
private static function get_initial_locale() {
        global 
$wp_local_package;

        include 
ABSPATH '/wp-includes/version.php';

        if ( ! empty( 
$wp_local_package ) ) {
            return 
$wp_local_package;
        }

        return 
'';
    }

    
/**
     * Generates a wp-config.php file.
     *
     * Creates a new wp-config.php with database constants, and verifies that
     * the database constants are correct.
     *
     * ## OPTIONS
     *
     * --dbname=<dbname>
     * : Set the database name.
     *
     * --dbuser=<dbuser>
     * : Set the database user.
     *
     * [--dbpass=<dbpass>]
     * : Set the database user password.
     *
     * [--dbhost=<dbhost>]
     * : Set the database host.
     * ---
     * default: localhost
     * ---
     *
     * [--dbprefix=<dbprefix>]
     * : Set the database table prefix.
     * ---
     * default: wp_
     * ---
     *
     * [--dbcharset=<dbcharset>]
     * : Set the database charset.
     * ---
     * default: utf8
     * ---
     *
     * [--dbcollate=<dbcollate>]
     * : Set the database collation.
     * ---
     * default:
     * ---
     *
     * [--locale=<locale>]
     * : Set the WPLANG constant. Defaults to $wp_local_package variable.
     *
     * [--extra-php]
     * : If set, the command copies additional PHP code into wp-config.php from STDIN.
     *
     * [--skip-salts]
     * : If set, keys and salts won't be generated, but should instead be passed via `--extra-php`.
     *
     * [--skip-check]
     * : If set, the database connection is not checked.
     *
     * [--force]
     * : Overwrites existing files, if present.
     *
     * [--config-file=<path>]
     * : Specify the file path to the config file to be created. Defaults to the root of the
     * WordPress installation and the filename "wp-config.php".
     *
     * [--insecure]
     * : Retry API download without certificate validation if TLS handshake fails. Note: This makes the request vulnerable to a MITM attack.
     *
     * ## EXAMPLES
     *
     *     # Standard wp-config.php file
     *     $ wp config create --dbname=testing --dbuser=wp --dbpass=securepswd --locale=ro_RO
     *     Success: Generated 'wp-config.php' file.
     *
     *     # Enable WP_DEBUG and WP_DEBUG_LOG
     *     $ wp config create --dbname=testing --dbuser=wp --dbpass=securepswd --extra-php <<PHP
     *     define( 'WP_DEBUG', true );
     *     define( 'WP_DEBUG_LOG', true );
     *     PHP
     *     Success: Generated 'wp-config.php' file.
     *
     *     # Avoid disclosing password to bash history by reading from password.txt
     *     # Using --prompt=dbpass will prompt for the 'dbpass' argument
     *     $ wp config create --dbname=testing --dbuser=wp --prompt=dbpass < password.txt
     *     Success: Generated 'wp-config.php' file.
     */
    
public function create$_$assoc_args ) {
        if ( ! 
Utils\get_flag_value$assoc_args'force' ) ) {
            if ( isset( 
$assoc_args['config-file'] ) && file_exists$assoc_args['config-file'] ) ) {
                
$this->config_file_already_exist_errorbasename$assoc_args['config-file'] ) );
            } elseif ( ! isset( 
$assoc_args['config-file'] ) && Utils\locate_wp_config() ) {
                
$this->config_file_already_exist_error'wp-config.php' );
            }
        }

        
$defaults   = [
            
'dbhost'      => 'localhost',
            
'dbpass'      => '',
            
'dbprefix'    => 'wp_',
            
'dbcharset'   => 'utf8',
            
'dbcollate'   => '',
            
'locale'      => self::get_initial_locale(),
            
'config-file' => rtrimABSPATH'/\\' ) . '/wp-config.php',
        ];
        
$assoc_args array_merge$defaults$assoc_args );
        if ( 
preg_match'|[^a-z0-9_]|i'$assoc_args['dbprefix'] ) ) {
            
WP_CLI::error'--dbprefix can only contain numbers, letters, and underscores.' );
        }

        
$mysql_db_connection_args = [
            
'execute' => ';',
            
'host'    => $assoc_args['dbhost'],
            
'user'    => $assoc_args['dbuser'],
            
'pass'    => $assoc_args['dbpass'],
        ];

        
// Check DB connection.
        
if ( ! Utils\get_flag_value$assoc_args'skip-check' ) ) {
            
Utils\run_mysql_command'/usr/bin/env mysql --no-defaults'$mysql_db_connection_args );
        }

        if ( ! 
Utils\get_flag_value$assoc_args'skip-salts' ) ) {
            try {
                
$assoc_args['keys-and-salts']    = true;
                
$assoc_args['auth-key']          = self::unique_key();
                
$assoc_args['secure-auth-key']   = self::unique_key();
                
$assoc_args['logged-in-key']     = self::unique_key();
                
$assoc_args['nonce-key']         = self::unique_key();
                
$assoc_args['auth-salt']         = self::unique_key();
                
$assoc_args['secure-auth-salt']  = self::unique_key();
                
$assoc_args['logged-in-salt']    = self::unique_key();
                
$assoc_args['nonce-salt']        = self::unique_key();
                
$assoc_args['wp-cache-key-salt'] = self::unique_key();
            } catch ( 
Exception $e ) {
                
$assoc_args['keys-and-salts']     = false;
                
$assoc_args['keys-and-salts-alt'] = self::fetch_remote_salts(
                    (bool) 
Utils\get_flag_value$assoc_args'insecure'false )
                );
            }
        }

        if ( 
Utils\wp_version_compare'4.0''<' ) ) {
            
$assoc_args['add-wplang'] = true;
        } else {
            
$assoc_args['add-wplang'] = false;
        }

        foreach ( 
$assoc_args as $key => $value ) {
            
$assoc_args$key ] = $this->escape_config_value$key$value );
        }

        
// 'extra-php' from STDIN is retrieved after escaping to avoid breaking
        // the PHP code.
        
if ( Utils\get_flag_value$assoc_args'extra-php' ) === true ) {
            
$assoc_args['extra-php'] = file_get_contents'php://stdin' );
        }

        
$command_root Utils\phar_safe_pathdirname__DIR__ ) );
        
$out          Utils\mustache_render"{$command_root}/templates/wp-config.mustache"$assoc_args );

        
$wp_config_file_name basename$assoc_args['config-file'] );
        
$bytes_written       file_put_contents$assoc_args['config-file'], $out );
        if ( ! 
$bytes_written ) {
            
WP_CLI::error"Could not create new '{$wp_config_file_name}' file." );
        } else {
            
WP_CLI::success"Generated '{$wp_config_file_name}' file." );
        }
    }

    
/**
     * Gives error when wp-config already exist and try to create it.
     *
     * @param string $wp_config_file_name Config file name.
     * @return void
     */
    
private function config_file_already_exist_error$wp_config_file_name ) {
        
WP_CLI::error"The '{$wp_config_file_name}' file already exists." );
    }

    
/**
     * Launches system editor to edit the wp-config.php file.
     *
     * ## OPTIONS
     *
     * [--config-file=<path>]
     * : Specify the file path to the config file to be edited. Defaults to the root of the
     * WordPress installation and the filename "wp-config.php".
     *
     * ## EXAMPLES
     *
     *     # Launch system editor to edit wp-config.php file
     *     $ wp config edit
     *
     *     # Edit wp-config.php file in a specific editor
     *     $ EDITOR=vim wp config edit
     *
     * @when before_wp_load
     */
    
public function edit$_$assoc_args ) {
        
$path                $this->get_config_path$assoc_args );
        
$wp_config_file_name basename$path );
        
$contents            file_get_contents$path );
        
$r                   Utils\launch_editor_for_input$contents$wp_config_file_name'php' );
        if ( 
false === $r ) {
            
WP_CLI::warning"No changes made to {$wp_config_file_name}, aborted." );
        } else {
            
file_put_contents$path$r );
        }
    }

    
/**
     * Gets the path to wp-config.php file.
     *
     * ## EXAMPLES
     *
     *     # Get wp-config.php file path
     *     $ wp config path
     *     /home/person/htdocs/project/wp-config.php
     *
     * @when before_wp_load
     */
    
public function path() {
        
WP_CLI::line$this->get_config_path( array() ) );
    }

    
/**
     * Lists variables, constants, and file includes defined in wp-config.php file.
     *
     * ## OPTIONS
     *
     * [<filter>...]
     * : Name or partial name to filter the list by.
     *
     * [--fields=<fields>]
     * : Limit the output to specific fields. Defaults to all fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * Dotenv is limited to non-object values.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - yaml
     *   - dotenv
     * ---
     *
     * [--strict]
     * : Enforce strict matching when a filter is provided.
     *
     * [--config-file=<path>]
     * : Specify the file path to the config file to be read. Defaults to the root of the
     * WordPress installation and the filename "wp-config.php".
     *
     * ## EXAMPLES
     *
     *     # List constants and variables defined in wp-config.php file.
     *     $ wp config list
     *     +------------------+------------------------------------------------------------------+----------+
     *     | key              | value                                                            | type     |
     *     +------------------+------------------------------------------------------------------+----------+
     *     | table_prefix     | wp_                                                              | variable |
     *     | DB_NAME          | wp_cli_test                                                      | constant |
     *     | DB_USER          | root                                                             | constant |
     *     | DB_PASSWORD      | root                                                             | constant |
     *     | AUTH_KEY         | r6+@shP1yO&$)1gdu.hl[/j;7Zrvmt~o;#WxSsa0mlQOi24j2cR,7i+QM/#7S:o^ | constant |
     *     | SECURE_AUTH_KEY  | iO-z!_m--YH$Tx2tf/&V,YW*13Z_HiRLqi)d?$o-tMdY+82pK$`T.NYW~iTLW;xp | constant |
     *     +------------------+------------------------------------------------------------------+----------+
     *
     *     # List only database user and password from wp-config.php file.
     *     $ wp config list DB_USER DB_PASSWORD --strict
     *     +------------------+-------+----------+
     *     | key              | value | type     |
     *     +------------------+-------+----------+
     *     | DB_USER          | root  | constant |
     *     | DB_PASSWORD      | root  | constant |
     *     +------------------+-------+----------+
     *
     *     # List all salts from wp-config.php file.
     *     $ wp config list _SALT
     *     +------------------+------------------------------------------------------------------+----------+
     *     | key              | value                                                            | type     |
     *     +------------------+------------------------------------------------------------------+----------+
     *     | AUTH_SALT        | n:]Xditk+_7>Qi=>BmtZHiH-6/Ecrvl(V5ceeGP:{>?;BT^=[B3-0>,~F5z$(+Q$ | constant |
     *     | SECURE_AUTH_SALT | ?Z/p|XhDw3w}?c.z%|+BAr|(Iv*H%%U+Du&kKR y?cJOYyRVRBeB[2zF-`(>+LCC | constant |
     *     | LOGGED_IN_SALT   | +$@(1{b~Z~s}Cs>8Y]6[m6~TnoCDpE>O%e75u}&6kUH!>q:7uM4lxbB6[1pa_X,q | constant |
     *     | NONCE_SALT       | _x+F li|QL?0OSQns1_JZ{|Ix3Jleox-71km/gifnyz8kmo=w-;@AE8W,(fP<N}2 | constant |
     *     +------------------+------------------------------------------------------------------+----------+
     *
     * @when before_wp_load
     * @subcommand list
     */
    
public function list_$args$assoc_args ) {
        
$path                $this->get_config_path$assoc_args );
        
$wp_config_file_name basename$path );
        
$strict              Utils\get_flag_value$assoc_args'strict' );
        if ( 
$strict && empty( $args ) ) {
            
WP_CLI::error'The --strict option can only be used in combination with a filter.' );
        }

        
$default_fields = [
            
'name',
            
'value',
            
'type',
        ];

        
$defaults = [
            
'fields' => implode','$default_fields ),
            
'format' => 'table',
        ];

        
$assoc_args array_merge$defaults$assoc_args );

        
$values self::get_wp_config_vars$path );

        if ( ! empty( 
$args ) ) {
            
$values $this->filter_values$values$args$strict );
        }

        if ( empty( 
$values ) ) {
            
WP_CLI::error"No matching entries found in '{$wp_config_file_name}'." );
        }

        if ( 
'dotenv' === $assoc_args['format'] ) {
            return 
array_walk$values, array( $this'print_dotenv' ) );
        }

        
Utils\format_items$assoc_args['format'], $values$assoc_args['fields'] );
    }

    
/**
     * Gets the value of a specific constant or variable defined in wp-config.php file.
     *
     * ## OPTIONS
     *
     * <name>
     * : Name of the wp-config.php constant or variable.
     *
     * [--type=<type>]
     * : Type of config value to retrieve. Defaults to 'all'.
     * ---
     * default: all
     * options:
     *   - constant
     *   - variable
     *   - all
     * ---
     *
     * [--format=<format>]
     * : Get value in a particular format.
     * Dotenv is limited to non-object values.
     * ---
     * default: var_export
     * options:
     *   - var_export
     *   - json
     *   - yaml
     *   - dotenv
     * ---
     *
     * [--config-file=<path>]
     * : Specify the file path to the config file to be read. Defaults to the root of the
     * WordPress installation and the filename "wp-config.php".
     *
     * ## EXAMPLES
     *
     *     # Get the table_prefix as defined in wp-config.php file.
     *     $ wp config get table_prefix
     *     wp_
     *
     * @when before_wp_load
     */
    
public function get$args$assoc_args ) {
        
$path                $this->get_config_path$assoc_args );
        
$wp_config_file_name basename$path );
        list( 
$name )        = $args;
        
$type                Utils\get_flag_value$assoc_args'type' );

        
$value $this->return_value$name$typeself::get_wp_config_vars$path ), $wp_config_file_name );
        
WP_CLI::print_value$value$assoc_args );
    }

    
/**
     * Get the array of wp-config.php constants and variables.
     *
     * @param string $wp_config_path Config file path
     *
     * @return array
     */
    
private static function get_wp_config_vars$wp_config_path '' ) {
        
$wp_cli_original_defined_constants get_defined_constants();
        
$wp_cli_original_defined_vars      get_defined_vars();
        
$wp_cli_original_includes          get_included_files();

        
// phpcs:ignore Squiz.PHP.Eval.Discouraged -- Don't have another way.
        
eval( WP_CLI::get_runner()->get_wp_config_code$wp_config_path ) );

        
$wp_config_vars      self::get_wp_config_diffget_defined_vars(), $wp_cli_original_defined_vars'variable', [ 'wp_cli_original_defined_vars' ] );
        
$wp_config_constants self::get_wp_config_diffget_defined_constants(), $wp_cli_original_defined_constants'constant' );

        foreach ( 
$wp_config_vars as $name => $value ) {
            if ( 
'wp_cli_original_includes' === $value['name'] ) {
                
$name_backup $name;
                break;
            }
        }

        unset( 
$wp_config_vars$name_backup ] );
        
$wp_config_vars           array_values$wp_config_vars );
        
$wp_config_includes       array_diffget_included_files(), $wp_cli_original_includes );
        
$wp_config_includes_array = [];

        foreach ( 
$wp_config_includes as $name => $value ) {
            
$wp_config_includes_array[] = [
                
'name'  => basename$value ),
                
'value' => $value,
                
'type'  => 'includes',
            ];
        }

        return 
array_merge$wp_config_vars$wp_config_constants$wp_config_includes_array );
    }

    
/**
     * Sets the value of a specific constant or variable defined in wp-config.php file.
     *
     * ## OPTIONS
     *
     * <name>
     * : Name of the wp-config.php constant or variable.
     *
     * <value>
     * : Value to set the wp-config.php constant or variable to.
     *
     * [--add]
     * : Add the value if it doesn't exist yet.
     * This is the default behavior, override with --no-add.
     *
     * [--raw]
     * : Place the value into the wp-config.php file as is, instead of as a quoted string.
     *
     * [--anchor=<anchor>]
     * : Anchor string where additions of new values are anchored around.
     * Defaults to "/* That's all, stop editing!".
     * The special case "EOF" string uses the end of the file as the anchor.
     *
     * [--placement=<placement>]
     * : Where to place the new values in relation to the anchor string.
     * ---
     * default: 'before'
     * options:
     *   - before
     *   - after
     * ---
     *
     * [--separator=<separator>]
     * : Separator string to put between an added value and its anchor string.
     * The following escape sequences will be recognized and properly interpreted: '\n' => newline, '\r' => carriage return, '\t' => tab.
     * Defaults to a single EOL ("\n" on *nix and "\r\n" on Windows).
     *
     * [--type=<type>]
     * : Type of the config value to set. Defaults to 'all'.
     * ---
     * default: all
     * options:
     *   - constant
     *   - variable
     *   - all
     * ---
     *
     * [--config-file=<path>]
     * : Specify the file path to the config file to be modified. Defaults to the root of the
     * WordPress installation and the filename "wp-config.php".
     *
     * ## EXAMPLES
     *
     *     # Set the WP_DEBUG constant to true.
     *     $ wp config set WP_DEBUG true --raw
     *
     * @when before_wp_load
     */
    
public function set$args$assoc_args ) {
        
$path                 $this->get_config_path$assoc_args );
        
$wp_config_file_name  basename$path );
        list( 
$name$value ) = $args;
        
$type                 Utils\get_flag_value$assoc_args'type' );

        
$options = [];

        
$option_flags = [
            
'raw'       => false,
            
'add'       => true,
            
'anchor'    => null,
            
'placement' => null,
            
'separator' => null,
        ];

        foreach ( 
$option_flags as $option => $default ) {
            
$option_value Utils\get_flag_value$assoc_args$option$default );
            if ( 
null !== $option_value ) {
                
$options$option ] = $option_value;
                if ( 
'separator' === $option ) {
                    
$options['separator'] = $this->parse_separator$options['separator'] );
                }
            }
        }

        
$adding false;
        try {
            
$config_transformer = new WPConfigTransformer$path );

            switch ( 
$type ) {
                case 
'all':
                    
$has_constant $config_transformer->exists'constant'$name );
                    
$has_variable $config_transformer->exists'variable'$name );
                    if ( 
$has_constant && $has_variable ) {
                        
WP_CLI::error"Found both a constant and a variable '{$name}' in the '{$wp_config_file_name}' file. Use --type=<type> to disambiguate." );
                    }
                    if ( ! 
$has_constant && ! $has_variable ) {
                        if ( ! 
$options['add'] ) {
                            
$message "The constant or variable '{$name}' is not defined in the '{$wp_config_file_name}' file.";
                            
WP_CLI::error$message );
                        }
                        
$type   'constant';
                        
$adding true;
                    } else {
                        
$type $has_constant 'constant' 'variable';
                    }
                    break;
                case 
'constant':
                case 
'variable':
                    if ( ! 
$config_transformer->exists$type$name ) ) {
                        if ( ! 
$options['add'] ) {
                            
WP_CLI::error"The {$type} '{$name}' is not defined in the '{$wp_config_file_name}' file." );
                        }
                        
$adding true;
                    }
            }

            
$config_transformer->update$type$name$value$options );

        } catch ( 
Exception $exception ) {
            
WP_CLI::error"Could not process the '{$wp_config_file_name}' transformation.\nReason: {$exception->getMessage()});
        }

        
$raw $options['raw'] ? 'raw ' '';
        if ( 
$adding ) {
            
$message "Added the {$type} '{$name}' to the '{$wp_config_file_name}' file with the {$raw}value '{$value}'.";
        } else {
            
$message "Updated the {$type} '{$name}' in the '{$wp_config_file_name}' file with the {$raw}value '{$value}'.";
        }

        
WP_CLI::success$message );
    }

    
/**
     * Deletes a specific constant or variable from the wp-config.php file.
     *
     * ## OPTIONS
     *
     * <name>
     * : Name of the wp-config.php constant or variable.
     *
     * [--type=<type>]
     * : Type of the config value to delete. Defaults to 'all'.
     * ---
     * default: all
     * options:
     *   - constant
     *   - variable
     *   - all
     * ---
     *
     * [--config-file=<path>]
     * : Specify the file path to the config file to be modified. Defaults to the root of the
     * WordPress installation and the filename "wp-config.php".
     *
     * ## EXAMPLES
     *
     *     # Delete the COOKIE_DOMAIN constant from the wp-config.php file.
     *     $ wp config delete COOKIE_DOMAIN
     *
     * @when before_wp_load
     */
    
public function delete$args$assoc_args ) {
        
$path                $this->get_config_path$assoc_args );
        
$wp_config_file_name basename$path );
        list( 
$name )        = $args;
        
$type                Utils\get_flag_value$assoc_args'type' );

        try {
            
$config_transformer = new WPConfigTransformer$path );

            switch ( 
$type ) {
                case 
'all':
                    
$has_constant $config_transformer->exists'constant'$name );
                    
$has_variable $config_transformer->exists'variable'$name );
                    if ( 
$has_constant && $has_variable ) {
                        
WP_CLI::error"Found both a constant and a variable '{$name}' in the '{$wp_config_file_name}' file. Use --type=<type> to disambiguate." );
                    }
                    if ( ! 
$has_constant && ! $has_variable ) {
                        
WP_CLI::error"The constant or variable '{$name}' is not defined in the '{$wp_config_file_name}' file." );
                    } else {
                        
$type $has_constant 'constant' 'variable';
                    }
                    break;
                case 
'constant':
                case 
'variable':
                    if ( ! 
$config_transformer->exists$type$name ) ) {
                        
WP_CLI::error"The {$type} '{$name}' is not defined in the '{$wp_config_file_name}' file." );
                    }
            }

            
$config_transformer->remove$type$name );

        } catch ( 
Exception $exception ) {
            
WP_CLI::error"Could not process the '{$wp_config_file_name}' transformation.\nReason: {$exception->getMessage()});
        }

        
WP_CLI::success"Deleted the {$type} '{$name}' from the '{$wp_config_file_name}' file." );
    }

    
/**
     * Checks whether a specific constant or variable exists in the wp-config.php file.
     *
     * ## OPTIONS
     *
     * <name>
     * : Name of the wp-config.php constant or variable.
     *
     * [--type=<type>]
     * : Type of the config value to set. Defaults to 'all'.
     * ---
     * default: all
     * options:
     *   - constant
     *   - variable
     *   - all
     * ---
     *
     * [--config-file=<path>]
     * : Specify the file path to the config file to be checked. Defaults to the root of the
     * WordPress installation and the filename "wp-config.php".
     *
     * ## EXAMPLES
     *
     *     # Check whether the DB_PASSWORD constant exists in the wp-config.php file.
     *     $ wp config has DB_PASSWORD
     *
     * @when before_wp_load
     */
    
public function has$args$assoc_args ) {
        
$path                $this->get_config_path$assoc_args );
        
$wp_config_file_name basename$path );
        list( 
$name )        = $args;
        
$type                Utils\get_flag_value$assoc_args'type' );

        try {
            
$config_transformer = new WPConfigTransformer$path );

            switch ( 
$type ) {
                case 
'all':
                    
$has_constant $config_transformer->exists'constant'$name );
                    
$has_variable $config_transformer->exists'variable'$name );
                    if ( 
$has_constant && $has_variable ) {
                        
WP_CLI::error"Found both a constant and a variable '{$name}' in the '{$wp_config_file_name}' file. Use --type=<type> to disambiguate." );
                    }
                    if ( ! 
$has_constant && ! $has_variable ) {
                        
WP_CLI::halt);
                    } else {
                        
WP_CLI::halt);
                    }
                    break;
                case 
'constant':
                case 
'variable':
                    if ( ! 
$config_transformer->exists$type$name ) ) {
                        
WP_CLI::halt);
                    }
                    
WP_CLI::halt);
            }
        } catch ( 
Exception $exception ) {
            
WP_CLI::error"Could not process the '{$wp_config_file_name}' transformation.\nReason: {$exception->getMessage()});
        }
    }

    
/**
     * Refreshes the salts defined in the wp-config.php file.
     *
     * ## OPTIONS
     *
     * [<keys>...]
     * : One ore more keys to shuffle. If none are provided, this falls back to the default WordPress Core salt keys.
     *
     * [--force]
     * : If an unknown key is requested to be shuffled, add it instead of throwing a warning.
     *
     * [--config-file=<path>]
     * : Specify the file path to the config file to be modified. Defaults to the root of the
     * WordPress installation and the filename "wp-config.php".
     *
     * [--insecure]
     * : Retry API download without certificate validation if TLS handshake fails. Note: This makes the request vulnerable to a MITM attack.
     *
     * ## EXAMPLES
     *
     *     # Get new salts for your wp-config.php file
     *     $ wp config shuffle-salts
     *     Success: Shuffled the salt keys.
     *
     *     # Add a cache key salt to the wp-config.php file
     *     $ wp config shuffle-salts WP_CACHE_KEY_SALT --force
     *
     * @subcommand shuffle-salts
     * @when before_wp_load
     */
    
public function shuffle_salts$args$assoc_args ) {

        
$keys  $args;
        
$force Utils\get_flag_value$assoc_args'force'false );

        if ( empty( 
$keys ) ) {
            
$keys self::DEFAULT_SALT_CONSTANTS;
        }

        
$secret_keys = [];

        try {
            foreach ( 
$keys as $key ) {
                if ( ! 
$force && ! in_array$keyself::DEFAULT_SALT_CONSTANTStrue ) ) {
                    
WP_CLI::warning"Could not shuffle the unknown key '{$key}'." );
                    continue;
                }
                
$secret_keys$key ] = trimself::unique_key() );
            }
        } catch ( 
Exception $ex ) {
            foreach ( 
$keys as $key ) {
                if ( ! 
in_array$keyself::DEFAULT_SALT_CONSTANTStrue ) ) {
                    if ( 
$force ) {
                        
WP_CLI::warning"Could not add the key '{$key}' because 'random_int()' is not supported." );
                    } else {
                        
WP_CLI::warning"Could not shuffle the unknown key '{$key}'." );
                    }
                }
            }

            
$remote_salts self::fetch_remote_salts( (bool) Utils\get_flag_value$assoc_args'insecure'false ) );
            
$remote_salts explode"\n"$remote_salts );
            foreach ( 
$remote_salts as $k => $salt ) {
                if ( ! empty( 
$salt ) ) {
                    
$key self::DEFAULT_SALT_CONSTANTS$k ];
                    if ( 
in_array$key$keystrue ) ) {
                        
$secret_keys$key ] = trimsubstr$salt2864 ) );
                    }
                }
            }
        }

        
$path $this->get_config_path$assoc_args );

        try {
            
$config_transformer = new WPConfigTransformer$path );
            foreach ( 
$secret_keys as $key => $value ) {
                
$config_transformer->update'constant'$key, (string) $value );
            }
        } catch ( 
Exception $exception ) {
            
$wp_config_file_name basename$path );
            
WP_CLI::error"Could not process the '{$wp_config_file_name}' transformation.\nReason: {$exception->getMessage()});
        }

        
WP_CLI::success'Shuffled the salt keys.' );

    }

    
/**
     * Filters wp-config.php file configurations.
     *
     * @param array $list
     * @param array $previous_list
     * @param string $type
     * @param array $exclude_list
     * @return array
     */
    
private static function get_wp_config_diff$list$previous_list$type$exclude_list = [] ) {
        
$result = [];
        foreach ( 
$list as $name => $val ) {
            if ( 
array_key_exists$name$previous_list ) || in_array$name$exclude_listtrue ) ) {
                continue;
            }
            
$out          = [];
            
$out['name']  = $name;
            
$out['value'] = $val;
            
$out['type']  = $type;
            
$result[]     = $out;
        }
        return 
$result;
    }

    
/**
     * Read the salts from the WordPress.org API.
     *
     * @param bool   $insecure Optional. Whether to retry without certificate validation on TLS handshake failure.
     * @return string String with a set of PHP define() statements to define the salts.
     * @throws ExitException If the remote request failed.
     */
    
private static function fetch_remote_salts$insecure false ) {
        try {
            
$salts = (string) ( new WpOrgApi( [ 'insecure' => $insecure ] ) )->get_salts();
        } catch ( 
Exception $exception ) {
            
WP_CLI::error$exception );
        }

        
// Adapt whitespace to adhere to WPCS.
        
$salts preg_replace'/define\(\'(.*?)\'\);/''define( \'$1\' );'$salts );

        return 
$salts;
    }

    
/**
     * Prints the value of a constant or variable defined in the wp-config.php file.
     *
     * If the constant or variable is not defined in the wp-config file then an error will be returned.
     *
     * @param string $name
     * @param string $type
     * @param string $type
     * @param string $wp_config_file_name Config file name
     * @return string The value of the requested constant or variable as defined in the wp-config.php file; if the
     *                requested constant or variable is not defined then the function will print an error and exit.
     */
    
private function return_value$name$type$values$wp_config_file_name ) {
        
$results = [];
        foreach ( 
$values as $value ) {
            if ( 
$name === $value['name'] && ( 'all' === $type || $type === $value['type'] ) ) {
                
$results[] = $value;
            }
        }

        if ( 
count$results ) > ) {
            
WP_CLI::error"Found both a constant and a variable '{$name}' in the '{$wp_config_file_name}' file. Use --type=<type> to disambiguate." );
        }

        if ( ! empty( 
$results ) ) {
            return 
$results[0]['value'];
        }

        
$type      'all' === $type 'constant or variable' $type;
        
$names     array_column$values'name' );
        
$candidate Utils\get_suggestion$name$names );

        if ( ! empty( 
$candidate ) && $candidate !== $name ) {
            
WP_CLI::error"The {$type} '{$name}' is not defined in the '{$wp_config_file_name}' file.\nDid you mean '{$candidate}'?" );
        }

        
WP_CLI::error"The {$type} '{$name}' is not defined in the '{$wp_config_file_name}' file." );
    }

    
/**
     * Generates a unique key/salt for the wp-config file.
     *
     * @throws Exception
     *
     * @return string
     */
    
private static function unique_key() {
        if ( ! 
function_exists'random_int' ) ) {
            throw new 
Exception"'random_int' does not exist" );
        }

        
$chars self::VALID_KEY_CHARACTERS;
        
$key   '';

        for ( 
$i 0$i 64$i++ ) {
            
// phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.random_intFound -- Will be called only if function exists.
            
$key .= substr$charsrandom_int0strlen$chars ) - ), );
        }

        return 
$key;
    }

    
/**
     * Filters the values based on a provider filter key.
     *
     * @param array $values
     * @param array $filters
     * @param bool $strict
     *
     * @return array
     */
    
private function filter_values$values$filters$strict ) {
        
$result = [];

        foreach ( 
$values as $value ) {
            foreach ( 
$filters as $filter ) {
                if ( 
$strict && $filter !== $value['name'] ) {
                    continue;
                }

                if ( 
false === strpos$value['name'], $filter ) ) {
                    continue;
                }

                
$result[] = $value;
            }
        }

        return 
$result;
    }

    
/**
     * Gets the path to the wp-config.php file or gives a helpful error if none found.
     *
     * @param array $assoc_args associative arguments given while calling wp config subcommand
     * @return string Path to wp-config.php file.
     */
    
private function get_config_path$assoc_args ) {
        if ( isset( 
$assoc_args['config-file'] ) ) {
            
$path $assoc_args['config-file'];
            if ( ! 
file_exists$path ) ) {
                
$this->config_file_not_found_errorbasename$assoc_args['config-file'] ) );
            }
        } else {
            
$path Utils\locate_wp_config();
            if ( ! 
$path ) {
                
$this->config_file_not_found_error'wp-config.php' );
            }
        }
        return 
$path;
    }

    
/**
     * Gives error the wp-config file not found
     *
     * @param string $wp_config_file_name Config file name.
     * @return void
     */
    
private function config_file_not_found_error$wp_config_file_name ) {
        
WP_CLI::error"'{$wp_config_file_name}' not found.\nEither create one manually or use `wp config create`." );
    }
    
/**
     * Parses the separator argument, to allow for special character handling.
     *
     * Does the following transformations:
     * - '\n' => "\n" (newline)
     * - '\r' => "\r" (carriage return)
     * - '\t' => "\t" (tab)
     *
     * @param string $separator Separator string to parse.
     *
     * @return mixed Parsed separator string.
     */
    
private function parse_separator$separator ) {
        
$separator str_replace(
            [ 
'\n''\r''\t' ],
            [ 
"\n""\r""\t" ],
            
$separator
        
);

        return 
$separator;
    }

    
/**
     * Writes a provided variable's key and value to stdout, in dotenv format.
     *
     * @param array $value
     */
    
private function print_dotenv( array $value ) {
        if ( ! isset( 
$value['name'] ) || ! isset( $value['type'] ) || 'constant' !== $value['type'] ) {
            return;
        }

        
$name           strtoupper$value['name'] );
        
$variable_value = isset( $value['value'] ) ? $value['value'] : '';

        
$variable_value str_replace"'""\'"$variable_value );

        if ( ! 
is_numeric$variable_value ) ) {
            
$variable_value "'{$variable_value}'";
        }

        
WP_CLI::line"{$name}={$variable_value});
    }

    
/**
     * Escape a config value so it can be safely used within single quotes.
     *
     * @param string $key   Key into the arguments array.
     * @param mixed  $value Value to escape.
     * @return mixed Escaped value.
     */
    
private function escape_config_value$key$value ) {
        
// Skip 'extra-php', it mustn't be escaped.
        
if ( 'extra-php' === $key ) {
            return 
$value;
        }

        
// Skip 'keys-and-salts-alt' and assume they are safe.
        
if ( 'keys-and-salts-alt' === $key && ! empty( $value ) ) {
            return 
$value;
        }

        if ( 
is_string$value ) ) {
            return 
addslashes$value );
        }

        return 
$value;
    }
}
<?
php

if ( ! class_exists'WP_CLI' ) ) {
    return;
}

$wpcli_config_autoloader dirname__FILE__ ) . '/vendor/autoload.php';
if ( 
file_exists$wpcli_config_autoloader ) ) {
    require_once 
$wpcli_config_autoloader;
}

WP_CLI::add_command'config''Config_Command' );
<?
php

use WP_CLI\Utils;
use 
WP_CLI\Formatter;

/**
 * Lists registered sidebars.
 *
 * A [sidebar](https://developer.wordpress.org/themes/functionality/sidebars/) is any widgetized area of your theme.
 *
 * ## EXAMPLES
 *
 *     # List sidebars
 *     $ wp sidebar list --fields=name,id --format=csv
 *     name,id
 *     "Widget Area",sidebar-1
 *     "Inactive Widgets",wp_inactive_widgets
 */
class Sidebar_Command extends WP_CLI_Command {

    private 
$fields = [
        
'name',
        
'id',
        
'description',
    ];

    
/**
     * Lists registered sidebars.
     *
     * ## OPTIONS
     *
     * [--fields=<fields>]
     * : Limit the output to specific object fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - ids
     *   - count
     *   - yaml
     * ---
     *
     * ## AVAILABLE FIELDS
     *
     * These fields will be displayed by default for each sidebar:
     *
     * * name
     * * id
     * * description
     *
     * These fields are optionally available:
     *
     * * class
     * * before_widget
     * * after_widget
     * * before_title
     * * after_title
     *
     * ## EXAMPLES
     *
     *     $ wp sidebar list --fields=name,id --format=csv
     *     name,id
     *     "Widget Area",sidebar-1
     *     "Inactive Widgets",wp_inactive_widgets
     *
     * @subcommand list
     */
    
public function list_$args$assoc_args ) {
        global 
$wp_registered_sidebars;

        
Utils\wp_register_unused_sidebar();

        if ( ! empty( 
$assoc_args['format'] ) && 'ids' === $assoc_args['format'] ) {
            
$sidebars wp_list_pluck$wp_registered_sidebars'id' );
        } else {
            
$sidebars $wp_registered_sidebars;
        }

        
$formatter = new Formatter$assoc_args$this->fields );
        
$formatter->display_items$sidebars );
    }

}
<?
php

use WP_CLI\Utils;
use 
WP_CLI\Formatter;

/**
 * Manages widgets, including adding and moving them within sidebars.
 *
 * A [widget](https://developer.wordpress.org/themes/functionality/widgets/) adds content and features to a widget area (also called a [sidebar](https://developer.wordpress.org/themes/functionality/sidebars/)).
 *
 * ## EXAMPLES
 *
 *     # List widgets on a given sidebar
 *     $ wp widget list sidebar-1
 *     +----------+------------+----------+----------------------+
 *     | name     | id         | position | options              |
 *     +----------+------------+----------+----------------------+
 *     | meta     | meta-6     | 1        | {"title":"Meta"}     |
 *     | calendar | calendar-2 | 2        | {"title":"Calendar"} |
 *     +----------+------------+----------+----------------------+
 *
 *     # Add a calendar widget to the second position on the sidebar
 *     $ wp widget add calendar sidebar-1 2
 *     Success: Added widget to sidebar.
 *
 *     # Update option(s) associated with a given widget
 *     $ wp widget update calendar-1 --title="Calendar"
 *     Success: Widget updated.
 *
 *     # Delete one or more widgets entirely
 *     $ wp widget delete calendar-2 archive-1
 *     Success: 2 widgets removed from sidebar.
 */
class Widget_Command extends WP_CLI_Command {

    private 
$fields = [
        
'name',
        
'id',
        
'position',
        
'options',
    ];

    
/**
     * Lists widgets associated with a sidebar.
     *
     * ## OPTIONS
     *
     * <sidebar-id>
     * : ID for the corresponding sidebar.
     *
     * [--fields=<fields>]
     * : Limit the output to specific object fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - ids
     *   - json
     *   - count
     *   - yaml
     * ---
     *
     * ## AVAILABLE FIELDS
     *
     * These fields will be displayed by default for each widget:
     *
     * * name
     * * id
     * * position
     * * options
     *
     * There are no optionally available fields.
     *
     * ## EXAMPLES
     *
     *     $ wp widget list sidebar-1 --fields=name,id --format=csv
     *     name,id
     *     meta,meta-5
     *     search,search-3
     *
     * @subcommand list
     */
    
public function list_$args$assoc_args ) {

        list( 
$sidebar_id ) = $args;

        
$this->validate_sidebar$sidebar_id );

        
$output_widgets $this->get_sidebar_widgets$sidebar_id );

        if ( ! empty( 
$assoc_args['format'] ) && 'ids' === $assoc_args['format'] ) {
            
$output_widgets wp_list_pluck$output_widgets'id' );
        }

        
$formatter = new Formatter$assoc_args$this->fields );
        
$formatter->display_items$output_widgets );

    }

    
/**
     * Adds a widget to a sidebar.
     *
     * Creates a new widget entry in the database, and associates it with the
     * sidebar.
     *
     * ## OPTIONS
     *
     * <name>
     * : Widget name.
     *
     * <sidebar-id>
     * : ID for the corresponding sidebar.
     *
     * [<position>]
     * : Widget's current position within the sidebar. Defaults to last
     *
     * [--<field>=<value>]
     * : Widget option to add, with its new value
     *
     * ## EXAMPLES
     *
     *     # Add a new calendar widget to sidebar-1 with title "Calendar"
     *     $ wp widget add calendar sidebar-1 2 --title="Calendar"
     *     Success: Added widget to sidebar.
     *
     * @subcommand add
     */
    
public function add$args$assoc_args ) {
        list( 
$name$sidebar_id ) = $args;

        
$this->validate_sidebar$sidebar_id );

        
$position count$args ) > 2
            
$args[2] - 1
            
count$this->get_sidebar_widgets$sidebar_id ) );

        
$widget $this->get_widget_obj$name );
        if ( 
false === $widget ) {
            
WP_CLI::error'Invalid widget type.' );
        }

        
/*
         * Adding a widget is as easy as:
         * 1. Creating a new widget option
         * 2. Adding the widget to the sidebar
         * 3. Positioning appropriately
         */
        
$widget_options $this->get_widget_options$name );
        
$option_keys    $widget_options;
        if ( ! isset( 
$widget_options['_multiwidget'] ) ) {
            
$widget_options['_multiwidget'] = 1;
        }
        unset( 
$option_keys['_multiwidget'] );
        
$option_keys                     array_keys$option_keys );
        
$last_key                        array_pop$option_keys );
        
$option_index                    $last_key 1;
        
$widget_options$option_index ] = $this->sanitize_widget_options$name$assoc_args, array() );
        
$this->update_widget_options$name$widget_options );

        
$widget_id $name '-' $option_index;
        
$this->move_sidebar_widget$widget_idnull$sidebar_idnull$position );

        
WP_CLI::success'Added widget to sidebar.' );

    }

    
/**
     * Updates options for an existing widget.
     *
     * ## OPTIONS
     *
     * <widget-id>
     * : Unique ID for the widget
     *
     * [--<field>=<value>]
     * : Field to update, with its new value
     *
     * ## EXAMPLES
     *
     *     # Change calendar-1 widget title to "Our Calendar"
     *     $ wp widget update calendar-1 --title="Our Calendar"
     *     Success: Widget updated.
     *
     * @subcommand update
     */
    
public function update$args$assoc_args ) {

        list( 
$widget_id ) = $args;
        if ( ! 
$this->validate_sidebar_widget$widget_id ) ) {
            
WP_CLI::error"Widget doesn't exist." );
        }

        if ( empty( 
$assoc_args ) ) {
            
WP_CLI::error'No options specified to update.' );
        }

        list( 
$name$option_index ) = $this->get_widget_data$widget_id );

        
$widget_options                  $this->get_widget_options$name );
        
$clean_options                   $this->sanitize_widget_options$name$assoc_args$widget_options$option_index ] );
        
$widget_options$option_index ] = array_merge( (array) $widget_options$option_index ], $clean_options );
        
$this->update_widget_options$name$widget_options );

        
WP_CLI::success'Widget updated.' );

    }

    
/**
     * Moves the position of a widget.
     *
     * Changes the order of a widget in its existing sidebar, or moves it to a
     * new sidebar.
     *
     * ## OPTIONS
     *
     * <widget-id>
     * : Unique ID for the widget
     *
     * [--position=<position>]
     * : Assign the widget to a new position.
     *
     * [--sidebar-id=<sidebar-id>]
     * : Assign the widget to a new sidebar
     *
     * ## EXAMPLES
     *
     *     # Change position of widget
     *     $ wp widget move recent-comments-2 --position=2
     *     Success: Widget moved.
     *
     *     # Move widget to Inactive Widgets
     *     $ wp widget move recent-comments-2 --sidebar-id=wp_inactive_widgets
     *     Success: Widget moved.
     *
     * @subcommand move
     */
    
public function move$args$assoc_args ) {

        list( 
$widget_id ) = $args;
        if ( ! 
$this->validate_sidebar_widget$widget_id ) ) {
            
WP_CLI::error"Widget doesn't exist." );
        }

        if ( empty( 
$assoc_args['position'] ) && empty( $assoc_args['sidebar-id'] ) ) {
            
WP_CLI::error'A new position or new sidebar must be specified.' );
        }

        list( 
$name$option_index$current_sidebar_id$current_sidebar_index ) = $this->get_widget_data$widget_id );

        
$new_sidebar_id = ! empty( $assoc_args['sidebar-id'] ) ? $assoc_args['sidebar-id'] : $current_sidebar_id;
        
$this->validate_sidebar$new_sidebar_id );

        
$new_sidebar_index = ! empty( $assoc_args['position'] ) ? $assoc_args['position'] - $current_sidebar_index;
        
// Moving between sidebars adds to the top.
        
if ( $new_sidebar_id !== $current_sidebar_id && $new_sidebar_index === $current_sidebar_index ) {
            
// Human-readable positions are different than numerically indexed array.
            
$new_sidebar_index 0;
        }

        
$this->move_sidebar_widget$widget_id$current_sidebar_id$new_sidebar_id$current_sidebar_index$new_sidebar_index );

        
WP_CLI::success'Widget moved.' );

    }

    
/**
     * Deactivates one or more widgets from an active sidebar.
     *
     * Moves widgets to Inactive Widgets.
     *
     * ## OPTIONS
     *
     * <widget-id>...
     * : Unique ID for the widget(s)
     *
     * ## EXAMPLES
     *
     *     # Deactivate the recent-comments-2 widget.
     *     $ wp widget deactivate recent-comments-2
     *     Success: 1 widget deactivated.
     *
     * @subcommand deactivate
     */
    
public function deactivate$args$assoc_args ) {

        
$count  0;
        
$errors 0;

        foreach ( 
$args as $widget_id ) {
            if ( ! 
$this->validate_sidebar_widget$widget_id ) ) {
                
WP_CLI::warning"Widget '{$widget_id}' doesn't exist." );
                
$errors++;
                continue;
            }

            list( 
$name$option_index$sidebar_id$sidebar_index ) = $this->get_widget_data$widget_id );
            if ( 
'wp_inactive_widgets' === $sidebar_id ) {
                
WP_CLI::warningsprintf"'%s' is already deactivated."$widget_id ) );
                continue;
            }

            
$this->move_sidebar_widget(
                
$widget_id,
                
$sidebar_id,
                
'wp_inactive_widgets',
                
$sidebar_index,
                
count$this->get_sidebar_widgets'wp_inactive_widgets' ) )
            );

            
$count++;

        }

        
Utils\report_batch_operation_results'widget''deactivate'count$args ), $count$errors );
    }

    
/**
     * Deletes one or more widgets from a sidebar.
     *
     * ## OPTIONS
     *
     * <widget-id>...
     * : Unique ID for the widget(s)
     *
     * ## EXAMPLES
     *
     *     # Delete the recent-comments-2 widget from its sidebar.
     *     $ wp widget delete recent-comments-2
     *     Success: Deleted 1 of 1 widgets.
     *
     * @subcommand delete
     */
    
public function delete$args$assoc_args ) {

        
$count  0;
        
$errors 0;

        foreach ( 
$args as $widget_id ) {
            if ( ! 
$this->validate_sidebar_widget$widget_id ) ) {
                
WP_CLI::warning"Widget '{$widget_id}' doesn't exist." );
                
$errors++;
                continue;
            }

            
// Remove the widget's settings.
            
list( $name$option_index$sidebar_id$sidebar_index ) = $this->get_widget_data$widget_id );
            
$widget_options $this->get_widget_options$name );
            unset( 
$widget_options$option_index ] );
            
$this->update_widget_options$name$widget_options );

            
// Remove the widget from the sidebar.
            
$all_widgets $this->wp_get_sidebars_widgets();
            unset( 
$all_widgets$sidebar_id ][ $sidebar_index ] );
            
$all_widgets$sidebar_id ] = array_values$all_widgets$sidebar_id ] );
            
update_option'sidebars_widgets'$all_widgets );

            
$count++;
        }

        
Utils\report_batch_operation_results'widget''delete'count$args ), $count$errors );
    }

    
/**
     * Resets sidebar.
     *
     * Removes all widgets from the sidebar and places them in Inactive Widgets.
     *
     * ## OPTIONS
     *
     * [<sidebar-id>...]
     * : One or more sidebars to reset.
     *
     * [--all]
     * : If set, all sidebars will be reset.
     *
     * ## EXAMPLES
     *
     *     # Reset a sidebar
     *     $ wp widget reset sidebar-1
     *     Success: Sidebar 'sidebar-1' reset.
     *
     *     # Reset multiple sidebars
     *     $ wp widget reset sidebar-1 sidebar-2
     *     Success: Sidebar 'sidebar-1' reset.
     *     Success: Sidebar 'sidebar-2' reset.
     *
     *     # Reset all sidebars
     *     $ wp widget reset --all
     *     Success: Sidebar 'sidebar-1' reset.
     *     Success: Sidebar 'sidebar-2' reset.
     *     Success: Sidebar 'sidebar-3' reset.
     */
    
public function reset$args$assoc_args ) {

        global 
$wp_registered_sidebars;

        
$all Utils\get_flag_value$assoc_args'all'false );

        
// Bail if no arguments and no all flag.
        
if ( ! $all && empty( $args ) ) {
            
WP_CLI::error'Please specify one or more sidebars, or use --all.' );
        }

        
// Fetch all sidebars if all flag is set.
        
if ( $all ) {
            
$args array_keys$wp_registered_sidebars );
        }

        
// Sidebar ID wp_inactive_widgets is reserved by WP core for inactive widgets.
        
if ( isset( $args['wp_inactive_widgets'] ) ) {
            unset( 
$args['wp_inactive_widgets'] );
        }

        
// Check if no registered sidebar.
        
if ( empty( $args ) ) {
            
WP_CLI::error'No sidebar registered.' );
        }

        
$count  0;
        
$errors 0;
        foreach ( 
$args as $sidebar_id ) {
            if ( ! 
array_key_exists$sidebar_id$wp_registered_sidebars ) ) {
                
WP_CLI::warningsprintf'Invalid sidebar: %s'$sidebar_id ) );
                
$errors++;
                continue;
            }

            
$widgets $this->get_sidebar_widgets$sidebar_id );
            if ( empty( 
$widgets ) ) {
                
WP_CLI::warningsprintf"Sidebar '%s' is already empty."$sidebar_id ) );
            } else {
                foreach ( 
$widgets as $widget ) {
                    
$widget_id $widget->id;
                    list( 
$name$option_index$new_sidebar_id$sidebar_index ) = $this->get_widget_data$widget_id );
                    
$this->move_sidebar_widget(
                        
$widget_id,
                        
$new_sidebar_id,
                        
'wp_inactive_widgets',
                        
$sidebar_index,
                        
count$this->get_sidebar_widgets'wp_inactive_widgets' ) )
                    );
                }
                
WP_CLI::logsprintf"Sidebar '%s' reset."$sidebar_id ) );
                
$count++;
            }
        }

        
Utils\report_batch_operation_results'sidebar''reset'count$args ), $count$errors );
    }

    
/**
     * Checks whether a sidebar is a valid sidebar
     *
     * @param string $sidebar_id
     */
    
private function validate_sidebar$sidebar_id ) {
        global 
$wp_registered_sidebars;

        
Utils\wp_register_unused_sidebar();

        if ( ! 
array_key_exists$sidebar_id$wp_registered_sidebars ) ) {
            
WP_CLI::error'Invalid sidebar.' );
        }
    }

    
/**
     * Checks whether the specified widget is on the sidebar
     *
     * @param string $widget_id
     */
    
private function validate_sidebar_widget$widget_id ) {

        
$sidebars_widgets $this->wp_get_sidebars_widgets();

        
$widget_exists false;
        foreach ( 
$sidebars_widgets as $sidebar_id => $widgets ) {

            if ( 
in_array$widget_id$widgetstrue ) ) {
                
$widget_exists true;
                break;
            }
        }
        return 
$widget_exists;
    }

    
/**
     * Gets the widgets (and their associated data) for a given sidebar
     *
     * @param string $sidebar_id
     * @return array
     */
    
private function get_sidebar_widgets$sidebar_id ) {

        
$all_widgets $this->wp_get_sidebars_widgets();

        if ( empty( 
$all_widgets$sidebar_id ] ) ) {
            return array();
        }

        
$prepared_widgets = array();
        foreach ( 
$all_widgets$sidebar_id ] as $key => $widget_id ) {

            
$prepared_widget = new stdClass();

            
$parts        explode'-'$widget_id );
            
$option_index array_pop$parts );
            
$widget_name  implode'-'$parts );

            
$prepared_widget->name     $widget_name;
            
$prepared_widget->id       $widget_id;
            
$prepared_widget->position $key 1;
            
$widget_options            get_option'widget_' $widget_name );
            
$prepared_widget->options  $widget_options$option_index ];

            
$prepared_widgets[] = $prepared_widget;
        }

        return 
$prepared_widgets;
    }

    
/**
     * Re-implementation of wp_get_sidebars_widgets()
     * because the original has a nasty global component
     */
    
private function wp_get_sidebars_widgets() {
        
$sidebars_widgets get_option'sidebars_widgets', array() );

        if ( 
is_array$sidebars_widgets ) && isset( $sidebars_widgets['array_version'] ) ) {
            unset( 
$sidebars_widgets['array_version'] );
        }

        return 
$sidebars_widgets;
    }

    
/**
     * Gets the widget's name, option index, sidebar, and sidebar index from its ID
     *
     * @param string $widget_id
     * @return array
     */
    
private function get_widget_data$widget_id ) {

        
$parts        explode'-'$widget_id );
        
$option_index array_pop$parts );
        
$name         implode'-'$parts );

        
$sidebar_id    false;
        
$sidebar_index false;
        
$all_widgets   $this->wp_get_sidebars_widgets();
        foreach ( 
$all_widgets as $s_id => &$widgets ) {

            
$key array_search$widget_id$widgetstrue );
            if ( 
false !== $key ) {
                
$sidebar_id    $s_id;
                
$sidebar_index $key;
                break;
            }
        }

        return array( 
$name$option_index$sidebar_id$sidebar_index );
    }

    
/**
     * Gets the options for a given widget
     *
     * @param string $name
     * @return array
     */
    
private function get_widget_options$name ) {
        return 
get_option'widget_' $name, array() );
    }

    
/**
     * Updates the options for a given widget
     *
     * @param string $name
     * @param mixed
     */
    
private function update_widget_options$name$value ) {
        
update_option'widget_' $name$value );
    }

    
/**
     * Repositions a widget within a sidebar or move to another sidebar.
     *
     * @param string $widget_id
     * @param string|null $current_sidebar_id
     * @param string $new_sidebar_id
     * @param int|null $current_index
     * @param int $new_index
     */
    
private function move_sidebar_widget$widget_id$current_sidebar_id$new_sidebar_id$current_index$new_index ) {

        
$all_widgets     $this->wp_get_sidebars_widgets();
        
$needs_placement true;
        
// Existing widget
        
if ( $current_sidebar_id && ! is_null$current_index ) ) {

            
$widgets $all_widgets$current_sidebar_id ];
            if ( 
$current_sidebar_id !== $new_sidebar_id ) {

                unset( 
$widgets$current_index ] );

            } else {

                
$part array_splice$widgets$current_index);
                
array_splice$widgets$new_index0$part );

                
$needs_placement false;

            }

            
$all_widgets$current_sidebar_id ] = array_values$widgets );

        }

        if ( 
$needs_placement ) {
            
$widgets                        = ! empty( $all_widgets$new_sidebar_id ] ) ? $all_widgets$new_sidebar_id ] : array();
            
$before                         array_slice$widgets0$new_indextrue );
            
$after                          array_slice$widgets$new_indexcount$widgets ), true );
            
$widgets                        array_merge$before, array( $widget_id ), $after );
            
$all_widgets$new_sidebar_id ] = array_values$widgets );
        }

        
update_option'sidebars_widgets'$all_widgets );

    }

    
/**
     * Gets a widget's instantiated object based on its name
     *
     * @param string $id_base Name of the widget
     * @return WP_Widget|false
     */
    
private function get_widget_obj$id_base ) {
        global 
$wp_widget_factory;

        
$widget wp_filter_object_list$wp_widget_factory->widgets, array( 'id_base' => $id_base ) );
        if ( empty( 
$widget ) ) {
            return 
false;
        }

        return 
array_pop$widget );
    }

    
/**
     * Cleans up a widget's options based on its update callback
     *
     * @param string $id_base Name of the widget
     * @param mixed $dirty_options
     * @param mixed $old_options
     * @return mixed
     */
    
private function sanitize_widget_options$id_base$dirty_options$old_options ) {

        
$widget $this->get_widget_obj$id_base );
        if ( empty( 
$widget ) ) {
            return array();
        }

        
// No easy way to determine expected array keys for $dirty_options
        // because Widget API dependent on the form fields
        // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Whitelisting due to above reason.
        
return @$widget->update$dirty_options$old_options );

    }

}
<?
php

if ( ! class_exists'WP_CLI' ) ) {
    return;
}

$wpcli_widget_autoloader dirname__FILE__ ) . '/vendor/autoload.php';
if ( 
file_exists$wpcli_widget_autoloader ) ) {
    require_once 
$wpcli_widget_autoloader;
}

WP_CLI::add_command'widget''Widget_Command' );
WP_CLI::add_command'sidebar''Sidebar_Command' );
<?
php

namespace WP_CLI\MaintenanceMode;

use 
WP_CLI;
use 
WP_CLI_Command;
use 
WP_Upgrader;
use 
WP_Filesystem_Base;

/**
 * Activates, deactivates or checks the status of the maintenance mode of a site.
 *
 * ## EXAMPLES
 *
 *     # Activate Maintenance mode.
 *     $ wp maintenance-mode activate
 *     Enabling Maintenance mode...
 *     Success: Activated Maintenance mode.
 *
 *     # Deactivate Maintenance mode.
 *     $ wp maintenance-mode deactivate
 *     Disabling Maintenance mode...
 *     Success: Deactivated Maintenance mode.
 *
 *     # Display Maintenance mode status.
 *     $ wp maintenance-mode status
 *     Maintenance mode is active.
 *
 *     # Get Maintenance mode status for scripting purpose.
 *     $ wp maintenance-mode is-active
 *     $ echo $?
 *     1
 *
 * @when    after_wp_load
 * @package wp-cli
 */
class MaintenanceModeCommand extends WP_CLI_Command {


    
/**
     * Instance of WP_Upgrader.
     *
     * @var WP_Upgrader
     */
    
private $upgrader;

    
/**
     * Instantiate a MaintenanceModeCommand object.
     */
    
public function __construct() {
        if ( ! 
class_exists'WP_Upgrader' ) ) {
            require_once 
ABSPATH 'wp-admin/includes/class-wp-upgrader.php';
        }
        
$this->upgrader = new WP_Upgrader( new WP_CLI\UpgraderSkin() );
        
$this->upgrader->init();
    }

    
/**
     * Activates maintenance mode.
     *
     * [--force]
     * : Force maintenance mode activation operation.
     *
     * ## EXAMPLES
     *
     *     $ wp maintenance-mode activate
     *     Enabling Maintenance mode...
     *     Success: Activated Maintenance mode.
     */
    
public function activate$_$assoc_args ) {
        if ( 
$this->get_maintenance_mode_status() && ! WP_CLI\Utils\get_flag_value$assoc_args'force' ) ) {
            
WP_CLI::error'Maintenance mode already activated.' );
        }

        
$this->upgrader->maintenance_modetrue );
        
WP_CLI::success'Activated Maintenance mode.' );
    }

    
/**
     * Deactivates maintenance mode.
     *
     * ## EXAMPLES
     *
     *     $ wp maintenance-mode deactivate
     *     Disabling Maintenance mode...
     *     Success: Deactivated Maintenance mode.
     */
    
public function deactivate() {
        if ( ! 
$this->get_maintenance_mode_status() ) {
            
WP_CLI::error'Maintenance mode already deactivated.' );
        }

        
$this->upgrader->maintenance_modefalse );
        
WP_CLI::success'Deactivated Maintenance mode.' );
    }

    
/**
     * Displays maintenance mode status.
     *
     * ## EXAMPLES
     *
     *     $ wp maintenance-mode status
     *     Maintenance mode is active.
     */
    
public function status() {
        
$status $this->get_maintenance_mode_status() ? 'active' 'not active';
        
WP_CLI::line"Maintenance mode is {$status}." );
    }

    
/**
     * Detects maintenance mode status.
     *
     * ## EXAMPLES
     *
     *     $ wp maintenance-mode is-active
     *     $ echo $?
     *     1
     *
     * @subcommand is-active
     */
    
public function is_active() {
        
WP_CLI::halt$this->get_maintenance_mode_status() ? );
    }

    
/**
     * Returns status of maintenance mode.
     *
     * @return bool
     */
    
private function get_maintenance_mode_status() {
        
$wp_filesystem $this->init_wp_filesystem();

        
$maintenance_file trailingslashit$wp_filesystem->abspath() ) . '.maintenance';

        return 
$wp_filesystem->exists$maintenance_file );
    }

    
/**
     * Initializes WP_Filesystem.
     *
     * @return WP_Filesystem_Base
     */
    
protected function init_wp_filesystem() {
        global 
$wp_filesystem;
        
WP_Filesystem();

        return 
$wp_filesystem;
    }
}
<?
php

if ( ! class_exists'WP_CLI' ) ) {
    return;
}

$wpcli_maintenance_mode_autoloader __DIR__ '/vendor/autoload.php';

if ( 
file_exists$wpcli_maintenance_mode_autoloader ) ) {
    require_once 
$wpcli_maintenance_mode_autoloader;
}

WP_CLI::add_command'maintenance-mode''\WP_CLI\MaintenanceMode\MaintenanceModeCommand' );
<?
php

namespace WP_CLI\Shell;

use 
WP_CLI;

class 
REPL {

    private 
$prompt;

    private 
$history_file;

    public function 
__construct$prompt ) {
        
$this->prompt $prompt;

        
$this->set_history_file();
    }

    public function 
start() {
        while ( 
true ) {
            
$line $this->prompt();

            if ( 
'' === $line ) {
                continue;
            }

            
$line rtrim$line';' ) . ';';

            if ( 
self::starts_withself::non_expressions(), $line ) ) {
                
ob_start();
                
// phpcs:ignore Squiz.PHP.Eval.Discouraged -- This is meant to be a REPL, no way to avoid eval.
                
eval( $line );
                
$out ob_get_clean();
                if ( 
strlen$out ) ) {
                    
$out rtrim$out"\n" ) . "\n";
                }
                
fwriteSTDOUT$out );
            } else {
                if ( ! 
self::starts_with'return'$line ) ) {
                    
$line 'return ' $line;
                }

                
// Write directly to STDOUT, to sidestep any output buffers created by plugins
                
ob_start();
                
// phpcs:ignore Squiz.PHP.Eval.Discouraged -- This is meant to be a REPL, no way to avoid eval.
                
$evl = eval( $line );
                
$out ob_get_clean();
                if ( 
strlen$out ) ) {
                    echo 
rtrim$out"\n" ) . "\n";
                }
                echo 
'=> ';
                
var_dump$evl );
                
fwriteSTDOUTob_get_clean() );
            }
        }
    }

    private static function 
non_expressions() {
        return 
implode(
            
'|',
            array(
                
'echo',
                
'global',
                
'unset',
                
'function',
                
'while',
                
'for',
                
'foreach',
                
'if',
                
'switch',
                
'include',
                
'include\_once',
                
'require',
                
'require\_once',
            )
        );
    }

    private function 
prompt() {
        
$full_line false;

        
$done false;
        do {
            
$prompt = ( ! $done && false !== $full_line ) ? '--> ' $this->prompt;

            
$fp popenself::create_prompt_cmd$prompt$this->history_file ), 'r' );

            
$line fgets$fp );

            
pclose$fp );

            if ( ! 
$line ) {
                break;
            }

            
$line rtrim$line"\n" );

            if ( 
$line && '\\' === $linestrlen$line ) - ] ) {
                
$line substr$line0, -);
            } else {
                
$done true;
            }

            
$full_line .= $line;

        } while ( ! 
$done );

        if ( 
false === $full_line ) {
            return 
'exit';
        }

        return 
$full_line;
    }

    private static function 
create_prompt_cmd$prompt$history_path ) {
        
$prompt       escapeshellarg$prompt );
        
$history_path escapeshellarg$history_path );
        if ( 
getenv'WP_CLI_CUSTOM_SHELL' ) ) {
            
$shell_binary getenv'WP_CLI_CUSTOM_SHELL' );
        } else {
            
$shell_binary '/bin/bash';
        }

        if ( ! 
is_file$shell_binary ) || ! is_readable$shell_binary ) ) {
            
WP_CLI::error"The shell binary '{$shell_binary}' is not valid. You can override the shell to be used through the WP_CLI_CUSTOM_SHELL environment variable." );
        }

        
$shell_binary escapeshellarg$shell_binary );

        
$cmd 'set -f; '
            
"history -r {$history_path}; "
            
'LINE=""; '
            
"read -re -p {$prompt} LINE; "
            
'[ $? -eq 0 ] || exit; '
            
'history -s "$LINE"; '
            
"history -w {$history_path}; "
            
'echo $LINE; ';

        return 
"{$shell_binary} -c " escapeshellarg$cmd );
    }

    private function 
set_history_file() {
        
$data getcwd() . get_current_user();

        
$this->history_file \WP_CLI\Utils\get_temp_dir() . 'wp-cli-history-' md5$data );
    }

    private static function 
starts_with$tokens$line ) {
        return 
preg_match"/^($tokens)[\(\s]+/"$line );
    }
}

<?
php

use WP_CLI\Utils;

class 
Shell_Command extends WP_CLI_Command {

    
/**
     * Opens an interactive PHP console for running and testing PHP code.
     *
     * `wp shell` allows you to evaluate PHP statements and expressions
     * interactively, from within a WordPress environment. Type a bit of code,
     * hit enter, and see the code execute right before you. Because WordPress
     * is loaded, you have access to all the functions, classes and globals
     * that you can use within a WordPress plugin, for example.
     *
     * ## OPTIONS
     *
     * [--basic]
     * : Force the use of WP-CLI's built-in PHP REPL, even if the Boris or
     * PsySH PHP REPLs are available.
     *
     * ## EXAMPLES
     *
     *     # Call get_bloginfo() to get the name of the site.
     *     $ wp shell
     *     wp> get_bloginfo( 'name' );
     *     => string(6) "WP-CLI"
     */
    
public function __invoke$_$assoc_args ) {
        
$implementations = array(
            
'Psy\\Shell',
            
'Boris\\Boris',
            
'WP_CLI\\Shell\\REPL',
        );

        if ( 
Utils\get_flag_value$assoc_args'basic' ) ) {
            
$class 'WP_CLI\\Shell\\REPL';
        } else {
            foreach ( 
$implementations as $candidate ) {
                if ( 
class_exists$candidate ) ) {
                    
$class $candidate;
                    break;
                }
            }
        }

        if ( 
'Psy\\Shell' === $class ) {
            
$shell = new Psy\Shell();
            
$shell->run();
        } else {
            
$repl = new $class'wp> ' );
            
$repl->start();
        }
    }
}
<?
php

if ( ! class_exists'WP_CLI' ) ) {
    return;
}

$wpcli_shell_autoloader dirname__FILE__ ) . '/vendor/autoload.php';
if ( 
file_exists$wpcli_shell_autoloader ) ) {
    require_once 
$wpcli_shell_autoloader;
}

WP_CLI::add_command'shell''Shell_Command' );
#!/usr/bin/env sh

# Special case for *AMP installers, since they normally don't set themselves
# as the default cli php out of the box.
for amp_php in $(cat $(dirname $0)/amp-paths.txt); do
    if [ -
x $amp_php ]; then
        
echo $amp_php
        
exit
    
fi
done

which php 
|| which php-cli

if [ $? -eq 0 ]; then
    
exit
fi

echo "no PHP binary found" >&2
exit 1
<?php
/**
 * List all contributors to this release.
 *
 * Usage: wp --require=utils/contrib-list.php contrib-list
 *
 * If you run into GitHub API rate limit issues, set a GITHUB_TOKEN
 * environment variable.
 */

use WP_CLI\Utils;

class 
Contrib_List_Command {

    
/**
     * List all contributors to this release.
     *
     * Run within the main WP-CLI project repository.
     *
     * ## OPTIONS
     *
     * [--format=<format>]
     * : Render output in a specific format.
     * ---
     * default: markdown
     * options:
     *   - markdown
     *   - html
     * ---
     *
     * @when before_wp_load
     */
    
public function __invoke$_$assoc_args ) {

        
$contributors       = array();
        
$contributor_count  0;
        
$pull_request_count 0;

        
// Get the contributors to the current open large project milestones
        
foreach ( array( 'wp-cli/wp-cli''wp-cli/handbook''wp-cli/wp-cli.github.com' ) as $repo ) {
            
$milestones self::get_project_milestones$repo );
            
// Cheap way to get the latest milestone
            
$milestone array_shift$milestones );
            
WP_CLI::log'Current open ' $repo ' milestone: ' $milestone->title );
            
$pull_requests     self::get_project_milestone_pull_requests$repo$milestone->number );
            
$repo_contributors self::parse_contributors_from_pull_requests$pull_requests );
            
WP_CLI::log' - Contributors: ' count$repo_contributors ) );
            
WP_CLI::log' - Pull requests: ' count$pull_requests ) );
            
$pull_request_count += count$pull_requests );
            
$contributors        array_merge$contributors$repo_contributors );
        }

        
// Identify all command dependencies and their contributors
        
$milestones self::get_project_milestones'wp-cli/wp-cli', array( 'state' => 'closed' ) );
        
// Cheap way to get the latest closed milestone
        
$milestone         array_shift$milestones );
        
$composer_lock_url sprintf'https://raw.githubusercontent.com/wp-cli/wp-cli/v%s/composer.lock'$milestone->title );
        
WP_CLI::log'Fetching ' $composer_lock_url );
        
$response Utils\http_request'GET'$composer_lock_url );
        if ( 
200 !== $response->status_code ) {
            
WP_CLI::errorsprintf'Could not fetch composer.json (HTTP code %d)'$response->status_code ) );
        }
        
$composer_json json_decode$response->bodytrue );
        foreach ( 
$composer_json['packages'] as $package ) {
            
$package_name       $package['name'];
            
$version_constraint str_replace'v'''$package['version'] );
            if ( ! 
preg_match'#^wp-cli/.+-command$#'$package_name ) ) {
                continue;
            }
            
// Closed milestones denote a tagged release
            
$milestones       self::get_project_milestones$package_name, array( 'state' => 'closed' ) );
            
$milestone_ids    = array();
            
$milestone_titles = array();
            foreach ( 
$milestones as $milestone ) {
                if ( ! 
version_compare$milestone->title$version_constraint'>' ) ) {
                    continue;
                }
                
$milestone_ids[]    = $milestone->number;
                
$milestone_titles[] = $milestone->title;
            }
            
// No shipped releases for this milestone.
            
if ( empty( $milestone_ids ) ) {
                continue;
            }
            
WP_CLI::log'Closed ' $package_name ' milestone(s): ' implode', '$milestone_titles ) );
            foreach ( 
$milestone_ids as $milestone_id ) {
                
$pull_requests     self::get_project_milestone_pull_requests$package_name$milestone_id );
                
$repo_contributors self::parse_contributors_from_pull_requests$pull_requests );
                
WP_CLI::log' - Contributors: ' count$repo_contributors ) );
                
WP_CLI::log' - Pull requests: ' count$pull_requests ) );
                
$pull_request_count += count$pull_requests );
                
$contributors        array_merge$contributors$repo_contributors );
            }
        }

        
WP_CLI::log'Total contributors: ' count$contributors ) );
        
WP_CLI::log'Total pull requests: ' $pull_request_count );

        
// Sort and render the contributor list
        
asort$contributorsSORT_NATURAL SORT_FLAG_CASE );
        if ( 
in_array$assoc_args['format'], array( 'markdown''html' ), true ) ) {
            
$contrib_list '';
            foreach ( 
$contributors as $url => $login ) {
                if ( 
'markdown' === $assoc_args['format'] ) {
                    
$contrib_list .= '[' $login '](' $url '), ';
                } elseif ( 
'html' === $assoc_args['format'] ) {
                    
$contrib_list .= '<a href="' $url '">' $login '</a>, ';
                }
            }
            
$contrib_list rtrim$contrib_list', ' );
            
WP_CLI::log$contrib_list );
        }
    }

    
/**
     * Get the milestones for a given project
     *
     * @param string $project
     * @return array
     */
    
private static function get_project_milestones$project$args = array() ) {
        
$request_url            sprintf'https://api.github.com/repos/%s/milestones'$project );
        list( 
$body$headers ) = self::make_github_api_request$request_url$args );
        return 
$body;
    }

    
/**
     * Get the pull requests assigned to a milestone of a given project
     *
     * @param string $project
     * @param integer $milestone_id
     * @return array
     */
    
private static function get_project_milestone_pull_requests$project$milestone_id ) {
        
$request_url   sprintf'https://api.github.com/repos/%s/issues'$project );
        
$args          = array(
            
'milestone' => $milestone_id,
            
'state'     => 'all',
        );
        
$pull_requests = array();
        do {
            list( 
$body$headers ) = self::make_github_api_request$request_url$args );
            foreach ( 
$body as $issue ) {
                if ( ! empty( 
$issue->pull_request ) ) {
                    
$pull_requests[] = $issue;
                }
            }
            
$args        = array();
            
$request_url false;
            
// Set $request_url to 'rel="next" if present'
            
if ( ! empty( $headers['Link'] ) ) {
                
$bits trimexplode','$headers['Link'] ) );
                foreach ( 
$bits as $bit ) {
                    if ( 
false !== stripos$bit'rel="next"' ) ) {
                        
$hrefandrel  explode'; '$bit );
                        
$request_url trim$hrefandrel[0], '<>' );
                        break;
                    }
                }
            }
        } while ( 
$request_url );
        return 
$pull_requests;
    }

    
/**
     * Parse the contributors from pull request objects
     *
     * @param array $pull_requests
     * @return array
     */
    
private static function parse_contributors_from_pull_requests$pull_requests ) {
        
$contributors = array();
        foreach ( 
$pull_requests as $pull_request ) {
            if ( ! empty( 
$pull_request->user ) ) {
                
$contributors$pull_request->user->html_url ] = $pull_request->user->login;
            }
        }
        return 
$contributors;
    }

    
/**
     * Make a request to the GitHub API
     *
     * @param string $url
     * @param string $args
     * @return array
     */
    
private static function make_github_api_request$url$args = array() ) {
        
$headers = array(
            
'Accept'     => 'application/vnd.github.v3+json',
            
'User-Agent' => 'WP-CLI',
        );
        
$token   getenv'GITHUB_TOKEN' );
        if ( 
false !== $token ) {
            
$headers['Authorization'] = 'token ' $token;
        }
        
$response Utils\http_request'GET'$url$args$headers );
        if ( 
200 !== $response->status_code ) {
            
WP_CLI::errorsprintf'GitHub API returned: %s (HTTP code %d)'$response->body$response->status_code ) );
        }
        return array( 
json_decode$response->body ), $response->headers );
    }

}

WP_CLI::add_command'contrib-list''Contrib_List_Command' );
<?
php

$file 
$argv[1];
if ( ! 
file_exists$file ) ) {
    echo 
'File does not exist.';
    exit( 
);
}

$contents file_get_contents$file );
$composer json_decode$contents );

if ( empty( 
$composer ) || ! is_object$composer ) ) {
    echo 
'Invalid composer.json for package.';
    exit( 
);
}

if ( empty( 
$composer->autoload->files ) ) {
    echo 
'composer.json must specify valid "autoload" => "files"';
    exit( 
);
}

echo 
implodePHP_EOL$composer->autoload->files );
exit( 
);
<?
php

namespace WP_CLI;

use 
ArrayObject;
use 
Exception;

class 
SearchReplacer {

    private 
$from;
    private 
$to;
    private 
$recurse_objects;
    private 
$regex;
    private 
$regex_flags;
    private 
$regex_delimiter;
    private 
$regex_limit;
    private 
$logging;
    private 
$log_data;
    private 
$max_recursion;

    
/**
     * @param string  $from            String we're looking to replace.
     * @param string  $to              What we want it to be replaced with.
     * @param bool    $recurse_objects Should objects be recursively replaced?
     * @param bool    $regex           Whether `$from` is a regular expression.
     * @param string  $regex_flags     Flags for regular expression.
     * @param string  $regex_delimiter Delimiter for regular expression.
     * @param bool    $logging         Whether logging.
     * @param integer $regex_limit     The maximum possible replacements for each pattern in each subject string.
     */
    
public function __construct$from$to$recurse_objects false$regex false$regex_flags ''$regex_delimiter '/'$logging false$regex_limit = -) {
        
$this->from            $from;
        
$this->to              $to;
        
$this->recurse_objects $recurse_objects;
        
$this->regex           $regex;
        
$this->regex_flags     $regex_flags;
        
$this->regex_delimiter $regex_delimiter;
        
$this->regex_limit     $regex_limit;
        
$this->logging         $logging;
        
$this->clear_log_data();

        
// Get the XDebug nesting level. Will be zero (no limit) if no value is set
        
$this->max_recursion intvalini_get'xdebug.max_nesting_level' ) );
    }

    
/**
     * Take a serialised array and unserialise it replacing elements as needed and
     * unserialising any subordinate arrays and performing the replace on those too.
     * Ignores any serialized objects unless $recurse_objects is set to true.
     *
     * @param array|string $data            The data to operate on.
     * @param bool         $serialised      Does the value of $data need to be unserialized?
     *
     * @return array       The original array with all elements replaced as needed.
     */
    
public function run$data$serialised false ) {
        return 
$this->run_recursively$data$serialised );
    }

    
/**
     * @param int          $recursion_level Current recursion depth within the original data.
     * @param array        $visited_data    Data that has been seen in previous recursion iterations.
     */
    
private function run_recursively$data$serialised$recursion_level 0$visited_data = array() ) {

        
// some unseriliased data cannot be re-serialised eg. SimpleXMLElements
        
try {

            if ( 
$this->recurse_objects ) {

                
// If we've reached the maximum recursion level, short circuit
                
if ( !== $this->max_recursion && $recursion_level >= $this->max_recursion ) {
                    return 
$data;
                }

                if ( 
is_array$data ) || is_object$data ) ) {
                    
// If we've seen this exact object or array before, short circuit
                    
if ( in_array$data$visited_datatrue ) ) {
                        return 
$data// Avoid infinite loops when there's a cycle
                    
}
                    
// Add this data to the list of
                    
$visited_data[] = $data;
                }
            }

            
$unserialized is_string$data ) ? @unserialize$data ) : false;
            if ( 
false !== $unserialized ) {
                
$data $this->run_recursively$unserializedtrue$recursion_level );
            } elseif ( 
is_array$data ) ) {
                
$keys array_keys$data );
                foreach ( 
$keys as $key ) {
                    
$data$key ] = $this->run_recursively$data$key ], false$recursion_level 1$visited_data );
                }
            } elseif ( 
$this->recurse_objects && ( is_object$data ) || $data instanceof \__PHP_Incomplete_Class ) ) {
                if ( 
$data instanceof \__PHP_Incomplete_Class ) {
                    
$array = new ArrayObject$data );
                    
\WP_CLI::warning(
                        
sprintf(
                            
'Skipping an uninitialized class "%s", replacements might not be complete.',
                            
$array['__PHP_Incomplete_Class_Name']
                        )
                    );
                } else {
                    foreach ( 
$data as $key => $value ) {
                        
$data->$key $this->run_recursively$valuefalse$recursion_level 1$visited_data );
                    }
                }
            } elseif ( 
is_string$data ) ) {
                if ( 
$this->logging ) {
                    
$old_data $data;
                }
                if ( 
$this->regex ) {
                    
$search_regex  $this->regex_delimiter;
                    
$search_regex .= $this->from;
                    
$search_regex .= $this->regex_delimiter;
                    
$search_regex .= $this->regex_flags;

                    
$result preg_replace$search_regex$this->to$data$this->regex_limit );
                    if ( 
null === $result || PREG_NO_ERROR !== preg_last_error() ) {
                        
\WP_CLI::warning(
                            
sprintf(
                                
'The provided regular expression threw a PCRE error - %s',
                                
$this->preg_error_message$result )
                            )
                        );
                    }
                    
$data $result;
                } else {
                    
$data str_replace$this->from$this->to$data );
                }
                if ( 
$this->logging && $old_data !== $data ) {
                    
$this->log_data[] = $old_data;
                }
            }

            if ( 
$serialised ) {
                return 
serialize$data );
            }
        } catch ( 
Exception $error ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch -- Deliberally empty.

        
}

        return 
$data;
    }

    
/**
     * Gets existing data saved for this run when logging.
     * @return array Array of data strings, prior to replacements.
     */
    
public function get_log_data() {
        return 
$this->log_data;
    }

    
/**
     * Clears data stored for logging.
     */
    
public function clear_log_data() {
        
$this->log_data = array();
    }

    
/**
     * Get the PCRE error constant name from an error value.
     *
     * @param  integer $error Error code.
     * @return string         Error constant name.
     */
    
private function preg_error_message$error ) {
        static 
$error_names null;

        if ( 
null === $error_names ) {
            
$definitions    get_defined_constantstrue );
            
$pcre_constants array_key_exists'pcre'$definitions )
                ? 
$definitions['pcre']
                : array();
            
$error_names    array_flip$pcre_constants );
        }

        return isset( 
$error_names$error ] )
            ? 
$error_names$error ]
            : 
'<unknown error>';
    }
}
<?
php

use cli\Colors;
use 
cli\Table;
use 
WP_CLI\Iterators;
use 
WP_CLI\SearchReplacer;
use 
WP_CLI\Utils;
use function 
cli\safe_substr;

class 
Search_Replace_Command extends WP_CLI_Command {

    private 
$dry_run;
    private 
$export_handle false;
    private 
$export_insert_size;
    private 
$recurse_objects;
    private 
$regex;
    private 
$regex_flags;
    private 
$regex_delimiter;
    private 
$regex_limit = -1;
    private 
$skip_tables;
    private 
$skip_columns;
    private 
$include_columns;
    private 
$format;
    private 
$report;
    private 
$verbose;

    private 
$report_changed_only;
    private 
$log_handle         null;
    private 
$log_before_context 40;
    private 
$log_after_context  40;
    private 
$log_prefixes       = array( '< ''> ' );
    private 
$log_colors;
    private 
$log_encoding;

    
/**
     * Searches/replaces strings in the database.
     *
     * Searches through all rows in a selection of tables and replaces
     * appearances of the first string with the second string.
     *
     * By default, the command uses tables registered to the `$wpdb` object. On
     * multisite, this will just be the tables for the current site unless
     * `--network` is specified.
     *
     * Search/replace intelligently handles PHP serialized data, and does not
     * change primary key values.
     *
     * ## OPTIONS
     *
     * <old>
     * : A string to search for within the database.
     *
     * <new>
     * : Replace instances of the first string with this new string.
     *
     * [<table>...]
     * : List of database tables to restrict the replacement to. Wildcards are
     * supported, e.g. `'wp_*options'` or `'wp_post*'`.
     *
     * [--dry-run]
     * : Run the entire search/replace operation and show report, but don't save
     * changes to the database.
     *
     * [--network]
     * : Search/replace through all the tables registered to $wpdb in a
     * multisite install.
     *
     * [--all-tables-with-prefix]
     * : Enable replacement on any tables that match the table prefix even if
     * not registered on $wpdb.
     *
     * [--all-tables]
     * : Enable replacement on ALL tables in the database, regardless of the
     * prefix, and even if not registered on $wpdb. Overrides --network
     * and --all-tables-with-prefix.
     *
     * [--export[=<file>]]
     * : Write transformed data as SQL file instead of saving replacements to
     * the database. If <file> is not supplied, will output to STDOUT.
     *
     * [--export_insert_size=<rows>]
     * : Define number of rows in single INSERT statement when doing SQL export.
     * You might want to change this depending on your database configuration
     * (e.g. if you need to do fewer queries). Default: 50
     *
     * [--skip-tables=<tables>]
     * : Do not perform the replacement on specific tables. Use commas to
     * specify multiple tables. Wildcards are supported, e.g. `'wp_*options'` or `'wp_post*'`.
     *
     * [--skip-columns=<columns>]
     * : Do not perform the replacement on specific columns. Use commas to
     * specify multiple columns.
     *
     * [--include-columns=<columns>]
     * : Perform the replacement on specific columns. Use commas to
     * specify multiple columns.
     *
     * [--precise]
     * : Force the use of PHP (instead of SQL) which is more thorough,
     * but slower.
     *
     * [--recurse-objects]
     * : Enable recursing into objects to replace strings. Defaults to true;
     * pass --no-recurse-objects to disable.
     *
     * [--verbose]
     * : Prints rows to the console as they're updated.
     *
     * [--regex]
     * : Runs the search using a regular expression (without delimiters).
     * Warning: search-replace will take about 15-20x longer when using --regex.
     *
     * [--regex-flags=<regex-flags>]
     * : Pass PCRE modifiers to regex search-replace (e.g. 'i' for case-insensitivity).
     *
     * [--regex-delimiter=<regex-delimiter>]
     * : The delimiter to use for the regex. It must be escaped if it appears in the search string. The default value is the result of `chr(1)`.
     *
     * [--regex-limit=<regex-limit>]
     * : The maximum possible replacements for the regex per row (or per unserialized data bit per row). Defaults to -1 (no limit).
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - count
     * ---
     *
     * [--report]
     * : Produce report. Defaults to true.
     *
     * [--report-changed-only]
     * : Report changed fields only. Defaults to false, unless logging, when it defaults to true.
     *
     * [--log[=<file>]]
     * : Log the items changed. If <file> is not supplied or is "-", will output to STDOUT.
     * Warning: causes a significant slow down, similar or worse to enabling --precise or --regex.
     *
     * [--before_context=<num>]
     * : For logging, number of characters to display before the old match and the new replacement. Default 40. Ignored if not logging.
     *
     * [--after_context=<num>]
     * : For logging, number of characters to display after the old match and the new replacement. Default 40. Ignored if not logging.
     *
     * ## EXAMPLES
     *
     *     # Search and replace but skip one column
     *     $ wp search-replace 'http://example.test' 'http://example.com' --skip-columns=guid
     *
     *     # Run search/replace operation but dont save in database
     *     $ wp search-replace 'foo' 'bar' wp_posts wp_postmeta wp_terms --dry-run
     *
     *     # Run case-insensitive regex search/replace operation (slow)
     *     $ wp search-replace '\[foo id="([0-9]+)"' '[bar id="\1"' --regex --regex-flags='i'
     *
     *     # Turn your production multisite database into a local dev database
     *     $ wp search-replace --url=example.com example.com example.test 'wp_*options' wp_blogs
     *
     *     # Search/replace to a SQL file without transforming the database
     *     $ wp search-replace foo bar --export=database.sql
     *
     *     # Bash script: Search/replace production to development url (multisite compatible)
     *     #!/bin/bash
     *     if $(wp --url=http://example.com core is-installed --network); then
     *         wp search-replace --url=http://example.com 'http://example.com' 'http://example.test' --recurse-objects --network --skip-columns=guid --skip-tables=wp_users
     *     else
     *         wp search-replace 'http://example.com' 'http://example.test' --recurse-objects --skip-columns=guid --skip-tables=wp_users
     *     fi
     */
    
public function __invoke$args$assoc_args ) {
        global 
$wpdb;
        
$old                   array_shift$args );
        
$new                   array_shift$args );
        
$total                 0;
        
$report                = array();
        
$this->dry_run         Utils\get_flag_value$assoc_args'dry-run' );
        
$php_only              Utils\get_flag_value$assoc_args'precise' );
        
$this->recurse_objects Utils\get_flag_value$assoc_args'recurse-objects'true );
        
$this->verbose         Utils\get_flag_value$assoc_args'verbose' );
        
$this->format          Utils\get_flag_value$assoc_args'format' );
        
$this->regex           Utils\get_flag_value$assoc_args'regex'false );

        if ( 
null !== $this->regex ) {
            
$default_regex_delimiter false;
            
$this->regex_flags       Utils\get_flag_value$assoc_args'regex-flags'false );
            
$this->regex_delimiter   Utils\get_flag_value$assoc_args'regex-delimiter''' );
            if ( 
'' === $this->regex_delimiter ) {
                
$this->regex_delimiter   chr);
                
$default_regex_delimiter true;
            }
        }

        
$regex_limit Utils\get_flag_value$assoc_args'regex-limit' );
        if ( 
null !== $regex_limit ) {
            if ( ! 
preg_match'/^(?:[0-9]+|-1)$/'$regex_limit ) || === (int) $regex_limit ) {
                
WP_CLI::error'`--regex-limit` expects a non-zero positive integer or -1.' );
            }
            
$this->regex_limit = (int) $regex_limit;
        }

        if ( ! empty( 
$this->regex ) ) {
            if ( 
'' === $this->regex_delimiter ) {
                
$this->regex_delimiter chr);
            }
            
$search_regex  $this->regex_delimiter;
            
$search_regex .= $old;
            
$search_regex .= $this->regex_delimiter;
            
$search_regex .= $this->regex_flags;

            
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Preventing a warning when testing the regex.
            
if ( false === @preg_match$search_regex'' ) ) {
                
$error              error_get_last();
                
$preg_error_message = ( ! empty( $error ) && array_key_exists'message'$error ) ) ? "\n{$error['message']}." '';
                if ( 
$default_regex_delimiter ) {
                    
$flags_msg $this->regex_flags "flags '$this->regex_flags'" 'no flags';
                    
$msg       "The regex pattern '$old' with default delimiter 'chr(1)' and {$flags_msg} fails.";
                } else {
                    
$msg "The regex '$search_regex' fails.";
                }
                
WP_CLI::error$msg $preg_error_message );
            }
        }

        
$this->skip_columns    explode','Utils\get_flag_value$assoc_args'skip-columns' ) );
        
$this->skip_tables     explode','Utils\get_flag_value$assoc_args'skip-tables' ) );
        
$this->include_columns array_filterexplode','Utils\get_flag_value$assoc_args'include-columns' ) ) );

        if ( 
$old === $new && ! $this->regex ) {
            
WP_CLI::warning"Replacement value '{$old}' is identical to search value '{$new}'. Skipping operation." );
            exit;
        }

        
$export Utils\get_flag_value$assoc_args'export' );
        if ( 
null !== $export ) {
            if ( 
$this->dry_run ) {
                
WP_CLI::error'You cannot supply --dry-run and --export at the same time.' );
            }
            if ( 
true === $export ) {
                
$this->export_handle STDOUT;
                
$this->verbose       false;
            } else {
                
$this->export_handle = @fopen$assoc_args['export'], 'w' );
                if ( 
false === $this->export_handle ) {
                    
$error error_get_last();
                    
WP_CLI::errorsprintf'Unable to open export file "%s" for writing: %s.'$assoc_args['export'], $error['message'] ) );
                }
            }
            
$export_insert_size Utils\get_flag_value$assoc_args'export_insert_size'50 );
            
// phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison -- See the code, this is deliberate.
            
if ( (int) $export_insert_size == $export_insert_size && $export_insert_size ) {
                
$this->export_insert_size $export_insert_size;
            }
            
$php_only true;
        }

        
$log Utils\get_flag_value$assoc_args'log' );
        if ( 
null !== $log ) {
            if ( 
true === $log || '-' === $log ) {
                
$this->log_handle STDOUT;
            } else {
                
$this->log_handle = @fopen$assoc_args['log'], 'w' );
                if ( 
false === $this->log_handle ) {
                    
$error error_get_last();
                    
WP_CLI::errorsprintf'Unable to open log file "%s" for writing: %s.'$assoc_args['log'], $error['message'] ) );
                }
            }
            if ( 
$this->log_handle ) {
                
$before_context Utils\get_flag_value$assoc_args'before_context' );
                if ( 
null !== $before_context && preg_match'/^[0-9]+$/'$before_context ) ) {
                    
$this->log_before_context = (int) $before_context;
                }

                
$after_context Utils\get_flag_value$assoc_args'after_context' );
                if ( 
null !== $after_context && preg_match'/^[0-9]+$/'$after_context ) ) {
                    
$this->log_after_context = (int) $after_context;
                }

                
$log_prefixes getenv'WP_CLI_SEARCH_REPLACE_LOG_PREFIXES' );
                if ( 
false !== $log_prefixes && preg_match'/^([^,]*),([^,]*)$/'$log_prefixes$matches ) ) {
                    
$this->log_prefixes = array( $matches[1], $matches[2] );
                }

                if ( 
STDOUT === $this->log_handle ) {
                    
$default_log_colors = array(
                        
'log_table_column_id' => '%B',
                        
'log_old'             => '%R',
                        
'log_new'             => '%G',
                    );
                } else {
                    
$default_log_colors = array(
                        
'log_table_column_id' => '',
                        
'log_old'             => '',
                        
'log_new'             => '',
                    );
                }

                
$log_colors getenv'WP_CLI_SEARCH_REPLACE_LOG_COLORS' );
                if ( 
false !== $log_colors && preg_match'/^([^,]*),([^,]*),([^,]*)$/'$log_colors$matches ) ) {
                    
$default_log_colors = array(
                        
'log_table_column_id' => $matches[1],
                        
'log_old'             => $matches[2],
                        
'log_new'             => $matches[3],
                    );
                }

                
$this->log_colors   $this->get_colors$assoc_args$default_log_colors );
                
$this->log_encoding === strpos$wpdb->charset'utf8' ) ? 'UTF-8' false;
            }
        }

        
$this->report Utils\get_flag_value$assoc_args'report'true );
        
// Defaults to true if logging, else defaults to false.
        
$this->report_changed_only Utils\get_flag_value$assoc_args'report-changed-only'null !== $this->log_handle );

        if ( 
$this->regex_flags ) {
            
$php_only true;
        }

        
// never mess with hashed passwords
        
$this->skip_columns[] = 'user_pass';

        
// Get table names based on leftover $args or supplied $assoc_args
        
$tables Utils\wp_get_table_names$args$assoc_args );

        foreach ( 
$tables as $table ) {

            foreach ( 
$this->skip_tables as $skip_table ) {
                if ( 
fnmatch$skip_table$table ) ) {
                    continue 
2;
                }
            }

            
$table_sql self::esc_sql_ident$table );

            if ( 
$this->export_handle ) {
                
fwrite$this->export_handle"\nDROP TABLE IF EXISTS $table_sql;\n" );

                
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- escaped through self::esc_sql_ident
                
$row $wpdb->get_row"SHOW CREATE TABLE $table_sql"ARRAY_N );

                
fwrite$this->export_handle$row[1] . ";\n" );
                list( 
$table_report$total_rows ) = $this->php_export_table$table$old$new );
                if ( 
$this->report ) {
                    
$report array_merge$report$table_report );
                }
                
$total += $total_rows;
                
// Don't perform replacements on the actual database
                
continue;
            }

            list( 
$primary_keys$columns$all_columns ) = self::get_columns$table );

            
// since we'll be updating one row at a time,
            // we need a primary key to identify the row
            
if ( empty( $primary_keys ) ) {

                
// wasn't updated, so skip to the next table
                
if ( $this->report_changed_only ) {
                    continue;
                }
                if ( 
$this->report ) {
                    
$report[] = array( $table'''skipped''' );
                } else {
                    
WP_CLI::warning$all_columns "No primary keys for table '$table'." "No such table '$table'." );
                }
                continue;
            }

            foreach ( 
$columns as $col ) {
                if ( ! empty( 
$this->include_columns ) && ! in_array$col$this->include_columnstrue ) ) {
                    continue;
                }

                if ( 
in_array$col$this->skip_columnstrue ) ) {
                    continue;
                }

                if ( 
$this->verbose && 'count' !== $this->format ) {
                    
$this->start_time microtimetrue );
                    
WP_CLI::logsprintf'Checking: %s.%s'$table$col ) );
                }

                if ( ! 
$php_only && ! $this->regex ) {
                    
$col_sql          self::esc_sql_ident$col );
                    
$wpdb->last_error '';

                    
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- escaped through self::esc_sql_ident
                    
$serial_row $wpdb->get_row"SELECT * FROM $table_sql WHERE $col_sql REGEXP '^[aiO]:[1-9]' LIMIT 1" );

                    
// When the regex triggers an error, we should fall back to PHP
                    
if ( false !== strpos$wpdb->last_error'ERROR 1139' ) ) {
                        
$serial_row true;
                    }
                }

                if ( 
$php_only || $this->regex || null !== $serial_row ) {
                    
$type  'PHP';
                    
$count $this->php_handle_col$col$primary_keys$table$old$new );
                } else {
                    
$type  'SQL';
                    
$count $this->sql_handle_col$col$primary_keys$table$old$new );
                }

                if ( 
$this->report && ( $count || ! $this->report_changed_only ) ) {
                    
$report[] = array( $table$col$count$type );
                }

                
$total += $count;
            }
        }

        if ( 
$this->export_handle && STDOUT !== $this->export_handle ) {
            
fclose$this->export_handle );
        }

        
// Only informational output after this point
        
if ( WP_CLI::get_config'quiet' ) || STDOUT === $this->export_handle ) {
            return;
        }

        if ( 
'count' === $this->format ) {
            
WP_CLI::line$total );
            return;
        }

        if ( 
$this->report && ! empty( $report ) ) {
            
$table = new Table();
            
$table->setHeaders( array( 'Table''Column''Replacements''Type' ) );
            
$table->setRows$report );
            
$table->display();
        }

        if ( ! 
$this->dry_run ) {
            if ( ! empty( 
$assoc_args['export'] ) ) {
                
$success_message === $total "Made 1 replacement and exported to {$assoc_args['export']}." "Made {$total} replacements and exported to {$assoc_args['export']}.";
            } else {
                
$success_message === $total 'Made 1 replacement.' "Made $total replacements.";
                if ( 
$total && 'Default' !== Utils\wp_get_cache_type() ) {
                    
$success_message .= ' Please remember to flush your persistent object cache with `wp cache flush`.';
                }
            }
            
WP_CLI::success$success_message );
        } else {
            
$success_message = ( === $total ) ? '%d replacement to be made.' '%d replacements to be made.';
            
WP_CLI::successsprintf$success_message$total ) );
        }
    }

    private function 
php_export_table$table$old$new ) {
        list( 
$primary_keys$columns$all_columns ) = self::get_columns$table );

        
$chunk_size getenv'BEHAT_RUN' ) ? 10 1000;
        
$args       = array(
            
'table'      => $table,
            
'fields'     => $all_columns,
            
'chunk_size' => $chunk_size,
        );

        
$replacer   = new SearchReplacer$old$new$this->recurse_objects$this->regex$this->regex_flags$this->regex_delimiterfalse$this->regex_limit );
        
$col_counts array_fill_keys$all_columns);
        if ( 
$this->verbose && 'table' === $this->format ) {
            
$this->start_time microtimetrue );
            
WP_CLI::logsprintf'Checking: %s'$table ) );
        }

        
$rows = array();
        foreach ( new 
Iterators\Table$args ) as $i => $row ) {
            
$row_fields = array();
            foreach ( 
$all_columns as $col ) {
                
$value $row->$col;
                if ( 
$value && ! in_array$col$primary_keystrue ) && ! in_array$col$this->skip_columnstrue ) ) {
                    
$new_value $replacer->run$value );
                    if ( 
$new_value !== $value ) {
                        
$col_counts$col ]++;
                        
$value $new_value;
                    }
                }
                
$row_fields$col ] = $value;
            }
            
$rows[] = $row_fields;
        }
        
$this->write_sql_row_fields$table$rows );

        
$table_report = array();
        
$total_rows   0;
        
$total_cols   0;
        foreach ( 
$col_counts as $col => $col_count ) {
            if ( 
$this->report && ( $col_count || ! $this->report_changed_only ) ) {
                
$table_report[] = array( $table$col$col_count'PHP' );
            }
            if ( 
$col_count ) {
                
$total_cols++;
                
$total_rows += $col_count;
            }
        }

        if ( 
$this->verbose && 'table' === $this->format ) {
            
$time roundmicrotimetrue ) - $this->start_time);
            
WP_CLI::logsprintf'%d columns and %d total rows affected using PHP (in %ss).'$total_cols$total_rows$time ) );
        }

        return array( 
$table_report$total_rows );
    }

    private function 
sql_handle_col$col$primary_keys$table$old$new ) {
        global 
$wpdb;

        
$table_sql self::esc_sql_ident$table );
        
$col_sql   self::esc_sql_ident$col );
        if ( 
$this->dry_run ) {
            if ( 
$this->log_handle ) {
                
$count $this->log_sql_diff$col$primary_keys$table$old$new );
            } else {
                
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- escaped through self::esc_sql_ident
                
$count $wpdb->get_var$wpdb->prepare"SELECT COUNT($col_sql) FROM $table_sql WHERE $col_sql LIKE BINARY %s;"'%' self::esc_like$old ) . '%' ) );
            }
        } else {
            if ( 
$this->log_handle ) {
                
$this->log_sql_diff$col$primary_keys$table$old$new );
            }
            
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- escaped through self::esc_sql_ident
            
$count $wpdb->query$wpdb->prepare"UPDATE $table_sql SET $col_sql = REPLACE($col_sql, %s, %s);"$old$new ) );
        }

        if ( 
$this->verbose && 'table' === $this->format ) {
            
$time roundmicrotimetrue ) - $this->start_time);
            
WP_CLI::logsprintf'%d rows affected using SQL (in %ss).'$count$time ) );
        }
        return 
$count;
    }

    private function 
php_handle_col$col$primary_keys$table$old$new ) {
        global 
$wpdb;

        
$count    0;
        
$replacer = new SearchReplacer$old$new$this->recurse_objects$this->regex$this->regex_flags$this->regex_delimiternull !== $this->log_handle$this->regex_limit );

        
$table_sql            self::esc_sql_ident$table );
        
$col_sql              self::esc_sql_ident$col );
        
$where                $this->regex '' " WHERE $col_sql$wpdb->prepare' LIKE BINARY %s''%' self::esc_like$old ) . '%' );
        
$escaped_primary_keys self::esc_sql_ident$primary_keys );
        
$primary_keys_sql     implode','$escaped_primary_keys );
        
$order_by_keys        array_map(
            static function ( 
$key ) {
                return 
"{$key} ASC";
            },
            
$escaped_primary_keys
        
);
        
$order_by_sql         'ORDER BY ' implode','$order_by_keys );
        
$limit                1000;
        
$offset               0;

        
// Updates have to be deferred to after the chunking is completed, as
        // the offset will otherwise not work correctly.
        
$updates = [];

        
// 2 errors:
        // - WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- escaped through self::esc_sql_ident
        // - WordPress.CodeAnalysis.AssignmentInCondition -- no reason to do copy-paste for a single valid assignment in while
        // phpcs:ignore
        
while ( $rows $wpdb->get_results"SELECT {$primary_keys_sql} FROM {$table_sql} {$where} {$order_by_sql} LIMIT {$limit} OFFSET {$offset}) ) {
            foreach ( 
$rows as $keys ) {
                
$where_sql '';
                foreach ( (array) 
$keys as $k => $v ) {
                    if ( 
'' !== $where_sql ) {
                        
$where_sql .= ' AND ';
                    }
                    
$where_sql .= self::esc_sql_ident$k ) . ' = ' self::esc_sql_value$v );
                }

                
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- escaped through self::esc_sql_ident
                
$col_value $wpdb->get_var"SELECT {$col_sql} FROM {$table_sql} WHERE {$where_sql});

                if ( 
'' === $col_value ) {
                    continue;
                }

                
$value $replacer->run$col_value );

                if ( 
$value === $col_value ) {
                    continue;
                }

                if ( 
$this->log_handle ) {
                    
$this->log_php_diff$col$keys$table$old$new$replacer->get_log_data() );
                    
$replacer->clear_log_data();
                }

                
$count++;
                if ( ! 
$this->dry_run ) {
                    
$update_where = array();
                    foreach ( (array) 
$keys as $k => $v ) {
                        
$update_where$k ] = $v;
                    }

                    
$updates[] = [ $table, array( $col => $value ), $update_where ];
                }
            }

            
$offset += $limit;
        }

        foreach ( 
$updates as $update ) {
            
$wpdb->update( ...$update );
        }

        if ( 
$this->verbose && 'table' === $this->format ) {
            
$time roundmicrotimetrue ) - $this->start_time);
            
WP_CLI::logsprintf'%d rows affected using PHP (in %ss).'$count$time ) );
        }

        return 
$count;
    }

    private function 
write_sql_row_fields$table$rows ) {
        global 
$wpdb;

        if ( empty( 
$rows ) ) {
            return;
        }

        
$table_sql self::esc_sql_ident$table );

        
$insert  "INSERT INTO $table_sql (";
        
$insert .= join', 'self::esc_sql_identarray_keys$rows[0] ) ) );
        
$insert .= ') VALUES ';
        
$insert .= "\n";

        
$sql    $insert;
        
$values = array();

        
$index              1;
        
$count              count$rows );
        
$export_insert_size $this->export_insert_size;

        foreach ( 
$rows as $row_fields ) {
            
$subs = array();

            foreach ( 
$row_fields as $field_value ) {
                if ( 
null === $field_value ) {
                    
$subs[] = 'NULL';
                } else {
                    
$subs[]   = '%s';
                    
$values[] = $field_value;
                }
            }

            
$sql .= '(' join', '$subs ) . ')';

            
// Add new insert statement if needed. Before this we close the previous with semicolon and write statement to sql-file.
            // "Statement break" is needed:
            //        1. When the loop is running every nth time (where n is insert statement size, $export_index_size). Remainder is zero also on first round, so it have to be excluded.
            //            $index % $export_insert_size == 0 && $index > 0
            //        2. Or when the loop is running last time
            //            $index == $count
            
if ( ( === $index $export_insert_size && $index ) || $index === $count ) {
                
$sql .= ";\n";

                if ( 
method_exists$wpdb'remove_placeholder_escape' ) ) {
                    
// since 4.8.3
                    // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- verified inputs above
                    
$sql $wpdb->remove_placeholder_escape$wpdb->prepare$sqlarray_values$values ) ) );
                } else {
                    
// 4.8.2 or less
                    // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- verified inputs above
                    
$sql $wpdb->prepare$sqlarray_values$values ) );
                }

                
fwrite$this->export_handle$sql );

                
// If there is still rows to loop, reset $sql and $values variables.
                
if ( $count $index ) {
                    
$sql    $insert;
                    
$values = array();
                }
            } else { 
// Otherwise just add comma and new line
                
$sql .= ",\n";
            }

            
$index++;
        }
    }

    private static function 
get_columns$table ) {
        global 
$wpdb;

        
$table_sql       self::esc_sql_ident$table );
        
$primary_keys    = array();
        
$text_columns    = array();
        
$all_columns     = array();
        
$suppress_errors $wpdb->suppress_errors();

        
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- escaped through self::esc_sql_ident
        
$results $wpdb->get_results"DESCRIBE $table_sql);

        if ( ! empty( 
$results ) ) {
            foreach ( 
$results as $col ) {
                if ( 
'PRI' === $col->Key ) {
                    
$primary_keys[] = $col->Field;
                }
                if ( 
self::is_text_col$col->Type ) ) {
                    
$text_columns[] = $col->Field;
                }
                
$all_columns[] = $col->Field;
            }
        }
        
$wpdb->suppress_errors$suppress_errors );
        return array( 
$primary_keys$text_columns$all_columns );
    }

    private static function 
is_text_col$type ) {
        foreach ( array( 
'text''varchar' ) as $token ) {
            if ( 
false !== strpos$type$token ) ) {
                return 
true;
            }
        }

        return 
false;
    }

    private static function 
esc_like$old ) {
        global 
$wpdb;

        
// Remove notices in 4.0 and support backwards compatibility
        
if ( method_exists$wpdb'esc_like' ) ) {
            
// 4.0
            
$old $wpdb->esc_like$old );
        } else {
            
// phpcs:ignore WordPress.WP.DeprecatedFunctions.like_escapeFound -- BC-layer for WP 3.9 or less.
            
$old like_escapeesc_sql$old ) ); // Note: this double escaping is actually necessary, even though `esc_like()` will be used in a `prepare()`.
        
}

        return 
$old;
    }

    
/**
     * Escapes (backticks) MySQL identifiers (aka schema object names) - i.e. column names, table names, and database/index/alias/view etc names.
     * See https://dev.mysql.com/doc/refman/5.5/en/identifiers.html
     *
     * @param string|array $idents A single identifier or an array of identifiers.
     * @return string|array An escaped string if given a string, or an array of escaped strings if given an array of strings.
     */
    
private static function esc_sql_ident$idents ) {
        
$backtick = static function ( $v ) {
            
// Escape any backticks in the identifier by doubling.
            
return '`' str_replace'`''``'$v ) . '`';
        };
        if ( 
is_string$idents ) ) {
            return 
$backtick$idents );
        }
        return 
array_map$backtick$idents );
    }

    
/**
     * Puts MySQL string values in single quotes, to avoid them being interpreted as column names.
     *
     * @param string|array $values A single value or an array of values.
     * @return string|array A quoted string if given a string, or an array of quoted strings if given an array of strings.
     */
    
private static function esc_sql_value$values ) {
        
$quote = static function ( $v ) {
            
// Don't quote integer values to avoid MySQL's implicit type conversion.
            
if ( preg_match'/^[+-]?[0-9]{1,20}$/'$v ) ) { // MySQL BIGINT UNSIGNED max 18446744073709551615 (20 digits).
                
return esc_sql$v );
            }

            
// Put any string values between single quotes.
            
return "'" esc_sql$v ) . "'";
        };

        if ( 
is_array$values ) ) {
            return 
array_map$quote$values );
        }

        return 
$quote$values );
    }

    
/**
     * Gets the color codes from the options if any, and returns the passed in array colorized with 2 elements per entry, a color code (or '') and a reset (or '').
     *
     * @param array $assoc_args The associative argument array passed to the command.
     * @param array $colors Array of default percent color code strings keyed by the color contexts.
     * @return array Array containing 2-element arrays keyed to the input $colors array.
     */
    
private function get_colors$assoc_args$colors ) {
        
$color_reset WP_CLI::colorize'%n' );

        
$color_code_callback = static function ( $v ) {
            return 
substr$v);
        };

        
$color_codes array_keysColors::getColors() );
        
$color_codes array_map$color_code_callback$color_codes );
        
$color_codes implode''$color_codes );

        
$color_codes_regex '/^(?:%[' $color_codes '])*$/';

        foreach ( 
array_keys$colors ) as $color_col ) {
            
$col_color_flag Utils\get_flag_value$assoc_args$color_col '_color' );
            if ( 
null !== $col_color_flag ) {
                if ( ! 
preg_match$color_codes_regex$col_color_flag$matches ) ) {
                    
WP_CLI::warning"Unrecognized percent color code '$col_color_flag' for '{$color_col}_color'." );
                } else {
                    
$colors$color_col ] = $matches[0];
                }
            }
            
$colors$color_col ] = $colors$color_col ] ? array( WP_CLI::colorize$colors$color_col ] ), $color_reset ) : array( '''' );
        }

        return 
$colors;
    }

    
/*
     * Logs the difference between old match and new replacement for SQL replacement.
     *
     * @param string $col Column being processed.
     * @param array $primary_keys Primary keys for table.
     * @param string $table Table being processed.
     * @param string $old Old value to match.
     * @param string $new New value to replace the old value with.
     * @return int Count of changed rows.
     */
    
private function log_sql_diff$col$primary_keys$table$old$new ) {
        global 
$wpdb;
        if ( 
$primary_keys ) {
            
$esc_primary_keys implode', 'self::esc_sql_ident$primary_keys ) );
            
$primary_keys_sql count$primary_keys ) > "CONCAT_WS(',', {$esc_primary_keys}), " "{$esc_primary_keys}, ";
        } else {
            
$primary_keys_sql '';
        }

        
$table_sql self::esc_sql_ident$table );
        
$col_sql   self::esc_sql_ident$col );

        
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- escaped through self::esc_sql_ident
        
$results $wpdb->get_results$wpdb->prepare"SELECT {$primary_keys_sql}{$col_sql} FROM {$table_sql} WHERE {$col_sql} LIKE BINARY %s"'%' self::esc_like$old ) . '%' ), ARRAY_N );

        if ( empty( 
$results ) ) {
            return 
0;
        }

        
$search_regex '/' preg_quote$old'/' ) . '/';

        foreach ( 
$results as $result ) {
            list( 
$keys$data ) = $primary_keys ? array( $result[0], $result[1] ) : array( null$result[0] );
            if ( 
preg_match_all$search_regex$data$matchesPREG_OFFSET_CAPTURE ) ) {
                list( 
$old_bits$new_bits ) = $this->log_bits$search_regex$data$matches$new );
                
$this->log_write$col$keys$table$old_bits$new_bits );
            }
        }
        return 
count$results );
    }

    
/*
     * Logs the difference between old matches and new replacements at the end of a PHP (regex) replacement of a database row.
     *
     * @param string $col Column being processed.
     * @param array $keys Associative array (or object) of primary key names and their values for the row being processed.
     * @param string $table Table being processed.
     * @param string $old Old value to match.
     * @param string $new New value to replace the old value with.
     * @param array $log_data Array of data strings before replacements.
     */
    
private function log_php_diff$col$keys$table$old$new$log_data ) {
        if ( 
$this->regex ) {
            
$search_regex $this->regex_delimiter $old $this->regex_delimiter $this->regex_flags;
        } else {
            
$search_regex '/' preg_quote$old'/' ) . '/';
        }

        
$old_bits = array();
        
$new_bits = array();
        foreach ( 
$log_data as $data ) {
            if ( 
preg_match_all$search_regex$data$matchesPREG_OFFSET_CAPTURE ) ) {
                
$bits     $this->log_bits$search_regex$data$matches$new );
                
$old_bits array_merge$old_bits$bits[0] );
                
$new_bits array_merge$new_bits$bits[1] );
            }
        }
        if ( 
$old_bits ) {
            
$this->log_write$col$keys$table$old_bits$new_bits );
        }
    }

    
/**
     * Returns the arrays of old matches and new replacements based on the passed-in matches, with context.
     *
     * @param string $search_regex The search regular expression.
     * @param string $old_data Existing data being processed.
     * @param array $old_matches Old matches array returned by `preg_match_all()`.
     * @param string $new New value to replace the old value with.
     * @return array Two element array containing the array of old match log strings and the array of new replacement log strings with before/after contexts.
     */
    
private function log_bits$search_regex$old_data$old_matches$new ) {
        
$encoding $this->log_encoding;
        if ( ! 
$encoding && ( $this->log_before_context || $this->log_after_context ) && function_exists'mb_detect_encoding' ) ) {
            
$encoding mb_detect_encoding$old_datanulltrue /*strict*/ );
        }

        
// Generate a new data matches analog of the old data matches by simulating a `preg_replace()`.
        
$is_regex    $this->regex;
        
$i           0;
        
$diff        0;
        
$new_matches = array();
        
$new_data    preg_replace_callback(
            
$search_regex,
            static function ( 
$matches ) use ( $old_matches$new$is_regex, &$new_matches, &$i, &$diff ) {
                if ( 
$is_regex ) {
                    
// Sub in any back references, "$1", "\2" etc, in the replacement string.
                    
$new preg_replace_callback(
                        
'/(?<!\\\\)(?:\\\\\\\\)*((?:\\\\|\\$)[0-9]{1,2}|\\${[0-9]{1,2}\\})/',
                        static function ( 
$m ) use ( $matches ) {
                            
$idx = (int) str_replace( array( '\\''$''{''}' ), ''$m[0] );
                            return isset( 
$matches$idx ] ) ? $matches$idx ] : '';
                        },
                        
$new
                    
);
                    
$new str_replace'\\\\''\\'$new ); // Unescape any backslashed backslashes.
                
}

                
$new_matches[0][ $i ][0] = $new;
                
$new_matches[0][ $i ][1] = $old_matches[0][ $i ][1] + $diff;

                
$diff += strlen$new ) - strlen$old_matches[0][ $i ][0] );
                
$i++;
                return 
$new;
            },
            
$old_data,
            
$this->regex_limit,
            
$match_cnt
        
);

        
$old_bits        = array();
        
$new_bits        = array();
        
$append_next     false;
        
$last_old_offset 0;
        
$last_new_offset 0;
        for ( 
$i 0$i $match_cnt$i++ ) {
            
$old_match  $old_matches[0][ $i ][0];
            
$old_offset $old_matches[0][ $i ][1];
            
$new_match  $new_matches[0][ $i ][0];
            
$new_offset $new_matches[0][ $i ][1];

            
$old_log $this->log_colors['log_old'][0] . $old_match $this->log_colors['log_old'][1];
            
$new_log $this->log_colors['log_new'][0] . $new_match $this->log_colors['log_new'][1];

            
$old_before      '';
            
$old_after       '';
            
$new_before      '';
            
$new_after       '';
            
$after_shortened false;

            
// Offsets are in bytes, so need to use `strlen()` and `substr()` before using `safe_substr()`.
            
if ( $this->log_before_context && $old_offset && ! $append_next ) {
                
$old_before safe_substrsubstr$old_data$last_old_offset$old_offset $last_old_offset ), -$this->log_before_contextnull /*length*/false /*is_width*/$encoding );
                
$new_before safe_substrsubstr$new_data$last_new_offset$new_offset $last_new_offset ), -$this->log_before_contextnull /*length*/false /*is_width*/$encoding );
            }
            if ( 
$this->log_after_context ) {
                
$old_end_offset $old_offset strlen$old_match );
                
$new_end_offset $new_offset strlen$new_match );
                
$old_after      safe_substrsubstr$old_data$old_end_offset ), 0$this->log_after_contextfalse /*is_width*/$encoding );
                
$new_after      safe_substrsubstr$new_data$new_end_offset ), 0$this->log_after_contextfalse /*is_width*/$encoding );
                
// To lessen context duplication in output, shorten the after context if it overlaps with the next match.
                
if ( $i $match_cnt && $old_end_offset strlen$old_after ) > $old_matches[0][ $i ][1] ) {
                    
$old_after       substr$old_after0$old_matches[0][ $i ][1] - $old_end_offset );
                    
$new_after       substr$new_after0$new_matches[0][ $i ][1] - $new_end_offset );
                    
$after_shortened true;
                    
// On the next iteration, will append with no before context.
                
}
            }

            if ( 
$append_next ) {
                
$cnt                   count$old_bits );
                
$old_bits$cnt ] .= $old_log $old_after;
                
$new_bits$cnt ] .= $new_log $new_after;
            } else {
                
$old_bits[] = $old_before $old_log $old_after;
                
$new_bits[] = $new_before $new_log $new_after;
            }
            
$append_next     $after_shortened;
            
$last_old_offset $old_offset;
            
$last_new_offset $new_offset;
        }

        return array( 
$old_bits$new_bits );
    }

    
/*
     * Outputs the log strings.
     *
     * @param string $col Column being processed.
     * @param array $keys Associative array (or object) of primary key names and their values for the row being processed.
     * @param string $table Table being processed.
     * @param array $old_bits Array of old match log strings.
     * @param array $new_bits Array of new replacement log strings.
     */
    
private function log_write$col$keys$table$old_bits$new_bits ) {
        
$id_log              $keys ? ( ':' implode',', (array) $keys ) ) : '';
        
$table_column_id_log $this->log_colors['log_table_column_id'][0] . $table '.' $col $id_log $this->log_colors['log_table_column_id'][1];

        
$old_log str_replace( array( "\r\n""\n" ), ' 'implode' [...] '$old_bits ) );
        
$new_log str_replace( array( "\r\n""\n" ), ' 'implode' [...] '$new_bits ) );

        if ( 
$this->log_prefixes[0] ) {
            
$old_log $this->log_colors['log_old'][0] . $this->log_prefixes[0] . $this->log_colors['log_old'][1] . $old_log;
        }
        if ( 
$this->log_prefixes[1] ) {
            
$new_log $this->log_colors['log_new'][0] . $this->log_prefixes[1] . $this->log_colors['log_new'][1] . $new_log;
        }

        
fwrite$this->log_handle"{$table_column_id_log}\n{$old_log}\n{$new_log}\n" );
    }

}
<?
php

if ( ! class_exists'WP_CLI' ) ) {
    return;
}

$wpcli_search_replace_autoloader dirname__FILE__ ) . '/vendor/autoload.php';
if ( 
file_exists$wpcli_search_replace_autoloader ) ) {
    require_once 
$wpcli_search_replace_autoloader;
}

WP_CLI::add_command'search-replace''Search_Replace_Command' );
<?
php

/*
 * WP_CLI: Copied and adapted from vendor/composer/composer/src/Composer/Json/JsonManipulator.php. Changes marked `WP_CLI`.
 *
 * This file is part of Composer.
 *
 * (c) Nils Adermann <[email protected]>
 *     Jordi Boggiano <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace WP_CLI// WP_CLI

use Composer\Json\JsonFile// WP_CLI
use Composer\Repository\PlatformRepository;

/**
 * @author Jordi Boggiano <[email protected]>
 */
class JsonManipulator
{
    private static 
$DEFINES '(?(DEFINE)
       (?<number>   -? (?= [1-9]|0(?!\d) ) \d+ (\.\d+)? ([eE] [+-]? \d+)? )
       (?<boolean>   true | false | null )
       (?<string>    " ([^"\\\\]* | \\\\ ["\\\\bfnrt\/] | \\\\ u [0-9a-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) ) )
    )'
;

    private 
$contents;
    private 
$newline;
    private 
$indent;

    public function 
__construct($contents)
    {
        
$contents trim($contents);
        if (
$contents === '') {
            
$contents '{}';
        }
        if (!
$this->pregMatch('#^\{(.*)\}$#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()
    {
        return 
$this->contents $this->newline;
    }

    public function 
addLink($type$package$constraint$sortPackages false$caseInsensitive false// WP_CLI: caseInsensitive.
    
{
        
$decoded JsonFile::parseJson($this->contents);

        
// no link of that type yet
        
if (!isset($decoded[$type])) {
            return 
$this->addMainKey($type, array($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 (!
$this->pregMatch($regex$this->contents$matches)) {
            return 
false;
        }

        
// WP_CLI: begin caseInsensitive.
        
if ($caseInsensitive) {
            
// Just zap any existing packages first in a case insensitive manner.
            
$this->removeSubNode($type$package$caseInsensitive);
            return 
$this->addLink($type$package$constraint$sortPackages);
        }
        
// WP_CLI: end caseInsensitive.

        
$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 (
$this->pregMatch($regex$links$packageMatches)) {
            
// update existing link
            
$existingPackage $packageMatches['package'];
            
$packageRegex str_replace('/''\\\\?/'preg_quote($existingPackage));
            
$links preg_replace_callback('{'.self::$DEFINES.'"'.$packageRegex.'"(?P<separator>\s*:\s*)(?&string)}ix', function ($m) use ($existingPackage$constraint) {
                return 
JsonFile::encode(str_replace('\\/''/'$existingPackage)) . $m['separator'] . '"' $constraint '"';
            }, 
$links);
        } else {
            if (
$this->pregMatch('#^\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($linkstrue);
            
$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 $packages
     */
    
private function sortPackages(array &$packages = array())
    {
        
$prefix = function ($requirement) {
            if (
preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX$requirement)) {
                return 
preg_replace(
                    array(
                        
'/^php/',
                        
'/^hhvm/',
                        
'/^ext/',
                        
'/^lib/',
                        
'/^\D/',
                    ),
                    array(
                        
'0-$0',
                        
'1-$0',
                        
'2-$0',
                        
'3-$0',
                        
'4-$0',
                    ),
                    
$requirement
                
);
            }

            return 
'5-'.$requirement;
        };

        
uksort($packages, function ($a$b) use ($prefix) {
            return 
strnatcmp($prefix($a), $prefix($b));
        });
    }

    public function 
addRepository($name$config)
    {
        return 
$this->addSubNode('repositories'$name$config);
    }

    public function 
removeRepository($name)
    {
        return 
$this->removeSubNode('repositories'$name);
    }

    public function 
addConfigSetting($name$value)
    {
        return 
$this->addSubNode('config'$name$value);
    }

    public function 
removeConfigSetting($name)
    {
        return 
$this->removeSubNode('config'$name);
    }

    public function 
addProperty($name$value)
    {
        if (
substr($name06) === 'extra.') {
            return 
$this->addSubNode('extra'substr($name6), $value);
        }

        return 
$this->addMainKey($name$value);
    }

    public function 
removeProperty($name)
    {
        if (
substr($name06) === 'extra.') {
            return 
$this->removeSubNode('extra'substr($name6));
        }

        return 
$this->removeMainKey($name);
    }

    public function 
addSubNode($mainNode$name$value$caseInsensitive false// WP_CLI: caseInsensitive.
    
{
        
$decoded JsonFile::parseJson($this->contents);

        
$subName null;
        if (
in_array($mainNode, array('config''extra')) && false !== strpos($name'.')) {
            list(
$name$subName) = explode('.'$name2);
        }

        
// no main node yet
        
if (!isset($decoded[$mainNode])) {
            if (
$subName !== null) {
                
$this->addMainKey($mainNode, array($name => array($subName => $value)));
            } else {
                
$this->addMainKey($mainNode, array($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 (!
$this->pregMatch($nodeRegex$this->contents$match)) {
                return 
false;
            }
        } catch (
\RuntimeException $e) {
            if (
$e->getCode() === PREG_BACKTRACK_LIMIT_ERROR) {
                return 
false;
            }
            throw 
$e;
        }

        
// WP_CLI: begin caseInsensitive.
        
if ($caseInsensitive) {
            
// Just zap any existing names first in a case insensitive manner.
            
$this->removeSubNode($mainNode$name$caseInsensitive);
            return 
$this->addSubNode($mainNode$name$value);
        }
        
// WP_CLI: end caseInsensitive.

        
$children $match['content'];
        
// invalid match due to un-regexable content, abort
        
if (!@json_decode($children)) {
            return 
false;
        }

        
$that $this;

        
// child exists
        
$childRegex '{'.self::$DEFINES.'(?P<start>"'.preg_quote($name).'"\s*:\s*)(?P<content>(?&json))(?P<end>,?)}x';
        if (
$this->pregMatch($childRegex$children$matches)) {
            
$children preg_replace_callback($childRegex, function ($matches) use ($name$subName$value$that) {
                if (
$subName !== null) {
                    
$curVal json_decode($matches['content'], true);
                    if (!
is_array($curVal)) {
                        
$curVal = array();
                    }
                    
$curVal[$subName] = $value;
                    
$value $curVal;
                }

                return 
$matches['start'] . $that->format($value1) . $matches['end'];
            }, 
$children);
        } else {
            
$this->pregMatch('#^{ \s*? (?P<content>\S+.*?)? (?P<trailingspace>\s*) }$#sx'$children$match);

            
$whitespace '';
            if (!empty(
$match['trailingspace'])) {
                
$whitespace $match['trailingspace'];
            }

            if (!empty(
$match['content'])) {
                if (
$subName !== null) {
                    
$value = array($subName => $value);
                }

                
// child missing but non empty children
                
$children preg_replace(
                    
'#'.$whitespace.'}$#',
                    
addcslashes(',' $this->newline $this->indent $this->indent JsonFile::encode($name).': '.$this->format($value1) . $whitespace '}''\\$'),
                    
$children
                
);
            } else {
                if (
$subName !== null) {
                    
$value = array($subName => $value);
                }

                
// children present but empty
                
$children '{' $this->newline $this->indent $this->indent JsonFile::encode($name).': '.$this->format($value1) . $whitespace '}';
            }
        }

        
$this->contents preg_replace_callback($nodeRegex, function ($m) use ($children) {
            return 
$m['start'] . $children $m['end'];
        }, 
$this->contents);

        return 
true;
    }

    public function 
removeSubNode($mainNode$name$caseInsensitive false// WP_CLI: caseInsensitive.
    
{
        
$decoded JsonFile::parseJson($this->contents);

        
// no node or empty node
        
if (empty($decoded[$mainNode])) {
            return 
true;
        }

        
// WP_CLI: begin caseInsensitive.
        
if ( $caseInsensitive ) {
            
// This is more or less a copy of the code at the start of `addLink()` above.
            
$regex '{'.self::$DEFINES.'^(?P<start>\s*\{\s*(?:(?&string)\s*:\s*(?&json)\s*,\s*)*?)'.
                
'(?P<property>'.preg_quote(JsonFile::encode($mainNode)).'\s*:\s*)(?P<value>(?&json))(?P<end>.*)}sx';
            if (!
$this->pregMatch($regex$this->contents$matches)) {
                return 
true;
            }

            
$value $matches['value']; // Renamed from `$links` in `addLink()` case above.

            // try to find existing values
            
$nameRegex str_replace('/''\\\\?/'preg_quote($name)); // Renamed from `$packageRegex` in `addLink()` case above.
            
$regex '{'.self::$DEFINES.'"(?P<name>'.$nameRegex.')"(\s*:\s*)(?&json)}ix'// Need `(?&json)` PCRE subroutine here, as opposed to `(?&string)` in `addLink()` case.
            
if (preg_match_all($regex$value$nameMatches)) {
                
// Just zap them all individually in a case sensitive manner.
                
foreach ( $nameMatches['name'] as $nameMatch ) {
                    
$this->removeSubNode($mainNode$nameMatch);
                }
            }

            return 
true;
        }
        
// WP_CLI: end caseInsensitive.

        // 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 (!
$this->pregMatch($nodeRegex$this->contents$match)) {
                return 
false;
            }
        } catch (
\RuntimeException $e) {
            if (
$e->getCode() === PREG_BACKTRACK_LIMIT_ERROR) {
                return 
false;
            }
            throw 
$e;
        }

        
$children $match['content'];

        
// invalid match due to un-regexable content, abort
        
if (!@json_decode($childrentrue)) {
            return 
false;
        }

        
$subName null;
        if (
in_array($mainNode, array('config''extra')) && false !== strpos($name'.')) {
            list(
$name$subName) = explode('.'$name2);
        }

        
// 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
        
if ($this->pregMatch('{"'.preg_quote($name).'"\s*:}i'$children)) {
            
// find best match for the value of "name"
            
if (preg_match_all('{'.self::$DEFINES.'"'.preg_quote($name).'"\s*:\s*(?:(?&json))}x'$children$matches)) {
                
$bestMatch '';
                foreach (
$matches[0] as $match) {
                    if (
strlen($bestMatch) < strlen($match)) {
                        
$bestMatch $match;
                    }
                }
                
$childrenClean preg_replace('{,\s*'.preg_quote($bestMatch).'}'''$children, -1$count); // WP_CLI: As the preg_match_all() above is case-sensitive, so should this be.
                
if (!== $count) {
                    
$childrenClean preg_replace('{'.preg_quote($bestMatch).'\s*,?\s*}'''$childrenClean, -1$count); // WP_CLI: ditto.
                    
if (!== $count) {
                        return 
false;
                    }
                }
            }
        } else {
            
$childrenClean $children;
        }

        
// no child data left, $name was the only key in
        
$this->pregMatch('#^{ \s*? (?P<content>\S+.*?)? (?P<trailingspace>\s*) }$#sx'$childrenClean$match);
        if (empty(
$match['content'])) {
            
$newline $this->newline;
            
$indent $this->indent;

            
$this->contents preg_replace_callback($nodeRegex, function ($matches) use ($indent$newline) {
                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($childrentrue);
                unset(
$curVal[$name][$subName]);
                
$this->addSubNode($mainNode$name$curVal[$name]);
            }

            return 
true;
        }

        
$that $this;
        
$this->contents preg_replace_callback($nodeRegex, function ($matches) use ($that$name$subName$childrenClean) {
            if (
$subName !== null) {
                
$curVal json_decode($matches['content'], true);
                unset(
$curVal[$name][$subName]);
                
$childrenClean $that->format($curVal0);
            }

            return 
$matches['start'] . $childrenClean $matches['end'];
        }, 
$this->contents);

        return 
true;
    }

    public function 
addMainKey($key$content)
    {
        
$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]) && $this->pregMatch($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 ($this->pregMatch('#[^{\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($key)
    {
        
$decoded JsonFile::parseJson($this->contents);

        if (!isset(
$decoded[$key])) {
            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 (
$this->pregMatch($regex$this->contents$matches)) {
            
// 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_match('#,\s*$#'$matches['start']) && preg_match('#^\}$#'$matches['end'])) {
                
$matches['start'] = rtrim(preg_replace('#,(\s*)$#''$1'$matches['start']), $this->indent);
            }

            
$this->contents $matches['start'] . $matches['end'];
            if (
preg_match('#^\{\s*\}\s*$#'$this->contents)) {
                
$this->contents "{\n}";
            }

            return 
true;
        }

        return 
false;
    }

    public function 
format($data$depth 0)
    {
        if (
is_array($data)) {
            
reset($data);

            if (
is_numeric(key($data))) {
                foreach (
$data as $key => $val) {
                    
$data[$key] = $this->format($val$depth 1);
                }

                return 
'['.implode(', '$data).']';
            }

            
$out '{' $this->newline;
            
$elems = array();
            foreach (
$data as $key => $val) {
                
$elems[] = str_repeat($this->indent$depth 2) . JsonFile::encode($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()
    {
        if (
$this->pregMatch('{^([ \t]+)"}m'$this->contents$match)) {
            
$this->indent $match[1];
        } else {
            
$this->indent '    ';
        }
    }

    protected function 
pregMatch($re$str, &$matches = array())
    {
        
$count preg_match($re$str$matches);

        if (
$count === false) {
            switch (
preg_last_error()) {
                case 
PREG_NO_ERROR:
                    throw new 
\RuntimeException('Failed to execute regex: PREG_NO_ERROR'PREG_NO_ERROR);
                case 
PREG_INTERNAL_ERROR:
                    throw new 
\RuntimeException('Failed to execute regex: PREG_INTERNAL_ERROR'PREG_INTERNAL_ERROR);
                case 
PREG_BACKTRACK_LIMIT_ERROR:
                    throw new 
\RuntimeException('Failed to execute regex: PREG_BACKTRACK_LIMIT_ERROR'PREG_BACKTRACK_LIMIT_ERROR);
                case 
PREG_RECURSION_LIMIT_ERROR:
                    throw new 
\RuntimeException('Failed to execute regex: PREG_RECURSION_LIMIT_ERROR'PREG_RECURSION_LIMIT_ERROR);
                case 
PREG_BAD_UTF8_ERROR:
                    throw new 
\RuntimeException('Failed to execute regex: PREG_BAD_UTF8_ERROR'PREG_BAD_UTF8_ERROR);
                case 
PREG_BAD_UTF8_OFFSET_ERROR:
                    throw new 
\RuntimeException('Failed to execute regex: PREG_BAD_UTF8_OFFSET_ERROR'PREG_BAD_UTF8_OFFSET_ERROR);
                case 
6// PREG_JIT_STACKLIMIT_ERROR
                    
if (PHP_VERSION_ID 70000) {
                        throw new 
\RuntimeException('Failed to execute regex: PREG_JIT_STACKLIMIT_ERROR'6);
                    }
                    
// fallthrough

                
default:
                    throw new 
\RuntimeException('Failed to execute regex: Unknown error');
            }
        }

        return 
$count;
    }
}
<?
php

use Composer\Composer;
use 
Composer\Config;
use 
Composer\Config\JsonConfigSource;
use 
Composer\DependencyResolver\Pool;
use 
Composer\DependencyResolver\Request;
use 
Composer\EventDispatcher\Event;
use 
Composer\Factory;
use 
Composer\IO\NullIO;
use 
Composer\Installer;
use 
Composer\Json\JsonFile;
use 
Composer\Package;
use 
Composer\Package\BasePackage;
use 
Composer\Package\PackageInterface;
use 
Composer\Package\Version\VersionParser;
use 
Composer\Package\Version\VersionSelector;
use 
Composer\Repository;
use 
Composer\Repository\CompositeRepository;
use 
Composer\Repository\ComposerRepository;
use 
Composer\Repository\RepositoryManager;
use 
Composer\Util\Filesystem;
use 
Composer\Util\HttpDownloader;
use 
WP_CLI\ComposerIO;
use 
WP_CLI\Extractor;
use 
WP_CLI\Utils;
use 
WP_CLI\JsonManipulator;
use 
WP_CLI\PackageManagerEventSubscriber;

/**
 * Lists, installs, and removes WP-CLI packages.
 *
 * WP-CLI packages are community-maintained projects built on WP-CLI. They can
 * contain WP-CLI commands, but they can also just extend WP-CLI in some way.
 *
 * Learn how to create your own command from the
 * [Commands Cookbook](https://make.wordpress.org/cli/handbook/commands-cookbook/)
 *
 * ## EXAMPLES
 *
 *     # List installed packages
 *     $ wp package list
 *     +-----------------------+------------------------------------------+---------+----------+
 *     | name                  | description                              | authors | version  |
 *     +-----------------------+------------------------------------------+---------+----------+
 *     | wp-cli/server-command | Start a development server for WordPress |         | dev-main |
 *     +-----------------------+------------------------------------------+---------+----------+
 *
 *     # Install the latest development version of the package
 *     $ wp package install wp-cli/server-command
 *     Installing package wp-cli/server-command (dev-main)
 *     Updating /home/person/.wp-cli/packages/composer.json to require the package...
 *     Using Composer to install the package...
 *     ---
 *     Loading composer repositories with package information
 *     Updating dependencies
 *     Resolving dependencies through SAT
 *     Dependency resolution completed in 0.005 seconds
 *     Analyzed 732 packages to resolve dependencies
 *     Analyzed 1034 rules to resolve dependencies
 *      - Installing package
 *     Writing lock file
 *     Generating autoload files
 *     ---
 *     Success: Package installed.
 *
 *     # Uninstall package
 *     $ wp package uninstall wp-cli/server-command
 *     Removing require statement from /home/person/.wp-cli/packages/composer.json
 *     Deleting package directory /home/person/.wp-cli/packages/vendor/wp-cli/server-command
 *     Regenerating Composer autoload.
 *     Success: Uninstalled package.
 *
 * @package WP-CLI
 *
 * @when before_wp_load
 */
class Package_Command extends WP_CLI_Command {

    const 
PACKAGE_INDEX_URL 'https://wp-cli.org/package-index/';
    const 
SSL_CERTIFICATE   '/rmccue/requests/library/Requests/Transport/cacert.pem';

    const 
DEFAULT_DEV_BRANCH_CONSTRAINTS 'dev-main || dev-master || dev-trunk';

    private 
$version_selector false;

    
/**
     * Default author data used while creating default WP-CLI packages composer.json.
     *
     * @var array
     */
    
private $author_data = [
        
'name'  => 'WP-CLI',
        
'email' => '[email protected]',
    ];

    
/**
     * Default repository data used while creating default WP-CLI packages composer.json.
     * @var array
     */
    
private $composer_type_package = [
        
'type' => 'composer',
        
'url'  => self::PACKAGE_INDEX_URL,
    ];

    
/**
     * Browses WP-CLI packages available for installation.
     *
     * Lists packages available for installation from the [Package Index](http://wp-cli.org/package-index/).
     * Although the package index will remain in place for backward compatibility reasons, it has been
     * deprecated and will not be updated further. Please refer to https://github.com/wp-cli/ideas/issues/51
     * to read about its potential replacement.
     *
     * ## OPTIONS
     *
     * [--fields=<fields>]
     * : Limit the output to specific fields. Defaults to all fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - ids
     *   - json
     *   - yaml
     * ---
     *
     * ## AVAILABLE FIELDS
     *
     * These fields will be displayed by default for each package:
     *
     * * name
     * * description
     * * authors
     * * version
     *
     * There are no optionally available fields.
     *
     * ## EXAMPLES
     *
     *     $ wp package browse --format=yaml
     *     ---
     *     10up/mu-migration:
     *       name: 10up/mu-migration
     *       description: A set of WP-CLI commands to support the migration of single WordPress instances to multisite
     *       authors: Nícholas André
     *       version: dev-main, dev-develop
     *     aaemnnosttv/wp-cli-dotenv-command:
     *       name: aaemnnosttv/wp-cli-dotenv-command
     *       description: Dotenv commands for WP-CLI
     *       authors: Evan Mattson
     *       version: v0.1, v0.1-beta.1, v0.2, dev-main, dev-dev, dev-develop, dev-tests/behat
     *     aaemnnosttv/wp-cli-http-command:
     *       name: aaemnnosttv/wp-cli-http-command
     *       description: WP-CLI command for using the WordPress HTTP API
     *       authors: Evan Mattson
     *       version: dev-main
     */
    
public function browse$_$assoc_args ) {
        
$this->set_composer_auth_env_var();
        if ( empty( 
$assoc_args['format'] ) || 'table' === $assoc_args['format'] ) {
            
WP_CLI::lineWP_CLI::colorize'%CAlthough the package index will remain in place for backward compatibility reasons, it has been deprecated and will not be updated further. Please refer to https://github.com/wp-cli/ideas/issues/51 to read about its potential replacement.%n' ) );
        }
        
$this->show_packages'browse'$this->get_community_packages(), $assoc_args );
    }

    
/**
     * Installs a WP-CLI package.
     *
     * Packages are required to be a valid Composer package, and can be
     * specified as:
     *
     * * Package name from WP-CLI's package index.
     * * Git URL accessible by the current shell user.
     * * Path to a directory on the local machine.
     * * Local or remote .zip file.
     *
     * Packages are installed to `~/.wp-cli/packages/` by default. Use the
     * `WP_CLI_PACKAGES_DIR` environment variable to provide a custom path.
     *
     * When installing a local directory, WP-CLI simply registers a
     * reference to the directory. If you move or delete the directory, WP-CLI's
     * reference breaks.
     *
     * When installing a .zip file, WP-CLI extracts the package to
     * `~/.wp-cli/packages/local/<package-name>`.
     *
     * ## OPTIONS
     *
     * <name|git|path|zip>
     * : Name, git URL, directory path, or .zip file for the package to install.
     * Names can optionally include a version constraint
     * (e.g. wp-cli/server-command:@stable).
     *
     * [--insecure]
     * : Retry downloads without certificate validation if TLS handshake fails. Note: This makes the request vulnerable to a MITM attack.
     *
     * ## EXAMPLES
     *
     *     # Install a package hosted at a git URL.
     *     $ wp package install runcommand/hook
     *
     *     # Install the latest stable version.
     *     $ wp package install wp-cli/server-command:@stable
     *
     *     # Install a package hosted at a GitLab.com URL.
     *     $ wp package install https://gitlab.com/foo/wp-cli-bar-command.git
     *
     *     # Install a package in a .zip file.
     *     $ wp package install google-sitemap-generator-cli.zip
     */
    
public function install$args$assoc_args ) {
        list( 
$package_name ) = $args;

        
$insecure = (bool) Utils\get_flag_value$assoc_args'insecure'false );

        
$this->set_composer_auth_env_var();
        
$git_package false;
        
$dir_package false;
        
$version     '';
        if ( 
$this->is_git_repository$package_name ) ) {
            if ( 
'' === $version ) {
                
$version "dev-{$this->get_github_default_branch$package_name$insecure )}";
            }
            
$git_package $package_name;
            
$matches     = [];
            if ( 
preg_match'#([^:\/]+\/[^\/]+)\.git#'$package_name$matches ) ) {
                
$package_name $this->check_git_package_name$matches[1], $package_name$version$insecure );
            } else {
                
WP_CLI::error"Couldn't parse package name from expected path '<name>/<package>'." );
            }
        } elseif ( ( 
false !== strpos$package_name'://' ) && false !== stripos$package_name'.zip' ) )
            || ( 
pathinfo$package_namePATHINFO_EXTENSION ) === 'zip' && is_file$package_name ) ) ) {
            
// Download the remote ZIP file to a temp directory
            
$temp false;
            if ( 
false !== strpos$package_name'://' ) ) {
                
$temp     Utils\get_temp_dir() . uniqid'wp-cli-package_'true /*more_entropy*/ ) . '.zip';
                
$options  = [
                    
'timeout'  => 600,
                    
'filename' => $temp,
                    
'insecure' => $insecure,
                ];
                
$response Utils\http_request'GET'$package_namenull, [], $options );
                if ( 
20 !== (int) substr$response->status_code0) ) {
                    @
unlink$temp ); // @codingStandardsIgnoreLine
                    
WP_CLI::errorsprintf"Couldn't download package from '%s' (HTTP code %d)."$package_name$response->status_code ) );
                }
                
$package_name $temp;
            }
            
$dir_package Utils\get_temp_dir() . uniqid'wp-cli-package_'true /*more_entropy*/ );
            try {
                
// Extract the package to get the package name
                
Extractor::extract$package_name$dir_package );
                if ( 
$temp ) {
                    
unlink$temp );
                    
$temp false;
                }
                list( 
$package_name$version ) = self::get_package_name_and_version_from_dir_package$dir_package );
                
// Move to a location based on the package name
                
$local_dir          rtrimWP_CLI::get_runner()->get_packages_dir_path(), '/' ) . '/local/';
                
$actual_dir_package $local_dir str_replace'/''-'$package_name );
                
Extractor::copy_overwrite_files$dir_package$actual_dir_package );
                
Extractor::rmdir$dir_package );
                
// Behold, the extracted package
                
$dir_package $actual_dir_package;
            } catch ( 
Exception $e ) {
                if ( 
$temp ) {
                    
unlink$temp );
                }
                if ( 
file_exists$dir_package ) ) {
                    try {
                        
Extractor::rmdir$dir_package );
                    } catch ( 
Exception $rmdir_e ) {
                        
WP_CLI::warning$rmdir_e->getMessage() );
                    }
                }
                
WP_CLI::error$e->getMessage() );
            }
        } elseif ( 
is_dir$package_name ) && file_exists$package_name '/composer.json' ) ) {
            
$dir_package $package_name;
            if ( ! 
Utils\is_path_absolute$dir_package ) ) {
                
$dir_package getcwd() . DIRECTORY_SEPARATOR $dir_package;
            }
            list( 
$package_name$version ) = self::get_package_name_and_version_from_dir_package$dir_package );
        } else {
            if ( 
false !== strpos$package_name':' ) ) {
                list( 
$package_name$version ) = explode':'$package_name );
            }
            
$package $this->get_package_by_shortened_identifier$package_name );
            if ( ! 
$package ) {
                
WP_CLI::errorsprintf"Invalid package: shortened identifier '%s' not found."$package_name ) );
            }
            if ( 
is_string$package ) ) {
                if ( 
$this->is_git_repository$package ) ) {
                    
$git_package $package;

                    if ( 
'' === $version ) {
                        
$version "dev-{$this->get_github_default_branch$package_name$insecure )}";
                    }

                    if ( 
'@stable' === $version ) {
                        
$tag     $this->get_github_latest_release_tag$package_name$insecure );
                        
$version $this->guess_version_constraint_from_tag$tag );
                    }
                    
$package_name $this->check_github_package_name$package_name$version$insecure );
                }
            } elseif ( 
$package_name !== $package->getPrettyName() ) {
                
// BC support for specifying lowercase names for mixed-case package index packages - don't bother warning.
                
$package_name $package->getPrettyName();
            }
        }

        if ( 
$this->is_composer_v2() ) {
            
$package_name function_exists'mb_strtolower' )
                ? 
mb_strtolower$package_name )
                : 
strtolower$package_name );
        }

        if ( 
'' === $version ) {
            
$version self::DEFAULT_DEV_BRANCH_CONSTRAINTS;
        }

        
WP_CLI::logsprintf'Installing package %s (%s)'$package_name$version ) );

        
// Read the WP-CLI packages composer.json and do some initial error checking.
        
list( $json_path$composer_backup$composer_backup_decoded ) = $this->get_composer_json_path_backup_decoded();

        
// Revert on shutdown if `$revert` is true (set to false on success).
        
$revert true;
        
$this->register_revert_shutdown_function$json_path$composer_backup$revert );

        
// Add the 'require' to composer.json
        
WP_CLI::logsprintf'Updating %s to require the package...'$json_path ) );
        
$json_manipulator = new JsonManipulator$composer_backup );
        
$json_manipulator->addMainKey'name''wp-cli/wp-cli' );
        
$json_manipulator->addMainKey'version'self::get_wp_cli_version_composer() );
        
$json_manipulator->addLink'require'$package_name$versionfalse /*sortPackages*/true /*caseInsensitive*/ );
        
$json_manipulator->addConfigSetting'secure-http'true );

        
$package_args = [];
        if ( 
$git_package ) {
            
WP_CLI::logsprintf'Registering %s as a VCS repository...'$git_package ) );
            
$package_args = [
                
'type' => 'vcs',
                
'url'  => $git_package,
            ];
            
$json_manipulator->addSubNode(
                
'repositories',
                
$package_name,
                
$package_args,
                
true /*caseInsensitive*/
            
);
        } elseif ( 
$dir_package ) {
            
WP_CLI::logsprintf'Registering %s as a path repository...'$dir_package ) );
            
$package_args = [
                
'type' => 'path',
                
'url'  => $dir_package,
            ];
            
$json_manipulator->addSubNode(
                
'repositories',
                
$package_name,
                
$package_args,
                
true /*caseInsensitive*/
            
);
        }
        
// If the composer file does not contain the current package index repository, refresh the repository definition.
        
if ( empty( $composer_backup_decoded['repositories']['wp-cli']['url'] ) || self::PACKAGE_INDEX_URL !== $composer_backup_decoded['repositories']['wp-cli']['url'] ) {
            
WP_CLI::log'Updating package index repository url...' );
            
$package_args $this->composer_type_package;
            
$json_manipulator->addRepository(
                
'wp-cli',
                
$package_args
            
);
        }

        
file_put_contents$json_path$json_manipulator->getContents() );
        
$composer $this->get_composer();

        
// Set up the EventSubscriber
        
$event_subscriber = new PackageManagerEventSubscriber();
        
$composer->getEventDispatcher()->addSubscriber$event_subscriber );
        
// Set up the installer
        
$install Installer::create( new ComposerIO(), $composer );
        
$install->setUpdatetrue ); // Installer class will only override composer.lock with this flag
        
$install->setPreferSourcetrue ); // Use VCS when VCS for easier contributions.

        // Try running the installer, but revert composer.json if failed
        
WP_CLI::log'Using Composer to install the package...' );
        
WP_CLI::log'---' );
        
$res false;
        try {
            
$res $install->run();
        } catch ( 
Exception $e ) {
            
WP_CLI::warning$e->getMessage() );
        }

        
// TODO: The --insecure flag should cause another Composer run with verify disabled.

        
WP_CLI::log'---' );

        if ( 
=== $res ) {
            
$revert false;
            
WP_CLI::success'Package installed.' );
        } else {
            
$res_msg $res " (Composer return code {$res})" ''// $res may be null apparently.
            
WP_CLI::debug"composer.json content:\n" file_get_contents$json_path ), 'packages' );
            
WP_CLI::error"Package installation failed{$res_msg}." );
        }
    }

    
/**
     * Lists installed WP-CLI packages.
     *
     * ## OPTIONS
     *
     * [--fields=<fields>]
     * : Limit the output to specific fields. Defaults to all fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - ids
     *   - json
     *   - yaml
     * ---
     *
     * ## AVAILABLE FIELDS
     *
     * These fields will be displayed by default for each package:
     *
     * * name
     * * authors
     * * version
     * * update
     * * update_version
     *
     * These fields are optionally available:
     *
     * * description
     *
     * ## EXAMPLES
     *
     *     $ wp package list
     *     +-----------------------+------------------------------------------+---------+----------+
     *     | name                  | description                              | authors | version  |
     *     +-----------------------+------------------------------------------+---------+----------+
     *     | wp-cli/server-command | Start a development server for WordPress |         | dev-main |
     *     +-----------------------+------------------------------------------+---------+----------+
     *
     * @subcommand list
     */
    
public function list_$args$assoc_args ) {
        
$this->set_composer_auth_env_var();
        
$this->show_packages'list'$this->get_installed_packages(), $assoc_args );
    }

    
/**
     * Gets the path to an installed WP-CLI package, or the package directory.
     *
     * If you want to contribute to a package, this is a great way to jump to it.
     *
     * ## OPTIONS
     *
     * [<name>]
     * : Name of the package to get the directory for.
     *
     * ## EXAMPLES
     *
     *     # Get package path
     *     $ wp package path
     *     /home/person/.wp-cli/packages/
     *
     *     # Change directory to package path
     *     $ cd $(wp package path) && pwd
     *     /home/vagrant/.wp-cli/packages
     */
    
public function path$args ) {
        
$packages_dir WP_CLI::get_runner()->get_packages_dir_path();
        if ( ! empty( 
$args ) ) {
            
$packages_dir .= 'vendor/' $args[0];
            if ( ! 
is_dir$packages_dir ) ) {
                
WP_CLI::error'Invalid package name.' );
            }
        }
        
WP_CLI::line$packages_dir );
    }

    
/**
     * Updates all installed WP-CLI packages to their latest version.
     *
     * ## EXAMPLES
     *
     *     $ wp package update
     *     Using Composer to update packages...
     *     ---
     *     Loading composer repositories with package information
     *     Updating dependencies
     *     Resolving dependencies through SAT
     *     Dependency resolution completed in 0.074 seconds
     *     Analyzed 1062 packages to resolve dependencies
     *     Analyzed 22383 rules to resolve dependencies
     *     Writing lock file
     *     Generating autoload files
     *     ---
     *     Success: Packages updated.
     */
    
public function update() {
        
$this->set_composer_auth_env_var();
        
$composer $this->get_composer();

        
// Set up the EventSubscriber
        
$event_subscriber = new PackageManagerEventSubscriber();
        
$composer->getEventDispatcher()->addSubscriber$event_subscriber );

        
// Set up the installer
        
$install Installer::create( new ComposerIO(), $composer );
        
$install->setUpdatetrue ); // Installer class will only override composer.lock with this flag
        
$install->setPreferSourcetrue ); // Use VCS when VCS for easier contributions.
        
WP_CLI::log'Using Composer to update packages...' );
        
WP_CLI::log'---' );
        
$res false;
        try {
            
$res $install->run();
        } catch ( 
Exception $e ) {
            
WP_CLI::warning$e->getMessage() );
        }
        
WP_CLI::log'---' );

        
// TODO: The --insecure (to be added here) flag should cause another Composer run with verify disabled.

        
if ( === $res ) {
            
WP_CLI::success'Packages updated.' );
        } else {
            
$res_msg $res " (Composer return code {$res})" ''// $res may be null apparently.
            
WP_CLI::error"Failed to update packages{$res_msg}." );
        }
    }

    
/**
     * Uninstalls a WP-CLI package.
     *
     * ## OPTIONS
     *
     * <name>
     * : Name of the package to uninstall.
     *
     * [--insecure]
     * : Retry downloads without certificate validation if TLS handshake fails. Note: This makes the request vulnerable to a MITM attack.
     *
     * ## EXAMPLES
     *
     *     $ wp package uninstall wp-cli/server-command
     *     Removing require statement from /home/person/.wp-cli/packages/composer.json
     *     Deleting package directory /home/person/.wp-cli/packages/vendor/wp-cli/server-command
     *     Regenerating Composer autoload.
     *     Success: Uninstalled package.
     */
    
public function uninstall$args$assoc_args ) {
        list( 
$package_name ) = $args;

        
$insecure = (bool) Utils\get_flag_value$assoc_args'insecure'false );

        
$this->set_composer_auth_env_var();
        
$package $this->get_installed_package_by_name$package_name );
        if ( 
false === $package ) {
            
$package_name $this->get_package_by_shortened_identifier$package_name );
            if ( 
false === $package_name ) {
                
WP_CLI::error'Package not installed.' );
            }
            
$version "dev-{$this->get_github_default_branch$package_name$insecure )}";
            
$matches = [];
            if ( 
preg_match'#^(?:https?://github\.com/|git@github\.com:)(?<repo_name>.*?).git$#'$package_name$matches ) ) {
                
$package_name $this->check_git_package_name$matches['repo_name'], $package_name$version$insecure );
            }
        } else {
            
$package_name $package->getPrettyName(); // Make sure package name is what's in composer.json.
        
}

        
// Read the WP-CLI packages composer.json and do some initial error checking.
        
list( $json_path$composer_backup$composer_backup_decoded ) = $this->get_composer_json_path_backup_decoded();

        
// Revert on shutdown if `$revert` is true (set to false on success).
        
$revert true;
        
$this->register_revert_shutdown_function$json_path$composer_backup$revert );

        
// Remove the 'require' from composer.json.
        
WP_CLI::logsprintf'Removing require statement for package \'%s\' from %s'$package_name$json_path ) );
        
$manipulator = new JsonManipulator$composer_backup );
        
$manipulator->removeSubNode'require'$package_nametrue /*caseInsensitive*/ );

        
// Remove the 'repository' details from composer.json.
        
WP_CLI::logsprintf'Removing repository details from %s'$json_path ) );
        
$manipulator->removeSubNode'repositories'$package_nametrue /*caseInsensitive*/ );

        
file_put_contents$json_path$manipulator->getContents() );
        
$composer $this->get_composer();

        
// Set up the installer.
        
$install Installer::create( new NullIO(), $composer );
        
$install->setUpdatetrue ); // Installer class will only override composer.lock with this flag
        
$install->setPreferSourcetrue ); // Use VCS when VCS for easier contributions.

        
WP_CLI::log'Removing package directories and regenerating autoloader...' );
        
$res false;
        try {
            
$res $install->run();
        } catch ( 
Exception $e ) {
            
WP_CLI::warning$e->getMessage() );
        }

        if ( 
=== $res ) {
            
$revert false;
            
WP_CLI::success'Uninstalled package.' );
        } else {
            
$res_msg $res " (Composer return code {$res})" ''// $res may be null apparently.
            
WP_CLI::error"Package removal failed{$res_msg}." );
        }
    }

    
/**
     * Checks whether a package is a WP-CLI community package based
     * on membership in our package index.
     *
     * @param object      $package     A package object
     * @return bool
     */
    
private function is_community_package$package ) {
        return 
$this->package_index()->hasPackage$package );
    }

    
/**
     * Gets a Composer instance.
     */
    
private function get_composer() {
        
$this->avoid_composer_ca_bundle();
        try {
            
$composer_path $this->get_composer_json_path();

            
// Composer's auto-load generating code makes some assumptions about where
            // the 'vendor-dir' is, and where Composer is running from.
            // Best to just pretend we're installing a package from ~/.wp-cli or similar
            
chdirpathinfo$composer_pathPATHINFO_DIRNAME ) );

            
// Prevent DateTime error/warning when no timezone set.
            // Note: The package is loaded before WordPress load, For environments that don't have set time in php.ini.
            // phpcs:ignore WordPress.DateTime.RestrictedFunctions.timezone_change_date_default_timezone_set,WordPress.PHP.NoSilencedErrors.Discouraged
            
date_default_timezone_set( @date_default_timezone_get() );

            
$composer Factory::create( new NullIO(), $composer_path );
        } catch ( 
Exception $e ) {
            
WP_CLI::errorsprintf'Failed to get composer instance: %s'$e->getMessage() ) );
        }
        return 
$composer;
    }

    
/**
     * Gets all of the community packages.
     *
     * @return array
     */
    
private function get_community_packages() {
        static 
$community_packages;

        if ( 
null === $community_packages ) {
            
$this->avoid_composer_ca_bundle();
            try {
                
$community_packages $this->package_index()->getPackages();
            } catch ( 
Exception $e ) {
                
WP_CLI::error$e->getMessage() );
            }
        }

        return 
$community_packages;
    }

    
/**
     * Gets the package index instance
     *
     * We need to construct the instance manually, because there's no way to select
     * a particular instance using $composer->getRepositoryManager()
     *
     * @return ComposerRepository
     */
    
private function package_index() {
        static 
$package_index;

        if ( ! 
$package_index ) {
            
$config_args = [
                
'config' => [
                    
'secure-http' => true,
                    
'home'        => dirname$this->get_composer_json_path() ),
                ],
            ];
            
$config      = new Config();
            
$config->merge$config_args );
            
$config->setConfigSource( new JsonConfigSource$this->get_composer_json() ) );

            
$io = new NullIO();
            try {
                if ( 
$this->is_composer_v2() ) {
                    
$http_downloader = new HttpDownloader$io$config );
                    
$package_index   = new ComposerRepository( [ 'url' => self::PACKAGE_INDEX_URL ], $io$config$http_downloader );
                } else {
                    
$package_index = new ComposerRepository( [ 'url' => self::PACKAGE_INDEX_URL ], $io$config );
                }
            } catch ( 
Exception $e ) {
                
WP_CLI::error$e->getMessage() );
            }
        }

        return 
$package_index;
    }

    
/**
     * Displays a set of packages
     *
     * @param string $context
     * @param array
     * @param array
     */
    
private function show_packages$context$packages$assoc_args ) {
        if ( 
'list' === $context ) {
            
$default_fields = [
                
'name',
                
'authors',
                
'version',
                
'update',
                
'update_version',
            ];
        } elseif ( 
'browse' === $context ) {
            
$default_fields = [
                
'name',
                
'description',
                
'authors',
                
'version',
            ];
        }
        
$defaults   = [
            
'fields' => implode','$default_fields ),
            
'format' => 'table',
        ];
        
$assoc_args array_merge$defaults$assoc_args );

        
$composer $this->get_composer();
        
$list     = [];
        foreach ( 
$packages as $package ) {
            
$name $package->getPrettyName();
            if ( isset( 
$list$name ] ) ) {
                
$list$name ]['version'][] = $package->getPrettyVersion();
            } else {
                
$package_output                = [];
                
$package_output['name']        = $package->getPrettyName();
                
$package_output['description'] = $package->getDescription();
                
$package_output['authors']     = implode', 'array_column( (array) $package->getAuthors(), 'name' ) );
                
$package_output['version']     = [ $package->getPrettyVersion() ];
                
$update                        'none';
                
$update_version                '';
                if ( 
'list' === $context ) {
                    try {
                        
$latest $this->find_latest_package$package$composernull );
                        if ( 
$latest && $latest->getFullPrettyVersion() !== $package->getFullPrettyVersion() ) {
                            
$update         'available';
                            
$update_version $latest->getPrettyVersion();
                        }
                    } catch ( 
Exception $e ) {
                        
WP_CLI::warning$e->getMessage() );
                        
$update         'error';
                        
$update_version $update;
                    }
                }
                
$package_output['update']         = $update;
                
$package_output['update_version'] = $update_version;
                
$package_output['pretty_name']    = $package->getPrettyName(); // Deprecated but kept for BC with package-command 1.0.8.
                
$list$package_output['name'] ]  = $package_output;
            }
        }

        
$list array_map(
            function( 
$package ) {
                
$package['version'] = implode', '$package['version'] );
                return 
$package;
            },
            
$list
        
);

        
ksort$list );
        if ( 
'ids' === $assoc_args['format'] ) {
            
$list array_keys$list );
        }
        
Utils\format_items$assoc_args['format'], $list$assoc_args['fields'] );
    }

    
/**
     * Gets a package by its shortened identifier.
     *
     * A shortened identifier has the form `<vendor>/<package>`.
     *
     * This method first checks the deprecated package index, for BC reasons,
     * and then falls back to the corresponding GitHub URL.
     *
     * @param string $package_name Name of the package to get.
     * @param bool   $insecure     Optional. Whether to insecurely retry downloads that failed TLS handshake. Defaults
     *                             to false.
     */
    
private function get_package_by_shortened_identifier$package_name$insecure false ) {
        
// Check the package index first, so we don't break existing behavior.
        
$lc_package_name strtolower$package_name ); // For BC check.
        
foreach ( $this->get_community_packages() as $package ) {
            if ( 
$package_name === $package->getPrettyName() ) {
                return 
$package;
            }
            
// For BC allow getting by lowercase name.
            
if ( $lc_package_name === $package->getName() ) {
                return 
$package;
            }
        }

        
$options = [ 'insecure' => $insecure ];

        
// Check if the package exists on Packagist.
        
$url      "https://repo.packagist.org/p/{$package_name}.json";
        
$response Utils\http_request'GET'$urlnull, [], $options );
        if ( 
20 === (int) substr$response->status_code0) ) {
            return 
$package_name;
        }

        
// Fall back to GitHub URL if we had no match yet.
        
$url          "https://github.com/{$package_name}.git";
        
$github_token getenv'GITHUB_TOKEN' ); // Use GITHUB_TOKEN if available to avoid authorization failures or rate-limiting.
        
$headers      $github_token ? [ 'Authorization' => 'token ' $github_token ] : [];
        
$response     Utils\http_request'GET'$urlnull /*data*/$headers$options );
        if ( 
20 === (int) substr$response->status_code0) ) {
            return 
$url;
        }

        return 
false;
    }

    
/**
     * Gets the installed community packages.
     */
    
private function get_installed_packages() {
        
$composer $this->get_composer();

        
$repo                   $composer->getRepositoryManager()->getLocalRepository();
        
$existing               json_decodefile_get_contents$this->get_composer_json_path() ), true );
        
$installed_package_keys = ! empty( $existing['require'] ) ? array_keys$existing['require'] ) : [];
        if ( empty( 
$installed_package_keys ) ) {
            return [];
        }
        
// For use by legacy incorrect name check.
        
$lc_installed_package_keys array_map'strtolower'$installed_package_keys );
        
$installed_packages        = [];
        foreach ( 
$repo->getCanonicalPackages() as $package ) {
            
$idx array_search$package->getName(), $lc_installed_package_keystrue );
            
// Use pretty name as it's case sensitive and what's in composer.json (or at least should be).
            
if ( in_array$package->getPrettyName(), $installed_package_keystrue ) ) {
                
$installed_packages[] = $package;
            } elseif ( 
false !== $idx ) { // Legacy incorrect name check.
                
if ( ! $this->is_composer_v2() ) {
                    
WP_CLI::warningsprintf"Found package '%s' misnamed '%s' in '%s'."$package->getPrettyName(), $installed_package_keys$idx ], $this->get_composer_json_path() ) );
                }
                
$installed_packages[] = $package;
            }
        }
        return 
$installed_packages;
    }

    
/**
     * Gets an installed package by its name.
     */
    
private function get_installed_package_by_name$package_name ) {
        foreach ( 
$this->get_installed_packages() as $package ) {
            if ( 
$package_name === $package->getPrettyName() ) {
                return 
$package;
            }
            
// Also check non-pretty (lowercase) name in case of legacy incorrect name.
            
if ( $package_name === $package->getName() ) {
                return 
$package;
            }
        }
        return 
false;
    }

    
/**
     * Checks if the package name provided is already installed.
     */
    
private function is_package_installed$package_name ) {
        if ( 
$this->get_installed_package_by_name$package_name ) ) {
            return 
true;
        } else {
            return 
false;
        }
    }

    
/**
     * Gets the name of the package from the composer.json in a directory path
     *
     * @param string $dir_package
     * @return array Two-element array containing package name and version.
     */
    
private static function get_package_name_and_version_from_dir_package$dir_package ) {
        
$composer_file $dir_package '/composer.json';
        if ( ! 
file_exists$composer_file ) ) {
            
WP_CLI::errorsprintf"Invalid package: composer.json file '%s' not found."$composer_file ) );
        }
        
$composer_data json_decodefile_get_contents$composer_file ), true );
        if ( 
null === $composer_data ) {
            
WP_CLI::errorsprintf"Invalid package: failed to parse composer.json file '%s' as json."$composer_file ) );
        }
        if ( empty( 
$composer_data['name'] ) ) {
            
WP_CLI::errorsprintf"Invalid package: no name in composer.json file '%s'."$composer_file ) );
        }
        
$package_name $composer_data['name'];
        
$version      self::DEFAULT_DEV_BRANCH_CONSTRAINTS;
        if ( ! empty( 
$composer_data['version'] ) ) {
            
$version $composer_data['version'];
        }
        return [ 
$package_name$version ];
    }

    
/**
     * Gets the WP-CLI packages composer.json object.
     */
    
private function get_composer_json() {
        return new 
JsonFile$this->get_composer_json_path() );
    }

    
/**
     * Gets the absolute path to the WP-CLI packages composer.json.
     */
    
private function get_composer_json_path() {
        static 
$composer_path;

        if ( 
null === $composer_path || getenv'WP_CLI_TEST_PACKAGE_GET_COMPOSER_JSON_PATH' ) ) {

            if ( 
getenv'WP_CLI_PACKAGES_DIR' ) ) {
                
$composer_path Utils\trailingslashitgetenv'WP_CLI_PACKAGES_DIR' ) ) . 'composer.json';
            } else {
                
$composer_path Utils\trailingslashitUtils\get_home_dir() ) . '.wp-cli/packages/composer.json';
            }

            
// `composer.json` and its directory might need to be created
            
if ( ! file_exists$composer_path ) ) {
                
$composer_path $this->create_default_composer_json$composer_path );
            } else {
                
$composer_path realpath$composer_path );
                if ( 
false === $composer_path ) {
                    
$error error_get_last();
                    
WP_CLI::errorsprintf"Composer path '%s' for packages/composer.json not found: %s"$composer_path$error['message'] ) );
                }
            }
        }

        return 
$composer_path;
    }

    
/**
     * Gets the WP-CLI version for composer.json
     */
    
private static function get_wp_cli_version_composer() {
        
preg_match'#^[0-9\.]+(-(alpha|beta)[^-]{0,})?#'WP_CLI_VERSION$matches );
        
$version = isset( $matches[0] ) ? $matches[0] : '';
        return 
$version;
    }

    
/**
     * Creates a default WP-CLI packages composer.json.
     *
     * @param string $composer_path Where the composer.json should be created
     * @return string Returns the absolute path of the newly created default WP-CLI packages composer.json.
     */
    
private function create_default_composer_json$composer_path ) {

        
$composer_dir pathinfo$composer_pathPATHINFO_DIRNAME );
        if ( ! 
is_dir$composer_dir ) ) {
            if ( ! @
mkdir$composer_dir0777true ) ) { // @codingStandardsIgnoreLine
                
$error error_get_last();
                
WP_CLI::errorsprintf"Composer directory '%s' for packages couldn't be created: %s"$composer_dir$error['message'] ) );
            }
        }

        
$composer_dir realpath$composer_dir );
        if ( 
false === $composer_dir ) {
            
$error error_get_last();
            
WP_CLI::errorsprintf"Composer directory '%s' for packages not found: %s"$composer_dir$error['message'] ) );
        }

        
$composer_path Utils\trailingslashit$composer_dir ) . Utils\basename$composer_path );

        
$json_file = new JsonFile$composer_path );

        
$repositories = (object) [
            
'wp-cli' => (object) $this->composer_type_package,
        ];

        
$options = [
            
'name'              => 'wp-cli/wp-cli',
            
'description'       => 'Installed community packages used by WP-CLI',
            
'version'           => self::get_wp_cli_version_composer(),
            
'authors'           => [ (object) $this->author_data ],
            
'homepage'          => self::PACKAGE_INDEX_URL,
            
'require'           => new stdClass(),
            
'require-dev'       => new stdClass(),
            
'minimum-stability' => 'dev',
            
'prefer-stable'     => true,
            
'license'           => 'MIT',
            
'repositories'      => $repositories,
        ];

        try {
            
$json_file->write$options );
        } catch ( 
Exception $e ) {
            
WP_CLI::error$e->getMessage() );
        }

        return 
$composer_path;
    }

    
/**
     * Given a package, this finds the latest package matching it
     *
     * @param  PackageInterface $package
     * @param  Composer         $composer
     * @param  string           $phpVersion
     * @param  bool             $minorOnly
     *
     * @return PackageInterface|null
     */
    
private function find_latest_packagePackageInterface $packageComposer $composer$php_version$minor_only false ) {
        
// Find the latest version allowed in this pool/repository set.
        
$name             $package->getPrettyName();
        
$version_selector $this->get_version_selector$composer );
        
$stability        $composer->getPackage()->getMinimumStability();
        
$flags            $composer->getPackage()->getStabilityFlags();
        if ( isset( 
$flags$name ] ) ) {
            
$stability array_search$flags$name ], BasePackage::$stabilitiestrue );
        }
        
$best_stability $stability;
        if ( 
$composer->getPackage()->getPreferStable() ) {
            
$best_stability $package->getStability();
        }
        
$target_version null;
        if ( 
=== strpos$package->getVersion(), 'dev-' ) ) {
            
$target_version $package->getVersion();
        }
        if ( 
null === $target_version && $minor_only ) {
            
$target_version '^' $package->getVersion();
        }

        if ( 
$this->is_composer_v2() ) {
            return 
$version_selector->findBestCandidate$name$target_version$best_stability );
        }

        return 
$version_selector->findBestCandidate$name$target_version$php_version$best_stability );
    }

    private function 
get_version_selectorComposer $composer ) {
        if ( ! 
$this->version_selector ) {
            if ( 
$this->is_composer_v2() ) {
                
$repository_set = new Repository\RepositorySet(
                    
$composer->getPackage()->getMinimumStability(),
                    
$composer->getPackage()->getStabilityFlags()
                );
                
$repository_set->addRepository( new CompositeRepository$composer->getRepositoryManager()->getRepositories() ) );
                
$this->version_selector = new VersionSelector$repository_set );
            } else {
                
$pool = new Pool$composer->getPackage()->getMinimumStability(), $composer->getPackage()->getStabilityFlags() );
                
$pool->addRepository( new CompositeRepository$composer->getRepositoryManager()->getRepositories() ) );
                
$this->version_selector = new VersionSelector$pool );
            }
        }

        return 
$this->version_selector;
    }

    
/**
     * Checks whether a given package is a git repository.
     *
     * @param string $package Package name to check.
     *
     * @return bool Whether the package is a git repository.
     */
    
private function is_git_repository$package ) {
        return 
'.git' === strtolowersubstr$package, -4) );
    }

    
/**
     * Checks that `$package_name` matches the name in composer.json at Github.com, and return corrected value if not.
     *
     * @param string $package_name Package name to check.
     * @param string $version      Optional. Package version. Defaults to empty string.
     * @param bool   $insecure     Optional. Whether to insecurely retry downloads that failed TLS handshake. Defaults
     *                             to false.
     */
    
private function check_github_package_name$package_name$version ''$insecure false ) {
        
$github_token getenv'GITHUB_TOKEN' ); // Use GITHUB_TOKEN if available to avoid authorization failures or rate-limiting.
        
$headers      $github_token ? [ 'Authorization' => 'token ' $github_token ] : [];
        
$options      = [ 'insecure' => $insecure ];

        
// Generate raw git URL of composer.json file.
        
$raw_content_url "https://raw.githubusercontent.com/{$package_name}/{$this->get_raw_git_version$version )}/composer.json";

        
$response Utils\http_request'GET'$raw_content_urlnull /*data*/$headers$options );
        if ( 
20 !== (int) substr$response->status_code0) ) {
            
// Could not get composer.json. Possibly private so warn and return best guess from input (always xxx/xxx).
            
WP_CLI::warning(
                
sprintf(
                    
"Couldn't download composer.json file from '%s' (HTTP code %d). Presuming package name is '%s'.",
                    
$raw_content_url,
                    
$response->status_code,
                    
$package_name
                
)
            );
            return 
$package_name;
        }

        
// Convert composer.json JSON to Array.
        
$composer_content_as_array json_decode$response->bodytrue );
        if ( 
null === $composer_content_as_array ) {
            
WP_CLI::errorsprintf"Failed to parse '%s' as json."$raw_content_url ) );
        }
        if ( empty( 
$composer_content_as_array['name'] ) ) {
            
WP_CLI::errorsprintf"Invalid package: no name in composer.json file '%s'."$raw_content_url ) );
        }

        
// Package name in composer.json that is hosted on GitHub.
        
$package_name_on_repo $composer_content_as_array['name'];

        
// If package name and repository name are not identical, then fix it.
        
if ( $package_name !== $package_name_on_repo ) {
            
WP_CLI::warningsprintf"Package name mismatch...Updating from git name '%s' to composer.json name '%s'."$package_name$package_name_on_repo ) );
            
$package_name $package_name_on_repo;
        }

        return 
$package_name;
    }

    
/**
     * Checks that `$package_name` matches the name in composer.json at the corresponding upstream repository, and return corrected value if not.
     *
     * @param string $package_name Package name to check.
     * @param string $url          URL to fetch the package from.
     * @param string $version      Optional. Package version. Defaults to empty string.
     * @param bool   $insecure     Optional. Whether to insecurely retry downloads that failed TLS handshake. Defaults
     *                             to false.
     */
    
private function check_git_package_name$package_name$url ''$version ''$insecure false ) {
        if ( 
$url && ( strpos$url'://gitlab.com/' ) !== false ) || ( strpos$url'[email protected]:' ) !== false ) ) {
            return 
$this->check_gitlab_package_name$package_name$version$insecure );
        }

        return 
$this->check_github_package_name$package_name$version$insecure );
    }

    
/**
     * Checks that `$package_name` matches the name in composer.json at GitLab.com, and return corrected value if not.
     *
     * @param string $package_name Package name to check.
     * @param string $version      Optional. Package version. Defaults to empty string.
     * @param bool   $insecure     Optional. Whether to insecurely retry downloads that failed TLS handshake. Defaults
     *                             to false.
     */
    
private function check_gitlab_package_name$package_name$version ''$insecure false ) {
        
// Generate raw git URL of composer.json file.
        
$raw_content_public_url  'https://gitlab.com/' $package_name '/-/raw/' $this->get_raw_git_version$version ) . '/composer.json';
        
$raw_content_private_url 'https://gitlab.com/api/v4/projects/' rawurlencode$package_name ) . '/repository/files/composer.json/raw?ref=' $this->get_raw_git_version$version );

        
$options = [ 'insecure' => $insecure ];

        
$response Utils\http_request'GET'$raw_content_public_urlnull /*data*/, [], $options );
        if ( 
$response->status_code 200 || $response->status_code >= 300 ) {
            
// Could not get composer.json. Possibly private so warn and return best guess from input (always xxx/xxx).
            
WP_CLI::warningsprintf"Couldn't download composer.json file from '%s' (HTTP code %d). Presuming package name is '%s'."$raw_content_public_url$response->status_code$package_name ) );
            return 
$package_name;
        }

        if ( 
strpos$response->headers['content-type'], 'text/html' ) === ) {
            
$gitlab_token getenv'GITLAB_TOKEN' ); // Use GITLAB_TOKEN if available to avoid authorization failures or rate-limiting.
            
$headers      $gitlab_token ? [ 'PRIVATE-TOKEN' => $gitlab_token ] : [];
            
$response     Utils\http_request'GET'$raw_content_private_urlnull /*data*/$headers$options );

            if ( 
$response->status_code 200 || $response->status_code >= 300 ) {
                
// Could not get composer.json. Possibly private so warn and return best guess from input (always xxx/xxx).
                
WP_CLI::warningsprintf"Couldn't download composer.json file from '%s' (HTTP code %d). Presuming package name is '%s'."$raw_content_private_url$response->status_code$package_name ) );
                return 
$package_name;
            }
        }

        
// Convert composer.json JSON to Array.
        
$composer_content_as_array json_decode$response->bodytrue );
        if ( 
null === $composer_content_as_array ) {
            
WP_CLI::errorsprintf"Failed to parse '%s' as json."$response->url ) );
        }
        if ( empty( 
$composer_content_as_array['name'] ) ) {
            
WP_CLI::errorsprintf"Invalid package: no name in composer.json file '%s'."$response->url ) );
        }

        
// Package name in composer.json that is hosted on Gitlab.
        
$package_name_on_repo $composer_content_as_array['name'];

        
// If package name and repository name are not identical, then fix it.
        
if ( $package_name !== $package_name_on_repo ) {
            
WP_CLI::warningsprintf"Package name mismatch...Updating from git name '%s' to composer.json name '%s'."$package_name$package_name_on_repo ) );
            
$package_name $package_name_on_repo;
        }
        return 
$package_name;
    }

    
/**
     * Get the version to use for raw GitHub request. Very basic.
     *
     * @string $version Package version.
     * @string Version to use for GitHub request.
     */
    
private function get_raw_git_version$version ) {
        if ( 
'' === $version ) {
            return 
'master';
        }

        
// If Composer hash given then just use whatever's after it.
        
$hash_pos strpos$version'#' );
        if ( 
false !== $hash_pos ) {
            return 
substr$version$hash_pos );
        }

        
// Strip any Composer 'dev-' prefix.
        
if ( === strncmp$version'dev-') ) {
            
$version substr$version);
        }

        
// Ignore/strip any relative suffixes.
        
return str_replace( [ '^''~' ], ''$version );
    }

    
/**
     * Gets the release tag for the latest stable release of a GitHub repository.
     *
     * @param string $package_name Name of the repository.
     *
     * @return string Release tag.
     */
    
private function get_github_latest_release_tag$package_name$insecure ) {
        
$url      "https://api.github.com/repos/{$package_name}/releases/latest";
        
$options  = [ 'insecure' => $insecure ];
        
$response Utils\http_request'GET'$urlnull, [], $options );
        if ( 
20 !== (int) substr$response->status_code0) ) {
            
WP_CLI::warning'Could not guess stable version from GitHub repository, falling back to master branch' );
            return 
'master';
        }

        
$package_data json_decode$response->body );
        if ( 
JSON_ERROR_NONE !== json_last_error() ) {
            
WP_CLI::warning'Could not guess stable version from GitHub repository, falling back to master branch' );
            return 
'master';
        }

        
$tag $package_data->tag_name;
        
WP_CLI::debug"Extracted latest stable release tag: {$tag}"'packages' );

        return 
$tag;
    }

    
/**
     * Guesses the version constraint from a release tag.
     *
     * @param string $tag Release tag to guess the version constraint from.
     *
     * @return string Version constraint.
     */
    
private function guess_version_constraint_from_tag$tag ) {
        
$matches = [];
        if ( 
!== preg_match'/(?:version|v)\s*((?:[0-9]+\.?)+)(?:-.*)/i'$tag$matches ) ) {
            return 
$tag;
        }

        
$constraint "^{$matches[1]}";
        
WP_CLI::debug"Guessing version constraint to use: {$constraint}"'packages' );

        return 
$constraint;
    }

    
/**
     * Sets `COMPOSER_AUTH` environment variable (which Composer merges into the config setup in `Composer\Factory::createConfig()`) depending on available environment variables.
     * Avoids authorization failures when accessing various sites.
     */
    
private function set_composer_auth_env_var() {
        
$changed       false;
        
$composer_auth getenv'COMPOSER_AUTH' );
        if ( 
false !== $composer_auth ) {
            
$composer_auth json_decode$composer_authtrue /*assoc*/ );
        }
        if ( empty( 
$composer_auth ) || ! is_array$composer_auth ) ) {
            
$composer_auth = [];
        }
        
$github_token getenv'GITHUB_TOKEN' );
        if ( ! isset( 
$composer_auth['github-oauth'] ) && is_string$github_token ) ) {
            
$composer_auth['github-oauth'] = [ 'github.com' => $github_token ];
            
$changed                       true;
        }
        if ( 
$changed ) {
            
putenv'COMPOSER_AUTH=' json_encode$composer_auth ) );
        }
    }

    
/**
     * Avoid using default Composer CA bundle if in phar as we don't include it.
     * See https://github.com/composer/ca-bundle/blob/1.1.0/src/CaBundle.php#L64
     */
    
private function avoid_composer_ca_bundle() {
        if ( 
Utils\inside_phar() && ! getenv'SSL_CERT_FILE' ) && ! getenv'SSL_CERT_DIR' ) && ! ini_get'openssl.cafile' ) && ! ini_get'openssl.capath' ) ) {
            
$certificate Utils\extract_from_pharWP_CLI_VENDOR_DIR self::SSL_CERTIFICATE );
            
putenv"SSL_CERT_FILE={$certificate});
        }
    }

    
/**
     * Reads the WP-CLI packages composer.json, checking validity and returning array containing its path, contents, and decoded contents.
     *
     * @return array Indexed array containing the path, the contents, and the decoded contents of the WP-CLI packages composer.json.
     */
    
private function get_composer_json_path_backup_decoded() {
        
$composer_json_obj $this->get_composer_json();
        
$json_path         $composer_json_obj->getPath();
        
$composer_backup   file_get_contents$json_path );
        if ( 
false === $composer_backup ) {
            
$error error_get_last();
            
WP_CLI::errorsprintf"Failed to read '%s': %s"$json_path$error['message'] ) );
        }
        try {
            
$composer_backup_decoded $composer_json_obj->read();
        } catch ( 
Exception $e ) {
            
WP_CLI::errorsprintf"Failed to parse '%s' as json: %s"$json_path$e->getMessage() ) );
        }

        return [ 
$json_path$composer_backup$composer_backup_decoded ];
    }

    
/**
     * Registers a PHP shutdown function to revert composer.json unless
     * referenced `$revert` flag is false.
     *
     * @param string $json_path       Path to composer.json.
     * @param string $composer_backup Original contents of composer.json.
     * @param bool   &$revert         Flags whether to revert or not.
     */
    
private function register_revert_shutdown_function$json_path$composer_backup, &$revert ) {
        
// Allocate all needed memory beforehand as much as possible.
        
$revert_msg      "Reverted composer.json.\n";
        
$revert_fail_msg "Failed to revert composer.json.\n";
        
$memory_msg      "WP-CLI ran out of memory. Please see https://bit.ly/wpclimem for further help.\n";
        
$memory_string   'Allowed memory size of';
        
$error_array     = [
            
'type'    => 42,
            
'message' => 'Some random dummy string to take up memory',
            
'file'    => 'Another random string, which would be a filename this time',
            
'line'    => 314,
        ];

        
register_shutdown_function(
            static function () use (
                
$json_path,
                
$composer_backup,
                &
$revert,
                
$revert_msg,
                
$revert_fail_msg,
                
$memory_msg,
                
$memory_string,
                
$error_array
            
) {
                if ( 
$revert ) {
                    if ( 
false !== file_put_contents$json_path$composer_backup ) ) {
                        
fwriteSTDERR$revert_msg );
                    } else {
                        
fwriteSTDERR$revert_fail_msg );
                    }
                }

                
$error_array error_get_last();
                if ( 
is_array$error_array ) && false !== strpos$error_array['message'], $memory_string ) ) {
                    
fwriteSTDERR$memory_msg );
                }
            }
        );
    }

    
/**
     * Check whether we are dealing with Composer version 2.0.0+.
     *
     * @return bool
     */
    
private function is_composer_v2() {
        return 
version_compareComposer::getVersion(), '2.0.0''>=' );
    }

    
/**
     * Try to retrieve default branch via GitHub API.
     *
     * @param string $package_name GitHub package name to retrieve the default branch from.
     * @param bool   $insecure     Optional. Whether to insecurely retry downloads that failed TLS handshake. Defaults
     *                             to false.
     * @return string Default branch, or 'master' if it could not be retrieved.
     */
    
private function get_github_default_branch$package_name$insecure false ) {
        
$github_token getenv'GITHUB_TOKEN' ); // Use GITHUB_TOKEN if available to avoid authorization failures or rate-limiting.
        
$headers      $github_token ? [ 'Authorization' => 'token ' $github_token ] : [];
        
$options      = [ 'insecure' => $insecure ];

        
$matches = [];
        if ( 
preg_match'#^(?:https?://github\.com/|git@github\.com:)(?<repo_name>.*?).git$#'$package_name$matches ) ) {
            
$package_name $matches['repo_name'];
        }

        
$github_api_repo_url "https://api.github.com/repos/{$package_name}";
        
$response            Utils\http_request'GET'$github_api_repo_urlnull /*data*/$headers$options );
        if ( 
20 !== (int) substr$response->status_code0) ) {
            
WP_CLI::warning(
                
sprintf(
                    
"Couldn't fetch default branch for package '%s' (HTTP code %d). Presuming default branch is 'master'.",
                    
$package_name,
                    
$response->status_code
                
)
            );
            return 
'master';
        }

        
$package_data json_decode$response->body );

        if ( 
JSON_ERROR_NONE !== json_last_error() ) {
            
WP_CLI::warning"Couldn't fetch default branch for package '%s' (failed to decode JSON response). Presuming default branch is 'master'." );
            return 
'master';
        }

        
$default_branch $package_data->default_branch;

        if ( ! 
is_string$default_branch ) || empty( $default_branch ) ) {
            
WP_CLI::warning(
                
sprintf(
                    
"Couldn't fetch default branch for package '%s'. Presuming default branch is 'master'.",
                    
$package_name
                
)
            );
            return 
'master';
        }

        
WP_CLI::debug"Detected package default branch: {$default_branch}"'packages' );

        return 
$default_branch;
    }
}
<?
php

if ( ! class_exists'WP_CLI' ) ) {
    return;
}

$wpcli_package_autoloader dirname__FILE__ ) . '/vendor/autoload.php';
if ( 
file_exists$wpcli_package_autoloader ) && ! class_exists'Package_Command' ) ) {
    require_once 
$wpcli_package_autoloader;
}
WP_CLI::add_command'package''Package_Command' );
<?
php
/**
 * An example application using php-cli-tools and Buzz
 */

require_once __DIR__ '/vendor/autoload.php';

define('BUZZ_PATH'realpath('../Buzz'));
define('SCRIPT_NAME'array_shift($argv));

require_once 
BUZZ_PATH '/lib/Buzz/ClassLoader.php';
Buzz\ClassLoader::register();

class 
HttpConsole {
    protected 
$_host;
    protected 
$_prompt;

    public function 
__construct($host) {
        
$this->_host 'http://' $host;
        
$this->_prompt '%K' $this->_host '%n/%K>%n ';
    }

    public function 
handleRequest($type$path) {
        
$request = new Buzz\Message\Request($type$path$this->_host);
        
$response = new Buzz\Message\Response;

        
$client = new Buzz\Client\FileGetContents();
        
$client->send($request$response);

        
// Display headers
        
foreach ($response->getHeaders() as $i => $header) {
            if (
$i == 0) {
                
\cli\line('%G{:header}%n'compact('header'));
                continue;
            }

            list(
$key$value) = explode(': '$header2);
            
\cli\line('%W{:key}%n: {:value}'compact('key''value'));
        }
        
\cli\line("\n");
        print 
$response->getContent() . "\n";

        switch (
$type) {
        }
    }

    public function 
run() {
        while (
true) {
            
$cmd \cli\prompt($this->_promptfalsenull);

            if (
preg_match('/^(HEAD|GET|POST|PUT|DELETE) (\S+)$/'$cmd$matches)) {
                
$this->handleRequest($matches[1], $matches[2]);
                continue;
            }

            if (
$cmd == '\q') {
                break;
            }
        }
    }
}

try {
    
$console = new HttpConsole(array_shift($argv) ?: '127.0.0.1:80');
    
$console->run();
} catch (
\Exception $e) {
    
\cli\err("\n\n%R" $e->getMessage() . "%n\n");
}

?>
<?php

error_reporting
(-1);
require_once 
__DIR__ '/vendor/autoload.php';


$args = new cli\Arguments(array(
    
'flags' => array(
        
'verbose' => array(
            
'description' => 'Turn on verbose mode',
            
'aliases'     => array('v')
        ),
        
'c' => array(
            
'description' => 'A counter to test stackable',
            
'stackable'   => true
        
)
    ),
    
'options' => array(
        
'user' => array(
            
'description' => 'Username for authentication',
            
'aliases'     => array('u')
        )
    ),
    
'strict' => true
));

try {
    
$args->parse();
} catch (
cli\InvalidArguments $e) {
    echo 
$e->getMessage() . "\n\n";
}

print_r($args->getArguments());
<?
php
/**
 * PHP Command Line Tools
 *
 * This source file is subject to the MIT license that is bundled
 * with this package in the file LICENSE.
 *
 * @author    James Logsdon <[email protected]>
 * @copyright 2010 James Logsdom (http://girsbrain.org)
 * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 */

namespace cli;

/**
 * A more complex type of Notifier, `Progress` Notifiers always have a maxim
 * value and generally show some form of percent complete or estimated time
 * to completion along with the standard Notifier displays.
 *
 * @see cli\Notify
 */
abstract class Progress extends \cli\Notify {
    protected 
$_total 0;

    
/**
     * Instantiates a Progress Notifier.
     *
     * @param string  $msg       The text to display next to the Notifier.
     * @param int     $total     The total number of ticks we will be performing.
     * @param int     $interval  The interval in milliseconds between updates.
     * @see cli\Progress::setTotal()
     */
    
public function __construct($msg$total$interval 100) {
        
parent::__construct($msg$interval);
        
$this->setTotal($total);
    }

    
/**
     * Set the max increments for this progress notifier.
     *
     * @param int  $total  The total number of times this indicator should be `tick`ed.
     * @throws \InvalidArgumentException  Thrown if the `$total` is less than 0.
     */
    
public function setTotal($total) {
        
$this->_total = (int)$total;

        if (
$this->_total 0) {
            throw new 
\InvalidArgumentException('Maximum value out of range, must be positive.');
        }
    }

    
/**
     * Reset the progress state so the same instance can be used in multiple loops.
     */
    
public function reset($total null) {
        
parent::reset();

        if (
$total) {
            
$this->setTotal($total);
        }
    }

    
/**
     * Behaves in a similar manner to `cli\Notify::current()`, but the output
     * is padded to match the length of `cli\Progress::total()`.
     *
     * @return string  The formatted and padded tick count.
     * @see cli\Progress::total()
     */
    
public function current() {
        
$size strlen($this->total());
        return 
str_pad(parent::current(), $size);
    }

    
/**
     * Returns the formatted total expected ticks.
     *
     * @return string  The formatted total ticks.
     */
    
public function total() {
        return 
number_format($this->_total);
    }

    
/**
     * Calculates the estimated total time for the tick count to reach the
     * total ticks given.
     *
     * @return int  The estimated total number of seconds for all ticks to be
     *              completed. This is not the estimated time left, but total.
     * @see cli\Notify::speed()
     * @see cli\Notify::elapsed()
     */
    
public function estimated() {
        
$speed $this->speed();
        if (!
$speed || !$this->elapsed()) {
            return 
0;
        }

        
$estimated round($this->_total $speed);
        return 
$estimated;
    }

    
/**
     * Forces the current tick count to the total ticks given at instatiation
     * time before passing on to `cli\Notify::finish()`.
     */
    
public function finish() {
        
$this->_current $this->_total;
        
parent::finish();
    }

    
/**
     * Increments are tick counter by the given amount. If no amount is provided,
     * the ticker is incremented by 1.
     *
     * @param int  $increment  The amount to increment by.
     */
    
public function increment($increment 1) {
        
$this->_current min($this->_total$this->_current $increment);
    }

    
/**
     * Calculate the percentage completed.
     *
     * @return float  The percent completed.
     */
    
public function percent() {
        if (
$this->_total == 0) {
            return 
1;
        }

        return (
$this->_current $this->_total);
    }
}
<?
php
/**
 * PHP Command Line Tools
 *
 * This source file is subject to the MIT license that is bundled
 * with this package in the file LICENSE.
 *
 * @author    James Logsdon <[email protected]>
 * @copyright 2010 James Logsdom (http://girsbrain.org)
 * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 */

namespace cli;

/**
 * The `Shell` class is a utility class for shell related tasks such as
 * information on width.
 */
class Shell {

    
/**
     * Returns the number of columns the current shell has for display.
     *
     * @return int  The number of columns.
     * @todo Test on more systems.
     */
    
static public function columns() {
        static 
$columns;

        if ( 
getenv'PHP_CLI_TOOLS_TEST_SHELL_COLUMNS_RESET' ) ) {
            
$columns null;
        }
        if ( 
null === $columns ) {
            if ( 
function_exists'exec' ) ) {
                if ( 
self::is_windows() ) {
                    
// Cater for shells such as Cygwin and Git bash where `mode CON` returns an incorrect value for columns.
                    
if ( ( $shell getenv'SHELL' ) ) && preg_match'/(?:bash|zsh)(?:\.exe)?$/'$shell ) && getenv'TERM' ) ) {
                        
$columns = (int) exec'tput cols' );
                    }
                    if ( ! 
$columns ) {
                        
$return_var = -1;
                        
$output = array();
                        
exec'mode CON'$output$return_var );
                        if ( 
=== $return_var && $output ) {
                            
// Look for second line ending in ": <number>" (searching for "Columns:" will fail on non-English locales).
                            
if ( preg_match'/:\s*[0-9]+\n[^:]+:\s*([0-9]+)\n/'implode"\n"$output ), $matches ) ) {
                                
$columns = (int) $matches[1];
                            }
                        }
                    }
                } else {
                    if ( ! ( 
$columns = (int) getenv'COLUMNS' ) ) ) {
                        
$size exec'/usr/bin/env stty size 2>/dev/null' );
                        if ( 
'' !== $size && preg_match'/[0-9]+ ([0-9]+)/'$size$matches ) ) {
                            
$columns = (int) $matches[1];
                        }
                        if ( ! 
$columns ) {
                            if ( 
getenv'TERM' ) ) {
                                
$columns = (int) exec'/usr/bin/env tput cols 2>/dev/null' );
                            }
                        }
                    }
                }
            }

            if ( ! 
$columns ) {
                
$columns 80// default width of cmd window on Windows OS
            
}
        }

        return 
$columns;
    }

    
/**
     * Checks whether the output of the current script is a TTY or a pipe / redirect
     *
     * Returns true if STDOUT output is being redirected to a pipe or a file; false is
     * output is being sent directly to the terminal.
     *
     * If an env variable SHELL_PIPE exists, returned result depends it's
     * value. Strings like 1, 0, yes, no, that validate to booleans are accepted.
     *
     * To enable ASCII formatting even when shell is piped, use the
     * ENV variable SHELL_PIPE=0
     *
     * @return bool
     */
    
static public function isPiped() {
        
$shellPipe getenv('SHELL_PIPE');

        if (
$shellPipe !== false) {
            return 
filter_var($shellPipeFILTER_VALIDATE_BOOLEAN);
        } else {
            if ( 
function_exists('stream_isatty') ) {
                return !
stream_isatty(STDOUT);
            } else {
                return (
function_exists('posix_isatty') && !posix_isatty(STDOUT));
            }
        }
    }

    
/**
     * Uses `stty` to hide input/output completely.
     * @param boolean $hidden Will hide/show the next data. Defaults to true.
     */
    
static public function hide($hidden true) {
        
system'stty ' . ( $hidden'-echo' 'echo' ) );
    }

    
/**
     * Is this shell in Windows?
     *
     * @return bool
     */
    
static private function is_windows() {
        return 
strtoupper(substr(PHP_OS03)) === 'WIN';
    }

}

?>
<?php
/**
 * PHP Command Line Tools
 *
 * This source file is subject to the MIT license that is bundled
 * with this package in the file LICENSE.
 *
 * @author    James Logsdon <[email protected]>
 * @copyright 2010 James Logsdom (http://girsbrain.org)
 * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 */

namespace cli\progress;

use 
cli;
use 
cli\Notify;
use 
cli\Progress;
use 
cli\Shell;
use 
cli\Streams;

/**
 * Displays a progress bar spanning the entire shell.
 *
 * Basic format:
 *
 *   ^MSG  PER% [=======================            ]  00:00 / 00:00$
 */
class Bar extends Progress {
    protected 
$_bars '=>';
    protected 
$_formatMessage '{:msg}  {:percent}% [';
    protected 
$_formatTiming '] {:elapsed} / {:estimated}';
    protected 
$_format '{:msg}{:bar}{:timing}';

    
/**
     * Prints the progress bar to the screen with percent complete, elapsed time
     * and estimated total time.
     *
     * @param boolean  $finish  `true` if this was called from
     *                          `cli\Notify::finish()`, `false` otherwise.
     * @see cli\out()
     * @see cli\Notify::formatTime()
     * @see cli\Notify::elapsed()
     * @see cli\Progress::estimated();
     * @see cli\Progress::percent()
     * @see cli\Shell::columns()
     */
    
public function display($finish false) {
        
$_percent $this->percent();

        
$percent str_pad(floor($_percent 100), 3);
        
$msg $this->_message;
        
$msg Streams::render($this->_formatMessagecompact('msg''percent'));

        
$estimated $this->formatTime($this->estimated());
        
$elapsed   str_pad($this->formatTime($this->elapsed()), strlen($estimated));
        
$timing    Streams::render($this->_formatTimingcompact('elapsed''estimated'));

        
$size Shell::columns();
        
$size -= strlen($msg $timing);
        if ( 
$size ) {
            
$size 0;
        }

        
$bar str_repeat($this->_bars[0], floor($_percent $size)) . $this->_bars[1];
        
// substr is needed to trim off the bar cap at 100%
        
$bar substr(str_pad($bar$size' '), 0$size);

        
Streams::out($this->_formatcompact('msg''bar''timing'));
    }

    
/**
     * This method augments the base definition from cli\Notify to optionally
     * allow passing a new message.
     *
     * @param int    $increment The amount to increment by.
     * @param string $msg       The text to display next to the Notifier. (optional)
     * @see cli\Notify::tick()
     */
    
public function tick($increment 1$msg null) {
        if (
$msg) {
            
$this->_message $msg;
        }
        
Notify::tick($increment);
    }
}
<?
php
/**
 * PHP Command Line Tools
 *
 * This source file is subject to the MIT license that is bundled
 * with this package in the file LICENSE.
 *
 * @author    James Logsdon <[email protected]>
 * @copyright 2010 James Logsdom (http://girsbrain.org)
 * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 */

namespace cli\notify;

use 
cli\Notify;
use 
cli\Streams;

/**
 * The `Spinner` Notifier displays an ASCII spinner.
 */
class Spinner extends Notify {
    protected 
$_chars '-\|/';
    protected 
$_format '{:msg} {:char}  ({:elapsed}, {:speed}/s)';
    protected 
$_iteration 0;

    
/**
     * Prints the current spinner position to `STDOUT` with the time elapsed
     * and tick speed.
     *
     * @param boolean  $finish  `true` if this was called from
     *                          `cli\Notify::finish()`, `false` otherwise.
     * @see cli\out_padded()
     * @see cli\Notify::formatTime()
     * @see cli\Notify::speed()
     */
    
public function display($finish false) {
        
$msg $this->_message;
        
$idx $this->_iteration++ % strlen($this->_chars);
        
$char $this->_chars[$idx];
        
$speed number_format(round($this->speed()));
        
$elapsed $this->formatTime($this->elapsed());

        
Streams::out_padded($this->_formatcompact('msg''char''elapsed''speed'));
    }
}
<?
php
/**
 * PHP Command Line Tools
 *
 * This source file is subject to the MIT license that is bundled
 * with this package in the file LICENSE.
 *
 * @author    James Logsdon <[email protected]>
 * @copyright 2010 James Logsdom (http://girsbrain.org)
 * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 */

namespace cli\notify;

use 
cli\Notify;
use 
cli\Streams;

/**
 * A Notifer that displays a string of periods.
 */
class Dots extends Notify {
    protected 
$_dots;
    protected 
$_format '{:msg}{:dots}  ({:elapsed}, {:speed}/s)';
    protected 
$_iteration;

    
/**
     * Instatiates a Notification object.
     *
     * @param string  $msg       The text to display next to the Notifier.
     * @param int     $dots      The number of dots to iterate through.
     * @param int     $interval  The interval in milliseconds between updates.
     * @throws \InvalidArgumentException
     */
    
public function __construct($msg$dots 3$interval 100) {
        
parent::__construct($msg$interval);
        
$this->_dots = (int)$dots;

        if (
$this->_dots <= 0) {
            throw new 
\InvalidArgumentException('Dot count out of range, must be positive.');
        }
    }

    
/**
     * Prints the correct number of dots to `STDOUT` with the time elapsed and
     * tick speed.
     *
     * @param boolean  $finish  `true` if this was called from
     *                          `cli\Notify::finish()`, `false` otherwise.
     * @see cli\out_padded()
     * @see cli\Notify::formatTime()
     * @see cli\Notify::speed()
     */
    
public function display($finish false) {
        
$repeat $this->_dots;
        if (!
$finish) {
            
$repeat $this->_iteration++ % $repeat;
        }

        
$msg $this->_message;
        
$dots str_pad(str_repeat('.'$repeat), $this->_dots);
        
$speed number_format(round($this->speed()));
        
$elapsed $this->formatTime($this->elapsed());

        
Streams::out_padded($this->_formatcompact('msg''dots''speed''elapsed'));
    }
}
<?
php
/**
 * PHP Command Line Tools
 *
 * This source file is subject to the MIT license that is bundled
 * with this package in the file LICENSE.
 *
 * @author    James Logsdon <[email protected]>
 * @copyright 2010 James Logsdom (http://girsbrain.org)
 * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 */

namespace cli;

/**
 * Change the color of text.
 *
 * Reference: http://graphcomp.com/info/specs/ansi_col.html#colors
 */
class Colors {
    static protected 
$_colors = array(
        
'color' => array(
            
'black'   => 30,
            
'red'     => 31,
            
'green'   => 32,
            
'yellow'  => 33,
            
'blue'    => 34,
            
'magenta' => 35,
            
'cyan'    => 36,
            
'white'   => 37
        
),
        
'style' => array(
            
'bright'     => 1,
            
'dim'        => 2,
            
'underline' => 4,
            
'blink'      => 5,
            
'reverse'    => 7,
            
'hidden'     => 8
        
),
        
'background' => array(
            
'black'   => 40,
            
'red'     => 41,
            
'green'   => 42,
            
'yellow'  => 43,
            
'blue'    => 44,
            
'magenta' => 45,
            
'cyan'    => 46,
            
'white'   => 47
        
)
    );
    static protected 
$_enabled null;

    static protected 
$_string_cache = array();

    static public function 
enable($force true) {
        
self::$_enabled $force === true true null;
    }

    static public function 
disable($force true) {
        
self::$_enabled $force === true false null;
    }

    
/**
     * Check if we should colorize output based on local flags and shell type.
     *
     * Only check the shell type if `Colors::$_enabled` is null and `$colored` is null.
     */
    
static public function shouldColorize($colored null) {
        return 
self::$_enabled === true ||
            (
self::$_enabled !== false &&
                (
$colored === true ||
                    (
$colored !== false && Streams::isTty())));
    }

    
/**
     * Set the color.
     *
     * @param string  $color  The name of the color or style to set.
     * @return string
     */
    
static public function color($color) {
        if (!
is_array($color)) {
            
$color compact('color');
        }

        
$color += array('color' => null'style' => null'background' => null);

        if (
$color['color'] == 'reset') {
            return 
"\033[0m";
        }

        
$colors = array();
        foreach (array(
'color''style''background') as $type) {
            
$code $color[$type];
            if (isset(
self::$_colors[$type][$code])) {
                
$colors[] = self::$_colors[$type][$code];
            }
        }

        if (empty(
$colors)) {
            
$colors[] = 0;
        }

        return 
"\033[" join(';'$colors) . "m";
    }

    
/**
     * Colorize a string using helpful string formatters. If the `Streams::$out` points to a TTY coloring will be enabled,
     * otherwise disabled. You can control this check with the `$colored` parameter.
     *
     * @param string   $string
     * @param boolean  $colored  Force enable or disable the colorized output. If left as `null` the TTY will control coloring.
     * @return string
     */
    
static public function colorize($string$colored null) {
        
$passed $string;

        if (!
self::shouldColorize($colored)) {
            
$return self::decolorize$passed/*keep_encodings*/ );
            
self::cacheString($passed$return);
            return 
$return;
        }

        
$md5 md5($passed);
        if (isset(
self::$_string_cache[$md5]['colorized'])) {
            return 
self::$_string_cache[$md5]['colorized'];
        }

        
$string str_replace('%%''%¾'$string);

        foreach (
self::getColors() as $key => $value) {
            
$string str_replace($keyself::color($value), $string);
        }

        
$string str_replace('%¾''%'$string);
        
self::cacheString($passed$string);

        return 
$string;
    }

    
/**
     * Remove color information from a string.
     *
     * @param string $string A string with color information.
     * @param int    $keep   Optional. If the 1 bit is set, color tokens (eg "%n") won't be stripped. If the 2 bit is set, color encodings (ANSI escapes) won't be stripped. Default 0.
     * @return string A string with color information removed.
     */
    
static public function decolorize$string$keep ) {
        if ( ! ( 
$keep ) ) {
            
// Get rid of color tokens if they exist
            
$string str_replace('%%''%¾'$string);
            
$string str_replace(array_keys(self::getColors()), ''$string);
            
$string str_replace('%¾''%'$string);
        }

        if ( ! ( 
$keep ) ) {
            
// Remove color encoding if it exists
            
foreach (self::getColors() as $key => $value) {
                
$string str_replace(self::color($value), ''$string);
            }
        }

        return 
$string;
    }

    
/**
     * Cache the original, colorized, and decolorized versions of a string.
     *
     * @param string $passed The original string before colorization.
     * @param string $colorized The string after running through self::colorize.
     * @param string $deprecated Optional. Not used. Default null.
     */
    
static public function cacheString$passed$colorized$deprecated null ) {
        
self::$_string_cache[md5($passed)] = array(
            
'passed'      => $passed,
            
'colorized'   => $colorized,
            
'decolorized' => self::decolorize($passed), // Not very useful but keep for BC.
        
);
    }

    
/**
     * Return the length of the string without color codes.
     *
     * @param string  $string  the string to measure
     * @return int
     */
    
static public function length($string) {
        return 
safe_strlenself::decolorize$string ) );
    }

    
/**
     * Return the width (length in characters) of the string without color codes if enabled.
     *
     * @param string      $string        The string to measure.
     * @param bool        $pre_colorized Optional. Set if the string is pre-colorized. Default false.
     * @param string|bool $encoding      Optional. The encoding of the string. Default false.
     * @return int
     */
    
static public function width$string$pre_colorized false$encoding false ) {
        return 
strwidth$pre_colorized || self::shouldColorize() ? self::decolorize$string$pre_colorized /*keep_tokens*/ ) : $string$encoding );
    }

    
/**
     * Pad the string to a certain display length.
     *
     * @param string      $string        The string to pad.
     * @param int         $length        The display length.
     * @param bool        $pre_colorized Optional. Set if the string is pre-colorized. Default false.
     * @param string|bool $encoding      Optional. The encoding of the string. Default false.
     * @param int         $pad_type      Optional. Can be STR_PAD_RIGHT, STR_PAD_LEFT, or STR_PAD_BOTH. If pad_type is not specified it is assumed to be STR_PAD_RIGHT.
     * @return string
     */
    
static public function pad$string$length$pre_colorized false$encoding false$pad_type STR_PAD_RIGHT ) {
        
$real_length self::width$string$pre_colorized$encoding );
        
$diff strlen$string ) - $real_length;
        
$length += $diff;

        return 
str_pad$string$length' '$pad_type );
    }

    
/**
     * Get the color mapping array.
     *
     * @return array Array of color tokens mapped to colors and styles.
     */
    
static public function getColors() {
        return array(
            
'%y' => array('color' => 'yellow'),
            
'%g' => array('color' => 'green'),
            
'%b' => array('color' => 'blue'),
            
'%r' => array('color' => 'red'),
            
'%p' => array('color' => 'magenta'),
            
'%m' => array('color' => 'magenta'),
            
'%c' => array('color' => 'cyan'),
            
'%w' => array('color' => 'white'),
            
'%k' => array('color' => 'black'),
            
'%n' => array('color' => 'reset'),
            
'%Y' => array('color' => 'yellow''style' => 'bright'),
            
'%G' => array('color' => 'green''style' => 'bright'),
            
'%B' => array('color' => 'blue''style' => 'bright'),
            
'%R' => array('color' => 'red''style' => 'bright'),
            
'%P' => array('color' => 'magenta''style' => 'bright'),
            
'%M' => array('color' => 'magenta''style' => 'bright'),
            
'%C' => array('color' => 'cyan''style' => 'bright'),
            
'%W' => array('color' => 'white''style' => 'bright'),
            
'%K' => array('color' => 'black''style' => 'bright'),
            
'%N' => array('color' => 'reset''style' => 'bright'),
            
'%3' => array('background' => 'yellow'),
            
'%2' => array('background' => 'green'),
            
'%4' => array('background' => 'blue'),
            
'%1' => array('background' => 'red'),
            
'%5' => array('background' => 'magenta'),
            
'%6' => array('background' => 'cyan'),
            
'%7' => array('background' => 'white'),
            
'%0' => array('background' => 'black'),
            
'%F' => array('style' => 'blink'),
            
'%U' => array('style' => 'underline'),
            
'%8' => array('style' => 'reverse'),
            
'%9' => array('style' => 'bright'),
            
'%_' => array('style' => 'bright')
        );
    }

    
/**
     * Get the cached string values.
     *
     * @return array The cached string values.
     */
    
static public function getStringCache() {
        return 
self::$_string_cache;
    }

    
/**
     * Clear the string cache.
     */
    
static public function clearStringCache() {
        
self::$_string_cache = array();
    }
}
<?
php
/**
 * PHP Command Line Tools
 *
 * This source file is subject to the MIT license that is bundled
 * with this package in the file LICENSE.
 *
 * @author    James Logsdon <[email protected]>
 * @copyright 2010 James Logsdom (http://girsbrain.org)
 * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 */

namespace cli;

use 
cli\arguments\Argument;
use 
cli\arguments\HelpScreen;
use 
cli\arguments\InvalidArguments;
use 
cli\arguments\Lexer;

/**
 * Parses command line arguments.
 */
class Arguments implements \ArrayAccess {
    protected 
$_flags = array();
    protected 
$_options = array();
    protected 
$_strict false;
    protected 
$_input = array();
    protected 
$_invalid = array();
    protected 
$_parsed;
    protected 
$_lexer;

    
/**
     * Initializes the argument parser. If you wish to change the default behaviour
     * you may pass an array of options as the first argument. Valid options are
     * `'help'` and `'strict'`, each a boolean.
     *
     * `'help'` is `true` by default, `'strict'` is false by default.
     *
     * @param  array  $options  An array of options for this parser.
     */
    
public function __construct($options = array()) {
        
$options += array(
            
'strict' => false,
            
'input'  => array_slice($_SERVER['argv'], 1)
        );

        
$this->_input $options['input'];
        
$this->setStrict($options['strict']);

        if (isset(
$options['flags'])) {
            
$this->addFlags($options['flags']);
        }
        if (isset(
$options['options'])) {
            
$this->addOptions($options['options']);
        }
    }

    
/**
     * Get the list of arguments found by the defined definitions.
     *
     * @return array
     */
    
public function getArguments() {
        if (!isset(
$this->_parsed)) {
            
$this->parse();
        }
        return 
$this->_parsed;
    }

    public function 
getHelpScreen() {
        return new 
HelpScreen($this);
    }

    
/**
     * Encodes the parsed arguments as JSON.
     *
     * @return string
     */
    
public function asJSON() {
        return 
json_encode($this->_parsed);
    }

    
/**
     * Returns true if a given argument was parsed.
     *
     * @param mixed  $offset  An Argument object or the name of the argument.
     * @return bool
     */
    
public function offsetExists($offset) {
        if (
$offset instanceOf Argument) {
            
$offset $offset->key;
        }

        return 
array_key_exists($offset$this->_parsed);
    }

    
/**
     * Get the parsed argument's value.
     *
     * @param mixed  $offset  An Argument object or the name of the argument.
     * @return mixed
     */
    
public function offsetGet($offset) {
        if (
$offset instanceOf Argument) {
            
$offset $offset->key;
        }

        if (isset(
$this->_parsed[$offset])) {
            return 
$this->_parsed[$offset];
        }
    }

    
/**
     * Sets the value of a parsed argument.
     *
     * @param mixed  $offset  An Argument object or the name of the argument.
     * @param mixed  $value   The value to set
     */
    
public function offsetSet($offset$value) {
        if (
$offset instanceOf Argument) {
            
$offset $offset->key;
        }

        
$this->_parsed[$offset] = $value;
    }

    
/**
     * Unset a parsed argument.
     *
     * @param mixed  $offset  An Argument object or the name of the argument.
     */
    
public function offsetUnset($offset) {
        if (
$offset instanceOf Argument) {
            
$offset $offset->key;
        }

        unset(
$this->_parsed[$offset]);
    }

    
/**
     * Adds a flag (boolean argument) to the argument list.
     *
     * @param mixed  $flag  A string representing the flag, or an array of strings.
     * @param array  $settings  An array of settings for this flag.
     * @setting string  description  A description to be shown in --help.
     * @setting bool    default  The default value for this flag.
     * @setting bool    stackable  Whether the flag is repeatable to increase the value.
     * @setting array   aliases  Other ways to trigger this flag.
     * @return $this
     */
    
public function addFlag($flag$settings = array()) {
        if (
is_string($settings)) {
            
$settings = array('description' => $settings);
        }
        if (
is_array($flag)) {
            
$settings['aliases'] = $flag;
            
$flag array_shift($settings['aliases']);
        }
        if (isset(
$this->_flags[$flag])) {
            
$this->_warn('flag already exists: ' $flag);
            return 
$this;
        }

        
$settings += array(
            
'default'     => false,
            
'stackable'   => false,
            
'description' => null,
            
'aliases'     => array()
        );

        
$this->_flags[$flag] = $settings;
        return 
$this;
    }

    
/**
     * Add multiple flags at once. The input array should be keyed with the
     * primary flag character, and the values should be the settings array
     * used by {addFlag}.
     *
     * @param array  $flags  An array of flags to add
     * @return $this
     */
    
public function addFlags($flags) {
        foreach (
$flags as $flag => $settings) {
            if (
is_numeric($flag)) {
                
$this->_warn('No flag character given');
                continue;
            }

            
$this->addFlag($flag$settings);
        }

        return 
$this;
    }

    
/**
     * Adds an option (string argument) to the argument list.
     *
     * @param mixed  $option  A string representing the option, or an array of strings.
     * @param array  $settings  An array of settings for this option.
     * @setting string  description  A description to be shown in --help.
     * @setting bool    default  The default value for this option.
     * @setting array   aliases  Other ways to trigger this option.
     * @return $this
     */
    
public function addOption($option$settings = array()) {
        if (
is_string($settings)) {
            
$settings = array('description' => $settings);
        }
        if (
is_array($option)) {
            
$settings['aliases'] = $option;
            
$option array_shift($settings['aliases']);
        }
        if (isset(
$this->_options[$option])) {
            
$this->_warn('option already exists: ' $option);
            return 
$this;
        }

        
$settings += array(
            
'default'     => null,
            
'description' => null,
            
'aliases'     => array()
        );

        
$this->_options[$option] = $settings;
        return 
$this;
    }

    
/**
     * Add multiple options at once. The input array should be keyed with the
     * primary option string, and the values should be the settings array
     * used by {addOption}.
     *
     * @param array  $options  An array of options to add
     * @return $this
     */
    
public function addOptions($options) {
        foreach (
$options as $option => $settings) {
            if (
is_numeric($option)) {
                
$this->_warn('No option string given');
                continue;
            }

            
$this->addOption($option$settings);
        }

        return 
$this;
    }

    
/**
     * Enable or disable strict mode. If strict mode is active any invalid
     * arguments found by the parser will throw `cli\arguments\InvalidArguments`.
     *
     * Even if strict is disabled, invalid arguments are logged and can be
     * retrieved with `cli\Arguments::getInvalidArguments()`.
     *
     * @param bool  $strict  True to enable, false to disable.
     * @return $this
     */
    
public function setStrict($strict) {
        
$this->_strict = (bool)$strict;
        return 
$this;
    }

    
/**
     * Get the list of invalid arguments the parser found.
     *
     * @return array
     */
    
public function getInvalidArguments() {
        return 
$this->_invalid;
    }

    
/**
     * Get a flag by primary matcher or any defined aliases.
     *
     * @param mixed  $flag  Either a string representing the flag or an
     *                      cli\arguments\Argument object.
     * @return array
     */
    
public function getFlag($flag) {
        if (
$flag instanceOf Argument) {
            
$obj  $flag;
            
$flag $flag->value;
        }

        if (isset(
$this->_flags[$flag])) {
            return 
$this->_flags[$flag];
        }

        foreach (
$this->_flags as $master => $settings) {
            if (
in_array($flag, (array)$settings['aliases'])) {
                if (isset(
$obj)) {
                    
$obj->key $master;
                }

                
$cache[$flag] =& $settings;
                return 
$settings;
            }
        }
    }

    public function 
getFlags() {
        return 
$this->_flags;
    }

    public function 
hasFlags() {
        return !empty(
$this->_flags);
    }

    
/**
     * Returns true if the given argument is defined as a flag.
     *
     * @param mixed  $argument  Either a string representing the flag or an
     *                          cli\arguments\Argument object.
     * @return bool
     */
    
public function isFlag($argument) {
        return (
null !== $this->getFlag($argument));
    }

    
/**
     * Returns true if the given flag is stackable.
     *
     * @param mixed  $flag  Either a string representing the flag or an
     *                      cli\arguments\Argument object.
     * @return bool
     */
    
public function isStackable($flag) {
        
$settings $this->getFlag($flag);

        return isset(
$settings) && (true === $settings['stackable']);
    }

    
/**
     * Get an option by primary matcher or any defined aliases.
     *
     * @param mixed  $option Either a string representing the option or an
     *                       cli\arguments\Argument object.
     * @return array
     */
    
public function getOption($option) {
        if (
$option instanceOf Argument) {
            
$obj $option;
            
$option $option->value;
        }

        if (isset(
$this->_options[$option])) {
            return 
$this->_options[$option];
        }

        foreach (
$this->_options as $master => $settings) {
            if (
in_array($option, (array)$settings['aliases'])) {
                if (isset(
$obj)) {
                    
$obj->key $master;
                }

                return 
$settings;
            }
        }
    }

    public function 
getOptions() {
        return 
$this->_options;
    }

    public function 
hasOptions() {
        return !empty(
$this->_options);
    }

    
/**
     * Returns true if the given argument is defined as an option.
     *
     * @param mixed  $argument  Either a string representing the option or an
     *                          cli\arguments\Argument object.
     * @return bool
     */
    
public function isOption($argument) {
        return (
null != $this->getOption($argument));
    }

    
/**
     * Parses the argument list with the given options. The returned argument list
     * will use either the first long name given or the first name in the list
     * if a long name is not given.
     *
     * @return array
     * @throws arguments\InvalidArguments
     */
    
public function parse() {
        
$this->_invalid = array();
        
$this->_parsed = array();
        
$this->_lexer = new Lexer($this->_input);

        
$this->_applyDefaults();

        foreach (
$this->_lexer as $argument) {
            if (
$this->_parseFlag($argument)) {
                continue;
            }
            if (
$this->_parseOption($argument)) {
                continue;
            }

            
array_push($this->_invalid$argument->raw);
        }

        if (
$this->_strict && !empty($this->_invalid)) {
            throw new 
InvalidArguments($this->_invalid);
        }
    }

    
/**
     * This applies the default values, if any, of all of the
     * flags and options, so that if there is a default value
     * it will be available.
     */
    
private function _applyDefaults() {
        foreach(
$this->_flags as $flag => $settings) {
            
$this[$flag] = $settings['default'];
        }

        foreach(
$this->_options as $option => $settings) {
            
// If the default is 0 we should still let it be set.
            
if (!empty($settings['default']) || $settings['default'] === 0) {
                
$this[$option] = $settings['default'];
            }
        }
    }

    private function 
_warn($message) {
        
trigger_error('[' __CLASS__ .'] ' $messageE_USER_WARNING);
    }

    private function 
_parseFlag($argument) {
        if (!
$this->isFlag($argument)) {
            return 
false;
        }

        if (
$this->isStackable($argument)) {
            if (!isset(
$this[$argument])) {
                
$this[$argument->key] = 0;
            }

            
$this[$argument->key] += 1;
        } else {
            
$this[$argument->key] = true;
        }

        return 
true;
    }

    private function 
_parseOption($option) {
        if (!
$this->isOption($option)) {
            return 
false;
        }

        
// Peak ahead to make sure we get a value.
        
if ($this->_lexer->end() || !$this->_lexer->peek->isValue) {
            
$optionSettings $this->getOption($option->key);

            if (empty(
$optionSettings['default']) && $optionSettings !== 0) {
                
// Oops! Got no value and no default , throw a warning and continue.
                
$this->_warn('no value given for ' $option->raw);
                
$this[$option->key] = null;
            } else {
                
// No value and we have a default, so we set to the default
                
$this[$option->key] = $optionSettings['default'];
            }
            return 
true;
        }

        
// Store as array and join to string after looping for values
        
$values = array();

        
// Loop until we find a flag in peak-ahead
        
foreach ($this->_lexer as $value) {
            
array_push($values$value->raw);

            if (!
$this->_lexer->end() && !$this->_lexer->peek->isValue) {
                break;
            }
        }

        
$this[$option->key] = join(' '$values);
        return 
true;
    }
}
<?
php
/**
 * PHP Command Line Tools
 *
 * This source file is subject to the MIT license that is bundled
 * with this package in the file LICENSE.
 *
 * @author    James Logsdon <[email protected]>
 * @copyright 2010 James Logsdom (http://girsbrain.org)
 * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 */

namespace cli;

use 
cli\Shell;
use 
cli\Streams;
use 
cli\table\Ascii;
use 
cli\table\Renderer;
use 
cli\table\Tabular;

/**
 * The `Table` class is used to display data in a tabular format.
 */
class Table {
    protected 
$_renderer;
    protected 
$_headers = array();
    protected 
$_footers = array();
    protected 
$_width = array();
    protected 
$_rows = array();

    
/**
     * Initializes the `Table` class.
     *
     * There are 3 ways to instantiate this class:
     *
     *  1. Pass an array of strings as the first parameter for the column headers
     *     and a 2-dimensional array as the second parameter for the data rows.
     *  2. Pass an array of hash tables (string indexes instead of numerical)
     *     where each hash table is a row and the indexes of the *first* hash
     *     table are used as the header values.
     *  3. Pass nothing and use `setHeaders()` and `addRow()` or `setRows()`.
     *
     * @param array  $headers  Headers used in this table. Optional.
     * @param array  $rows     The rows of data for this table. Optional.
     * @param array  $footers  Footers used in this table. Optional.
     */
    
public function __construct(array $headers null, array $rows null, array $footers null) {
        if (!empty(
$headers)) {
            
// If all the rows is given in $headers we use the keys from the
            // first row for the header values
            
if ($rows === null) {
                
$rows $headers;
                
$keys array_keys(array_shift($headers));
                
$headers = array();

                foreach (
$keys as $header) {
                    
$headers[$header] = $header;
                }
            }

            
$this->setHeaders($headers);
            
$this->setRows($rows);
        }

        if (!empty(
$footers)) {
            
$this->setFooters($footers);
        }

        if (
Shell::isPiped()) {
            
$this->setRenderer(new Tabular());
        } else {
            
$this->setRenderer(new Ascii());
        }
    }

    public function 
resetTable()
    {
        
$this->_headers = array();
        
$this->_width = array();
        
$this->_rows = array();
        
$this->_footers = array();
        return 
$this;
    }

    
/**
     * Sets the renderer used by this table.
     *
     * @param table\Renderer  $renderer  The renderer to use for output.
     * @see   table\Renderer
     * @see   table\Ascii
     * @see   table\Tabular
     */
    
public function setRenderer(Renderer $renderer) {
        
$this->_renderer $renderer;
    }

    
/**
     * Loops through the row and sets the maximum width for each column.
     *
     * @param array  $row  The table row.
     * @return array $row
     */
    
protected function checkRow(array $row) {
        foreach (
$row as $column => $str) {
            
$width Colors::width$str$this->isAsciiPreColorized$column ) );
            if (!isset(
$this->_width[$column]) || $width $this->_width[$column]) {
                
$this->_width[$column] = $width;
            }
        }

        return 
$row;
    }

    
/**
     * Output the table to `STDOUT` using `cli\line()`.
     *
     * If STDOUT is a pipe or redirected to a file, should output simple
     * tab-separated text. Otherwise, renders table with ASCII table borders
     *
     * @uses cli\Shell::isPiped() Determine what format to output
     *
     * @see cli\Table::renderRow()
     */
    
public function display() {
        foreach( 
$this->getDisplayLines() as $line ) {
            
Streams::line$line );
        }
    }

    
/**
     * Get the table lines to output.
     *
     * @see cli\Table::display()
     * @see cli\Table::renderRow()
     *
     * @return array
     */
    
public function getDisplayLines() {
        
$this->_renderer->setWidths($this->_width$fallback true);
        
$border $this->_renderer->border();

        
$out = array();
        if (isset(
$border)) {
            
$out[] = $border;
        }
        
$out[] = $this->_renderer->row($this->_headers);
        if (isset(
$border)) {
            
$out[] = $border;
        }

        foreach (
$this->_rows as $row) {
            
$row $this->_renderer->row($row);
            
$row explodePHP_EOL$row );
            
$out array_merge$out$row );
        }

        if (isset(
$border)) {
            
$out[] = $border;
        }

        if (
$this->_footers) {
            
$out[] = $this->_renderer->row($this->_footers);
            if (isset(
$border)) {
                
$out[] = $border;
            }
        }
        return 
$out;
    }

    
/**
     * Sort the table by a column. Must be called before `cli\Table::display()`.
     *
     * @param int  $column  The index of the column to sort by.
     */
    
public function sort($column) {
        if (!isset(
$this->_headers[$column])) {
            
trigger_error('No column with index ' $columnE_USER_NOTICE);
            return;
        }

        
usort($this->_rows, function($a$b) use ($column) {
            return 
strcmp($a[$column], $b[$column]);
        });
    }

    
/**
     * Set the headers of the table.
     *
     * @param array  $headers  An array of strings containing column header names.
     */
    
public function setHeaders(array $headers) {
        
$this->_headers $this->checkRow($headers);
    }

    
/**
     * Set the footers of the table.
     *
     * @param array  $footers  An array of strings containing column footers names.
     */
    
public function setFooters(array $footers) {
        
$this->_footers $this->checkRow($footers);
    }


    
/**
     * Add a row to the table.
     *
     * @param array  $row  The row data.
     * @see cli\Table::checkRow()
     */
    
public function addRow(array $row) {
        
$this->_rows[] = $this->checkRow($row);
    }

    
/**
     * Clears all previous rows and adds the given rows.
     *
     * @param array  $rows  A 2-dimensional array of row data.
     * @see cli\Table::addRow()
     */
    
public function setRows(array $rows) {
        
$this->_rows = array();
        foreach (
$rows as $row) {
            
$this->addRow($row);
        }
    }

    public function 
countRows() {
        return 
count($this->_rows);
    }

    
/**
     * Set whether items in an Ascii table are pre-colorized.
     *
     * @param bool|array $precolorized A boolean to set all columns in the table as pre-colorized, or an array of booleans keyed by column index (number) to set individual columns as pre-colorized.
     * @see cli\Ascii::setPreColorized()
     */
    
public function setAsciiPreColorized$pre_colorized ) {
        if ( 
$this->_renderer instanceof Ascii ) {
            
$this->_renderer->setPreColorized$pre_colorized );
        }
    }

    
/**
     * Is a column in an Ascii table pre-colorized?
     *
     * @param int $column Column index to check.
     * @return bool True if whole Ascii table is marked as pre-colorized, or if the individual column is pre-colorized; else false.
     * @see cli\Ascii::isPreColorized()
     */
    
private function isAsciiPreColorized$column ) {
        if ( 
$this->_renderer instanceof Ascii ) {
            return 
$this->_renderer->isPreColorized$column );
        }
        return 
false;
    }
}
<?
php

namespace cli;

class 
Streams {

    protected static 
$out STDOUT;
    protected static 
$in STDIN;
    protected static 
$err STDERR;

    static function 
_call$func$args ) {
        
$method __CLASS__ '::' $func;
        return 
call_user_func_array$method$args );
    }

    static public function 
isTty() {
        if ( 
function_exists('stream_isatty') ) {
            return !
stream_isatty(static::$out);
        } else {
            return (
function_exists('posix_isatty') && !posix_isatty(static::$out));
        }
    }

    
/**
     * Handles rendering strings. If extra scalar arguments are given after the `$msg`
     * the string will be rendered with `sprintf`. If the second argument is an `array`
     * then each key in the array will be the placeholder name. Placeholders are of the
     * format {:key}.
     *
     * @param string   $msg  The message to render.
     * @param mixed    ...   Either scalar arguments or a single array argument.
     * @return string  The rendered string.
     */
    
public static function render$msg ) {
        
$args func_get_args();

        
// No string replacement is needed
        
if( count$args ) == || ( is_string$args[1] ) && '' === $args[1] ) ) {
            return 
Colors::shouldColorize() ? Colors::colorize$msg ) : $msg;
        }

        
// If the first argument is not an array just pass to sprintf
        
if( !is_array$args[1] ) ) {
            
// Colorize the message first so sprintf doesn't bitch at us
            
if ( Colors::shouldColorize() ) {
                
$args[0] = Colors::colorize$args[0] );
            }

            
// Escape percent characters for sprintf
            
$args[0] = preg_replace('/(%([^\w]|$))/'"%$1"$args[0]);

            return 
call_user_func_array'sprintf'$args );
        }

        
// Here we do named replacement so formatting strings are more understandable
        
foreach( $args[1] as $key => $value ) {
            
$msg str_replace'{:' $key '}'$value$msg );
        }
        return 
Colors::shouldColorize() ? Colors::colorize$msg ) : $msg;
    }

    
/**
     * Shortcut for printing to `STDOUT`. The message and parameters are passed
     * through `sprintf` before output.
     *
     * @param string  $msg  The message to output in `printf` format.
     * @param mixed   ...   Either scalar arguments or a single array argument.
     * @return void
     * @see \cli\render()
     */
    
public static function out$msg ) {
        
fwrite( static::$outself::_call'render'func_get_args() ) );
    }

    
/**
     * Pads `$msg` to the width of the shell before passing to `cli\out`.
     *
     * @param string  $msg  The message to pad and pass on.
     * @param mixed   ...   Either scalar arguments or a single array argument.
     * @return void
     * @see cli\out()
     */
    
public static function out_padded$msg ) {
        
$msg self::_call'render'func_get_args() );
        
self::outstr_pad$msg\cli\Shell::columns() ) );
    }

    
/**
     * Prints a message to `STDOUT` with a newline appended. See `\cli\out` for
     * more documentation.
     *
     * @see cli\out()
     */
    
public static function line$msg '' ) {
        
// func_get_args is empty if no args are passed even with the default above.
        
$args array_mergefunc_get_args(), array( '' ) );
        
$args[0] .= "\n";

        
self::_call'out'$args );
    }

    
/**
     * Shortcut for printing to `STDERR`. The message and parameters are passed
     * through `sprintf` before output.
     *
     * @param string  $msg  The message to output in `printf` format. With no string,
     *                      a newline is printed.
     * @param mixed   ...   Either scalar arguments or a single array argument.
     * @return void
     */
    
public static function err$msg '' ) {
        
// func_get_args is empty if no args are passed even with the default above.
        
$args array_mergefunc_get_args(), array( '' ) );
        
$args[0] .= "\n";
        
fwrite( static::$errself::_call'render'$args ) );
    }

    
/**
     * Takes input from `STDIN` in the given format. If an end of transmission
     * character is sent (^D), an exception is thrown.
     *
     * @param string  $format  A valid input format. See `fscanf` for documentation.
     *                         If none is given, all input up to the first newline
     *                         is accepted.
     * @param boolean $hide    If true will hide what the user types in.
     * @return string  The input with whitespace trimmed.
     * @throws \Exception  Thrown if ctrl-D (EOT) is sent as input.
     */
    
public static function input$format null$hide false ) {
        if ( 
$hide )
            
Shell::hide();

        if( 
$format ) {
            
fscanf( static::$in$format "\n"$line );
        } else {
            
$line fgets( static::$in );
        }

        if ( 
$hide ) {
            
Shell::hidefalse );
            echo 
"\n";
        }

        if( 
$line === false ) {
            throw new 
\Exception'Caught ^D during input' );
        }

        return 
trim$line );
    }

    
/**
     * Displays an input prompt. If no default value is provided the prompt will
     * continue displaying until input is received.
     *
     * @param string      $question The question to ask the user.
     * @param bool|string $default  A default value if the user provides no input.
     * @param string      $marker   A string to append to the question and default value
     *                              on display.
     * @param boolean     $hide     Optionally hides what the user types in.
     * @return string  The users input.
     * @see cli\input()
     */
    
public static function prompt$question$default null$marker ': '$hide false ) {
        if( 
$default && strpos$question'[' ) === false ) {
            
$question .= ' [' $default ']';
        }

        while( 
true ) {
            
self::out$question $marker );
            
$line self::inputnull$hide );

            if ( 
trim$line ) !== '' )
                return 
$line;
            if( 
$default !== false )
                return 
$default;
        }
    }

    
/**
     * Presents a user with a multiple choice question, useful for 'yes/no' type
     * questions (which this public static function defaults too).
     *
     * @param string  $question  The question to ask the user.
     * @param string  $choice    A string of characters allowed as a response. Case is ignored.
     * @param string  $default   The default choice. NULL if a default is not allowed.
     * @return string  The users choice.
     * @see cli\prompt()
     */
    
public static function choose$question$choice 'yn'$default 'n' ) {
        if( !
is_string$choice ) ) {
            
$choice join''$choice );
        }

        
// Make every choice character lowercase except the default
        
$choice str_ireplace$defaultstrtoupper$default ), strtolower$choice ) );
        
// Seperate each choice with a forward-slash
        
$choices trimjoin'/'preg_split'//'$choice ) ), '/' );

        while( 
true ) {
            
$line self::promptsprintf'%s? [%s]'$question$choices ), $default'' );

            if( 
stripos$choice$line ) !== false ) {
                return 
strtolower$line );
            }
            if( !empty( 
$default ) ) {
                return 
strtolower$default );
            }
        }
    }

    
/**
     * Displays an array of strings as a menu where a user can enter a number to
     * choose an option. The array must be a single dimension with either strings
     * or objects with a `__toString()` method.
     *
     * @param array   $items    The list of items the user can choose from.
     * @param string  $default  The index of the default item.
     * @param string  $title    The message displayed to the user when prompted.
     * @return string  The index of the chosen item.
     * @see cli\line()
     * @see cli\input()
     * @see cli\err()
     */
    
public static function menu$items$default null$title 'Choose an item' ) {
        
$map array_values$items );

        if( 
$default && strpos$title'[' ) === false && isset( $items[$default] ) ) {
            
$title .= ' [' $items[$default] . ']';
        }

        foreach( 
$map as $idx => $item ) {
            
self::line'  %d. %s'$idx 1, (string)$item );
        }
        
self::line();

        while( 
true ) {
            
fwrite( static::$outsprintf'%s: '$title ) );
            
$line self::input();

            if( 
is_numeric$line ) ) {
                
$line--;
                if( isset( 
$map[$line] ) ) {
                    return 
array_search$map[$line], $items );
                }

                if( 
$line || $line >= count$map ) ) {
                    
self::err'Invalid menu selection: out of range' );
                }
            } else if( isset( 
$default ) ) {
                return 
$default;
            }
        }
    }

    
/**
     * Sets one of the streams (input, output, or error) to a `stream` type resource.
     *
     * Valid $whichStream values are:
     *    - 'in'   (default: STDIN)
     *    - 'out'  (default: STDOUT)
     *    - 'err'  (default: STDERR)
     *
     * Any custom streams will be closed for you on shutdown, so please don't close stream
     * resources used with this method.
     *
     * @param string    $whichStream  The stream property to update
     * @param resource  $stream       The new stream resource to use
     * @return void
     * @throws \Exception Thrown if $stream is not a resource of the 'stream' type.
     */
    
public static function setStream$whichStream$stream ) {
        if( !
is_resource$stream ) || get_resource_type$stream ) !== 'stream' ) {
            throw new 
\Exception'Invalid resource type!' );
        }
        if( 
property_exists__CLASS__$whichStream ) ) {
            static::${
$whichStream} = $stream;
        }
        
register_shutdown_function( function() use ($stream) {
            
fclose$stream );
        } );
    }

}
<?
php
/**
 * PHP Command Line Tools
 *
 * This source file is subject to the MIT license that is bundled
 * with this package in the file LICENSE.
 *
 * @author    James Logsdon <[email protected]>
 * @copyright 2010 James Logsdom (http://girsbrain.org)
 * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 */

namespace cli\arguments;

/**
 * Thrown when undefined arguments are detected in strict mode.
 */
class InvalidArguments extends \InvalidArgumentException {
    protected 
$arguments;

    
/**
     * @param array  $arguments  A list of arguments that do not fit the profile.
     */
    
public function __construct(array $arguments) {
        
$this->arguments $arguments;
        
$this->message $this->_generateMessage();
    }

    
/**
     * Get the arguments that caused the exception.
     *
     * @return array
     */
    
public function getArguments() {
        return 
$this->arguments;
    }

    private function 
_generateMessage() {
        return 
'unknown argument' .
            (
count($this->arguments) > 's' '') .
            
': ' join(', '$this->arguments);
    }
}
<?
php
/**
 * PHP Command Line Tools
 *
 * This source file is subject to the MIT license that is bundled
 * with this package in the file LICENSE.
 *
 * @author    James Logsdon <[email protected]>
 * @copyright 2010 James Logsdom (http://girsbrain.org)
 * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 */

namespace cli\arguments;

use 
cli\Memoize;

/**
 * Represents an Argument or a value and provides several helpers related to parsing an argument list.
 */
class Argument extends Memoize {
    
/**
     * The canonical name of this argument, used for aliasing.
     *
     * @param string
     */
    
public $key;

    private 
$_argument;
    private 
$_raw;

    
/**
     * @param string  $argument  The raw argument, leading dashes included.
     */
    
public function __construct($argument) {
        
$this->_raw $argument;
        
$this->key =& $this->_argument;

        if (
$this->isLong) {
            
$this->_argument substr($this->_raw2);
        } else if (
$this->isShort) {
            
$this->_argument substr($this->_raw1);
        } else {
            
$this->_argument $this->_raw;
        }
    }

    
/**
     * Returns the raw input as a string.
     *
     * @return string
     */
    
public function __toString() {
        return (string)
$this->_raw;
    }

    
/**
     * Returns the formatted argument string.
     *
     * @return string
     */
    
public function value() {
        return 
$this->_argument;
    }

    
/**
     * Returns the raw input.
     *
     * @return mixed
     */
    
public function raw() {
        return 
$this->_raw;
    }

    
/**
     * Returns true if the string matches the pattern for long arguments.
     *
     * @return bool
     */
    
public function isLong() {
        return (
== strncmp($this->_raw'--'2));
    }

    
/**
     * Returns true if the string matches the pattern for short arguments.
     *
     * @return bool
     */
    
public function isShort() {
        return !
$this->isLong && (== strncmp($this->_raw'-'1));
    }

    
/**
     * Returns true if the string matches the pattern for arguments.
     *
     * @return bool
     */
    
public function isArgument() {
        return 
$this->isShort() || $this->isLong();
    }

    
/**
     * Returns true if the string matches the pattern for values.
     *
     * @return bool
     */
    
public function isValue() {
        return !
$this->isArgument;
    }

    
/**
     * Returns true if the argument is short but contains several characters. Each
     * character is considered a separate argument.
     *
     * @return bool
     */
    
public function canExplode() {
        return 
$this->isShort && strlen($this->_argument) > 1;
    }

    
/**
     * Returns all but the first character of the argument, removing them from the
     * objects representation at the same time.
     *
     * @return array
     */
    
public function exploded() {
        
$exploded = array();

        for (
$i strlen($this->_argument); $i 0$i--) {
            
array_push($exploded$this->_argument[$i 1]);
        }

        
$this->_argument array_pop($exploded);
        
$this->_raw      '-' $this->_argument;
        return 
$exploded;
    }
}
<?
php
/**
 * PHP Command Line Tools
 *
 * This source file is subject to the MIT license that is bundled
 * with this package in the file LICENSE.
 *
 * @author    James Logsdon <[email protected]>
 * @copyright 2010 James Logsdom (http://girsbrain.org)
 * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 */

namespace cli\arguments;

use 
cli\Memoize;

class 
Lexer extends Memoize implements \Iterator {
    private 
$_items = array();
    private 
$_index 0;
    private 
$_length 0;
    private 
$_first true;

    
/**
     * @param array  $items  A list of strings to process as tokens.
     */
    
public function __construct(array $items) {
        
$this->_items $items;
        
$this->_length count($items);
    }

    
/**
     * The current token.
     *
     * @return string
     */
    
public function current() {
        return 
$this->_item;
    }

    
/**
     * Peek ahead to the next token without moving the cursor.
     *
     * @return Argument
     */
    
public function peek() {
        return new 
Argument($this->_items[0]);
    }

    
/**
     * Move the cursor forward 1 element if it is valid.
     */
    
public function next() {
        if (
$this->valid()) {
            
$this->_shift();
        }
    }

    
/**
     * Return the current position of the cursor.
     *
     * @return int
     */
    
public function key() {
        return 
$this->_index;
    }

    
/**
     * Move forward 1 element and, if the method hasn't been called before, reset
     * the cursor's position to 0.
     */
    
public function rewind() {
        
$this->_shift();
        if (
$this->_first) {
            
$this->_index 0;
            
$this->_first false;
        }
    }

    
/**
     * Returns true if the cursor has not reached the end of the list.
     *
     * @return bool
     */
    
public function valid() {
        return (
$this->_index $this->_length);
    }

    
/**
     * Push an element to the front of the stack.
     *
     * @param mixed  $item  The value to set
     */
    
public function unshift($item) {
        
array_unshift($this->_items$item);
        
$this->_length += 1;
    }

    
/**
     * Returns true if the cursor is at the end of the list.
     *
     * @return bool
     */
    
public function end() {
        return (
$this->_index 1) == $this->_length;
    }

    private function 
_shift() {
        
$this->_item = new Argument(array_shift($this->_items));
        
$this->_index += 1;
        
$this->_explode();
        
$this->_unmemo('peek');
    }

    private function 
_explode() {
        if (!
$this->_item->canExplode) {
            return 
false;
        }

        foreach (
$this->_item->exploded as $piece) {
            
$this->unshift('-' $piece);
        }
    }
}
<?
php
/**
 * PHP Command Line Tools
 *
 * This source file is subject to the MIT license that is bundled
 * with this package in the file LICENSE.
 *
 * @author    James Logsdon <[email protected]>
 * @copyright 2010 James Logsdom (http://girsbrain.org)
 * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 */

namespace cli\arguments;

use 
cli\Arguments;

/**
 * Arguments help screen renderer
 */
class HelpScreen {
    protected 
$_flags = array();
    protected 
$_maxFlag 0;
    protected 
$_options = array();
    protected 
$_maxOption 0;

    public function 
__construct(Arguments $arguments) {
        
$this->setArguments($arguments);
    }

    public function 
__toString() {
        return 
$this->render();
    }

    public function 
setArguments(Arguments $arguments) {
        
$this->consumeArgumentFlags($arguments);
        
$this->consumeArgumentOptions($arguments);
    }

    public function 
consumeArgumentFlags(Arguments $arguments) {
        
$data $this->_consume($arguments->getFlags());

        
$this->_flags $data[0];
        
$this->_flagMax $data[1];
    }

    public function 
consumeArgumentOptions(Arguments $arguments) {
        
$data $this->_consume($arguments->getOptions());

        
$this->_options $data[0];
        
$this->_optionMax $data[1];
    }

    public function 
render() {
        
$help = array();

        
array_push($help$this->_renderFlags());
        
array_push($help$this->_renderOptions());

        return 
join("\n\n"$help);
    }

    private function 
_renderFlags() {
        if (empty(
$this->_flags)) {
            return 
null;
        }

        return 
"Flags\n" $this->_renderScreen($this->_flags$this->_flagMax);
    }

    private function 
_renderOptions() {
        if (empty(
$this->_options)) {
            return 
null;
        }

        return 
"Options\n" $this->_renderScreen($this->_options$this->_optionMax);
    }

    private function 
_renderScreen($options$max) {
        
$help = array();
        foreach (
$options as $option => $settings) {
            
$formatted '  ' str_pad($option$max);

            
$dlen 80 $max;

            
$description str_split($settings['description'], $dlen);
            
$formatted.= '  ' array_shift($description);

            if (
$settings['default']) {
                
$formatted .= ' [default: ' $settings['default'] . ']';
            }

            
$pad str_repeat(' '$max 3);
            while (
$desc array_shift($description)) {
                
$formatted .= "\n${pad}${desc}";
            }

            
array_push($help$formatted);
        }

        return 
join("\n"$help);
    }

    private function 
_consume($options) {
        
$max 0;
        
$out = array();

        foreach (
$options as $option => $settings) {
            
$names = array('--' $option);

            foreach (
$settings['aliases'] as $alias) {
                
array_push($names'-' $alias);
            }

            
$names join(', '$names);
            
$max max(strlen($names), $max);
            
$out[$names] = $settings;
        }

        return array(
$out$max);
    }
}

<?
php
/**
 * PHP Command Line Tools
 *
 * This source file is subject to the MIT license that is bundled
 * with this package in the file LICENSE.
 *
 * @author    James Logsdon <[email protected]>
 * @copyright 2010 James Logsdom (http://girsbrain.org)
 * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 */

namespace cli;

abstract class 
Memoize {
    protected 
$_memoCache = array();

    public function 
__get($name) {
        if (isset(
$this->_memoCache[$name])) {
            return 
$this->_memoCache[$name];
        }

        
// Hide probable private methods
        
if (== strncmp($name'_'1)) {
            return (
$this->_memoCache[$name] = null);
        }

        if (!
method_exists($this$name)) {
            return (
$this->_memoCache[$name] = null);
        }

        
$method = array($this$name);
        (
$this->_memoCache[$name] = call_user_func($method));
        return 
$this->_memoCache[$name];
    }

    protected function 
_unmemo($name) {
        if (
$name === true) {
            
$this->_memoCache = array();
        } else {
            unset(
$this->_memoCache[$name]);
        }
    }
}
<?
php
/**
 * PHP Command Line Tools
 *
 * This source file is subject to the MIT license that is bundled
 * with this package in the file LICENSE.
 *
 * @author    Ryan Sullivan <[email protected]>
 * @copyright 2010 James Logsdom (http://girsbrain.org)
 * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 */

namespace cli\tree;

/**
 * Tree renderers are used to change how a tree is displayed.
 */
abstract class Renderer {

    
/**
     * @param array $tree
     * @return string|null
     */
    
abstract public function render(array $tree);

}
<?
php
/**
 * PHP Command Line Tools
 *
 * This source file is subject to the MIT license that is bundled
 * with this package in the file LICENSE.
 *
 * @author    Ryan Sullivan <[email protected]>
 * @copyright 2010 James Logsdom (http://girsbrain.org)
 * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 */

namespace cli\tree;

/**
 * The ASCII renderer renders trees with ASCII lines.
 */
class Ascii extends Renderer {

    
/**
     * @param array $tree
     * @return string
     */
    
public function render(array $tree)
    {
        
$output '';

        
$treeIterator = new \RecursiveTreeIterator(
            new 
\RecursiveArrayIterator($tree),
            
\RecursiveTreeIterator::SELF_FIRST
        
);

        foreach (
$treeIterator as $val)
        {
            
$output .= $val "\n";
        }

        return 
$output;
    }

}
<?
php
/**
 * PHP Command Line Tools
 *
 * This source file is subject to the MIT license that is bundled
 * with this package in the file LICENSE.
 *
 * @author    Ryan Sullivan <[email protected]>
 * @copyright 2010 James Logsdom (http://girsbrain.org)
 * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 */

namespace cli\tree;

/**
 * The ASCII renderer renders trees with ASCII lines.
 */
class Markdown extends Renderer {

    
/**
     * How many spaces to indent by
     * @var int
     */
    
protected $_padding 2;

    
/**
     * @param int $padding Optional. Default 2.
     */
    
function __construct($padding null)
    {
        if (
$padding)
        {
            
$this->_padding $padding;
        }
    }

    
/**
     * Renders the tree
     *
     * @param array $tree
     * @param int $level Optional
     * @return string
     */
    
public function render(array $tree$level 0)
    {
        
$output '';

        foreach (
$tree as $label => $next)
        {

            if (
is_string($next))
            {
                
$label $next;
            }

            
// Output the label
            
$output .= sprintf("%s- %s\n"str_repeat(' '$level $this->_padding), $label);

            
// Next level
            
if (is_array($next))
            {
                
$output .= $this->render($next$level 1);
            }

        }

        return 
$output;
    }

}
<?
php

/**
 * PHP Command Line Tools
 *
 * This source file is subject to the MIT license that is bundled
 * with this package in the file LICENSE.
 *
 * @author    James Logsdon <[email protected]>
 * @copyright 2010 James Logsdom (http://girsbrain.org)
 * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 */

namespace cli;

/**
 * Handles rendering strings. If extra scalar arguments are given after the `$msg`
 * the string will be rendered with `sprintf`. If the second argument is an `array`
 * then each key in the array will be the placeholder name. Placeholders are of the
 * format {:key}.
 *
 * @param string   $msg  The message to render.
 * @param mixed    ...   Either scalar arguments or a single array argument.
 * @return string  The rendered string.
 */
function render$msg ) {
    return 
Streams::_call'render'func_get_args() );
}

/**
 * Shortcut for printing to `STDOUT`. The message and parameters are passed
 * through `sprintf` before output.
 *
 * @param string  $msg  The message to output in `printf` format.
 * @param mixed   ...   Either scalar arguments or a single array argument.
 * @return void
 * @see \cli\render()
 */
function out$msg ) {
    
Streams::_call'out'func_get_args() );
}

/**
 * Pads `$msg` to the width of the shell before passing to `cli\out`.
 *
 * @param string  $msg  The message to pad and pass on.
 * @param mixed   ...   Either scalar arguments or a single array argument.
 * @return void
 * @see cli\out()
 */
function out_padded$msg ) {
    
Streams::_call'out_padded'func_get_args() );
}

/**
 * Prints a message to `STDOUT` with a newline appended. See `\cli\out` for
 * more documentation.
 *
 * @see cli\out()
 */
function line$msg '' ) {
    
Streams::_call'line'func_get_args() );
}

/**
 * Shortcut for printing to `STDERR`. The message and parameters are passed
 * through `sprintf` before output.
 *
 * @param string  $msg  The message to output in `printf` format. With no string,
 *                      a newline is printed.
 * @param mixed   ...   Either scalar arguments or a single array argument.
 * @return void
 */
function err$msg '' ) {
    
Streams::_call'err'func_get_args() );
}

/**
 * Takes input from `STDIN` in the given format. If an end of transmission
 * character is sent (^D), an exception is thrown.
 *
 * @param string  $format  A valid input format. See `fscanf` for documentation.
 *                         If none is given, all input up to the first newline
 *                         is accepted.
 * @return string  The input with whitespace trimmed.
 * @throws \Exception  Thrown if ctrl-D (EOT) is sent as input.
 */
function input$format null ) {
    return 
Streams::input$format );
}

/**
 * Displays an input prompt. If no default value is provided the prompt will
 * continue displaying until input is received.
 *
 * @param string  $question The question to ask the user.
 * @param string  $default  A default value if the user provides no input.
 * @param string  $marker   A string to append to the question and default value on display.
 * @param boolean $hide     If the user input should be hidden
 * @return string  The users input.
 * @see cli\input()
 */
function prompt$question$default false$marker ': '$hide false ) {
    return 
Streams::prompt$question$default$marker$hide );
}

/**
 * Presents a user with a multiple choice question, useful for 'yes/no' type
 * questions (which this function defaults too).
 *
 * @param string      $question   The question to ask the user.
 * @param string      $choice
 * @param string|null $default    The default choice. NULL if a default is not allowed.
 * @internal param string $valid  A string of characters allowed as a response. Case
 *                                is ignored.
 * @return string  The users choice.
 * @see      cli\prompt()
 */
function choose$question$choice 'yn'$default 'n' ) {
    return 
Streams::choose$question$choice$default );
}

/**
 * Does the same as {@see choose()}, but always asks yes/no and returns a boolean
 *
 * @param string    $question  The question to ask the user.
 * @param bool|null $default   The default choice, in a boolean format.
 * @return bool
 */
function confirm$question$default false ) {
    if ( 
is_bool$default ) ) {
        
$default $default'y' 'n';
    }
    
$result  choose$question'yn'$default );
    return 
$result == 'y';
}

/**
 * Displays an array of strings as a menu where a user can enter a number to
 * choose an option. The array must be a single dimension with either strings
 * or objects with a `__toString()` method.
 *
 * @param array  $items   The list of items the user can choose from.
 * @param string $default The index of the default item.
 * @param string $title   The message displayed to the user when prompted.
 * @return string  The index of the chosen item.
 * @see cli\line()
 * @see cli\input()
 * @see cli\err()
 */
function menu$items$default null$title 'Choose an item' ) {
    return 
Streams::menu$items$default$title );
}

/**
 * Attempts an encoding-safe way of getting string length. If intl extension or PCRE with '\X' or mb_string extension aren't
 * available, falls back to basic strlen.
 *
 * @param  string      $str      The string to check.
 * @param  string|bool $encoding Optional. The encoding of the string. Default false.
 * @return int  Numeric value that represents the string's length
 */
function safe_strlen$str$encoding false ) {
    
// Allow for selective testings - "1" bit set tests grapheme_strlen(), "2" preg_match_all( '/\X/u' ), "4" mb_strlen(), "other" strlen().
    
$test_safe_strlen getenv'PHP_CLI_TOOLS_TEST_SAFE_STRLEN' );

    
// Assume UTF-8 if no encoding given - `grapheme_strlen()` will return null if given non-UTF-8 string.
    
if ( ( ! $encoding || 'UTF-8' === $encoding ) && can_use_icu() && null !== ( $length grapheme_strlen$str ) ) ) {
        if ( ! 
$test_safe_strlen || ( $test_safe_strlen ) ) {
            return 
$length;
        }
    }
    
// Assume UTF-8 if no encoding given - `preg_match_all()` will return false if given non-UTF-8 string.
    
if ( ( ! $encoding || 'UTF-8' === $encoding ) && can_use_pcre_x() && false !== ( $length preg_match_all'/\X/u'$str$dummy /*needed for PHP 5.3*/ ) ) ) {
        if ( ! 
$test_safe_strlen || ( $test_safe_strlen ) ) {
            return 
$length;
        }
    }
    
// Legacy encodings and old PHPs will reach here.
    
if ( function_exists'mb_strlen' ) && ( $encoding || function_exists'mb_detect_encoding' ) ) ) {
        if ( ! 
$encoding ) {
            
$encoding mb_detect_encoding$strnulltrue /*strict*/ );
        }
        
$length $encoding mb_strlen$str$encoding ) : mb_strlen$str ); // mbstring funcs can fail if given `$encoding` arg that evals to false.
        
if ( 'UTF-8' === $encoding ) {
            
// Subtract combining characters.
            
$length -= preg_match_allget_unicode_regexs'm' ), $str$dummy /*needed for PHP 5.3*/ );
        }
        if ( ! 
$test_safe_strlen || ( $test_safe_strlen ) ) {
            return 
$length;
        }
    }
    return 
strlen$str );
}

/**
 * Attempts an encoding-safe way of getting a substring. If intl extension or PCRE with '\X' or mb_string extension aren't
 * available, falls back to substr().
 *         
 * @param  string        $str      The input string.
 * @param  int           $start    The starting position of the substring.
 * @param  int|bool|null $length   Optional, unless $is_width is set. Maximum length of the substring. Default false. Negative not supported.
 * @param  int|bool      $is_width Optional. If set and encoding is UTF-8, $length (which must be specified) is interpreted as spacing width. Default false.
 * @param  string|bool   $encoding Optional. The encoding of the string. Default false.
 * @return bool|string  False if given unsupported args, otherwise substring of string specified by start and length parameters
 */
function safe_substr$str$start$length false$is_width false$encoding false ) {
    
// Negative $length or $is_width and $length not specified not supported.
    
if ( $length || ( $is_width && ( null === $length || false === $length ) ) ) {
        return 
false;
    }
    
// Need this for normalization below and other uses.
    
$safe_strlen safe_strlen$str$encoding );

    
// Normalize `$length` when not specified - PHP 5.3 substr takes false as full length, PHP > 5.3 takes null.
    
if ( null === $length || false === $length ) {
        
$length $safe_strlen;
    }
    
// Normalize `$start` - various methods treat this differently.
    
if ( $start $safe_strlen ) {
        return 
'';
    }
    if ( 
$start && -$start $safe_strlen ) {
        
$start 0;
    }

    
// Allow for selective testings - "1" bit set tests grapheme_substr(), "2" preg_split( '/\X/' ), "4" mb_substr(), "8" substr().
    
$test_safe_substr getenv'PHP_CLI_TOOLS_TEST_SAFE_SUBSTR' );

    
// Assume UTF-8 if no encoding given - `grapheme_substr()` will return false (not null like `grapheme_strlen()`) if given non-UTF-8 string.
    
if ( ( ! $encoding || 'UTF-8' === $encoding ) && can_use_icu() && false !== ( $try grapheme_substr$str$start$length ) ) ) {
        if ( ! 
$test_safe_substr || ( $test_safe_substr ) ) {
            return 
$is_width _safe_substr_eaw$try$length ) : $try;
        }
    }
    
// Assume UTF-8 if no encoding given - `preg_split()` returns a one element array if given non-UTF-8 string (PHP bug) so need to check `preg_last_error()`.
    
if ( ( ! $encoding || 'UTF-8' === $encoding ) && can_use_pcre_x() ) {
        if ( 
false !== ( $try preg_split'/(\X)/u'$str$safe_strlen 1PREG_SPLIT_DELIM_CAPTURE PREG_SPLIT_NO_EMPTY ) ) && ! preg_last_error() ) {
            
$try implode''array_slice$try$start$length ) );
            if ( ! 
$test_safe_substr || ( $test_safe_substr ) ) {
                return 
$is_width _safe_substr_eaw$try$length ) : $try;
            }
        }
    }
    
// Legacy encodings and old PHPs will reach here.
    
if ( function_exists'mb_substr' ) && ( $encoding || function_exists'mb_detect_encoding' ) ) ) {
        if ( ! 
$encoding ) {
            
$encoding mb_detect_encoding$strnulltrue /*strict*/ );
        }
        
// Bug: not adjusting for combining chars.
        
$try $encoding mb_substr$str$start$length$encoding ) : mb_substr$str$start$length ); // mbstring funcs can fail if given `$encoding` arg that evals to false.
        
if ( 'UTF-8' === $encoding && $is_width ) {
            
$try _safe_substr_eaw$try$length );
        }
        if ( ! 
$test_safe_substr || ( $test_safe_substr ) ) {
            return 
$try;
        }
    }
    return 
substr$str$start$length );
}

/**
 * Internal function used by `safe_substr()` to adjust for East Asian double-width chars.
 *
 * @return string
 */
function _safe_substr_eaw$str$length ) {
    
// Set the East Asian Width regex.
    
$eaw_regex get_unicode_regexs'eaw' );

    
// If there's any East Asian double-width chars...
    
if ( preg_match$eaw_regex$str ) ) {
        
// Note that if the length ends in the middle of a double-width char, the char is excluded, not included.

        // See if it's all EAW.
        
if ( function_exists'mb_substr' ) && preg_match_all$eaw_regex$str$dummy /*needed for PHP 5.3*/ ) === $length ) {
            
// Just halve the length so (rounded down to a minimum of 1).
            
$str mb_substr$str0max( (int) ( $length ), ), 'UTF-8' );
        } else {
            
// Explode string into an array of UTF-8 chars. Based on core `_mb_substr()` in "wp-includes/compat.php".
            
$chars preg_split'/([\x00-\x7f\xc2-\xf4][^\x00-\x7f\xc2-\xf4]*)/'$str$length 1PREG_SPLIT_DELIM_CAPTURE PREG_SPLIT_NO_EMPTY );
            
$cnt mincount$chars ), $length );
            
$width $length;

            for ( 
$length 0$length $cnt && $width 0$length++ ) {
                
$width -= preg_match$eaw_regex$chars$length ] ) ? 1;
            }
            
// Round down to a minimum of 1.
            
if ( $width && $length ) {
                
$length--;
            }
            return 
join''array_slice$chars0$length ) );
        }
    }
    return 
$str;
}

/**
 * An encoding-safe way of padding string length for display
 *
 * @param  string      $string   The string to pad.
 * @param  int         $length   The length to pad it to.
 * @param  string|bool $encoding Optional. The encoding of the string. Default false.
 * @return string
 */
function safe_str_pad$string$length$encoding false ) {
    
$real_length strwidth$string$encoding );
    
$diff strlen$string ) - $real_length;
    
$length += $diff;

    return 
str_pad$string$length );
}

/**
 * Get width of string, ie length in characters, taking into account multi-byte and mark characters for UTF-8, and multi-byte for non-UTF-8.
 *
 * @param  string      $string   The string to check.
 * @param  string|bool $encoding Optional. The encoding of the string. Default false.
 * @return int  The string's width.
 */
function strwidth$string$encoding false ) {
    
// Set the East Asian Width and Mark regexs.
    
list( $eaw_regex$m_regex ) = get_unicode_regexs();

    
// Allow for selective testings - "1" bit set tests grapheme_strlen(), "2" preg_match_all( '/\X/u' ), "4" mb_strwidth(), "other" safe_strlen().
    
$test_strwidth getenv'PHP_CLI_TOOLS_TEST_STRWIDTH' );

    
// Assume UTF-8 if no encoding given - `grapheme_strlen()` will return null if given non-UTF-8 string.
    
if ( ( ! $encoding || 'UTF-8' === $encoding ) && can_use_icu() && null !== ( $width grapheme_strlen$string ) ) ) {
        if ( ! 
$test_strwidth || ( $test_strwidth ) ) {
            return 
$width preg_match_all$eaw_regex$string$dummy /*needed for PHP 5.3*/ );
        }
    }
    
// Assume UTF-8 if no encoding given - `preg_match_all()` will return false if given non-UTF-8 string.
    
if ( ( ! $encoding || 'UTF-8' === $encoding ) && can_use_pcre_x() && false !== ( $width preg_match_all'/\X/u'$string$dummy /*needed for PHP 5.3*/ ) ) ) {
        if ( ! 
$test_strwidth || ( $test_strwidth ) ) {
            return 
$width preg_match_all$eaw_regex$string$dummy /*needed for PHP 5.3*/ );
        }
    }
    
// Legacy encodings and old PHPs will reach here.
    
if ( function_exists'mb_strwidth' ) && ( $encoding || function_exists'mb_detect_encoding' ) ) ) {
        if ( ! 
$encoding ) {
            
$encoding mb_detect_encoding$stringnulltrue /*strict*/ );
        }
        
$width $encoding mb_strwidth$string$encoding ) : mb_strwidth$string ); // mbstring funcs can fail if given `$encoding` arg that evals to false.
        
if ( 'UTF-8' === $encoding ) {
            
// Subtract combining characters.
            
$width -= preg_match_all$m_regex$string$dummy /*needed for PHP 5.3*/ );
        }
        if ( ! 
$test_strwidth || ( $test_strwidth ) ) {
            return 
$width;
        }
    }
    return 
safe_strlen$string$encoding );
}

/**
 * Returns whether ICU is modern enough not to flake out.
 *
 * @return bool
 */
function can_use_icu() {
    static 
$can_use_icu null;

    if ( 
null === $can_use_icu ) {
        
// Choosing ICU 54, Unicode 7.0.
        
$can_use_icu defined'INTL_ICU_VERSION' ) && version_compareINTL_ICU_VERSION'54.1''>=' ) && function_exists'grapheme_strlen' ) && function_exists'grapheme_substr' );
    }

    return 
$can_use_icu;
}

/**
 * Returns whether PCRE Unicode extended grapheme cluster '\X' is available for use.
 *
 * @return bool
 */
function can_use_pcre_x() {
    static 
$can_use_pcre_x null;

    if ( 
null === $can_use_pcre_x ) {
        
// '\X' introduced (as Unicde extended grapheme cluster) in PCRE 8.32 - see https://vcs.pcre.org/pcre/code/tags/pcre-8.32/ChangeLog?view=markup line 53.
        // Older versions of PCRE were bundled with PHP <= 5.3.23 & <= 5.4.13.
        
$pcre_version substrPCRE_VERSION0strspnPCRE_VERSION'0123456789.' ) ); // Remove any trailing date stuff.
        
$can_use_pcre_x version_compare$pcre_version'8.32''>=' ) && false !== @preg_match'/\X/u''' );
    }

    return 
$can_use_pcre_x;
}

/**
 * Get the regexs generated from Unicode data.
 *
 * @param string $idx Optional. Return a specific regex only. Default null.
 * @return array|string  Returns keyed array if not given $idx or $idx doesn't exist, otherwise the specific regex string.
 */
function get_unicode_regexs$idx null ) {
    static 
$eaw_regex// East Asian Width regex. Characters that count as 2 characters as they're "wide" or "fullwidth". See http://www.unicode.org/reports/tr11/tr11-19.html
    
static $m_regex// Mark characters regex (Unicode property "M") - mark combining "Mc", mark enclosing "Me" and mark non-spacing "Mn" chars that should be ignored for spacing purposes.
    
if ( null === $eaw_regex ) {
        
// Load both regexs generated from Unicode data.
        
require __DIR__ '/unicode/regex.php';
    }

    if ( 
null !== $idx ) {
        if ( 
'eaw' === $idx ) {
            return 
$eaw_regex;
        }
        if ( 
'm' === $idx ) {
            return 
$m_regex;
        }
    }

    return array( 
$eaw_regex$m_regex, );
}
<?
php
/**
 * PHP Command Line Tools
 *
 * This source file is subject to the MIT license that is bundled
 * with this package in the file LICENSE.
 *
 * @author    James Logsdon <[email protected]>
 * @copyright 2010 James Logsdom (http://girsbrain.org)
 * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 */

namespace cli\table;

/**
 * Table renderers are used to change how a table is displayed.
 */
abstract class Renderer {
    protected 
$_widths = array();

    public function 
__construct(array $widths = array()) {
        
$this->setWidths($widths);
    }

    
/**
     * Set the widths of each column in the table.
     *
     * @param array  $widths    The widths of the columns.
     * @param bool   $fallback  Whether to use these values as fallback only.
     */
    
public function setWidths(array $widths$fallback false) {
        if (
$fallback) {
            foreach ( 
$this->_widths as $index => $value ) {
                
$widths[$index] = $value;
            }
        }
        
$this->_widths $widths;
    }

    
/**
     * Render a border for the top and bottom and separating the headers from the
     * table rows.
     *
     * @return string  The table border.
     */
    
public function border() {
        return 
null;
    }

    
/**
     * Renders a row for output.
     *
     * @param array  $row  The table row.
     * @return string  The formatted table row.
     */
    
abstract public function row(array $row);
}
<?
php
/**
 * PHP Command Line Tools
 *
 * This source file is subject to the MIT license that is bundled
 * with this package in the file LICENSE.
 *
 * @author    James Logsdon <[email protected]>
 * @copyright 2010 James Logsdom (http://girsbrain.org)
 * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 */

namespace cli\table;

/**
 * The tabular renderer is used for displaying data in a tabular format.
 */
class Tabular extends Renderer {
    
/**
     * Renders a row for output.
     *
     * @param array  $row  The table row.
     * @return string  The formatted table row.
     */
    
public function row(array $row) {
        return 
implode("\t"array_values($row));
    }
}
<?
php
/**
 * PHP Command Line Tools
 *
 * This source file is subject to the MIT license that is bundled
 * with this package in the file LICENSE.
 *
 * @author    James Logsdon <[email protected]>
 * @copyright 2010 James Logsdom (http://girsbrain.org)
 * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 */

namespace cli\table;

use 
cli\Colors;
use 
cli\Shell;

/**
 * The ASCII renderer renders tables with ASCII borders.
 */
class Ascii extends Renderer {
    protected 
$_characters = array(
        
'corner'  => '+',
        
'line'    => '-',
        
'border'  => '|',
        
'padding' => ' ',
    );
    protected 
$_border null;
    protected 
$_constraintWidth null;
    protected 
$_pre_colorized false;

    
/**
     * Set the widths of each column in the table.
     *
     * @param array  $widths    The widths of the columns.
     * @param bool   $fallback  Whether to use these values as fallback only.
     */
    
public function setWidths(array $widths$fallback false) {
        if (
$fallback) {
            foreach ( 
$this->_widths as $index => $value ) {
                
$widths[$index] = $value;
            }
        }
        
$this->_widths $widths;

        if ( 
is_null$this->_constraintWidth ) ) {
            
$this->_constraintWidth = (int) Shell::columns();
        }
        
$col_count count$widths );
        
$col_borders_count $col_count ? ( ( $col_count ) * strlen$this->_characters['border'] ) ) : 0;
        
$table_borders_count strlen$this->_characters['border'] ) * 2;
        
$col_padding_count $col_count strlen$this->_characters['padding'] ) * 2;
        
$max_width $this->_constraintWidth $col_borders_count $table_borders_count $col_padding_count;

        if ( 
$widths && $max_width && array_sum$widths ) > $max_width ) {

            
$avg floor$max_width count$widths ) );
            
$resize_widths = array();
            
$extra_width 0;
            foreach( 
$widths as $width ) {
                if ( 
$width $avg ) {
                    
$resize_widths[] = $width;
                } else {
                    
$extra_width $extra_width + ( $avg $width );
                }
            }

            if ( ! empty( 
$resize_widths ) && $extra_width ) {
                
$avg_extra_width floor$extra_width count$resize_widths ) );
                foreach( 
$widths as &$width ) {
                    if ( 
in_array$width$resize_widths ) ) {
                        
$width $avg $avg_extra_width;
                        
array_shift$resize_widths );
                        
// Last item gets the cake
                        
if ( empty( $resize_widths ) ) {
                            
$width 0// Zero it so not in sum.
                            
$width $max_width array_sum$widths );
                        }
                    }
                }
            }

        }

        
$this->_widths $widths;
    }

    
/**
     * Set the constraint width for the table
     *
     * @param int $constraintWidth
     */
    
public function setConstraintWidth$constraintWidth ) {
        
$this->_constraintWidth $constraintWidth;
    }

    
/**
     * Set the characters used for rendering the Ascii table.
     *
     * The keys `corner`, `line` and `border` are used in rendering.
     *
     * @param $characters  array  Characters used in rendering.
     */
    
public function setCharacters(array $characters) {
        
$this->_characters array_merge($this->_characters$characters);
    }

    
/**
     * Render a border for the top and bottom and separating the headers from the
     * table rows.
     *
     * @return string  The table border.
     */
    
public function border() {
        if (!isset(
$this->_border)) {
            
$this->_border $this->_characters['corner'];
            foreach (
$this->_widths as $width) {
                
$this->_border .= str_repeat($this->_characters['line'], $width 2);
                
$this->_border .= $this->_characters['corner'];
            }
        }

        return 
$this->_border;
    }

    
/**
     * Renders a row for output.
     *
     * @param array  $row  The table row.
     * @return string  The formatted table row.
     */
    
public function row( array $row ) {

        
$extra_row_count 0;

        if ( 
count$row ) > ) {
            
$extra_rows array_fill0count$row ), array() );

            foreach( 
$row as $col => $value ) {

                
$value str_replace( array( "\r\n""\n" ), ' '$value );

                
$col_width $this->_widths$col ];
                
$encoding function_exists'mb_detect_encoding' ) ? mb_detect_encoding$valuenulltrue /*strict*/ ) : false;
                
$original_val_width Colors::width$valueself::isPreColorized$col ), $encoding );
                if ( 
$col_width && $original_val_width $col_width ) {
                    
$row$col ] = \cli\safe_substr$value0$col_widthtrue /*is_width*/$encoding );
                    
$value \cli\safe_substr$value\cli\safe_strlen$row$col ], $encoding ), null /*length*/false /*is_width*/$encoding );
                    
$i 0;
                    do {
                        
$extra_value \cli\safe_substr$value0$col_widthtrue /*is_width*/$encoding );
                        
$val_width Colors::width$extra_valueself::isPreColorized$col ), $encoding );
                        if ( 
$val_width ) {
                            
$extra_rows$col ][] = $extra_value;
                            
$value \cli\safe_substr$value\cli\safe_strlen$extra_value$encoding ), null /*length*/false /*is_width*/$encoding );
                            
$i++;
                            if ( 
$i $extra_row_count ) {
                                
$extra_row_count $i;
                            }
                        }
                    } while( 
$value );
                }

            }
        }

        
$row array_map(array($this'padColumn'), $rowarray_keys($row));
        
array_unshift($row''); // First border
        
array_push($row''); // Last border

        
$ret join($this->_characters['border'], $row);
        if ( 
$extra_row_count ) {
            foreach( 
$extra_rows as $col => $col_values ) {
                while( 
count$col_values ) < $extra_row_count ) {
                    
$col_values[] = '';
                }
            }

            do {
                
$row_values = array();
                
$has_more false;
                foreach( 
$extra_rows as $col => &$col_values ) {
                    
$row_values$col ] = array_shift$col_values );
                    if ( 
count$col_values ) ) {
                        
$has_more true;
                    }
                }

                
$row_values array_map(array($this'padColumn'), $row_valuesarray_keys($row_values));
                
array_unshift($row_values''); // First border
                
array_push($row_values''); // Last border

                
$ret .= PHP_EOL join($this->_characters['border'], $row_values);
            } while( 
$has_more );
        }
        return 
$ret;
    }

    private function 
padColumn($content$column) {
        return 
$this->_characters['padding'] . Colors::pad$content$this->_widths$column ], $this->isPreColorized$column ) ) . $this->_characters['padding'];
    }

    
/**
     * Set whether items are pre-colorized.
     *
     * @param bool|array $colorized A boolean to set all columns in the table as pre-colorized, or an array of booleans keyed by column index (number) to set individual columns as pre-colorized.
     */
    
public function setPreColorized$pre_colorized ) {
        
$this->_pre_colorized $pre_colorized;
    }

    
/**
     * Is a column pre-colorized?
     *
     * @param int $column Column index to check.
     * @return bool True if whole table is marked as pre-colorized, or if the individual column is pre-colorized; else false.
     */
    
public function isPreColorized$column ) {
        if ( 
is_bool$this->_pre_colorized ) ) {
            return 
$this->_pre_colorized;
        }
        if ( 
is_array$this->_pre_colorized ) && isset( $this->_pre_colorized$column ] ) ) {
            return 
$this->_pre_colorized$column ];
        }
        return 
false;
    }
}
<?
php
/**
 * PHP Command Line Tools
 *
 * This source file is subject to the MIT license that is bundled
 * with this package in the file LICENSE.
 *
 * @author    Ryan Sullivan <[email protected]>
 * @copyright 2010 James Logsdom (http://girsbrain.org)
 * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 */

namespace cli;

/**
 * The `Tree` class is used to display data in a tree-like format.
 */
class Tree {

    protected 
$_renderer;
    protected 
$_data = array();

    
/**
     * Sets the renderer used by this tree.
     *
     * @param tree\Renderer  $renderer  The renderer to use for output.
     * @see   tree\Renderer
     * @see   tree\Ascii
     * @see   tree\Markdown
     */
    
public function setRenderer(tree\Renderer $renderer) {
        
$this->_renderer $renderer;
    }

    
/**
     * Set the data.
     * Format:
     *     [
     *         'Label' => [
     *             'Thing' => ['Thing'],
     *         ],
     *         'Thing',
     *     ]
     * @param array $data
     */
    
public function setData(array $data)
    {
        
$this->_data $data;
    }

    
/**
     * Render the tree and return it as a string.
     *
     * @return string|null
     */
    
public function render()
    {
        return 
$this->_renderer->render($this->_data);
    }

    
/**
     * Display the rendered tree
     */
    
public function display()
    {
        echo 
$this->render();
    }

}
<?
php
/**
 * PHP Command Line Tools
 *
 * This source file is subject to the MIT license that is bundled
 * with this package in the file LICENSE.
 *
 * @author    James Logsdon <[email protected]>
 * @copyright 2010 James Logsdom (http://girsbrain.org)
 * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 */

namespace cli;

use 
cli\Streams;

/**
 * The `Notify` class is the basis of all feedback classes, such as Indicators
 * and Progress meters. The default behaviour is to refresh output after 100ms
 * have passed. This is done to preventing the screen from flickering and keep
 * slowdowns from output to a minimum.
 *
 * The most basic form of Notifier has no maxim, and simply displays a series
 * of characters to indicate progress is being made.
 */
abstract class Notify {
    protected 
$_current 0;
    protected 
$_first true;
    protected 
$_interval;
    protected 
$_message;
    protected 
$_start;
    protected 
$_timer;

    
/**
     * Instatiates a Notification object.
     *
     * @param string  $msg       The text to display next to the Notifier.
     * @param int     $interval  The interval in milliseconds between updates.
     */
    
public function __construct($msg$interval 100) {
        
$this->_message $msg;
        
$this->_interval = (int)$interval;
    }

    
/**
     * This method should be used to print out the Notifier. This method is
     * called from `cli\Notify::tick()` after `cli\Notify::$_interval` has passed.
     *
     * @abstract
     * @param boolean  $finish
     * @see cli\Notify::tick()
     */
    
abstract public function display($finish false);

    
/**
     * Reset the notifier state so the same instance can be used in multiple loops.
     */
    
public function reset() {
        
$this->_current 0;
        
$this->_first true;
        
$this->_start null;
        
$this->_timer null;
    }

    
/**
     * Returns the formatted tick count.
     *
     * @return string  The formatted tick count.
     */
    
public function current() {
        return 
number_format($this->_current);
    }

    
/**
     * Calculates the time elapsed since the Notifier was first ticked.
     *
     * @return int  The elapsed time in seconds.
     */
    
public function elapsed() {
        if (!
$this->_start) {
            return 
0;
        }

        
$elapsed time() - $this->_start;
        return 
$elapsed;
    }

    
/**
     * Calculates the speed (number of ticks per second) at which the Notifier
     * is being updated.
     *
     * @return int  The number of ticks performed in 1 second.
     */
    
public function speed() {
        static 
$tick$iteration 0$speed 0;

        if (!
$this->_start) {
            return 
0;
        } else if (!
$tick) {
            
$tick $this->_start;
        }

        
$now microtime(true);
        
$span $now $tick;
        if (
$span 1) {
            
$iteration++;
            
$tick $now;
            
$speed = ($this->_current $iteration) / $span;
        }

        return 
$speed;
    }

    
/**
     * Takes a time span given in seconds and formats it for display. The
     * returned string will be in MM:SS form.
     *
     * @param int  $time The time span in seconds to format.
     * @return string  The formatted time span.
     */
    
public function formatTime($time) {
        return 
floor($time 60) . ':' str_pad($time 6020STR_PAD_LEFT);
    }

    
/**
     * Finish our Notification display. Should be called after the Notifier is
     * no longer needed.
     *
     * @see cli\Notify::display()
     */
    
public function finish() {
        
Streams::out("\r");
        
$this->display(true);
        
Streams::line();
    }

    
/**
     * Increments are tick counter by the given amount. If no amount is provided,
     * the ticker is incremented by 1.
     *
     * @param int  $increment  The amount to increment by.
     */
    
public function increment($increment 1) {
        
$this->_current += $increment;
    }

    
/**
     * Determines whether the display should be updated or not according to
     * our interval setting.
     *
     * @return boolean  `true` if the display should be updated, `false` otherwise.
     */
    
public function shouldUpdate() {
        
$now microtime(true) * 1000;

        if (empty(
$this->_timer)) {
            
$this->_start = (int)(($this->_timer $now) / 1000);
            return 
true;
        }

        if ((
$now $this->_timer) > $this->_interval) {
            
$this->_timer $now;
            return 
true;
        }
        return 
false;
    }

    
/**
     * This method is the meat of all Notifiers. First we increment the ticker
     * and then update the display if enough time has passed since our last tick.
     *
     * @param int  $increment  The amount to increment by.
     * @see cli\Notify::increment()
     * @see cli\Notify::shouldUpdate()
     * @see cli\Notify::display()
     */
    
public function tick($increment 1) {
        
$this->increment($increment);

        if (
$this->shouldUpdate()) {
            
Streams::out("\r");
            
$this->display();
        }
    }
}
<?
php
// Generated by "gen_east_asian_width.php" from "http://www.unicode.org/Public/10.0.0/ucd/EastAsianWidth.txt".
$eaw_regex '/\xe1(?:\x84[\x80-\xbf]|\x85[\x80-\x9f])|\xe2(?:\x8c[\x9a\x9b\xa9\xaa]|\x8f[\xa9-\xac\xb0\xb3]|\x97[\xbd\xbe]|\x98[\x94\x95]|\x99[\x88-\x93\xbf]|\x9a[\x93\xa1\xaa\xab\xbd\xbe]|\x9b[\x84\x85\x8e\x94\xaa\xb2\xb3\xb5\xba\xbd]|\x9c[\x85\x8a\x8b\xa8]|\x9d[\x8c\x8e\x93-\x95\x97]|\x9e[\x95-\x97\xb0\xbf]|\xac[\x9b\x9c]|\xad[\x90\x95]|\xba[\x80-\x99\x9b-\xbf]|\xbb[\x80-\xb3]|[\xbc-\xbe][\x80-\xbf]|\xbf[\x80-\x95\xb0-\xbb])|\xe3(?:\x80[\x80-\xbe]|\x81[\x81-\xbf]|\x82[\x80-\x96\x99-\xbf]|\x83[\x80-\xbf]|\x84[\x85-\xae\xb1-\xbf]|\x85[\x80-\xbf]|\x86[\x80-\x8e\x90-\xba]|\x87[\x80-\xa3\xb0-\xbf]|\x88[\x80-\x9e\xa0-\xbf]|\x89[\x80-\x87\x90-\xbf]|\x8a[\x80-\xbf]|\x8b[\x80-\xbe]|[\x8c-\xbf][\x80-\xbf])|\xe4(?:[\x80-\xb6][\x80-\xbf]|[\xb8-\xbf][\x80-\xbf])|[\xe5-\xe9][\x80-\xbf][\x80-\xbf]|\xea(?:[\x80-\x91][\x80-\xbf]|\x92[\x80-\x8c\x90-\xbf]|\x93[\x80-\x86]|\xa5[\xa0-\xbc]|[\xb0-\xbf][\x80-\xbf])|[\xeb\xec][\x80-\xbf][\x80-\xbf]|\xed(?:[\x80-\x9d][\x80-\xbf]|\x9e[\x80-\xa3])|\xef(?:[\xa4-\xab][\x80-\xbf]|\xb8[\x90-\x99\xb0-\xbf]|\xb9[\x80-\x92\x94-\xa6\xa8-\xab]|\xbc[\x81-\xbf]|\xbd[\x80-\xa0]|\xbf[\xa0-\xa6])|\xf0(?:\x96\xbf[\xa0\xa1]|\x97[\x80-\xbf][\x80-\xbf]|\x98(?:[\x80-\x9e][\x80-\xbf]|\x9f[\x80-\xac]|[\xa0-\xaa][\x80-\xbf]|\xab[\x80-\xb2])|\x9b(?:[\x80-\x83][\x80-\xbf]|\x84[\x80-\x9e]|\x85[\xb0-\xbf]|[\x86-\x8a][\x80-\xbf]|\x8b[\x80-\xbb])|\x9f(?:\x80\x84|\x83\x8f|\x86[\x8e\x91-\x9a]|\x88[\x80-\x82\x90-\xbb]|\x89[\x80-\x88\x90\x91\xa0-\xa5]|\x8c[\x80-\xa0\xad-\xb5\xb7-\xbf]|\x8d[\x80-\xbc\xbe\xbf]|\x8e[\x80-\x93\xa0-\xbf]|\x8f[\x80-\x8a\x8f-\x93\xa0-\xb0\xb4\xb8-\xbf]|\x90[\x80-\xbe]|\x91[\x80\x82-\xbf]|\x92[\x80-\xbf]|\x93[\x80-\xbc\xbf]|\x94[\x80-\xbd]|\x95[\x8b-\x8e\x90-\xa7\xba]|\x96[\x95\x96\xa4]|\x97[\xbb-\xbf]|\x98[\x80-\xbf]|\x99[\x80-\x8f]|\x9a[\x80-\xbf]|\x9b[\x80-\x85\x8c\x90-\x92\xab\xac\xb4-\xb8]|\xa4[\x90-\xbe]|\xa5[\x80-\x8c\x90-\xab]|\xa6[\x80-\x97]|\xa7[\x80\x90-\xa6])|[\xa0-\xae][\x80-\xbf][\x80-\xbf]|\xaf(?:[\x80-\xbe][\x80-\xbf]|\xbf[\x80-\xbd])|[\xb0-\xbe][\x80-\xbf][\x80-\xbf]|\xbf(?:[\x80-\xbe][\x80-\xbf]|\xbf[\x80-\xbd]))/'// 181738 code points.

// Generated by "gen_cat_regex_alts.php" from "http://www.unicode.org/Public/10.0.0/ucd/UnicodeData.txt".
$m_regex '/\xcc[\x80-\xbf]|\xcd[\x80-\xaf]|\xd2[\x83-\x89]|\xd6[\x91-\xbd\xbf]|\xd7[\x81\x82\x84\x85\x87]|\xd8[\x90-\x9a]|\xd9[\x8b-\x9f\xb0]|\xdb[\x96-\x9c\x9f-\xa4\xa7\xa8\xaa-\xad]|\xdc[\x91\xb0-\xbf]|\xdd[\x80-\x8a]|\xde[\xa6-\xb0]|\xdf[\xab-\xb3]|\xe0(?:\xa0[\x96-\x99\x9b-\xa3\xa5-\xa7\xa9-\xad]|\xa1[\x99-\x9b]|\xa3[\x94-\xa1\xa3-\xbf]|\xa4[\x80-\x83\xba-\xbc\xbe\xbf]|\xa5[\x80-\x8f\x91-\x97\xa2\xa3]|\xa6[\x81-\x83\xbc\xbe\xbf]|\xa7[\x80-\x84\x87\x88\x8b-\x8d\x97\xa2\xa3]|\xa8[\x81-\x83\xbc\xbe\xbf]|\xa9[\x80-\x82\x87\x88\x8b-\x8d\x91\xb0\xb1\xb5]|\xaa[\x81-\x83\xbc\xbe\xbf]|\xab[\x80-\x85\x87-\x89\x8b-\x8d\xa2\xa3\xba-\xbf]|\xac[\x81-\x83\xbc\xbe\xbf]|\xad[\x80-\x84\x87\x88\x8b-\x8d\x96\x97\xa2\xa3]|\xae[\x82\xbe\xbf]|\xaf[\x80-\x82\x86-\x88\x8a-\x8d\x97]|\xb0[\x80-\x83\xbe\xbf]|\xb1[\x80-\x84\x86-\x88\x8a-\x8d\x95\x96\xa2\xa3]|\xb2[\x81-\x83\xbc\xbe\xbf]|\xb3[\x80-\x84\x86-\x88\x8a-\x8d\x95\x96\xa2\xa3]|\xb4[\x80-\x83\xbb\xbc\xbe\xbf]|\xb5[\x80-\x84\x86-\x88\x8a-\x8d\x97\xa2\xa3]|\xb6[\x82\x83]|\xb7[\x8a\x8f-\x94\x96\x98-\x9f\xb2\xb3]|\xb8[\xb1\xb4-\xba]|\xb9[\x87-\x8e]|\xba[\xb1\xb4-\xb9\xbb\xbc]|\xbb[\x88-\x8d]|\xbc[\x98\x99\xb5\xb7\xb9\xbe\xbf]|\xbd[\xb1-\xbf]|\xbe[\x80-\x84\x86\x87\x8d-\x97\x99-\xbc]|\xbf\x86)|\xe1(?:\x80[\xab-\xbe]|\x81[\x96-\x99\x9e-\xa0\xa2-\xa4\xa7-\xad\xb1-\xb4]|\x82[\x82-\x8d\x8f\x9a-\x9d]|\x8d[\x9d-\x9f]|\x9c[\x92-\x94\xb2-\xb4]|\x9d[\x92\x93\xb2\xb3]|\x9e[\xb4-\xbf]|\x9f[\x80-\x93\x9d]|\xa0[\x8b-\x8d]|\xa2[\x85\x86\xa9]|\xa4[\xa0-\xab\xb0-\xbb]|\xa8[\x97-\x9b]|\xa9[\x95-\x9e\xa0-\xbc\xbf]|\xaa[\xb0-\xbe]|\xac[\x80-\x84\xb4-\xbf]|\xad[\x80-\x84\xab-\xb3]|\xae[\x80-\x82\xa1-\xad]|\xaf[\xa6-\xb3]|\xb0[\xa4-\xb7]|\xb3[\x90-\x92\x94-\xa8\xad\xb2-\xb4\xb7-\xb9]|\xb7[\x80-\xb9\xbb-\xbf])|\xe2(?:\x83[\x90-\xb0]|\xb3[\xaf-\xb1]|\xb5\xbf|\xb7[\xa0-\xbf])|\xe3(?:\x80[\xaa-\xaf]|\x82[\x99\x9a])|\xea(?:\x99[\xaf-\xb2\xb4-\xbd]|\x9a[\x9e\x9f]|\x9b[\xb0\xb1]|\xa0[\x82\x86\x8b\xa3-\xa7]|\xa2[\x80\x81\xb4-\xbf]|\xa3[\x80-\x85\xa0-\xb1]|\xa4[\xa6-\xad]|\xa5[\x87-\x93]|\xa6[\x80-\x83\xb3-\xbf]|\xa7[\x80\xa5]|\xa8[\xa9-\xb6]|\xa9[\x83\x8c\x8d\xbb-\xbd]|\xaa[\xb0\xb2-\xb4\xb7\xb8\xbe\xbf]|\xab[\x81\xab-\xaf\xb5\xb6]|\xaf[\xa3-\xaa\xac\xad])|\xef(?:\xac\x9e|\xb8[\x80-\x8f\xa0-\xaf])|\xf0(?:\x90(?:\x87\xbd|\x8b\xa0|\x8d[\xb6-\xba]|\xa8[\x81-\x83\x85\x86\x8c-\x8f\xb8-\xba\xbf]|\xab[\xa5\xa6])|\x91(?:\x80[\x80-\x82\xb8-\xbf]|\x81[\x80-\x86\xbf]|\x82[\x80-\x82\xb0-\xba]|\x84[\x80-\x82\xa7-\xb4]|\x85\xb3|\x86[\x80-\x82\xb3-\xbf]|\x87[\x80\x8a-\x8c]|\x88[\xac-\xb7\xbe]|\x8b[\x9f-\xaa]|\x8c[\x80-\x83\xbc\xbe\xbf]|\x8d[\x80-\x84\x87\x88\x8b-\x8d\x97\xa2\xa3\xa6-\xac\xb0-\xb4]|\x90[\xb5-\xbf]|\x91[\x80-\x86]|\x92[\xb0-\xbf]|\x93[\x80-\x83]|\x96[\xaf-\xb5\xb8-\xbf]|\x97[\x80\x9c\x9d]|\x98[\xb0-\xbf]|\x99\x80|\x9a[\xab-\xb7]|\x9c[\x9d-\xab]|\xa8[\x81-\x8a\xb3-\xb9\xbb-\xbe]|\xa9[\x87\x91-\x9b]|\xaa[\x8a-\x99]|\xb0[\xaf-\xb6\xb8-\xbf]|\xb2[\x92-\xa7\xa9-\xb6]|\xb4[\xb1-\xb6\xba\xbc\xbd\xbf]|\xb5[\x80-\x85\x87])|\x96(?:\xab[\xb0-\xb4]|\xac[\xb0-\xb6]|\xbd[\x91-\xbe]|\xbe[\x8f-\x92])|\x9b\xb2[\x9d\x9e]|\x9d(?:\x85[\xa5-\xa9\xad-\xb2\xbb-\xbf]|\x86[\x80-\x82\x85-\x8b\xaa-\xad]|\x89[\x82-\x84]|\xa8[\x80-\xb6\xbb-\xbf]|\xa9[\x80-\xac\xb5]|\xaa[\x84\x9b-\x9f\xa1-\xaf])|\x9e(?:\x80[\x80-\x86\x88-\x98\x9b-\xa1\xa3\xa4\xa6-\xaa]|\xa3[\x90-\x96]|\xa5[\x84-\x8a]))|\xf3\xa0(?:[\x84-\x86][\x80-\xbf]|\x87[\x80-\xaf])/'// 2177 code points.
<?php

namespace WP_CLI\Embeds;

use 
WP_CLI;
use 
WP_CLI\Utils;
use 
WP_CLI_Command;

class 
Fetch_Command extends WP_CLI_Command {
    
/**
     * Attempts to convert a URL into embed HTML.
     *
     * In non-raw mode, starts by checking the URL against the regex of the registered embed handlers.
     * If none of the regex matches and it's enabled, then the URL will be given to the WP_oEmbed class.
     *
     * In raw mode, checks the providers directly and returns the data.
     *
     * ## OPTIONS
     *
     * <url>
     * : URL to retrieve oEmbed data for.
     *
     * [--width=<width>]
     * : Width of the embed in pixels.
     *
     * [--height=<height>]
     * : Height of the embed in pixels.
     *
     * [--post-id=<id>]
     * : Cache oEmbed response for a given post.
     *
     * [--discover]
     * : Enable oEmbed discovery. Defaults to true.
     *
     * [--skip-cache]
     * : Ignore already cached oEmbed responses. Has no effect if using the 'raw' option, which doesn't use the cache.
     *
     * [--skip-sanitization]
     * : Remove the filter that WordPress from 4.4 onwards uses to sanitize oEmbed responses. Has no effect if using the 'raw' option, which by-passes sanitization.
     *
     * [--do-shortcode]
     * : If the URL is handled by a registered embed handler and returns a shortcode, do shortcode and return result. Has no effect if using the 'raw' option, which by-passes handlers.
     *
     * [--limit-response-size=<size>]
     * : Limit the size of the resulting HTML when using discovery. Default 150 KB (the standard WordPress limit). Not compatible with 'no-discover'.
     *
     * [--raw]
     * : Return the raw oEmbed response instead of the resulting HTML. Ignores the cache and does not sanitize responses or use registered embed handlers.
     *
     * [--raw-format=<json|xml>]
     * : Render raw oEmbed data in a particular format. Defaults to json. Can only be specified in conjunction with the 'raw' option.
     * ---
     * options:
     *   - json
     *   - xml
     * ---
     *
     * ## EXAMPLES
     *
     *     # Get embed HTML for a given URL.
     *     $ wp embed fetch https://www.youtube.com/watch?v=dQw4w9WgXcQ
     *     <iframe width="525" height="295" src="https://www.youtube.com/embed/dQw4w9WgXcQ?feature=oembed" ...
     *
     *     # Get raw oEmbed data for a given URL.
     *     $ wp embed fetch https://www.youtube.com/watch?v=dQw4w9WgXcQ --raw
     *     {"author_url":"https:\/\/www.youtube.com\/user\/RickAstleyVEVO","width":525,"version":"1.0", ...
     */
    
public function __invoke$args$assoc_args ) {
        
/** @var \WP_Embed $wp_embed */
        
global $wp_embed;

        
$url                 $args[0];
        
$raw                 Utils\get_flag_value$assoc_args'raw' );
        
$raw_format          Utils\get_flag_value$assoc_args'raw-format' );
        
$post_id             Utils\get_flag_value$assoc_args'post-id' );
        
$discover            Utils\get_flag_value$assoc_args'discover' );
        
$response_size_limit Utils\get_flag_value$assoc_args'limit-response-size' );
        
$width               Utils\get_flag_value$assoc_args'width' );
        
$height              Utils\get_flag_value$assoc_args'height' );

        
// The `$key_suffix` used for caching is part based on serializing the attributes array without normalizing it first so need to try to replicate that.
        
$oembed_args = array();

        if ( 
null !== $width ) {
            
$oembed_args['width'] = $width// Keep as string as if from a shortcode attribute.
        
}
        if ( 
null !== $height ) {
            
$oembed_args['height'] = $height// Keep as string as if from a shortcode attribute.
        
}
        if ( 
null !== $discover ) {
            
$oembed_args['discover'] = $discover '1' '0'// Make it a string as if from a shortcode attribute.
        
}

        
$discover null === $discover true : (bool) $discover// Will use to set `$oembed_args['discover']` when not calling `WP_Embed::shortcode()`.

        
if ( ! $discover && null !== $response_size_limit ) {
            
WP_CLI::error"The 'limit-response-size' option can only be used with discovery." );
        }

        if ( ! 
$raw && null !== $raw_format ) {
            
WP_CLI::error"The 'raw-format' option can only be used with the 'raw' option." );
        }

        if ( 
$response_size_limit ) {
            if ( 
Utils\wp_version_compare'4.0''<' ) ) {
                
WP_CLI::warning"The 'limit-response-size' option only works for WordPress 4.0 onwards." );
                
// Fall through anyway...
            
}
            
add_filter(
                
'oembed_remote_get_args',
                function ( 
$args ) use ( $response_size_limit ) {
                    
$args['limit_response_size'] = $response_size_limit;

                    return 
$args;
                },
                
PHP_INT_MAX
            
);
        }

        
// If raw, query providers directly, by-passing cache.
        
if ( $raw ) {
            
$oembed = new oEmbed(); // Needs to be here to make sure `_wp_oembed_get_object()` defined (in "wp-includes/class-oembed.php" for WP < 4.7).

            
$oembed_args['discover'] = $discover;

            
// Make 'oembed_dataparse' filter a no-op so get raw unsanitized data.
            
remove_all_filters'oembed_dataparse' ); // Save a few cycles.
            
add_filter(
                
'oembed_dataparse',
                function ( 
$return$data$url ) {
                    return 
$data;
                },
                
PHP_INT_MAX,
                
3
            
);

            
// Allow `wp_filter_pre_oembed_result()` to provide local URLs (WP >= 4.5.3).
            // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Using WP Core hook.
            
$data apply_filters'pre_oembed_result'null$url$oembed_args );

            if ( 
null === $data ) {
                
$provider $oembed->get_provider$url$oembed_args );

                if ( ! 
$provider ) {
                    if ( ! 
$discover ) {
                        
WP_CLI::error'No oEmbed provider found for given URL. Maybe try discovery?' );
                    } else {
                        
WP_CLI::error'No oEmbed provider found for given URL.' );
                    }
                }

                
$data $oembed->fetch$provider$url$oembed_args );
            }

            if ( 
false === $data ) {
                
WP_CLI::error'There was an error fetching the oEmbed data.' );
            }

            if ( 
'xml' === $raw_format ) {
                if ( ! 
class_exists'SimpleXMLElement' ) ) {
                    
WP_CLI::error"The PHP extension 'SimpleXMLElement' is not available but is required for XML-formatted output." );
                }
                
WP_CLI::log$this->oembed_create_xml( (array) $data ) );
            } else {
                
WP_CLI::logjson_encode$data ) );
            }

            return;
        }

        if ( 
$post_id ) {
            
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- the request is asking for a post id directly so need to override the global.
            
$GLOBALS['post'] = get_post$post_id );
            if ( 
null === $GLOBALS['post'] ) {
                
WP_CLI::warningsprintf"Post id '%s' not found."$post_id ) );
            }
        }

        if ( 
Utils\get_flag_value$assoc_args'skip-sanitization' ) ) {
            
remove_filter'oembed_dataparse''wp_filter_oembed_result'10);
        }

        if ( 
Utils\get_flag_value$assoc_args'skip-cache' ) ) {
            
$wp_embed->usecache false;
            
// In order to skip caching, also need `$cached_recently` to be false in `WP_Embed::shortcode()`, so set TTL to zero.
            
add_filter'oembed_ttl''__return_zero'PHP_INT_MAX );
        } else {
            
$wp_embed->usecache true;
        }

        
// `WP_Embed::shortcode()` sets the 'discover' attribute based on 'embed_oembed_discover' filter, no matter what's passed to it.
        
add_filter'embed_oembed_discover'$discover '__return_true' '__return_false'PHP_INT_MAX );

        
// For WP < 4.9, `WP_Embed::shortcode()` won't check providers if no post_id supplied, so set `maybe_make_link()` to return false so can check and do it ourselves.
        // Also set if WP < 4.4 and don't have 'unfiltered_html' privileges on post.
        
$check_providers Utils\wp_version_compare'4.9''<' ) && ( ! $post_id || ( Utils\wp_version_compare'4.4''<' ) && ! author_can$post_id'unfiltered_html' ) ) );
        if ( 
$check_providers ) {
            
add_filter'embed_maybe_make_link''__return_false'PHP_INT_MAX );
        }

        
$html $wp_embed->shortcode$oembed_args$url );

        if ( 
false === $html && $check_providers ) {

            
// Check providers.
            
$oembed_args['discover'] = $discover;
            
$html                    wp_oembed_get$url$oembed_args );

            
// `wp_oembed_get()` returns zero-length string instead of false on failure due to `_strip_newlines()` 'oembed_dataparse' filter so make sure false.
            
if ( '' === $html ) {
                
$html false;
            }
        }

        if ( 
false !== $html && '[' === substr$html0) && Utils\get_flag_value$assoc_args'do-shortcode' ) ) {
            
$html do_shortcode$htmltrue );
        }

        if ( 
false === $html ) {
            
WP_CLI::error'There was an error fetching the oEmbed data.' );
        }

        
WP_CLI::log$html );
    }

    
/**
     * Creates an XML string from a given array.
     *
     * Same as `\_oembed_create_xml()` in "wp-includes\embed.php" introduced in WP 4.4.0. Polyfilled as marked private (and also to cater for older WP versions).
     *
     * @see _oembed_create_xml()
     *
     * @param array            $data The original oEmbed response data.
     * @param \SimpleXMLElement $node Optional. XML node to append the result to recursively.
     * @return string|false XML string on success, false on error.
     */
    
protected function oembed_create_xml$data$node null ) {
        if ( ! 
is_array$data ) || empty( $data ) ) {
            return 
false;
        }

        if ( 
null === $node ) {
            
$node = new \SimpleXMLElement'<oembed></oembed>' );
        }

        foreach ( 
$data as $key => $value ) {
            if ( 
is_numeric$key ) ) {
                
$key 'oembed';
            }

            if ( 
is_array$value ) ) {
                
$item $node->addChild$key );
                
$this->oembed_create_xml$value$item );
            } else {
                
$node->addChild$keyesc_html$value ) );
            }
        }

        return 
$node->asXML();
    }
}
<?
php

namespace WP_CLI\Embeds;

use 
WP_CLI;
use 
WP_CLI\Process;
use 
WP_CLI\Utils;
use 
WP_CLI\Formatter;
use 
WP_CLI_Command;

/**
 * Finds, triggers, and deletes oEmbed caches.
 */
class Cache_Command extends WP_CLI_Command {

    
/**
     * Deletes all oEmbed caches for a given post.
     *
     * oEmbed caches for a post are stored in the post's metadata.
     *
     * ## OPTIONS
     *
     * <post_id>
     * : ID of the post to clear the cache for.
     *
     * ## EXAMPLES
     *
     *     # Clear cache for a post
     *     $ wp embed cache clear 123
     *     Success: Cleared oEmbed cache.
     */
    
public function clear$args$assoc_args ) {
        
/** @var \WP_Embed $wp_embed */
        
global $wp_embed;

        
$post_id $args[0];

        
$post_metas get_post_custom_keys$post_id );

        if ( 
$post_metas ) {
            
$post_metas array_filter(
                
$post_metas,
                function ( 
$v ) {
                    return 
'_oembed_' === substr$v0);
                }
            );
        }

        if ( empty( 
$post_metas ) ) {
            
WP_CLI::error'No cache to clear!' );
        }

        
$wp_embed->delete_oembed_caches$post_id );

        
WP_CLI::success'Cleared oEmbed cache.' );
    }

    
/**
     * Finds an oEmbed cache post ID for a given URL.
     *
     * Starting with WordPress 4.9, embeds that aren't associated with a specific post will be cached in
     * a new oembed_cache post type. There can be more than one such entry for a url depending on attributes and context.
     *
     * Not to be confused with oEmbed caches for a given post which are stored in the post's metadata.
     *
     * ## OPTIONS
     *
     * <url>
     * : URL to retrieve oEmbed data for.
     *
     * [--width=<width>]
     * : Width of the embed in pixels. Part of cache key so must match. Defaults to `content_width` if set else 500px, so is theme and context dependent.
     *
     * [--height=<height>]
     * : Height of the embed in pixels. Part of cache key so must match. Defaults to 1.5 * default width (`content_width` or 500px), to a maximum of 1000px.
     *
     * [--discover]
     * : Whether to search with the discover attribute set or not. Part of cache key so must match. If not given, will search with attribute: unset, '1', '0', returning first.
     *
     * ## EXAMPLES
     *
     *     # Find cache post ID for a given URL.
     *     $ wp embed cache find https://www.youtube.com/watch?v=dQw4w9WgXcQ --width=500
     *     123
     */
    
public function find$args$assoc_args ) {
        if ( 
Utils\wp_version_compare'4.9''<' ) ) {
            
WP_CLI::error'Requires WordPress 4.9 or greater.' );
        }

        
/** @var \WP_Embed $wp_embed */
        
global $wp_embed;

        
$url      $args[0];
        
$width    Utils\get_flag_value$assoc_args'width' );
        
$height   Utils\get_flag_value$assoc_args'height' );
        
$discover Utils\get_flag_value$assoc_args'discover' );

        
// The `$key_suffix` used for caching is part based on serializing the attributes array without normalizing it first so need to try to replicate that.
        
$oembed_args = array();

        if ( 
null !== $width ) {
            
$oembed_args['width'] = $width// Keep as string as if from a shortcode attribute.
        
}
        if ( 
null !== $height ) {
            
$oembed_args['height'] = $height// Keep as string as if from a shortcode attribute.
        
}
        if ( 
null !== $discover ) {
            
$discovers = array( ( $discover ) ? '1' '0' );
        } else {
            
$discovers = array( null'1''0' );
        }

        
$attr wp_parse_args$oembed_argswp_embed_defaults$url ) );

        foreach ( 
$discovers as $discover ) {
            if ( 
null !== $discover ) {
                
$attr['discover'] = $discover;
            }
            
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize -- needed to mimic WP Core behavior. See: \WP_Embed::shortcode.
            
$key_suffix md5$url serialize$attr ) );

            
$cached_post_id $wp_embed->find_oembed_post_id$key_suffix );

            if ( 
$cached_post_id ) {
                
WP_CLI::line$cached_post_id );

                return;
            }
        }

        
WP_CLI::error'No cache post ID found!' );
    }

    
/**
     * Triggers the caching of all oEmbed results for a given post.
     *
     * oEmbed caches for a post are stored in the post's metadata.
     *
     * ## OPTIONS
     *
     * <post_id>
     * : ID of the post to do the caching for.
     *
     * ## EXAMPLES
     *
     *     # Triggers cache for a post
     *     $ wp embed cache trigger 456
     *     Success: Caching triggered!
     */
    
public function trigger$args$assoc_args ) {
        
/** @var \WP_Embed $wp_embed */
        
global $wp_embed;

        
$post_id    $args[0];
        
$post       get_post$post_id );
        
$post_types get_post_types( array( 'show_ui' => true ) );

        if ( empty( 
$post->ID ) ) {
            
WP_CLI::warningsprintf"Post id '%s' not found."$post_id ) );

            return;
        }

        
/** This filter is documented in wp-includes/class-wp-embed.php */
        // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Using WP Core hook.
        
if ( ! in_array$post->post_typeapply_filters'embed_cache_oembed_types'$post_types ), true ) ) {
            
WP_CLI::warningsprintf"Cannot cache oEmbed results for '%s' post type"$post->post_type ) );

            return;
        }

        
$wp_embed->cache_oembed$post_id );

        
WP_CLI::success'Caching triggered!' );
    }
}
<?
php

namespace WP_CLI\Embeds;

use 
WP_CLI\Dispatcher\CommandNamespace;

/**
 * Inspects oEmbed providers, clears embed cache, and more.
 *
 * @package wp-cli
 */
class Embeds_Namespace extends CommandNamespace {
}
<?
php

namespace WP_CLI\Embeds;

use 
WP_CLI;
use 
WP_CLI\Formatter;
use 
WP_CLI_Command;

/**
 * Retrieves embed handlers.
 */
class Handler_Command extends WP_CLI_Command {
    protected 
$default_fields = array(
        
'id',
        
'regex',
    );

    
/**
     * Lists all available embed handlers.
     *
     * ## OPTIONS
     *
     * [--field=<field>]
     * : Display the value of a single field
     *
     * [--fields=<fields>]
     * : Limit the output to specific fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     * ---
     *
     * ## AVAILABLE FIELDS
     *
     * These fields will be displayed by default for each handler:
     *
     * * id
     * * regex
     *
     * These fields are optionally available:
     *
     * * callback
     * * priority
     *
     * ## EXAMPLES
     *
     *     # List id,regex,priority fields of available handlers.
     *     $ wp embed handler list --fields=priority,id
     *     +----------+-------------------+
     *     | priority | id                |
     *     +----------+-------------------+
     *     | 10       | youtube_embed_url |
     *     | 9999     | audio             |
     *     | 9999     | video             |
     *
     * @subcommand list
     */
    
public function list_handlers$args$assoc_args ) {
        
/** @var \WP_Embed $wp_embed */
        
global $wp_embed;

        
$all_handlers = array();

        
ksort$wp_embed->handlers );
        foreach ( 
$wp_embed->handlers as $priority => $handlers ) {
            foreach ( 
$handlers as $id => $handler ) {
                
$all_handlers[] = array(
                    
'id'       => $id,
                    
'regex'    => $handler['regex'],
                    
'callback' => $handler['callback'],
                    
'priority' => $priority,
                );
            }
        }

        
$formatter $this->get_formatter$assoc_args );
        
$formatter->display_items$all_handlers );
    }

    
/**
     * Get Formatter object based on supplied parameters.
     *
     * @param array $assoc_args Parameters passed to command. Determines formatting.
     * @return \WP_CLI\Formatter
     */
    
protected function get_formatter( &$assoc_args ) {
        return new 
Formatter$assoc_args$this->default_fields );
    }
}
<?
php

namespace WP_CLI\Embeds;

use 
WP_CLI;
use 
WP_CLI\Formatter;
use 
WP_CLI\Utils;
use 
WP_CLI_Command;

/**
 * Retrieves oEmbed providers.
 */
class Provider_Command extends WP_CLI_Command {
    protected 
$default_fields = array(
        
'format',
        
'endpoint',
    );

    
/**
     * Lists all available oEmbed providers.
     *
     * ## OPTIONS
     *
     * [--field=<field>]
     * : Display the value of a single field
     *
     * [--fields=<fields>]
     * : Limit the output to specific fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     * ---
     *
     * [--force-regex]
     * : Turn the asterisk-type provider URLs into regexes.
     *
     * ## AVAILABLE FIELDS
     *
     * These fields will be displayed by default for each provider:
     *
     * * format
     * * endpoint
     *
     * This field is optionally available:
     *
     * * regex
     *
     * ## EXAMPLES
     *
     *     # List format,endpoint fields of available providers.
     *     $ wp embed provider list --fields=format,endpoint
     *     +------------------------------+-----------------------------------------+
     *     | format                       | endpoint                                |
     *     +------------------------------+-----------------------------------------+
     *     | #https?://youtu\.be/.*#i     | https://www.youtube.com/oembed          |
     *     | #https?://flic\.kr/.*#i      | https://www.flickr.com/services/oembed/ |
     *     | #https?://wordpress\.tv/.*#i | https://wordpress.tv/oembed/            |
     *
     * @subcommand list
     */
    
public function list_providers$args$assoc_args ) {

        
$oembed = new oEmbed();

        
$force_regex Utils\get_flag_value$assoc_args'force-regex' );

        
$providers = array();

        foreach ( (array) 
$oembed->providers as $matchmask => $data ) {
            list( 
$providerurl$regex ) = $data;

            
// Turn the asterisk-type provider URLs into regex
            
if ( $force_regex && ! $regex ) {
                
$matchmask '#' str_replace'___wildcard___''(.+)'preg_quotestr_replace'*''___wildcard___'$matchmask ), '#' ) ) . '#i';
                
$matchmask preg_replace'|^#http\\\://|''#https?\://'$matchmask );
            }

            
$providers[] = array(
                
'format'   => $matchmask,
                
'endpoint' => $providerurl,
                
'regex'    => $regex '1' '0',
            );
        }

        
$formatter $this->get_formatter$assoc_args );
        
$formatter->display_items$providers );
    }

    
/**
     * Gets the matching provider for a given URL.
     *
     * ## OPTIONS
     *
     * <url>
     * : URL to retrieve provider for.
     *
     * [--discover]
     * : Whether to use oEmbed discovery or not. Defaults to true.
     *
     * [--limit-response-size=<size>]
     * : Limit the size of the resulting HTML when using discovery. Default 150 KB (the standard WordPress limit). Not compatible with 'no-discover'.
     *
     * [--link-type=<json|xml>]
     * : Whether to accept only a certain link type when using discovery. Defaults to any (json or xml), preferring json. Not compatible with 'no-discover'.
     * ---
     * options:
     *   - json
     *   - xml
     * ---
     *
     * ## EXAMPLES
     *
     *     # Get the matching provider for the URL.
     *     $ wp embed provider match https://www.youtube.com/watch?v=dQw4w9WgXcQ
     *     https://www.youtube.com/oembed
     *
     * @subcommand match
     */
    
public function match_provider$args$assoc_args ) {
        
$oembed = new oEmbed();

        
$url                 $args[0];
        
$discover            \WP_CLI\Utils\get_flag_value$assoc_args'discover'true );
        
$response_size_limit \WP_CLI\Utils\get_flag_value$assoc_args'limit-response-size' );
        
$link_type           \WP_CLI\Utils\get_flag_value$assoc_args'link-type' );

        if ( ! 
$discover && ( null !== $response_size_limit || null !== $link_type ) ) {
            if ( 
null !== $response_size_limit && null !== $link_type ) {
                
$msg "The 'limit-response-size' and 'link-type' options can only be used with discovery.";
            } elseif ( 
null !== $response_size_limit ) {
                
$msg "The 'limit-response-size' option can only be used with discovery.";
            } else {
                
$msg "The 'link-type' option can only be used with discovery.";
            }
            
WP_CLI::error$msg );
        }

        if ( 
$response_size_limit ) {
            if ( 
Utils\wp_version_compare'4.0''<' ) ) {
                
WP_CLI::warning"The 'limit-response-size' option only works for WordPress 4.0 onwards." );
                
// Fall through anyway...
            
}
            
add_filter(
                
'oembed_remote_get_args',
                function ( 
$args ) use ( $response_size_limit ) {
                    
$args['limit_response_size'] = $response_size_limit;
                    return 
$args;
                }
            );
        }

        if ( 
$link_type ) {
            
// Filter discovery response.
            
add_filter(
                
'oembed_linktypes',
                function ( 
$linktypes ) use ( $link_type ) {
                    foreach ( 
$linktypes as $mime_type => $linktype_format ) {
                        if ( 
$link_type !== $linktype_format ) {
                            unset( 
$linktypes$mime_type ] );
                        }
                    }
                    return 
$linktypes;
                }
            );
        }

        
$oembed_args = array(
            
'discover' => $discover,
        );

        
$provider $oembed->get_provider$url$oembed_args );

        if ( ! 
$provider ) {
            if ( ! 
$discover ) {
                
WP_CLI::error'No oEmbed provider found for given URL. Maybe try discovery?' );
            } else {
                
WP_CLI::error'No oEmbed provider found for given URL.' );
            }
        }

        
WP_CLI::line$provider );
    }

    
/**
     * Get Formatter object based on supplied parameters.
     *
     * @param array $assoc_args Parameters passed to command. Determines formatting.
     * @return \WP_CLI\Formatter
     */
    
protected function get_formatter( &$assoc_args ) {
        return new 
Formatter$assoc_args$this->default_fields );
    }
}
<?
php

namespace WP_CLI\Embeds;

if ( ! 
class_exists'\WP_oEmbed' ) ) {
    require_once 
ABSPATH WPINC '/class-oembed.php';
}

/**
 * Polyfill for older WP versions.
 */
class oEmbed extends \WP_oEmbed {

    
/**
     * Takes a URL and returns the corresponding oEmbed provider's URL, if there is one.
     *
     * @since 4.0.0
     *
     * @see WP_oEmbed::discover()
     *
     * @param string        $url  The URL to the content.
     * @param string|array  $args Optional provider arguments.
     * @return false|string False on failure, otherwise the oEmbed provider URL.
     */
    
public function get_provider$url$args '' ) {
        if ( 
method_exists'WP_oEmbed''get_provider' ) ) {
            return 
parent::get_provider$url$args );
        }

        
$args wp_parse_args$args );

        
$provider false;

        if ( ! isset( 
$args['discover'] ) ) {
            
$args['discover'] = true;
        }

        foreach ( 
$this->providers as $matchmask => $data ) {
            list( 
$providerurl$regex ) = $data;

            
// Turn the asterisk-type provider URLs into regex
            
if ( ! $regex ) {
                
$matchmask '#' str_replace'___wildcard___''(.+)'preg_quotestr_replace'*''___wildcard___'$matchmask ), '#' ) ) . '#i';
                
$matchmask preg_replace'|^#http\\\://|''#https?\://'$matchmask );
            }

            if ( 
preg_match$matchmask$url ) ) {
                
$provider str_replace'{format}''json'$providerurl ); // JSON is easier to deal with than XML
                
break;
            }
        }

        if ( ! 
$provider && $args['discover'] ) {
            
$provider $this->discover$url );
        }

        return 
$provider;
    }
}
<?
php

if ( ! class_exists'WP_CLI' ) ) {
    return;
}

$wpcli_embed_autoloader __DIR__ '/vendor/autoload.php';

if ( 
file_exists$wpcli_embed_autoloader ) ) {
    require_once 
$wpcli_embed_autoloader;
}

if ( 
class_exists'WP_CLI\Dispatcher\CommandNamespace' ) ) {
    
WP_CLI::add_command'embed''\WP_CLI\Embeds\Embeds_Namespace' );
}

WP_CLI::add_command'embed fetch''\WP_CLI\Embeds\Fetch_Command' );

WP_CLI::add_command'embed provider''\WP_CLI\Embeds\Provider_Command' );

WP_CLI::add_command'embed handler''\WP_CLI\Embeds\Handler_Command' );

WP_CLI::add_command'embed cache''\WP_CLI\Embeds\Cache_Command' );
<?
php

if ( ! class_exists'WP_CLI' ) ) {
    return;
}

$wpcli_core_autoloader dirname__FILE__ ) . '/vendor/autoload.php';
if ( 
file_exists$wpcli_core_autoloader ) ) {
    require_once 
$wpcli_core_autoloader;
}

WP_CLI::add_command'core''Core_Command' );
<?
php

namespace WP_CLI\Core;

/**
 * A Core Upgrader class that leaves packages intact by default.
 *
 * @package wp-cli
 */
class NonDestructiveCoreUpgrader extends CoreUpgrader {
    
// phpcs:ignore Generic.CodeAnalysis.UselessOverridingMethod.Found -- Provide default value.
    
public function unpack_package$package$delete_package false ) {
        return 
parent::unpack_package$package$delete_package );
    }
}

<?
php

namespace WP_CLI\Core;

use 
Exception;
use 
Requests_Response;
use 
WP_CLI;
use 
WP_CLI\Utils;
use 
WP_Error;
use 
Core_Upgrader as DefaultCoreUpgrader;
use 
WP_Filesystem_Base;

/**
 * A Core Upgrader class that caches the download, and uses cached if available.
 *
 * @package wp-cli
 */
class CoreUpgrader extends DefaultCoreUpgrader {

    
/**
     * Whether to retry without certificate validation on TLS handshake failure.
     *
     * @var bool
     */
    
private $insecure;

    
/**
     * CoreUpgrader constructor.
     *
     * @param WP_Upgrader_Skin|null $skin
     */
    
public function __construct$skin null$insecure false ) {
        
$this->insecure $insecure;
        
parent::__construct$skin );
    }

    
/**
     * Caches the download, and uses cached if available.
     *
     * @access public
     *
     * @param string $package          The URI of the package. If this is the full path to an
     *                                 existing local file, it will be returned untouched.
     * @param bool   $check_signatures Whether to validate file signatures. Default false.
     * @param array  $hook_extra       Extra arguments to pass to the filter hooks. Default empty array.
     * @return string|WP_Error The full path to the downloaded package file, or a WP_Error object.
     */
    
public function download_package$package$check_signatures false$hook_extra = [] ) {

        
/**
         * Filter whether to return the package.
         *
         * @since 3.7.0
         *
         * @param bool    $reply   Whether to bail without returning the package. Default is false.
         * @param string  $package The package file name.
         * @param object  $this    The WP_Upgrader instance.
         */
        
$reply apply_filters(
            
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Override existing hook from Core.
            
'upgrader_pre_download',
            
false,
            
$package,
            
$this,
            
$hook_extra
        
);
        if ( 
false !== $reply ) {
            return 
$reply;
        }

        
// Check if package is a local or remote file. Bail if it's local.
        
if ( ! preg_match'!^(http|https|ftp)://!i'$package ) && file_exists$package ) ) {
            return 
$package;
        }

        if ( empty( 
$package ) ) {
            return new 
WP_Error'no_package'$this->strings['no_package'] );
        }

        
$filename  pathinfo$packagePATHINFO_FILENAME );
        
$extension pathinfo$packagePATHINFO_EXTENSION );

        
$temp Utils\get_temp_dir() . uniqid'wp_' ) . ".{$extension}";
        
register_shutdown_function(
            function () use ( 
$temp ) {
                if ( 
file_exists$temp ) ) {
                    
unlink$temp );
                }
            }
        );

        
$cache      WP_CLI::get_cache();
        
$update     $GLOBALS['wpcli_core_update_obj'];
        
$cache_key  "core/{$filename}-{$update->locale}.{$extension}";
        
$cache_file $cache->has$cache_key );

        if ( 
$cache_file && false === stripos$package'https://wordpress.org/nightly-builds/' )
            && 
false === stripos$package'http://wordpress.org/nightly-builds/' ) ) {
            
WP_CLI::log"Using cached file '{$cache_file}'..." );
            
copy$cache_file$temp );
            return 
$temp;
        }

        
/*
         * Download to a temporary file because piping from cURL to tar is flaky
         * on MinGW (and probably in other environments too).
         */
        
$headers = [ 'Accept' => 'application/json' ];
        
$options = [
            
'timeout'       => 600,  // 10 minutes ought to be enough for everybody.
            
'filename'      => $temp,
            
'halt_on_error' => false,
            
'insecure'      => $this->insecure,
        ];

        
$this->skin->feedback'downloading_package'$package );

        
/** @var Requests_Response|null $req */
        
try {
            
$response Utils\http_request'GET'$packagenull$headers$options );
        } catch ( 
Exception $e ) {
            return new 
WP_Error'download_failed'$e->getMessage() );
        }

        if ( ! 
is_null$response ) && 200 !== (int) $response->status_code ) {
            return new 
WP_Error'download_failed'$this->strings['download_failed'] );
        }

        if ( 
false === stripos$package'https://wordpress.org/nightly-builds/' ) ) {
            
$cache->import$cache_key$temp );
        }

        return 
$temp;
    }

    
/**
     * Upgrade WordPress core.
     *
     * @access public
     *
     * @global WP_Filesystem_Base $wp_filesystem Subclass
     * @global callable           $_wp_filesystem_direct_method
     *
     * @param object $current Response object for whether WordPress is current.
     * @param array  $args {
     *        Optional. Arguments for upgrading WordPress core. Default empty array.
     *
     *        @type bool $pre_check_md5    Whether to check the file checksums before
     *                                     attempting the upgrade. Default true.
     *        @type bool $attempt_rollback Whether to attempt to rollback the chances if
     *                                     there is a problem. Default false.
     *        @type bool $do_rollback      Whether to perform this "upgrade" as a rollback.
     *                                     Default false.
     * }
     * @return null|false|WP_Error False or WP_Error on failure, null on success.
     */
    
public function upgrade$current$args = [] ) {
        
set_error_handler( [ __CLASS__'error_handler' ], E_USER_WARNING E_USER_NOTICE );

        
$result parent::upgrade$current$args );

        
restore_error_handler();

        return 
$result;
    }

    
/**
     * Error handler to ignore failures on accessing SSL "https://api.wordpress.org/core/checksums/1.0/" in `get_core_checksums()` which seem to occur intermittently.
     */
    
public static function error_handler$errno$errstr$errfile$errline$errcontext null ) {
        
// If ignoring E_USER_WARNING | E_USER_NOTICE, default.
        
if ( ! ( error_reporting() & $errno ) ) {
            return 
false;
        }
        
// If not in "wp-admin/includes/update.php", default.
        
$update_php 'wp-admin/includes/update.php';
        if ( 
!== substr_compare$errfile$update_php, -strlen$update_php ) ) ) {
            return 
false;
        }
        
// Else assume it's in `get_core_checksums()` and just ignore it.
        
return true;
    }
}
<?
php

use Composer\Semver\Comparator;
use 
WP_CLI\Extractor;
use 
WP_CLI\Iterators\Table as TableIterator;
use 
WP_CLI\Utils;
use 
WP_CLI\Formatter;
use 
WP_CLI\WpOrgApi;

/**
 * Downloads, installs, updates, and manages a WordPress installation.
 *
 * ## EXAMPLES
 *
 *     # Download WordPress core
 *     $ wp core download --locale=nl_NL
 *     Downloading WordPress 4.5.2 (nl_NL)...
 *     md5 hash verified: c5366d05b521831dd0b29dfc386e56a5
 *     Success: WordPress downloaded.
 *
 *     # Install WordPress
 *     $ wp core install --url=example.com --title=Example --admin_user=supervisor --admin_password=strongpassword [email protected]
 *     Success: WordPress installed successfully.
 *
 *     # Display the WordPress version
 *     $ wp core version
 *     4.5.2
 *
 * @package wp-cli
 */
class Core_Command extends WP_CLI_Command {

    
/**
     * Checks for WordPress updates via Version Check API.
     *
     * Lists the most recent versions when there are updates available,
     * or success message when up to date.
     *
     * ## OPTIONS
     *
     * [--minor]
     * : Compare only the first two parts of the version number.
     *
     * [--major]
     * : Compare only the first part of the version number.
     *
     * [--field=<field>]
     * : Prints the value of a single field for each update.
     *
     * [--fields=<fields>]
     * : Limit the output to specific object fields. Defaults to version,update_type,package_url.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - count
     *   - json
     *   - yaml
     * ---
     *
     * ## EXAMPLES
     *
     *     $ wp core check-update
     *     +---------+-------------+-------------------------------------------------------------+
     *     | version | update_type | package_url                                                 |
     *     +---------+-------------+-------------------------------------------------------------+
     *     | 4.5.2   | major       | https://downloads.wordpress.org/release/wordpress-4.5.2.zip |
     *     +---------+-------------+-------------------------------------------------------------+
     *
     * @subcommand check-update
     */
    
public function check_update$_$assoc_args ) {

        
$updates $this->get_updates$assoc_args );
        if ( 
$updates ) {
            
$updates   array_reverse$updates );
            
$formatter = new Formatter(
                
$assoc_args,
                [ 
'version''update_type''package_url' ]
            );
            
$formatter->display_items$updates );
        } elseif ( empty( 
$assoc_args['format'] ) || 'table' === $assoc_args['format'] ) {
            
WP_CLI::success'WordPress is at the latest version.' );
        }
    }

    
/**
     * Downloads core WordPress files.
     *
     * Downloads and extracts WordPress core files to the specified path. Uses
     * current directory when no path is specified. Downloaded build is verified
     * to have the correct md5 and then cached to the local filesytem.
     * Subsequent uses of command will use the local cache if it still exists.
     *
     * ## OPTIONS
     *
     * [<download-url>]
     * : Download directly from a provided URL instead of fetching the URL from the wordpress.org servers.
     *
     * [--path=<path>]
     * : Specify the path in which to install WordPress. Defaults to current
     * directory.
     *
     * [--locale=<locale>]
     * : Select which language you want to download.
     *
     * [--version=<version>]
     * : Select which version you want to download. Accepts a version number, 'latest' or 'nightly'.
     *
     * [--skip-content]
     * : Download WP without the default themes and plugins.
     *
     * [--force]
     * : Overwrites existing files, if present.
     *
     * [--insecure]
     * : Retry download without certificate validation if TLS handshake fails. Note: This makes the request vulnerable to a MITM attack.
     *
     * ## EXAMPLES
     *
     *     $ wp core download --locale=nl_NL
     *     Downloading WordPress 4.5.2 (nl_NL)...
     *     md5 hash verified: c5366d05b521831dd0b29dfc386e56a5
     *     Success: WordPress downloaded.
     *
     * @when before_wp_load
     */
    
public function download$args$assoc_args ) {

        
$download_dir = ! empty( $assoc_args['path'] )
            ? ( 
rtrim$assoc_args['path'], '/\\' ) . '/' )
            : 
ABSPATH;

        
$wordpress_present is_readable$download_dir 'wp-load.php' );

        if ( 
$wordpress_present && ! Utils\get_flag_value$assoc_args'force' ) ) {
            
WP_CLI::error'WordPress files seem to already be present here.' );
        }

        if ( ! 
is_dir$download_dir ) ) {
            if ( ! 
is_writabledirname$download_dir ) ) ) {
                
WP_CLI::error"Insufficient permission to create directory '{$download_dir}'." );
            }

            
WP_CLI::log"Creating directory '{$download_dir}'." );
            if ( ! @
mkdir$download_dir0777true /*recursive*/ ) ) {
                
$error error_get_last();
                
WP_CLI::error"Failed to create directory '{$download_dir}': {$error['message']}." );
            }
        }

        if ( ! 
is_writable$download_dir ) ) {
            
WP_CLI::error"'{$download_dir}' is not writable by current user." );
        }

        
$locale       = (string) Utils\get_flag_value$assoc_args'locale''en_US' );
        
$skip_content = (bool) Utils\get_flag_value$assoc_args'skip-content'false );
        
$insecure     = (bool) Utils\get_flag_value$assoc_args'insecure'false );

        
$download_url array_shift$args );
        
$from_url     = ! empty( $download_url );

        if ( 
$from_url ) {
            
$version null;
            if ( isset( 
$assoc_args['version'] ) ) {
                
WP_CLI::error'Version option is not available for URL downloads.' );
            }
            if ( 
$skip_content || 'en_US' !== $locale ) {
                
WP_CLI::error'Skip content and locale options are not available for URL downloads.' );
            }
        } elseif ( isset( 
$assoc_args['version'] ) && 'latest' !== $assoc_args['version'] ) {
            
$version $assoc_args['version'];
            if ( 
in_arraystrtolower$version ), [ 'trunk''nightly' ], true ) ) {
                
$version 'nightly';
            }

            
// Nightly builds and skip content are only available in .zip format.
            
$extension = ( ( 'nightly' === $version ) || $skip_content )
                ? 
'zip'
                
'tar.gz';

            
$download_url $this->get_download_url$version$locale$extension );
        } else {
            try {
                
$offer = ( new WpOrgApi( [ 'insecure' => $insecure ] ) )
                    ->
get_core_download_offer$locale );
            } catch ( 
Exception $exception ) {
                
WP_CLI::error$exception );
            }
            if ( ! 
$offer ) {
                
WP_CLI::error"The requested locale ({$locale}) was not found." );
            }
            
$version      $offer['current'];
            
$download_url $offer['download'];
            if ( ! 
$skip_content ) {
                
$download_url str_replace'.zip''.tar.gz'$download_url );
            }
        }

        if ( 
'nightly' === $version && 'en_US' !== $locale ) {
            
WP_CLI::error'Nightly builds are only available for the en_US locale.' );
        }

        
$from_version '';
        if ( 
file_exists$download_dir 'wp-includes/version.php' ) ) {
            
$wp_details   self::get_wp_details$download_dir );
            
$from_version $wp_details['wp_version'];
        }

        if ( 
$from_url ) {
            
WP_CLI::log"Downloading from {$download_url} ..." );
        } else {
            
WP_CLI::log"Downloading WordPress {$version} ({$locale})..." );
        }

        
$path_parts pathinfo$download_url );
        
$extension  'tar.gz';
        if ( 
'zip' === $path_parts['extension'] ) {
            
$extension 'zip';
            if ( ! 
class_exists'ZipArchive' ) ) {
                
WP_CLI::error'Extracting a zip file requires ZipArchive.' );
            }
        }

        if ( 
$skip_content && 'zip' !== $extension ) {
            
WP_CLI::error'Skip content is only available for ZIP files.' );
        }

        
$cache WP_CLI::get_cache();
        if ( 
$from_url ) {
            
$cache_file null;
        } else {
            
$cache_key  "core/wordpress-{$version}-{$locale}.{$extension}";
            
$cache_file $cache->has$cache_key );
        }

        
$bad_cache false;
        if ( 
$cache_file ) {
            
WP_CLI::log"Using cached file '{$cache_file}'..." );
            
$skip_content_cache_file $skip_content self::strip_content_dir$cache_file ) : null;
            try {
                
Extractor::extract$skip_content_cache_file ?: $cache_file$download_dir );
            } catch ( 
Exception $exception ) {
                
WP_CLI::warning'Extraction failed, downloading a new copy...' );
                
$bad_cache true;
            }
        }

        if ( ! 
$cache_file || $bad_cache ) {
            
// We need to use a temporary file because piping from cURL to tar is flaky
            // on MinGW (and probably in other environments too).
            
$temp Utils\get_temp_dir() . uniqid'wp_' ) . ".{$extension}";
            
register_shutdown_function(
                function () use ( 
$temp ) {
                    if ( 
file_exists$temp ) ) {
                        
unlink$temp );
                    }
                }
            );

            
$headers = [ 'Accept' => 'application/json' ];
            
$options = [
                
'timeout'  => 600,  // 10 minutes ought to be enough for everybody
                
'filename' => $temp,
                
'insecure' => $insecure,
            ];

            
$response Utils\http_request'GET'$download_urlnull$headers$options );

            if ( 
404 === (int) $response->status_code ) {
                
WP_CLI::error'Release not found. Double-check locale or version.' );
            } elseif ( 
20 !== (int) substr$response->status_code0) ) {
                
WP_CLI::error"Couldn't access download URL (HTTP code {$response->status_code})." );
            }

            if ( 
'nightly' !== $version ) {
                unset( 
$options['filename'] );
                
$md5_response Utils\http_request'GET'$download_url '.md5'null, [], $options );
                if ( 
$md5_response->status_code >= 200 && $md5_response->status_code 300 ) {
                    
$md5_file md5_file$temp );

                    if ( 
$md5_file === $md5_response->body ) {
                        
WP_CLI::log'md5 hash verified: ' $md5_file );
                    } else {
                        
WP_CLI::error"md5 hash for download ({$md5_file}) is different than the release hash ({$md5_response->body})." );
                    }
                } else {
                    
WP_CLI::warning"Couldn't access md5 hash for release ({$download_url}.md5, HTTP code {$md5_response->status_code})." );
                }
            } else {
                
WP_CLI::warning'md5 hash checks are not available for nightly downloads.' );
            }

            
$skip_content_temp $skip_content self::strip_content_dir$temp ) : null;

            try {
                
Extractor::extract$skip_content_temp ?: $temp$download_dir );
            } catch ( 
Exception $exception ) {
                
WP_CLI::error"Couldn't extract WordPress archive. {$exception->getMessage()});
            }

            
// Do not use the cache for nightly builds or for downloaded URLs
            // (the URL could be something like "latest.zip" or "nightly.zip").
            
if ( ! $from_url && 'nightly' !== $version ) {
                
$cache->import$cache_key$temp );
            }
        }

        if ( 
$wordpress_present ) {
            
$this->cleanup_extra_files$from_version$version$locale$insecure );
        }

        
WP_CLI::success'WordPress downloaded.' );
    }

    
/**
     * Checks if WordPress is installed.
     *
     * Determines whether WordPress is installed by checking if the standard
     * database tables are installed. Doesn't produce output; uses exit codes
     * to communicate whether WordPress is installed.
     *
     * [--network]
     * : Check if this is a multisite installation.
     *
     * ## EXAMPLES
     *
     *     # Check whether WordPress is installed; exit status 0 if installed, otherwise 1
     *     $ wp core is-installed
     *     $ echo $?
     *     1
     *
     *     # Bash script for checking whether WordPress is installed or not
     *     if ! wp core is-installed; then
     *         wp core install
     *     fi
     *
     * @subcommand is-installed
     */
    
public function is_installed$args$assoc_args ) {
        if ( 
is_blog_installed()
            && ( ! 
Utils\get_flag_value$assoc_args'network' )
                || 
is_multisite() ) ) {
            
WP_CLI::halt);
        }

        
WP_CLI::halt);
    }

    
/**
     * Runs the standard WordPress installation process.
     *
     * Creates the WordPress tables in the database using the URL, title, and
     * default admin user details provided. Performs the famous 5 minute install
     * in seconds or less.
     *
     * Note: if you've installed WordPress in a subdirectory, then you'll need
     * to `wp option update siteurl` after `wp core install`. For instance, if
     * WordPress is installed in the `/wp` directory and your domain is example.com,
     * then you'll need to run `wp option update siteurl http://example.com/wp` for
     * your WordPress installation to function properly.
     *
     * Note: When using custom user tables (e.g. `CUSTOM_USER_TABLE`), the admin
     * email and password are ignored if the user_login already exists. If the
     * user_login doesn't exist, a new user will be created.
     *
     * ## OPTIONS
     *
     * --url=<url>
     * : The address of the new site.
     *
     * --title=<site-title>
     * : The title of the new site.
     *
     * --admin_user=<username>
     * : The name of the admin user.
     *
     * [--admin_password=<password>]
     * : The password for the admin user. Defaults to randomly generated string.
     *
     * --admin_email=<email>
     * : The email address for the admin user.
     *
     * [--skip-email]
     * : Don't send an email notification to the new admin user.
     *
     * ## EXAMPLES
     *
     *     # Install WordPress in 5 seconds
     *     $ wp core install --url=example.com --title=Example --admin_user=supervisor --admin_password=strongpassword [email protected]
     *     Success: WordPress installed successfully.
     *
     *     # Install WordPress without disclosing admin_password to bash history
     *     $ wp core install --url=example.com --title=Example --admin_user=supervisor [email protected] --prompt=admin_password < admin_password.txt
     */
    
public function install$args$assoc_args ) {
        if ( 
$this->do_install$assoc_args ) ) {
            
WP_CLI::success'WordPress installed successfully.' );
        } else {
            
WP_CLI::log'WordPress is already installed.' );
        }
    }

    
/**
     * Transforms an existing single-site installation into a multisite installation.
     *
     * Creates the multisite database tables, and adds the multisite constants
     * to wp-config.php.
     *
     * For those using WordPress with Apache, remember to update the `.htaccess`
     * file with the appropriate multisite rewrite rules.
     *
     * [Review the multisite documentation](https://codex.wordpress.org/Create_A_Network)
     * for more details about how multisite works.
     *
     * ## OPTIONS
     *
     * [--title=<network-title>]
     * : The title of the new network.
     *
     * [--base=<url-path>]
     * : Base path after the domain name that each site url will start with.
     * ---
     * default: /
     * ---
     *
     * [--subdomains]
     * : If passed, the network will use subdomains, instead of subdirectories. Doesn't work with 'localhost'.
     *
     * ## EXAMPLES
     *
     *     $ wp core multisite-convert
     *     Set up multisite database tables.
     *     Added multisite constants to wp-config.php.
     *     Success: Network installed. Don't forget to set up rewrite rules.
     *
     * @subcommand multisite-convert
     * @alias install-network
     */
    
public function multisite_convert$args$assoc_args ) {
        if ( 
is_multisite() ) {
            
WP_CLI::error'This already is a multisite installation.' );
        }

        
$assoc_args self::set_multisite_defaults$assoc_args );
        if ( ! isset( 
$assoc_args['title'] ) ) {
            
// translators: placeholder is blog name
            
$assoc_args['title'] = sprintf_x'%s Sites''Default network name' ), get_option'blogname' ) );
        }

        if ( 
$this->multisite_convert_$assoc_args ) ) {
            
WP_CLI::success"Network installed. Don't forget to set up rewrite rules (and a .htaccess file, if using Apache)." );
        }
    }

    
/**
     * Installs WordPress multisite from scratch.
     *
     * Creates the WordPress tables in the database using the URL, title, and
     * default admin user details provided. Then, creates the multisite tables
     * in the database and adds multisite constants to the wp-config.php.
     *
     * For those using WordPress with Apache, remember to update the `.htaccess`
     * file with the appropriate multisite rewrite rules.
     *
     * ## OPTIONS
     *
     * [--url=<url>]
     * : The address of the new site.
     *
     * [--base=<url-path>]
     * : Base path after the domain name that each site url in the network will start with.
     * ---
     * default: /
     * ---
     *
     * [--subdomains]
     * : If passed, the network will use subdomains, instead of subdirectories. Doesn't work with 'localhost'.
     *
     * --title=<site-title>
     * : The title of the new site.
     *
     * --admin_user=<username>
     * : The name of the admin user.
     * ---
     * default: admin
     * ---
     *
     * [--admin_password=<password>]
     * : The password for the admin user. Defaults to randomly generated string.
     *
     * --admin_email=<email>
     * : The email address for the admin user.
     *
     * [--skip-email]
     * : Don't send an email notification to the new admin user.
     *
     * [--skip-config]
     * : Don't add multisite constants to wp-config.php.
     *
     * ## EXAMPLES
     *
     *     $ wp core multisite-install --title="Welcome to the WordPress" \
     *     > --admin_user="admin" --admin_password="password" \
     *     > --admin_email="[email protected]"
     *     Single site database tables already present.
     *     Set up multisite database tables.
     *     Added multisite constants to wp-config.php.
     *     Success: Network installed. Don't forget to set up rewrite rules.
     *
     * @subcommand multisite-install
     */
    
public function multisite_install$args$assoc_args ) {
        if ( 
$this->do_install$assoc_args ) ) {
            
WP_CLI::log'Created single site database tables.' );
        } else {
            
WP_CLI::log'Single site database tables already present.' );
        }

        
$assoc_args self::set_multisite_defaults$assoc_args );
        
// translators: placeholder is user supplied title
        
$assoc_args['title'] = sprintf_x'%s Sites''Default network name' ), $assoc_args['title'] );

        
// Overwrite runtime args, to avoid mismatches.
        
$consts_to_args = [
            
'SUBDOMAIN_INSTALL'    => 'subdomains',
            
'PATH_CURRENT_SITE'    => 'base',
            
'SITE_ID_CURRENT_SITE' => 'site_id',
            
'BLOG_ID_CURRENT_SITE' => 'blog_id',
        ];

        foreach ( 
$consts_to_args as $const => $arg ) {
            if ( 
defined$const ) ) {
                
$assoc_args$arg ] = constant$const );
            }
        }

        if ( ! 
$this->multisite_convert_$assoc_args ) ) {
            return;
        }

        
// Do the steps that were skipped by populate_network(),
        // which checks is_multisite().
        
if ( is_multisite() ) {
            
$site_user get_user_by'email'$assoc_args['admin_email'] );
            
self::add_site_admins$site_user );
            
$domain self::get_clean_basedomain();
            
self::create_initial_blog(
                
$assoc_args['site_id'],
                
$assoc_args['blog_id'],
                
$domain,
                
$assoc_args['base'],
                
$assoc_args['subdomains'],
                
$site_user
            
);
        }

        
WP_CLI::success"Network installed. Don't forget to set up rewrite rules (and a .htaccess file, if using Apache)." );
    }

    private static function 
set_multisite_defaults$assoc_args ) {
        
$defaults = [
            
'subdomains' => false,
            
'base'       => '/',
            
'site_id'    => 1,
            
'blog_id'    => 1,
        ];

        return 
array_merge$defaults$assoc_args );
    }

    private function 
do_install$assoc_args ) {
        if ( 
is_blog_installed() ) {
            return 
false;
        }

        if ( 
true === Utils\get_flag_value$assoc_args'skip-email' ) ) {
            if ( ! 
function_exists'wp_new_blog_notification' ) ) {
                function 
wp_new_blog_notification() {
                    
// Silence is golden
                
}
            }
            
// WP 4.9.0 - skip "Notice of Admin Email Change" email as well (https://core.trac.wordpress.org/ticket/39117).
            
add_filter'send_site_admin_email_change_email''__return_false' );
        }

        require_once 
ABSPATH 'wp-admin/includes/upgrade.php';

        
$defaults = [
            
'title'          => '',
            
'admin_user'     => '',
            
'admin_email'    => '',
            
'admin_password' => '',
        ];

        
$args wp_parse_args$assoc_args$defaults );

        
// Support prompting for the `--url=<url>`,
        // which is normally a runtime argument
        
if ( isset( $assoc_args['url'] ) ) {
            
WP_CLI::set_url$assoc_args['url'] );
        }

        
$public   true;
        
$password = empty( $args['admin_password'] )
            ? 
wp_generate_password18 )
            : 
$args['admin_password'];

        if ( ! 
is_email$args['admin_email'] ) ) {
            
WP_CLI::error"The '{$args['admin_email']}' email address is invalid." );
        }

        
$result wp_install(
            
$args['title'],
            
$args['admin_user'],
            
$args['admin_email'],
            
$public,
            
'',
            
$password
        
);

        if ( 
is_wp_error$result ) ) {
            
$reason WP_CLI::error_to_string$result );
            
WP_CLI::error"Installation failed ({$reason})." );
        }

        if ( ! empty( 
$GLOBALS['wpdb']->last_error ) ) {
            
WP_CLI::error'Installation produced database errors, and may have partially or completely failed.' );
        }

        if ( empty( 
$args['admin_password'] ) ) {
            
WP_CLI::log"Admin password: {$result['password']});
        }

        
// Confirm the uploads directory exists
        
$upload_dir wp_upload_dir();
        if ( ! empty( 
$upload_dir['error'] ) ) {
            
WP_CLI::warning$upload_dir['error'] );
        }

        return 
true;
    }

    private function 
multisite_convert_$assoc_args ) {
        global 
$wpdb;

        require_once 
ABSPATH 'wp-admin/includes/upgrade.php';

        
$domain self::get_clean_basedomain();
        if ( 
'localhost' === $domain && ! empty( $assoc_args['subdomains'] ) ) {
            
WP_CLI::error"Multisite with subdomains cannot be configured when domain is 'localhost'." );
        }

        
// need to register the multisite tables manually for some reason
        
foreach ( $wpdb->tables'ms_global' ) as $table => $prefixed_table ) {
            
$wpdb->$table $prefixed_table;
        }

        
install_network();

        
$result populate_network(
            
$assoc_args['site_id'],
            
$domain,
            
get_option'admin_email' ),
            
$assoc_args['title'],
            
$assoc_args['base'],
            
$assoc_args['subdomains']
        );

        
$site_id $wpdb->get_var"SELECT id FROM $wpdb->site);
        
$site_id = ( null === $site_id ) ? : (int) $site_id;

        if ( 
true === $result ) {
            
WP_CLI::log'Set up multisite database tables.' );
        } elseif ( 
is_wp_error$result ) ) {
            switch ( 
$result->get_error_code() ) {

                case 
'siteid_exists':
                    
WP_CLI::log$result->get_error_message() );
                    return 
false;

                case 
'no_wildcard_dns':
                    
WP_CLI::warning__'Wildcard DNS may not be configured correctly.' ) );
                    break;

                default:
                    
WP_CLI::error$result );
            }
        }

        
// delete_site_option() cleans the alloptions cache to prevent dupe option
        
delete_site_option'upload_space_check_disabled' );
        
update_site_option'upload_space_check_disabled');

        if ( ! 
is_multisite() ) {
            
$subdomain_export Utils\get_flag_value$assoc_args'subdomains' ) ? 'true' 'false';
            
$ms_config        = <<<EOT
define( 'WP_ALLOW_MULTISITE', true );
define( 'MULTISITE', true );
define( 'SUBDOMAIN_INSTALL', 
{$subdomain_export} );
\$base = '
{$assoc_args['base']}';
define( 'DOMAIN_CURRENT_SITE', '
{$domain}' );
define( 'PATH_CURRENT_SITE', '
{$assoc_args['base']}' );
define( 'SITE_ID_CURRENT_SITE', 
{$site_id} );
define( 'BLOG_ID_CURRENT_SITE', 1 );
EOT;

            
$wp_config_path Utils\locate_wp_config();
            if ( 
true === Utils\get_flag_value$assoc_args'skip-config' ) ) {
                
WP_CLI::log"Addition of multisite constants to 'wp-config.php' skipped. You need to add them manually:\n{$ms_config});
            } elseif ( 
is_writable$wp_config_path ) && self::modify_wp_config$ms_config ) ) {
                
WP_CLI::log"Added multisite constants to 'wp-config.php'." );
            } else {
                
WP_CLI::warning"Multisite constants could not be written to 'wp-config.php'. You may need to add them manually:\n{$ms_config});
            }
        } else {
            
/* Multisite constants are defined, therefore we already have an empty site_admins site meta.
             *
             * Code based on parts of delete_network_option. */
            
$rows $wpdb->get_results"SELECT meta_id, site_id FROM {$wpdb->sitemeta} WHERE meta_key = 'site_admins' AND meta_value = ''" );

            foreach ( 
$rows as $row ) {
                
wp_cache_delete"{$row->site_id}:site_admins"'site-options' );

                
$wpdb->delete(
                    
$wpdb->sitemeta,
                    [ 
'meta_id' => $row->meta_id ]
                );
            }
        }

        return 
true;
    }

    
// copied from populate_network()
    
private static function create_initial_blog$network_id$blog_id$domain$path,
        
$subdomain_install$site_user ) {
        global 
$wpdb$current_site$wp_rewrite;

        
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- This is meant to replace Core functionality.
        
$current_site            = new stdClass();
        
$current_site->domain    $domain;
        
$current_site->path      $path;
        
$current_site->site_name ucfirst$domain );
        
$blog_data               = [
            
'site_id'    => $network_id,
            
'domain'     => $domain,
            
'path'       => $path,
            
'registered' => current_time'mysql' ),
        ];
        
$wpdb->insert$wpdb->blogs$blog_data );
        
$current_site->blog_id $wpdb->insert_id;
        
$blog_id               $wpdb->insert_id;
        
update_user_meta$site_user->ID'source_domain'$domain );
        
update_user_meta$site_user->ID'primary_blog'$blog_id );

        if ( 
$subdomain_install ) {
            
$wp_rewrite->set_permalink_structure'/%year%/%monthnum%/%day%/%postname%/' );
        } else {
            
$wp_rewrite->set_permalink_structure'/blog/%year%/%monthnum%/%day%/%postname%/' );
        }

        
flush_rewrite_rules();
    }

    
// copied from populate_network()
    
private static function add_site_admins$site_user ) {
        
$site_admins = [ $site_user->user_login ];
        
$users       get_users( [ 'fields' => [ 'ID''user_login' ] ] );
        if ( 
$users ) {
            foreach ( 
$users as $user ) {
                if ( 
is_super_admin$user->ID )
                    && ! 
in_array$user->user_login$site_adminstrue ) ) {
                    
$site_admins[] = $user->user_login;
                }
            }
        }

        
update_site_option'site_admins'$site_admins );
    }

    private static function 
modify_wp_config$content ) {
        
$wp_config_path Utils\locate_wp_config();

        
$token           "/* That's all, stop editing!";
        
$config_contents file_get_contents$wp_config_path );
        if ( 
false === strpos$config_contents$token ) ) {
            return 
false;
        }

        list( 
$before$after ) = explode$token$config_contents );

        
$content trim$content );

        
file_put_contents(
            
$wp_config_path,
            
"{$before}\n\n{$content}\n\n{$token}{$after}"
        
);

        return 
true;
    }

    private static function 
get_clean_basedomain() {
        
$domain preg_replace'|https?://|'''get_option'siteurl' ) );
        
$slash  strpos$domain'/' );
        if ( 
false !== $slash ) {
            
$domain substr$domain0$slash );
        }
        return 
$domain;
    }

    
/**
     * Displays the WordPress version.
     *
     * ## OPTIONS
     *
     * [--extra]
     * : Show extended version information.
     *
     * ## EXAMPLES
     *
     *     # Display the WordPress version
     *     $ wp core version
     *     4.5.2
     *
     *     # Display WordPress version along with other information
     *     $ wp core version --extra
     *     WordPress version: 4.5.2
     *     Database revision: 36686
     *     TinyMCE version:   4.310 (4310-20160418)
     *     Package language:  en_US
     *
     * @when before_wp_load
     */
    
public function version$args = [], $assoc_args = [] ) {
        
$details self::get_wp_details();

        if ( ! 
Utils\get_flag_value$assoc_args'extra' ) ) {
            
WP_CLI::line$details['wp_version'] );
            return;
        }

        
$match                   = [];
        
$found_version           preg_match'/(\d)(\d+)-/'$details['tinymce_version'], $match );
        
$human_readable_tiny_mce $found_version "{$match[1]}.{$match[2]}'';

        echo 
Utils\mustache_render(
            
self::get_template_path'versions.mustache' ),
            [
                
'wp-version'    => $details['wp_version'],
                
'db-version'    => $details['wp_db_version'],
                
'local-package' => empty( $details['wp_local_package'] )
                    ? 
'en_US'
                    
$details['wp_local_package'],
                
'mce-version'   => $human_readable_tiny_mce
                    
"{$human_readable_tiny_mce} ({$details['tinymce_version']})"
                    
$details['tinymce_version'],
            ]
        );
    }

    
/**
     * Gets version information from `wp-includes/version.php`.
     *
     * @return array {
     *     @type string $wp_version The WordPress version.
     *     @type int $wp_db_version The WordPress DB revision.
     *     @type string $tinymce_version The TinyMCE version.
     *     @type string $wp_local_package The TinyMCE version.
     * }
     */
    
private static function get_wp_details$abspath ABSPATH ) {
        
$versions_path $abspath 'wp-includes/version.php';

        if ( ! 
is_readable$versions_path ) ) {
            
WP_CLI::error(
                
"This does not seem to be a WordPress installation.\n" .
                
'Pass --path=`path/to/wordpress` or run `wp core download`.'
            
);
        }

        
$version_content file_get_contents$versions_pathnullnull62048 );

        
$vars   = [ 'wp_version''wp_db_version''tinymce_version''wp_local_package' ];
        
$result = [];

        foreach ( 
$vars as $var_name ) {
            
$result$var_name ] = self::find_var$var_name$version_content );
        }

        return 
$result;
    }

    
/**
     * Gets the template path based on installation type.
     */
    
private static function get_template_path$template ) {
        
$command_root  Utils\phar_safe_pathdirname__DIR__ ) );
        
$template_path "{$command_root}/templates/{$template}";

        if ( ! 
file_exists$template_path ) ) {
            
WP_CLI::error"Couldn't find {$template});
        }

        return 
$template_path;
    }

    
/**
     * Searches for the value assigned to variable `$var_name` in PHP code `$code`.
     *
     * This is equivalent to matching the `\$VAR_NAME = ([^;]+)` regular expression and returning
     * the first match either as a `string` or as an `integer` (depending if it's surrounded by
     * quotes or not).
     *
     * @param string $var_name Variable name to search for.
     * @param string $code PHP code to search in.
     *
     * @return int|string|null
     */
    
private static function find_var$var_name$code ) {
        
$start strpos$code'$' $var_name ' = ' );

        if ( ! 
$start ) {
            return 
null;
        }

        
$start $start strlen$var_name ) + 3;
        
$end   strpos$code';'$start );

        
$value substr$code$start$end $start );

        return 
trim$value" '" );
    }

    
/**
     * Security copy of the core function with Requests - Gets the checksums for the given version of WordPress.
     *
     * @param string $version  Version string to query.
     * @param string $locale   Locale to query.
     * @param bool   $insecure Whether to retry without certificate validation on TLS handshake failure.
     * @return string|array String message on failure. An array of checksums on success.
     */
    
private static function get_core_checksums$version$locale$insecure ) {
        
$query http_build_querycompact'version''locale' ), null'&' );
        
$url   "https://api.wordpress.org/core/checksums/1.0/?{$query}";

        
$headers = [ 'Accept' => 'application/json' ];
        
$options = [
            
'timeout'  => 30,
            
'insecure' => $insecure,
        ];

        
$response Utils\http_request'GET'$urlnull$headers$options );

        if ( ! 
$response->success || 200 !== (int) $response->status_code ) {
            return 
"Checksum request '{$url}' failed (HTTP {$response->status_code}).";
        }

        
$body trim$response->body );
        
$body json_decode$bodytrue );

        if ( ! 
is_array$body )
            || ! isset( 
$body['checksums'] )
            || ! 
is_array$body['checksums'] ) ) {
            return 
"Checksums not available for WordPress {$version}/{$locale}.";
        }

        return 
$body['checksums'];
    }

    
/**
     * Updates WordPress to a newer version.
     *
     * Defaults to updating WordPress to the latest version.
     *
     * If you see "Error: Another update is currently in progress.", you may
     * need to run `wp option delete core_updater.lock` after verifying another
     * update isn't actually running.
     *
     * ## OPTIONS
     *
     * [<zip>]
     * : Path to zip file to use, instead of downloading from wordpress.org.
     *
     * [--minor]
     * : Only perform updates for minor releases (e.g. update from WP 4.3 to 4.3.3 instead of 4.4.2).
     *
     * [--version=<version>]
     * : Update to a specific version, instead of to the latest version. Alternatively accepts 'nightly'.
     *
     * [--force]
     * : Update even when installed WP version is greater than the requested version.
     *
     * [--locale=<locale>]
     * : Select which language you want to download.
     *
     * [--insecure]
     * : Retry download without certificate validation if TLS handshake fails. Note: This makes the request vulnerable to a MITM attack.
     *
     * ## EXAMPLES
     *
     *     # Update WordPress
     *     $ wp core update
     *     Updating to version 4.5.2 (en_US)...
     *     Downloading update from https://downloads.wordpress.org/release/wordpress-4.5.2-no-content.zip...
     *     Unpacking the update...
     *     Cleaning up files...
     *     No files found that need cleaning up
     *     Success: WordPress updated successfully.
     *
     *     # Update WordPress to latest version of 3.8 release
     *     $ wp core update --version=3.8 ../latest.zip
     *     Updating to version 3.8 ()...
     *     Unpacking the update...
     *     Cleaning up files...
     *     File removed: wp-admin/js/tags-box.js
     *     ...
     *     File removed: wp-admin/js/updates.min.
     *     377 files cleaned up
     *     Success: WordPress updated successfully.
     *
     *     # Update WordPress to 3.1 forcefully
     *     $ wp core update --version=3.1 --force
     *     Updating to version 3.1 (en_US)...
     *     Downloading update from https://wordpress.org/wordpress-3.1.zip...
     *     Unpacking the update...
     *     Warning: Checksums not available for WordPress 3.1/en_US. Please cleanup files manually.
     *     Success: WordPress updated successfully.
     *
     * @alias upgrade
     */
    
public function update$args$assoc_args ) {
        global 
$wp_version;

        
$update   null;
        
$upgrader 'WP_CLI\\Core\\CoreUpgrader';

        if ( 
'trunk' === Utils\get_flag_value$assoc_args'version' ) ) {
            
$assoc_args['version'] = 'nightly';
        }

        if ( ! empty( 
$args[0] ) ) {

            
// ZIP path or URL is given
            
$upgrader 'WP_CLI\\Core\\NonDestructiveCoreUpgrader';
            
$version  Utils\get_flag_value$assoc_args'version' );

            
$update = (object) [
                
'response' => 'upgrade',
                
'current'  => $version,
                
'download' => $args[0],
                
'packages' => (object) [
                    
'partial'     => null,
                    
'new_bundled' => null,
                    
'no_content'  => null,
                    
'full'        => $args[0],
                ],
                
'version'  => $version,
                
'locale'   => null,
            ];

        } elseif ( empty( 
$assoc_args['version'] ) ) {

            
// Update to next release
            
wp_version_check();
            
$from_api get_site_transient'update_core' );

            if ( 
Utils\get_flag_value$assoc_args'minor' ) ) {
                foreach ( 
$from_api->updates as $offer ) {
                    
$sem_ver Utils\get_named_sem_ver$offer->version$wp_version );
                    if ( ! 
$sem_ver || 'patch' !== $sem_ver ) {
                        continue;
                    }
                    
$update $offer;
                    break;
                }
                if ( empty( 
$update ) ) {
                    
WP_CLI::success'WordPress is at the latest minor release.' );
                    return;
                }
            } else {
                if ( ! empty( 
$from_api->updates ) ) {
                    list( 
$update ) = $from_api->updates;
                }
            }
        } elseif ( 
Utils\wp_version_compare$assoc_args['version'], '<' )
            || 
'nightly' === $assoc_args['version']
            || 
Utils\get_flag_value$assoc_args'force' ) ) {

            
// Specific version is given
            
$version $assoc_args['version'];
            
$locale  Utils\get_flag_value$assoc_args'locale'get_locale() );

            
$new_package $this->get_download_url$version$locale );

            
$update = (object) [
                
'response' => 'upgrade',
                
'current'  => $assoc_args['version'],
                
'download' => $new_package,
                
'packages' => (object) [
                    
'partial'     => null,
                    
'new_bundled' => null,
                    
'no_content'  => null,
                    
'full'        => $new_package,
                ],
                
'version'  => $version,
                
'locale'   => $locale,
            ];

        }

        if ( ! empty( 
$update )
            && ( 
$update->version !== $wp_version
            
|| Utils\get_flag_value$assoc_args'force' ) ) ) {

            require_once 
ABSPATH 'wp-admin/includes/upgrade.php';

            if ( 
$update->version ) {
                
WP_CLI::log"Updating to version {$update->version} ({$update->locale})..." );
            } else {
                
WP_CLI::log'Starting update...' );
            }

            
$from_version $wp_version;
            
$insecure     = (bool) Utils\get_flag_value$assoc_args'insecure'false );

            
$GLOBALS['wpcli_core_update_obj'] = $update;
            
$result                           Utils\get_upgrader$upgrader$insecure )->upgrade$update );
            unset( 
$GLOBALS['wpcli_core_update_obj'] );

            if ( 
is_wp_error$result ) ) {
                
$message WP_CLI::error_to_string$result );
                if ( 
'up_to_date' !== $result->get_error_code() ) {
                    
WP_CLI::error$message );
                } else {
                    
WP_CLI::success$message );
                }
            } else {

                
$to_version '';
                if ( 
file_existsABSPATH 'wp-includes/version.php' ) ) {
                    
$wp_details self::get_wp_details();
                    
$to_version $wp_details['wp_version'];
                }

                
$locale = (string) Utils\get_flag_value$assoc_args'locale'get_locale() );
                
$this->cleanup_extra_files$from_version$to_version$locale$insecure );

                
WP_CLI::success'WordPress updated successfully.' );
            }
        } else {
            
WP_CLI::success'WordPress is up to date.' );
        }
    }

    
/**
     * Runs the WordPress database update procedure.
     *
     * [--network]
     * : Update databases for all sites on a network
     *
     * [--dry-run]
     * : Compare database versions without performing the update.
     *
     * ## EXAMPLES
     *
     *     # Update the WordPress database
     *     $ wp core update-db
     *     Success: WordPress database upgraded successfully from db version 36686 to 35700.
     *
     *     # Update databases for all sites on a network
     *     $ wp core update-db --network
     *     WordPress database upgraded successfully from db version 35700 to 29630 on example.com/
     *     Success: WordPress database upgraded on 123/123 sites
     *
     * @subcommand update-db
     */
    
public function update_db$args$assoc_args ) {
        global 
$wpdb$wp_db_version$wp_current_db_version;

        
$network Utils\get_flag_value$assoc_args'network' );
        if ( 
$network && ! is_multisite() ) {
            
WP_CLI::error'This is not a multisite installation.' );
        }

        
$dry_run Utils\get_flag_value$assoc_args'dry-run' );
        if ( 
$dry_run ) {
            
WP_CLI::log'Performing a dry run, with no database modification.' );
        }

        if ( 
$network ) {
            
$iterator_args = [
                
'table' => $wpdb->blogs,
                
'where' => [
                    
'spam'     => 0,
                    
'deleted'  => 0,
                    
'archived' => 0,
                ],
            ];
            
$it            = new TableIterator$iterator_args );
            
$success       0;
            
$total         0;
            
$site_ids      = [];
            foreach ( 
$it as $blog ) {
                
$total++;
                
$site_ids[] = $blog->site_id;
                
$url        $blog->domain $blog->path;
                
$cmd        "--url={$url} core update-db";
                if ( 
$dry_run ) {
                    
$cmd .= ' --dry-run';
                }
                
$process WP_CLI::runcommand(
                    
$cmd,
                    [
                        
'return'     => 'all',
                        
'exit_error' => false,
                    ]
                );
                if ( 
=== (int) $process->return_code ) {
                    
// See if we can parse the stdout
                    
if ( preg_match'#Success: (.+)#'$process->stdout$matches ) ) {
                        
$message rtrim$matches[1], '.' );
                        
$message "{$message} on {$url}";
                    } else {
                        
$message "Database upgraded successfully on {$url}";
                    }
                    
WP_CLI::log$message );
                    
$success++;
                } else {
                    
WP_CLI::warning"Database failed to upgrade on {$url});
                }
            }
            if ( ! 
$dry_run && $total && $success === $total ) {
                foreach ( 
array_unique$site_ids ) as $site_id ) {
                    
update_metadata'site'$site_id'wpmu_upgrade_site'$wp_db_version );
                }
            }
            
WP_CLI::success"WordPress database upgraded on {$success}/{$total} sites." );
        } else {
            require_once 
ABSPATH 'wp-admin/includes/upgrade.php';
            
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Replacing WP Core behavior is the goal here.
            
$wp_current_db_version = (int) __get_option'db_version' );
            if ( 
$wp_db_version !== $wp_current_db_version ) {
                if ( 
$dry_run ) {
                    
WP_CLI::success"WordPress database will be upgraded from db version {$wp_current_db_version} to {$wp_db_version}." );
                } else {
                    
// WP upgrade isn't too fussy about generating MySQL warnings such as "Duplicate key name" during an upgrade so suppress.
                    
$wpdb->suppress_errors();

                    
// WP upgrade expects `$_SERVER['HTTP_HOST']` to be set in `wp_guess_url()`, otherwise get PHP notice.
                    
if ( ! isset( $_SERVER['HTTP_HOST'] ) ) {
                        
$_SERVER['HTTP_HOST'] = 'http://example.com';
                    }

                    
wp_upgrade();

                    
WP_CLI::success"WordPress database upgraded successfully from db version {$wp_current_db_version} to {$wp_db_version}." );
                }
            } else {
                
WP_CLI::success"WordPress database already at latest db version {$wp_db_version}." );
            }
        }
    }

    
/**
     * Gets download url based on version, locale and desired file type.
     *
     * @param $version
     * @param string $locale
     * @param string $file_type
     * @return string
     */
    
private function get_download_url$version$locale 'en_US'$file_type 'zip' ) {

        if ( 
'nightly' === $version ) {
            if ( 
'zip' === $file_type ) {
                return 
'https://wordpress.org/nightly-builds/wordpress-latest.zip';
            } else {
                
WP_CLI::error'Nightly builds are only available in .zip format.' );
            }
        }

        
$locale_subdomain 'en_US' === $locale '' substr$locale0) . '.';
        
$locale_suffix    'en_US' === $locale '' "-{$locale}";

        return 
"https://{$locale_subdomain}wordpress.org/wordpress-{$version}{$locale_suffix}.{$file_type}";
    }

    
/**
     * Returns update information.
     */
    
private function get_updates$assoc_args ) {
        
wp_version_check();
        
$from_api get_site_transient'update_core' );
        if ( ! 
$from_api ) {
            return [];
        }

        
$compare_version str_replace'-src'''$GLOBALS['wp_version'] );

        
$updates = [
            
'major' => false,
            
'minor' => false,
        ];
        foreach ( 
$from_api->updates as $offer ) {

            
$update_type Utils\get_named_sem_ver$offer->version$compare_version );
            if ( ! 
$update_type ) {
                continue;
            }

            
// WordPress follow its own versioning which is roughly equivalent to semver
            
if ( 'minor' === $update_type ) {
                
$update_type 'major';
            } elseif ( 
'patch' === $update_type ) {
                
$update_type 'minor';
            }

            if ( ! empty( 
$updates$update_type ] ) && ! Comparator::greaterThan$offer->version$updates$update_type ]['version'] ) ) {
                continue;
            }

            
$updates$update_type ] = [
                
'version'     => $offer->version,
                
'update_type' => $update_type,
                
'package_url' => ! empty( $offer->packages->partial ) ? $offer->packages->partial $offer->packages->full,
            ];
        }

        foreach ( 
$updates as $type => $value ) {
            if ( empty( 
$value ) ) {
                unset( 
$updates$type ] );
            }
        }

        foreach ( [ 
'major''minor' ] as $type ) {
            if ( 
true === Utils\get_flag_value$assoc_args$type ) ) {
                return ! empty( 
$updates$type ] )
                    ? [ 
$updates$type ] ]
                    : 
false;
            }
        }
        return 
array_values$updates );
    }

    
/**
     * Clean up extra files.
     *
     * @param string $version_from Starting version that the installation was updated from.
     * @param string $version_to   Target version that the installation is updated to.
     * @param string $locale       Locale of the installation.
     * @param bool   $insecure     Whether to retry without certificate validation on TLS handshake failure.
     */
    
private function cleanup_extra_files$version_from$version_to$locale$insecure ) {
        if ( ! 
$version_from || ! $version_to ) {
            
WP_CLI::warning'Failed to find WordPress version. Please cleanup files manually.' );
            return;
        }

        
$old_checksums self::get_core_checksums$version_from$locale ?: 'en_US'$insecure );
        if ( ! 
is_array$old_checksums ) ) {
            
WP_CLI::warning"{$old_checksums} Please cleanup files manually." );
            return;
        }
        
$new_checksums self::get_core_checksums$version_to$locale ?: 'en_US'$insecure );
        if ( ! 
is_array$new_checksums ) ) {
            
WP_CLI::warning"{$new_checksums} Please cleanup files manually." );
            return;
        }

        
// Compare the files from the old version and the new version in a case-insensitive manner,
        // to prevent files being incorrectly deleted on systems with case-insensitive filesystems
        // when core changes the case of filenames.
        // The main logic for this was taken from the Joomla project and adapted for WP.
        // See: https://github.com/joomla/joomla-cms/blob/bb5368c7ef9c20270e6e9fcc4b364cd0849082a5/administrator/components/com_admin/script.php#L8158

        
$old_filepaths array_keys$old_checksums );
        
$new_filepaths array_keys$new_checksums );

        
$new_filepaths array_combinearray_map'strtolower'$new_filepaths ), $new_filepaths );

        
$old_filepaths_to_check array_diff$old_filepaths$new_filepaths );

        foreach ( 
$old_filepaths_to_check as $old_filepath_to_check ) {
            
$old_realpath realpathABSPATH $old_filepath_to_check );

            
// On Unix without incorrectly cased file.
            
if ( false === $old_realpath ) {
                continue;
            }

            
$lowercase_old_filepath_to_check strtolower$old_filepath_to_check );

            if ( ! 
array_key_exists$lowercase_old_filepath_to_check$new_filepaths ) ) {
                
$files_to_remove[] = $old_filepath_to_check;
                continue;
            }

            
// We are now left with only the files that are similar from old to new except for their case.

            
$old_basename      basename$old_realpath );
            
$new_filepath      $new_filepaths$lowercase_old_filepath_to_check ];
            
$expected_basename basename$new_filepath );
            
$new_realpath      realpathABSPATH $new_filepath );
            
$new_basename      basename$new_realpath );

            
// On Windows or Unix with only the incorrectly cased file.
            
if ( $new_basename !== $expected_basename ) {
                
WP_CLI::debug"Renaming file '{$old_filepath_to_check}' => '{$new_filepath}'"'core' );

                
renameABSPATH $old_filepath_to_checkABSPATH $old_filepath_to_check '.tmp' );
                
renameABSPATH $old_filepath_to_check '.tmp'ABSPATH $new_filepath );

                continue;
            }

            
// There might still be an incorrectly cased file on other OS than Windows.
            
if ( basename$old_filepath_to_check ) === $old_basename ) {
                
// Check if case-insensitive file system, eg on OSX.
                
if ( fileinode$old_realpath ) === fileinode$new_realpath ) ) {
                    
// Check deeper because even realpath or glob might not return the actual case.
                    
if ( ! in_array$expected_basenamescandirdirname$new_realpath ) ), true ) ) {
                        
WP_CLI::debug"Renaming file '{$old_filepath_to_check}' => '{$new_filepath}'"'core' );

                        
renameABSPATH $old_filepath_to_checkABSPATH $old_filepath_to_check '.tmp' );
                        
renameABSPATH $old_filepath_to_check '.tmp'ABSPATH $new_filepath );
                    }
                } else {
                    
// On Unix with both files: Delete the incorrectly cased file.
                    
$files_to_remove[] = $old_filepath_to_check;
                }
            }
        }

        if ( ! empty( 
$files_to_remove ) ) {
            
WP_CLI::log'Cleaning up files...' );

            
$count 0;
            foreach ( 
$files_to_remove as $file ) {

                
// wp-content should be considered user data
                
if ( === stripos$file'wp-content' ) ) {
                    continue;
                }

                if ( 
file_existsABSPATH $file ) ) {
                    
unlinkABSPATH $file );
                    
WP_CLI::log"File removed: {$file});
                    
$count++;
                }
            }

            if ( 
$count ) {
                
WP_CLI::lognumber_format$count ) . ' files cleaned up.' );
            } else {
                
WP_CLI::log'No files found that need cleaning up.' );
            }
        }
    }

    private static function 
strip_content_dir$zip_file ) {
        
$new_zip_file Utils\get_temp_dir() . uniqid'wp_' ) . '.zip';
        
register_shutdown_function(
            function () use ( 
$new_zip_file ) {
                if ( 
file_exists$new_zip_file ) ) {
                    
unlink$new_zip_file );
                }
            }
        );
        
// Duplicate file to avoid modifying the original, which could be cache.
        
if ( ! copy$zip_file$new_zip_file ) ) {
            
WP_CLI::error'Failed to copy ZIP file.' );
        }
        
$zip = new ZipArchive();
        
$res $zip->open$new_zip_file );
        if ( 
true === $res ) {
            
// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
            
for ( $i 0$i $zip->numFiles$i++ ) {
                
$info $zip->statIndex$i );
                if ( 
false !== stripos$info['name'], 'wp-content/' ) ) {
                    
$zip->deleteIndex$i );
                }
            }
            
$zip->close();
            return 
$new_zip_file;
        } else {
            
WP_CLI::error'ZipArchive failed to open ZIP file.' );
        }
    }

}
<?
php

/**
 * Adds, removes, fetches, and flushes the WP Object Cache object.
 *
 * By default, the WP Object Cache exists in PHP memory for the length of the
 * request (and is emptied at the end). Use a persistent object cache drop-in
 * to persist the object cache between requests.
 *
 * [Read the codex article](https://codex.wordpress.org/Class_Reference/WP_Object_Cache)
 * for more detail.
 *
 * ## EXAMPLES
 *
 *     # Set cache.
 *     $ wp cache set my_key my_value my_group 300
 *     Success: Set object 'my_key' in group 'my_group'.
 *
 *     # Get cache.
 *     $ wp cache get my_key my_group
 *     my_value
 *
 * @package wp-cli
 */
class Cache_Command extends WP_CLI_Command {

    
/**
     * Adds a value to the object cache.
     *
     * Errors if a value already exists for the key, which means the value can't
     * be added.
     *
     * ## OPTIONS
     *
     * <key>
     * : Cache key.
     *
     * <value>
     * : Value to add to the key.
     *
     * [<group>]
     * : Method for grouping data within the cache which allows the same key to be used across groups.
     * ---
     * default: default
     * ---
     *
     * [<expiration>]
     * : Define how long to keep the value, in seconds. `0` means as long as possible.
     * ---
     * default: 0
     * ---
     *
     * ## EXAMPLES
     *
     *     # Add cache.
     *     $ wp cache add my_key my_group my_value 300
     *     Success: Added object 'my_key' in group 'my_value'.
     */
    
public function add$args$assoc_args ) {
        list( 
$key$value$group$expiration ) = $args;

        if ( ! 
wp_cache_add$key$value$group$expiration ) ) {
            
WP_CLI::error"Could not add object '$key' in group '$group'. Does it already exist?" );
        }

        
WP_CLI::success"Added object '$key' in group '$group'." );
    }

    
/**
     * Decrements a value in the object cache.
     *
     * Errors if the value can't be decremented.
     *
     * ## OPTIONS
     *
     * <key>
     * : Cache key.
     *
     * [<offset>]
     * : The amount by which to decrement the item's value.
     * ---
     * default: 1
     * ---
     *
     * [<group>]
     * : Method for grouping data within the cache which allows the same key to be used across groups.
     * ---
     * default: default
     * ---
     *
     * ## EXAMPLES
     *
     *     # Decrease cache value.
     *     $ wp cache decr my_key 2 my_group
     *     48
     */
    
public function decr$args$assoc_args ) {
        list( 
$key$offset$group ) = $args;
        
$value wp_cache_decr$key$offset$group );

        if ( 
false === $value ) {
            
WP_CLI::error'The value was not decremented.' );
        }

        
WP_CLI::print_value$value$assoc_args );
    }

    
/**
     * Removes a value from the object cache.
     *
     * Errors if the value can't be deleted.
     *
     * ## OPTIONS
     *
     * <key>
     * : Cache key.
     *
     * [<group>]
     * : Method for grouping data within the cache which allows the same key to be used across groups.
     * ---
     * default: default
     * ---
     *
     * ## EXAMPLES
     *
     *     # Delete cache.
     *     $ wp cache delete my_key my_group
     *     Success: Object deleted.
     */
    
public function delete$args$assoc_args ) {
        list( 
$key$group ) = $args;
        
$result              wp_cache_delete$key$group );

        if ( 
false === $result ) {
            
WP_CLI::error'The object was not deleted.' );
        }

        
WP_CLI::success'Object deleted.' );
    }

    
/**
     * Flushes the object cache.
     *
     * For WordPress multisite instances using a persistent object cache,
     * flushing the object cache will typically flush the cache for all sites.
     * Beware of the performance impact when flushing the object cache in
     * production.
     *
     * Errors if the object cache can't be flushed.
     *
     * ## EXAMPLES
     *
     *     # Flush cache.
     *     $ wp cache flush
     *     Success: The cache was flushed.
     */
    
public function flush$args$assoc_args ) {
        
$value wp_cache_flush();

        if ( 
false === $value ) {
            
WP_CLI::error'The object cache could not be flushed.' );
        }

        
WP_CLI::success'The cache was flushed.' );
    }

    
/**
     * Gets a value from the object cache.
     *
     * Errors if the value doesn't exist.
     *
     * ## OPTIONS
     *
     * <key>
     * : Cache key.
     *
     * [<group>]
     * : Method for grouping data within the cache which allows the same key to be used across groups.
     * ---
     * default: default
     * ---
     *
     * ## EXAMPLES
     *
     *     # Get cache.
     *     $ wp cache get my_key my_group
     *     my_value
     */
    
public function get$args$assoc_args ) {
        list( 
$key$group ) = $args;
        
$value               wp_cache_get$key$group );

        if ( 
false === $value ) {
            
WP_CLI::error"Object with key '$key' and group '$group' not found." );
        }

        
WP_CLI::print_value$value$assoc_args );
    }

    
/**
     * Increments a value in the object cache.
     *
     * Errors if the value can't be incremented.
     *
     * ## OPTIONS
     *
     * <key>
     * : Cache key.
     *
     * [<offset>]
     * : The amount by which to increment the item's value.
     * ---
     * default: 1
     * ---
     *
     * [<group>]
     * : Method for grouping data within the cache which allows the same key to be used across groups.
     * ---
     * default: default
     * ---
     *
     * ## EXAMPLES
     *
     *     # Increase cache value.
     *     $ wp cache incr my_key 2 my_group
     *     50
     */
    
public function incr$args$assoc_args ) {
        list( 
$key$offset$group ) = $args;
        
$value wp_cache_incr$key$offset$group );

        if ( 
false === $value ) {
            
WP_CLI::error'The value was not incremented.' );
        }

        
WP_CLI::print_value$value$assoc_args );
    }

    
/**
     * Replaces a value in the object cache, if the value already exists.
     *
     * Errors if the value can't be replaced.
     *
     * ## OPTIONS
     *
     * <key>
     * : Cache key.
     *
     * <value>
     * : Value to replace.
     *
     * [<group>]
     * : Method for grouping data within the cache which allows the same key to be used across groups.
     * ---
     * default: default
     * ---
     *
     * [<expiration>]
     * : Define how long to keep the value, in seconds. `0` means as long as possible.
     * ---
     * default: 0
     * ---
     *
     * ## EXAMPLES
     *
     *     # Replace cache.
     *     $ wp cache replace my_key new_value my_group
     *     Success: Replaced object 'my_key' in group 'my_group'.
     */
    
public function replace$args$assoc_args ) {
        list( 
$key$value$group$expiration ) = $args;
        
$result wp_cache_replace$key$value$group$expiration );

        if ( 
false === $result ) {
            
WP_CLI::error"Could not replace object '$key' in group '$group'. Does it not exist?" );
        }

        
WP_CLI::success"Replaced object '$key' in group '$group'." );
    }

    
/**
     * Sets a value to the object cache, regardless of whether it already exists.
     *
     * Errors if the value can't be set.
     *
     * ## OPTIONS
     *
     * <key>
     * : Cache key.
     *
     * <value>
     * : Value to set on the key.
     *
     * [<group>]
     * : Method for grouping data within the cache which allows the same key to be used across groups.
     * ---
     * default: default
     * ---
     *
     * [<expiration>]
     * : Define how long to keep the value, in seconds. `0` means as long as possible.
     * ---
     * default: 0
     * ---
     *
     * ## EXAMPLES
     *
     *     # Set cache.
     *     $ wp cache set my_key my_value my_group 300
     *     Success: Set object 'my_key' in group 'my_group'.
     */
    
public function set$args$assoc_args ) {
        list( 
$key$value$group$expiration ) = $args;
        
$result wp_cache_set$key$value$group$expiration );

        if ( 
false === $result ) {
            
WP_CLI::error"Could not add object '$key' in group '$group'." );
        }

        
WP_CLI::success"Set object '$key' in group '$group'." );
    }

    
/**
     * Attempts to determine which object cache is being used.
     *
     * Note that the guesses made by this function are based on the
     * WP_Object_Cache classes that define the 3rd party object cache extension.
     * Changes to those classes could render problems with this function's
     * ability to determine which object cache is being used.
     *
     * ## EXAMPLES
     *
     *     # Check cache type.
     *     $ wp cache type
     *     Default
     */
    
public function type$args$assoc_args ) {
        
$message WP_CLI\Utils\wp_get_cache_type();
        
WP_CLI::line$message );
    }

}
<?
php

use WP_CLI\Utils;

/**
 * Adds, gets, and deletes entries in the WordPress Transient Cache.
 *
 * By default, the transient cache uses the WordPress database to persist values
 * between requests. On a single site installation, values are stored in the
 * `wp_options` table. On a multisite installation, values are stored in the
 * `wp_options` or the `wp_sitemeta` table, depending on use of the `--network`
 * flag.
 *
 * When a persistent object cache drop-in is installed (e.g. Redis or Memcached),
 * the transient cache skips the database and simply wraps the WP Object Cache.
 *
 * ## EXAMPLES
 *
 *     # Set transient.
 *     $ wp transient set sample_key "test data" 3600
 *     Success: Transient added.
 *
 *     # Get transient.
 *     $ wp transient get sample_key
 *     test data
 *
 *     # Delete transient.
 *     $ wp transient delete sample_key
 *     Success: Transient deleted.
 *
 *     # Delete expired transients.
 *     $ wp transient delete --expired
 *     Success: 12 expired transients deleted from the database.
 *
 *     # Delete all transients.
 *     $ wp transient delete --all
 *     Success: 14 transients deleted from the database.
 */
class Transient_Command extends WP_CLI_Command {

    
/**
     * Gets a transient value.
     *
     * For a more complete explanation of the transient cache, including the
     * network|site cache, please see docs for `wp transient`.
     *
     * ## OPTIONS
     *
     * <key>
     * : Key for the transient.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - yaml
     * ---
     *
     * [--network]
     * : Get the value of a network|site transient. On single site, this is
     * is a specially-named cache key. On multisite, this is a global cache
     * (instead of local to the site).
     *
     * ## EXAMPLES
     *
     *     $ wp transient get sample_key
     *     test data
     *
     *     $ wp transient get random_key
     *     Warning: Transient with key "random_key" is not set.
     */
    
public function get$args$assoc_args ) {
        list( 
$key ) = $args;

        
$func  Utils\get_flag_value$assoc_args'network' ) ? 'get_site_transient' 'get_transient';
        
$value $func$key );

        if ( 
false === $value ) {
            
WP_CLI::warning'Transient with key "' $key '" is not set.' );
            exit;
        }

        
WP_CLI::print_value$value$assoc_args );
    }

    
/**
     * Sets a transient value.
     *
     * `<expiration>` is the time until expiration, in seconds.
     *
     * For a more complete explanation of the transient cache, including the
     * network|site cache, please see docs for `wp transient`.
     *
     * ## OPTIONS
     *
     * <key>
     * : Key for the transient.
     *
     * <value>
     * : Value to be set for the transient.
     *
     * [<expiration>]
     * : Time until expiration, in seconds.
     *
     * [--network]
     * : Set the value of a network|site transient. On single site, this is
     * is a specially-named cache key. On multisite, this is a global cache
     * (instead of local to the site).
     *
     * ## EXAMPLES
     *
     *     $ wp transient set sample_key "test data" 3600
     *     Success: Transient added.
     */
    
public function set$args$assoc_args ) {
        list( 
$key$value ) = $args;

        
$expiration Utils\get_flag_value$args2);

        
$func Utils\get_flag_value$assoc_args'network' ) ? 'set_site_transient' 'set_transient';
        if ( 
$func$key$value$expiration ) ) {
            
WP_CLI::success'Transient added.' );
        } else {
            
WP_CLI::error'Transient could not be set.' );
        }
    }

    
/**
     * Deletes a transient value.
     *
     * For a more complete explanation of the transient cache, including the
     * network|site cache, please see docs for `wp transient`.
     *
     * ## OPTIONS
     *
     * [<key>]
     * : Key for the transient.
     *
     * [--network]
     * : Delete the value of a network|site transient. On single site, this is
     * is a specially-named cache key. On multisite, this is a global cache
     * (instead of local to the site).
     *
     * [--all]
     * : Delete all transients.
     *
     * [--expired]
     * : Delete all expired transients.
     *
     * ## EXAMPLES
     *
     *     # Delete transient.
     *     $ wp transient delete sample_key
     *     Success: Transient deleted.
     *
     *     # Delete expired transients.
     *     $ wp transient delete --expired
     *     Success: 12 expired transients deleted from the database.
     *
     *     # Delete expired site transients.
     *     $ wp transient delete --expired --network
     *     Success: 1 expired transient deleted from the database.
     *
     *     # Delete all transients.
     *     $ wp transient delete --all
     *     Success: 14 transients deleted from the database.
     *
     *     # Delete all site transients.
     *     $ wp transient delete --all --network
     *     Success: 2 transients deleted from the database.
     *
     *     # Delete all transients in a multsite.
     *     $ wp transient delete --all --network && wp site list --field=url | xargs -n1 -I % wp --url=% transient delete --all
     */
    
public function delete$args$assoc_args ) {
        
$key = ( ! empty( $args ) ) ? $args[0] : null;

        
$all     Utils\get_flag_value$assoc_args'all' );
        
$expired Utils\get_flag_value$assoc_args'expired' );
        
$network Utils\get_flag_value$assoc_args'network' );

        if ( 
true === $all ) {
            
$this->delete_all$network );
            return;
        } elseif ( 
true === $expired ) {
            
$this->delete_expired$network );
            return;
        }

        if ( ! 
$key ) {
            
WP_CLI::error'Please specify transient key, or use --all or --expired.' );
        }

        
$func $network 'delete_site_transient' 'delete_transient';

        if ( 
$func$key ) ) {
            
WP_CLI::success'Transient deleted.' );
        } else {
            
$func Utils\get_flag_value$assoc_args'network' ) ? 'get_site_transient' 'get_transient';
            if ( 
$func$key ) ) {
                
WP_CLI::error'Transient was not deleted even though the transient appears to exist.' );
            } else {
                
WP_CLI::warning'Transient was not deleted; however, the transient does not appear to exist.' );
            }
        }
    }

    
/**
     * Determines the type of transients implementation.
     *
     * Indicates whether the transients API is using an object cache or the
     * database.
     *
     * For a more complete explanation of the transient cache, including the
     * network|site cache, please see docs for `wp transient`.
     *
     * ## EXAMPLES
     *
     *     $ wp transient type
     *     Transients are saved to the database.
     */
    
public function type() {
        if ( 
wp_using_ext_object_cache() ) {
            
$message 'Transients are saved to the object cache.';
        } else {
            
$message 'Transients are saved to the database.';
        }

        
WP_CLI::line$message );
    }

    
/**
     * Lists transients and their values.
     *
     * ## OPTIONS
     *
     * [--search=<pattern>]
     * : Use wildcards ( * and ? ) to match transient name.
     *
     * [--exclude=<pattern>]
     * : Pattern to exclude. Use wildcards ( * and ? ) to match transient name.
     *
     * [--network]
     * : Get the values of network|site transients. On single site, this is
     * a specially-named cache key. On multisite, this is a global cache
     * (instead of local to the site).
     *
     * [--unserialize]
     * : Unserialize transient values in output.
     *
     * [--human-readable]
     * : Human-readable output for expirations.
     *
     * [--fields=<fields>]
     * : Limit the output to specific object fields.
     *
     * [--format=<format>]
     * : The serialization format for the value.
     * ---
     * default: table
     * options:
     *   - table
     *   - json
     *   - csv
     *   - count
     *   - yaml
     * ---
     *
     * ## AVAILABLE FIELDS
     *
     * This field will be displayed by default for each matching option:
     *
     * * name
     * * value
     * * expiration
     *
     * ## EXAMPLES
     *
     *     # List all transients
     *     $ wp transient list
    *      +------+-------+---------------+
    *      | name | value | expiration    |
    *      +------+-------+---------------+
    *      | foo  | bar   | 39 mins       |
    *      | foo2 | bar2  | no expiration |
    *      | foo3 | bar2  | expired       |
    *      | foo4 | bar4  | 4 hours       |
    *      +------+-------+---------------+
     *
     * @subcommand list
     */
    
public function list_$args$assoc_args ) {
        global 
$wpdb;

        
$network        Utils\get_flag_value$assoc_args'network'false );
        
$unserialize    Utils\get_flag_value$assoc_args'unserialize'false );
        
$human_readable Utils\get_flag_value$assoc_args'human-readable'false );

        
$fields = array( 'name''value''expiration' );
        if ( isset( 
$assoc_args['fields'] ) ) {
            
$fields explode','$assoc_args['fields'] );
        }

        
$pattern '%';
        
$exclude '';
        if ( isset( 
$assoc_args['search'] ) ) {
            
$pattern Utils\esc_like$assoc_args['search'] );
            
// Substitute wildcards.
            
$pattern str_replace(
                array( 
'*''?' ),
                array( 
'%''_' ),
                
$pattern
            
);
        }
        if ( isset( 
$assoc_args['exclude'] ) ) {
            
$exclude Utils\esc_like$assoc_args['exclude'] );
            
// Substitute wildcards.
            
$exclude str_replace(
                array( 
'*''?' ),
                array( 
'%''_' ),
                
$exclude
            
);
        }

        if ( 
$network ) {
            if ( 
is_multisite() ) {
                
$where  $wpdb->prepare(
                    
'WHERE `meta_key` LIKE %s',
                    
Utils\esc_like'_site_transient_' ) . $pattern
                
);
                
$where .= $wpdb->prepare(
                    
' AND meta_key NOT LIKE %s',
                    
Utils\esc_like'_site_transient_timeout_' ) . '%'
                
);
                if ( 
$exclude ) {
                    
$where .= $wpdb->prepare(
                        
' AND meta_key NOT LIKE %s',
                        
Utils\esc_like'_site_transient_' ) . $exclude
                    
);
                }

                
$query "SELECT `meta_key` as `name`, `meta_value` as `value` FROM {$wpdb->sitemeta} {$where}";
            } else {
                
$where  $wpdb->prepare(
                    
'WHERE `option_name` LIKE %s',
                    
Utils\esc_like'_site_transient_' ) . $pattern
                
);
                
$where .= $wpdb->prepare(
                    
' AND option_name NOT LIKE %s',
                    
Utils\esc_like'_site_transient_timeout_' ) . '%'
                
);
                if ( 
$exclude ) {
                    
$where .= $wpdb->prepare(
                        
' AND option_name NOT LIKE %s',
                        
Utils\esc_like'_site_transient_' ) . $exclude
                    
);
                }

                
$query "SELECT `option_name` as `name`, `option_value` as `value` FROM {$wpdb->options} {$where}";
            }
        } else {
            
$where  $wpdb->prepare(
                
'WHERE `option_name` LIKE %s',
                
Utils\esc_like'_transient_' ) . $pattern
            
);
            
$where .= $wpdb->prepare(
                
' AND option_name NOT LIKE %s',
                
Utils\esc_like'_transient_timeout_' ) . '%'
            
);
            if ( 
$exclude ) {
                
$where .= $wpdb->prepare(
                    
' AND option_name NOT LIKE %s',
                    
Utils\esc_like'_transient_' ) . $exclude
                
);
            }

            
$query "SELECT `option_name` as `name`, `option_value` as `value` FROM {$wpdb->options} {$where}";
        }

        
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Prepared properly above.
        
$results $wpdb->get_results$query );

        foreach ( 
$results as $result ) {
            
$result->name       str_replace( array( '_site_transient_''_transient_' ), ''$result->name );
            
$result->expiration $this->get_transient_expiration$result->name$network$human_readable );

            if ( 
$unserialize ) {
                
$result->value maybe_unserialize$result->value );
            }
        }

        
$formatter = new \WP_CLI\Formatter(
            
$assoc_args,
            
$fields
        
);
        
$formatter->display_items$results );
    }

    
/**
     * Retrieves the expiration time.
     *
     * @param string $name              Transient name.
     * @param bool   $is_site_transient Optional. Whether this is a site transient. Default false.
     * @param bool   $human_readable    Optional. Whether to return the difference between now and the
     *                                  expiration time in a human-readable format. Default false.
     * @return string Expiration time string.
     */
    
private function get_transient_expiration$name$is_site_transient false$human_readable false ) {
        if ( 
$is_site_transient ) {
            if ( 
is_multisite() ) {
                
$expiration = (int) get_site_option'_site_transient_timeout_' $name );
            } else {
                
$expiration = (int) get_option'_site_transient_timeout_' $name );
            }
        } else {
            
$expiration = (int) get_option'_transient_timeout_' $name );
        }

        if ( 
=== $expiration ) {
            return 
$human_readable 'never expires' 'false';
        }

        if ( ! 
$human_readable ) {
            return 
$expiration;
        }

        
$now time();

        if ( 
$now $expiration ) {
            return 
'expired';
        }

        return 
human_time_diff$now$expiration );
    }

    
/**
     * Deletes all expired transients.
     *
     * Only deletes the expired transients from the database.
     *
     * @param bool $network Whether to delete transients or network|site transients.
     */
    
private function delete_expired$network ) {
        global 
$wpdb;

        
$count 0;

        if ( ! 
$network ) {
            
$count += $wpdb->query(
                
$wpdb->prepare(
                    
"DELETE a, b FROM {$wpdb->options} a, {$wpdb->options} b
                        WHERE a.option_name LIKE %s
                        AND a.option_name NOT LIKE %s
                        AND b.option_name = CONCAT( '_transient_timeout_', SUBSTRING( a.option_name, 12 ) )
                        AND b.option_value < %d"
,
                    
Utils\esc_like'_transient_' ) . '%',
                    
Utils\esc_like'_transient_timeout_' ) . '%',
                    
time()
                )
            );
        } else {
            if ( ! 
is_multisite() ) {
                
// Non-Multisite stores site transients in the options table.
                
$count += $wpdb->query(
                    
$wpdb->prepare(
                        
"DELETE a, b FROM {$wpdb->options} a, {$wpdb->options} b
                            WHERE a.option_name LIKE %s
                            AND a.option_name NOT LIKE %s
                            AND b.option_name = CONCAT( '_site_transient_timeout_', SUBSTRING( a.option_name, 17 ) )
                            AND b.option_value < %d"
,
                        
Utils\esc_like'_site_transient_' ) . '%',
                        
Utils\esc_like'_site_transient_timeout_' ) . '%',
                        
time()
                    )
                );
            } else {
                
// Multisite stores site transients in the sitemeta table.
                
$count += $wpdb->query(
                    
$wpdb->prepare(
                        
"DELETE a, b FROM {$wpdb->sitemeta} a, {$wpdb->sitemeta} b
                            WHERE a.meta_key LIKE %s
                            AND a.meta_key NOT LIKE %s
                            AND b.meta_key = CONCAT( '_site_transient_timeout_', SUBSTRING( a.meta_key, 17 ) )
                            AND b.meta_value < %d"
,
                        
Utils\esc_like'_site_transient_' ) . '%',
                        
Utils\esc_like'_site_transient_timeout_' ) . '%',
                        
time()
                    )
                );
            }
        }

        
// The above queries delete the transient and the transient timeout
        // thus each transient is counted twice.
        
$count $count 2;

        if ( 
$count ) {
            
WP_CLI::success(
                
sprintf(
                    
'%d expired %s deleted from the database.',
                    
$count,
                    
Utils\pluralize'transient'$count )
                )
            );
        } else {
            
WP_CLI::success'No expired transients found.' );
        }

        if ( 
wp_using_ext_object_cache() ) {
            
WP_CLI::warning'Transients are stored in an external object cache, and this command only deletes those stored in the database. You must flush the cache to delete all transients.' );
        }
    }

    
/**
     * Deletes all transients.
     *
     * Only deletes the transients from the database.
     *
     * @param bool $network Whether to delete transients or network|site transients.
     */
    
private function delete_all$network ) {
        global 
$wpdb;

        
// To ensure proper count values we first delete all transients with a timeout
        // and then the remaining transients without a timeout.
        
$count 0;

        if ( ! 
$network ) {
            
$deleted $wpdb->query(
                
$wpdb->prepare(
                    
"DELETE a, b FROM {$wpdb->options} a, {$wpdb->options} b
                        WHERE a.option_name LIKE %s
                        AND a.option_name NOT LIKE %s
                        AND b.option_name = CONCAT( '_transient_timeout_', SUBSTRING( a.option_name, 12 ) )"
,
                    
Utils\esc_like'_transient_' ) . '%',
                    
Utils\esc_like'_transient_timeout_' ) . '%'
                
)
            );

            
$count += $deleted 2// Ignore affected rows for timeouts.

            
$count += $wpdb->query(
                
$wpdb->prepare(
                    
"DELETE FROM $wpdb->options WHERE option_name LIKE %s",
                    
Utils\esc_like'_transient_' ) . '%'
                
)
            );
        } else {
            if ( ! 
is_multisite() ) {
                
// Non-Multisite stores site transients in the options table.
                
$deleted $wpdb->query(
                    
$wpdb->prepare(
                        
"DELETE a, b FROM {$wpdb->options} a, {$wpdb->options} b
                            WHERE a.option_name LIKE %s
                            AND a.option_name NOT LIKE %s
                            AND b.option_name = CONCAT( '_site_transient_timeout_', SUBSTRING( a.option_name, 17 ) )"
,
                        
Utils\esc_like'_site_transient_' ) . '%',
                        
Utils\esc_like'_site_transient_timeout_' ) . '%'
                    
)
                );

                
$count += $deleted 2// Ignore affected rows for timeouts.

                
$count += $wpdb->query(
                    
$wpdb->prepare(
                        
"DELETE FROM $wpdb->options WHERE option_name LIKE %s",
                        
Utils\esc_like'_site_transient_' ) . '%'
                    
)
                );
            } else {
                
// Multisite stores site transients in the sitemeta table.
                
$deleted $wpdb->query(
                    
$wpdb->prepare(
                        
"DELETE a, b FROM {$wpdb->sitemeta} a, {$wpdb->sitemeta} b
                            WHERE a.meta_key LIKE %s
                            AND a.meta_key NOT LIKE %s
                            AND b.meta_key = CONCAT( '_site_transient_timeout_', SUBSTRING( a.meta_key, 17 ) )"
,
                        
Utils\esc_like'_site_transient_' ) . '%',
                        
Utils\esc_like'_site_transient_timeout_' ) . '%'
                    
)
                );

                
$count += $deleted 2// Ignore affected rows for timeouts.

                
$count += $wpdb->query(
                    
$wpdb->prepare(
                        
"DELETE FROM $wpdb->sitemeta WHERE meta_key LIKE %s",
                        
Utils\esc_like'_site_transient_' ) . '%'
                    
)
                );
            }
        }

        if ( 
$count ) {
            
WP_CLI::success(
                
sprintf(
                    
'%d %s deleted from the database.',
                    
$count,
                    
Utils\pluralize'transient'$count )
                )
            );
        } else {
            
WP_CLI::success'No transients found.' );
        }

        if ( 
wp_using_ext_object_cache() ) {
            
WP_CLI::warning'Transients are stored in an external object cache, and this command only deletes those stored in the database. You must flush the cache to delete all transients.' );
        }
    }
}
<?
php

if ( ! class_exists'WP_CLI' ) ) {
    return;
}

$wpcli_cache_autoloader dirname__FILE__ ) . '/vendor/autoload.php';
if ( 
file_exists$wpcli_cache_autoloader ) ) {
    require_once 
$wpcli_cache_autoloader;
}

WP_CLI::add_command'cache''Cache_Command' );
WP_CLI::add_command'transient''Transient_Command' );
<?
php

use WP_CLI\Utils;

/**
 * Imports files as attachments, regenerates thumbnails, or lists registered image sizes.
 *
 * ## EXAMPLES
 *
 *     # Re-generate all thumbnails, without confirmation.
 *     $ wp media regenerate --yes
 *     Found 3 images to regenerate.
 *     1/3 Regenerated thumbnails for "Sydney Harbor Bridge" (ID 760).
 *     2/3 Regenerated thumbnails for "Boardwalk" (ID 757).
 *     3/3 Regenerated thumbnails for "Sunburst Over River" (ID 756).
 *     Success: Regenerated 3 of 3 images.
 *
 *     # Import a local image and set it to be the featured image for a post.
 *     $ wp media import ~/Downloads/image.png --post_id=123 --title="A downloaded picture" --featured_image
 *     Success: Imported file '/home/person/Downloads/image.png' as attachment ID 1753 and attached to post 123 as featured image.
 *
 *     # List all registered image sizes
 *     $ wp media image-size
 *     +---------------------------+-------+--------+-------+
 *     | name                      | width | height | crop  |
 *     +---------------------------+-------+--------+-------+
 *     | full                      |       |        | N/A   |
 *     | twentyfourteen-full-width | 1038  | 576    | hard  |
 *     | large                     | 1024  | 1024   | soft  |
 *     | medium_large              | 768   | 0      | soft  |
 *     | medium                    | 300   | 300    | soft  |
 *     | thumbnail                 | 150   | 150    | hard  |
 *     +---------------------------+-------+--------+-------+
 *
 * @package wp-cli
 */
class Media_Command extends WP_CLI_Command {

    
/**
     * Clear the WP object cache after this many regenerations/imports.
     *
     * @var integer
     */
    
const WP_CLEAR_OBJECT_CACHE_INTERVAL 500;

    
/**
     * Regenerates thumbnails for one or more attachments.
     *
     * ## OPTIONS
     *
     * [<attachment-id>...]
     * : One or more IDs of the attachments to regenerate.
     *
     * [--image_size=<image_size>]
     * : Name of the image size to regenerate. Only thumbnails of this image size will be regenerated, thumbnails of other image sizes will not.
     *
     * [--skip-delete]
     * : Skip deletion of the original thumbnails. If your thumbnails are linked from sources outside your control, it's likely best to leave them around. Defaults to false.
     *
     * [--only-missing]
     * : Only generate thumbnails for images missing image sizes.
     *
     * [--yes]
     * : Answer yes to the confirmation message. Confirmation only shows when no IDs passed as arguments.
     *
     * ## EXAMPLES
     *
     *     # Regenerate thumbnails for given attachment IDs.
     *     $ wp media regenerate 123 124 125
     *     Found 3 images to regenerate.
     *     1/3 Regenerated thumbnails for "Vertical Image" (ID 123).
     *     2/3 Regenerated thumbnails for "Horizontal Image" (ID 124).
     *     3/3 Regenerated thumbnails for "Beautiful Picture" (ID 125).
     *     Success: Regenerated 3 of 3 images.
     *
     *     # Regenerate all thumbnails, without confirmation.
     *     $ wp media regenerate --yes
     *     Found 3 images to regenerate.
     *     1/3 Regenerated thumbnails for "Sydney Harbor Bridge" (ID 760).
     *     2/3 Regenerated thumbnails for "Boardwalk" (ID 757).
     *     3/3 Regenerated thumbnails for "Sunburst Over River" (ID 756).
     *     Success: Regenerated 3 of 3 images.
     *
     *     # Re-generate all thumbnails that have IDs between 1000 and 2000.
     *     $ seq 1000 2000 | xargs wp media regenerate
     *     Found 4 images to regenerate.
     *     1/4 Regenerated thumbnails for "Vertical Featured Image" (ID 1027).
     *     2/4 Regenerated thumbnails for "Horizontal Featured Image" (ID 1022).
     *     3/4 Regenerated thumbnails for "Unicorn Wallpaper" (ID 1045).
     *     4/4 Regenerated thumbnails for "I Am Worth Loving Wallpaper" (ID 1023).
     *     Success: Regenerated 4 of 4 images.
     *
     *     # Re-generate only the thumbnails of "large" image size for all images.
     *     $ wp media regenerate --image_size=large
     *     Do you really want to regenerate the "large" image size for all images? [y/n] y
     *     Found 3 images to regenerate.
     *     1/3 Regenerated "large" thumbnail for "Sydney Harbor Bridge" (ID 760).
     *     2/3 No "large" thumbnail regeneration needed for "Boardwalk" (ID 757).
     *     3/3 Regenerated "large" thumbnail for "Sunburst Over River" (ID 756).
     *     Success: Regenerated 3 of 3 images.
     */
    
public function regenerate$args$assoc_args = array() ) {
        
$assoc_args wp_parse_args(
            
$assoc_args,
            [ 
'image_size' => '' ]
        );

        
$image_size $assoc_args['image_size'];
        if ( 
$image_size && ! in_array$image_sizeget_intermediate_image_sizes(), true ) ) {
            
WP_CLI::errorsprintf'Unknown image size "%s".'$image_size ) );
        }

        if ( empty( 
$args ) ) {
            if ( 
$image_size ) {
                
WP_CLI::confirmsprintf'Do you really want to regenerate the "%s" image size for all images?'$image_size ), $assoc_args );
            } else {
                
WP_CLI::confirm'Do you really want to regenerate all images?'$assoc_args );
            }
        }

        
$skip_delete  Utils\get_flag_value$assoc_args'skip-delete' );
        
$only_missing Utils\get_flag_value$assoc_args'only-missing' );
        if ( 
$only_missing ) {
            
$skip_delete true;
        }

        
$additional_mime_types = array();

        if ( 
Utils\wp_version_compare'4.7''>=' ) ) {
            
$additional_mime_types[] = 'application/pdf';
        }

        
$images $this->get_images$args$additional_mime_types );
        
$count  $images->post_count;

        if ( ! 
$count ) {
            
WP_CLI::warning'No images found.' );
            return;
        }

        
WP_CLI::log(
            
sprintf(
                
'Found %1$d %2$s to regenerate.',
                
$count,
                
_n'image''images'$count )
            )
        );

        if ( 
$image_size ) {
            
$image_size_filters $this->add_image_size_filters$image_size );
        }

        
$number    0;
        
$successes 0;
        
$errors    0;
        
$skips     0;
        foreach ( 
$images->posts as $post ) {
            
$number++;
            if ( 
=== $number self::WP_CLEAR_OBJECT_CACHE_INTERVAL ) {
                
Utils\wp_clear_object_cache();
            }
            
$this->process_regeneration$post->ID$skip_delete$only_missing$image_size$number '/' $count$successes$errors$skips );
        }

        if ( 
$image_size ) {
            
$this->remove_image_size_filters$image_size_filters );
        }

        
Utils\report_batch_operation_results'image''regenerate'$count$successes$errors$skips );
    }

    
/**
     * Creates attachments from local files or URLs.
     *
     * ## OPTIONS
     *
     * <file>...
     * : Path to file or files to be imported. Supports the glob(3) capabilities of the current shell.
     *     If file is recognized as a URL (for example, with a scheme of http or ftp), the file will be
     *     downloaded to a temp file before being sideloaded.
     *
     * [--post_id=<post_id>]
     * : ID of the post to attach the imported files to.
     *
     * [--title=<title>]
     * : Attachment title (post title field).
     *
     * [--caption=<caption>]
     * : Caption for attachent (post excerpt field).
     *
     * [--alt=<alt_text>]
     * : Alt text for image (saved as post meta).
     *
     * [--desc=<description>]
     * : "Description" field (post content) of attachment post.
     *
     * [--skip-copy]
     * : If set, media files (local only) are imported to the library but not moved on disk.
     * File names will not be run through wp_unique_filename() with this set.
     *
     * [--preserve-filetime]
     * : Use the file modified time as the post published & modified dates.
     * Remote files will always use the current time.
     *
     * [--featured_image]
     * : If set, set the imported image as the Featured Image of the post its attached to.
     *
     * [--porcelain]
     * : Output just the new attachment ID.
     *
     * ## EXAMPLES
     *
     *     # Import all jpgs in the current user's "Pictures" directory, not attached to any post.
     *     $ wp media import ~/Pictures/**\/*.jpg
     *     Imported file '/home/person/Pictures/landscape-photo.jpg' as attachment ID 1751.
     *     Imported file '/home/person/Pictures/fashion-icon.jpg' as attachment ID 1752.
     *     Success: Imported 2 of 2 items.
     *
     *     # Import a local image and set it to be the post thumbnail for a post.
     *     $ wp media import ~/Downloads/image.png --post_id=123 --title="A downloaded picture" --featured_image
     *     Imported file '/home/person/Downloads/image.png' as attachment ID 1753 and attached to post 123 as featured image.
     *     Success: Imported 1 of 1 images.
     *
     *     # Import a local image, but set it as the featured image for all posts.
     *     # 1. Import the image and get its attachment ID.
     *     # 2. Assign the attachment ID as the featured image for all posts.
     *     $ ATTACHMENT_ID="$(wp media import ~/Downloads/image.png --porcelain)"
     *     $ wp post list --post_type=post --format=ids | xargs -d ' ' -I % wp post meta add % _thumbnail_id $ATTACHMENT_ID
     *     Success: Added custom field.
     *     Success: Added custom field.
     *
     *     # Import an image from the web.
     *     $ wp media import http://s.wordpress.org/style/images/wp-header-logo.png --title='The WordPress logo' --alt="Semantic personal publishing"
     *     Imported file 'http://s.wordpress.org/style/images/wp-header-logo.png' as attachment ID 1755.
     *     Success: Imported 1 of 1 images.
     *
     *     # Get the URL for an attachment after import.
     *     $ wp media import http://s.wordpress.org/style/images/wp-header-logo.png --porcelain | xargs -I {} wp post list --post__in={} --field=url --post_type=attachment
     *     http://wordpress-develop.dev/wp-header-logo/
     */
    
public function import$args$assoc_args = array() ) {
        
$assoc_args wp_parse_args(
            
$assoc_args,
            array(
                
'title'   => '',
                
'caption' => '',
                
'alt'     => '',
                
'desc'    => '',
            )
        );

        
// Assume the most generic term
        
$noun 'item';

        
// Current site's timezone offset.
        
$gmt_offset get_option'gmt_offset' );

        
// Use the noun `image` when sure the media file is an image
        
if ( Utils\get_flag_value$assoc_args'featured_image' ) || $assoc_args['alt'] ) {
            
$noun 'image';
        }

        if ( isset( 
$assoc_args['post_id'] ) ) {
            if ( ! 
get_post$assoc_args['post_id'] ) ) {
                
WP_CLI::warning'Invalid --post_id' );
                
$assoc_args['post_id'] = false;
            }
        } else {
            
$assoc_args['post_id'] = false;
        }

        
$number    0;
        
$successes 0;
        
$errors    0;
        foreach ( 
$args as $file ) {
            
$number++;
            if ( 
=== $number self::WP_CLEAR_OBJECT_CACHE_INTERVAL ) {
                
Utils\wp_clear_object_cache();
            }

            
// phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url -- parse_url will only be used in absence of wp_parse_url.
            
$is_file_remote function_exists'wp_parse_url' ) ? wp_parse_url$filePHP_URL_HOST ) : parse_url$filePHP_URL_HOST );
            
$orig_filename  $file;
            
$file_time      '';

            if ( empty( 
$is_file_remote ) ) {
                if ( ! 
file_exists$file ) ) {
                    
WP_CLI::warning"Unable to import file '$file'. Reason: File doesn't exist." );
                    
$errors++;
                    continue;
                }
                if ( 
Utils\get_flag_value$assoc_args'skip-copy' ) ) {
                    
$tempfile $file;
                } else {
                    
$tempfile $this->make_copy$file );
                }
                
$name Utils\basename$file );

                if ( 
Utils\get_flag_value$assoc_args'preserve-filetime' ) ) {
                    
$file_time = @filemtime$file );
                }
            } else {
                
$tempfile download_url$file );
                if ( 
is_wp_error$tempfile ) ) {
                    
WP_CLI::warning(
                        
sprintf(
                            
"Unable to import file '%s'. Reason: %s",
                            
$file,
                            
implode', '$tempfile->get_error_messages() )
                        )
                    );
                    
$errors++;
                    continue;
                }
                
$name strtokUtils\basename$file ), '?' );
            }

            
$file_array = array(
                
'tmp_name' => $tempfile,
                
'name'     => $name,
            );

            
$post_array = array(
                
'post_title'   => $assoc_args['title'],
                
'post_excerpt' => $assoc_args['caption'],
                
'post_content' => $assoc_args['desc'],
            );

            if ( ! empty( 
$file_time ) ) {
                
$post_array['post_date']         = gmdate'Y-m-d H:i:s'$file_time + ( $gmt_offset HOUR_IN_SECONDS ) );
                
$post_array['post_date_gmt']     = gmdate'Y-m-d H:i:s'$file_time );
                
$post_array['post_modified']     = gmdate'Y-m-d H:i:s'$file_time + ( $gmt_offset HOUR_IN_SECONDS ) );
                
$post_array['post_modified_gmt'] = gmdate'Y-m-d H:i:s'$file_time );
            }

            
$post_array wp_slash$post_array );

            
// use image exif/iptc data for title and caption defaults if possible
            
if ( empty( $post_array['post_title'] ) || empty( $post_array['post_excerpt'] ) ) {
                
// @codingStandardsIgnoreStart
                
$image_meta = @wp_read_image_metadata$tempfile );
                
// @codingStandardsIgnoreEnd
                
if ( ! empty( $image_meta ) ) {
                    if ( empty( 
$post_array['post_title'] ) && trim$image_meta['title'] ) && ! is_numericsanitize_title$image_meta['title'] ) ) ) {
                        
$post_array['post_title'] = $image_meta['title'];
                    }

                    if ( empty( 
$post_array['post_excerpt'] ) && trim$image_meta['caption'] ) ) {
                        
$post_array['post_excerpt'] = $image_meta['caption'];
                    }
                }
            }

            if ( empty( 
$post_array['post_title'] ) ) {
                
$post_array['post_title'] = preg_replace'/\.[^.]+$/'''Utils\basename$file ) );
            }

            if ( 
Utils\get_flag_value$assoc_args'skip-copy' ) ) {
                
$wp_filetype                  wp_check_filetype$filenull );
                
$post_array['post_mime_type'] = $wp_filetype['type'];
                
$post_array['post_status']    = 'inherit';

                
$success wp_insert_attachment$post_array$file$assoc_args['post_id'] );
                if ( 
is_wp_error$success ) ) {
                    
WP_CLI::warning(
                        
sprintf(
                            
"Unable to insert file '%s'. Reason: %s",
                            
$orig_filename,
                            
implode', '$success->get_error_messages() )
                        )
                    );
                    
$errors++;
                    continue;
                }
                
wp_update_attachment_metadata$successwp_generate_attachment_metadata$success$file ) );
            } else {
                
// Deletes the temporary file.
                
$success media_handle_sideload$file_array$assoc_args['post_id'], $assoc_args['title'], $post_array );
                if ( 
is_wp_error$success ) ) {
                    
WP_CLI::warning(
                        
sprintf(
                            
"Unable to import file '%s'. Reason: %s",
                            
$orig_filename,
                            
implode', '$success->get_error_messages() )
                        )
                    );
                    
$errors++;
                    continue;
                }
            }

            
// Set alt text
            
if ( $assoc_args['alt'] ) {
                
update_post_meta$success'_wp_attachment_image_alt'wp_slash$assoc_args['alt'] ) );
            }

            
// Set as featured image, if --post_id and --featured_image are set
            
if ( $assoc_args['post_id'] && Utils\get_flag_value$assoc_args'featured_image' ) ) {
                
update_post_meta$assoc_args['post_id'], '_thumbnail_id'$success );
            }

            
$attachment_success_text '';
            if ( 
$assoc_args['post_id'] ) {
                
$attachment_success_text " and attached to post {$assoc_args['post_id']}";
                if ( 
Utils\get_flag_value$assoc_args'featured_image' ) ) {
                    
$attachment_success_text .= ' as featured image';
                }
            }

            if ( 
Utils\get_flag_value$assoc_args'porcelain' ) ) {
                
WP_CLI::line$success );
            } else {
                
WP_CLI::log(
                    
sprintf(
                        
"Imported file '%s' as attachment ID %d%s.",
                        
$orig_filename,
                        
$success,
                        
$attachment_success_text
                    
)
                );
            }
            
$successes++;
        }

        
// Report the result of the operation
        
if ( ! Utils\get_flag_value$assoc_args'porcelain' ) ) {
            
Utils\report_batch_operation_results$noun'import'count$args ), $successes$errors );
        } elseif ( 
$errors ) {
            
WP_CLI::halt);
        }
    }

    
/**
     * Lists image sizes registered with WordPress.
     *
     * ## OPTIONS
     *
     * [--fields=<fields>]
     * : Limit the output to specific fields. Defaults to all fields.
     *
     * [--format=<format>]
     * : Render output in a specific format
     * ---
     * default: table
     * options:
     *   - table
     *   - json
     *   - csv
     *   - yaml
     *   - count
     * ---
     *
     * ## AVAILABLE FIELDS
     *
     * These fields will be displayed by default for each image size:
     * * name
     * * width
     * * height
     * * crop
     * * ratio
     *
     * ## EXAMPLES
     *
     *     # List all registered image sizes
     *     $ wp media image-size
     *     +---------------------------+-------+--------+-------+-------+
     *     | name                      | width | height | crop  | ratio |
     *     +---------------------------+-------+--------+-------+-------+
     *     | full                      |       |        | N/A   | N/A   |
     *     | twentyfourteen-full-width | 1038  | 576    | hard  | 173:96|
     *     | large                     | 1024  | 1024   | soft  | N/A   |
     *     | medium_large              | 768   | 0      | soft  | N/A   |
     *     | medium                    | 300   | 300    | soft  | N/A   |
     *     | thumbnail                 | 150   | 150    | hard  | 1:1   |
     *     +---------------------------+-------+--------+-------+-------+
     *
     * @subcommand image-size
     */
    
public function image_size$args$assoc_args ) {
        
$assoc_args array_merge(
            array(
                
'fields' => 'name,width,height,crop,ratio',
            ),
            
$assoc_args
        
);

        
$sizes $this->get_registered_image_sizes();

        
usort(
            
$sizes,
            function( 
$a$b ) {
                if ( 
$a['width'] === $b['width'] ) {
                    return 
0;
                }
                return ( 
$a['width'] < $b['width'] ) ? : -1;
            }
        );
        
array_unshift(
            
$sizes,
            array(
                
'name'   => 'full',
                
'width'  => '',
                
'height' => '',
                
'crop'   => 'N/A',
                
'ratio'  => 'N/A',
            )
        );
        
WP_CLI\Utils\format_items$assoc_args['format'], $sizesexplode','$assoc_args['fields'] ) );
    }

    private function 
get_ratio$width$height ) {
        if ( 
=== $height ) {
            return 
"0:{$width}";
        }

        if ( 
=== $width ) {
            return 
"{$height}:0";
        }

        
$gcd          $this->gcd$width$height );
        
$width_ratio  $width $gcd;
        
$height_ratio $height $gcd;

        return 
"{$width_ratio}:{$height_ratio}";
    }

    private function 
gcd$num1$num2 ) {
        while ( 
!== $num2 ) {
            
$t    $num1 $num2;
            
$num1 $num2;
            
$num2 $t;
        }
        return 
$num1;
    }

    
// wp_tempnam() inexplicably forces a .tmp extension, which spoils MIME type detection
    
private function make_copy$path ) {
        
$dir      get_temp_dir();
        
$filename Utils\basename$path );
        if ( empty( 
$filename ) ) {
            
$filename time();
        }

        
$filename $dir wp_unique_filename$dir$filename );
        if ( ! 
copy$path$filename ) ) {
            
WP_CLI::error"Could not create temporary file for $path." );
        }

        return 
$filename;
    }

    private function 
process_regeneration$id$skip_delete$only_missing$image_size$progress, &$successes, &$errors, &$skips ) {

        
$title get_the_title$id );
        if ( 
'' === $title ) {
            
// If audio or video cover art then the id is the sub attachment id, which has no title.
            
if ( metadata_exists'post'$id'_cover_hash' ) ) {
                
// Unfortunately the only way to get the attachment title would be to do a non-indexed query against the meta value of `_thumbnail_id`. So don't.
                
$att_desc sprintf'cover attachment (ID %d)'$id );
            } else {
                
$att_desc sprintf'"(no title)" (ID %d)'$id );
            }
        } else {
            
$att_desc sprintf'"%1$s" (ID %2$d)'$title$id );
        }
        
$thumbnail_desc $image_size sprintf'"%s" thumbnail'$image_size ) : 'thumbnail';

        
$fullsizepath $this->get_attached_file$id );

        if ( 
false === $fullsizepath || ! file_exists$fullsizepath ) ) {
            
WP_CLI::warning"Can't find $att_desc." );
            
$errors++;
            return;
        }

        
$is_pdf 'application/pdf' === get_post_mime_type$id );

        
$needs_regeneration $this->needs_regeneration$id$fullsizepath$is_pdf$image_size$skip_delete$skip_it );

        if ( 
$skip_it ) {
            
WP_CLI::log"$progress Skipped $thumbnail_desc regeneration for $att_desc." );
            
$skips++;
            return;
        }

        if ( 
$only_missing && ! $needs_regeneration ) {
            
WP_CLI::log"$progress No $thumbnail_desc regeneration needed for $att_desc." );
            
$successes++;
            return;
        }

        
$metadata wp_generate_attachment_metadata$id$fullsizepath );
        if ( 
is_wp_error$metadata ) ) {
            
WP_CLI::warningsprintf'%s (ID %d)'$metadata->get_error_message(), $id ) );
            
WP_CLI::log"$progress Couldn't regenerate thumbnails for $att_desc." );
            
$errors++;
            return;
        }

        
// Note it's possible for no metadata to be generated for PDFs if restricted to a specific image size.
        
if ( empty( $metadata ) && ! ( $is_pdf && $image_size ) ) {
            
WP_CLI::warningsprintf'No metadata. (ID %d)'$id ) );
            
WP_CLI::log"$progress Couldn't regenerate thumbnails for $att_desc." );
            
$errors++;
            return;
        }

        if ( 
$image_size ) {
            if ( 
$this->update_attachment_metadata_for_image_size$id$metadata$image_size ) ) {
                
WP_CLI::log"$progress Regenerated $thumbnail_desc for $att_desc." );
            } else {
                
WP_CLI::log"$progress No $thumbnail_desc regeneration needed for $att_desc." );
            }
        } else {
            
wp_update_attachment_metadata$id$metadata );

            
WP_CLI::log"$progress Regenerated thumbnails for $att_desc." );
        }
        
$successes++;
    }

    private function 
remove_old_images$metadata$fullsizepath$image_size ) {

        if ( empty( 
$metadata['sizes'] ) ) {
            return;
        }

        if ( 
$image_size ) {
            if ( empty( 
$metadata['sizes'][ $image_size ] ) ) {
                return;
            }
            
$metadata['sizes'] = array( $image_size => $metadata['sizes'][ $image_size ] );
        }

        
$dir_path dirname$fullsizepath ) . '/';

        foreach ( 
$metadata['sizes'] as $size_info ) {
            
$intermediate_path $dir_path $size_info['file'];

            if ( 
$intermediate_path === $fullsizepath ) {
                continue;
            }

            if ( 
file_exists$intermediate_path ) ) {
                
unlink$intermediate_path );
            }
        }
    }

    private function 
needs_regeneration$att_id$fullsizepath$is_pdf$image_size$skip_delete, &$skip_it ) {

        
// Assume not skipping.
        
$skip_it false;

        
// Note: zero-length string returned if no metadata, for instance if PDF or non-standard image (eg an SVG).
        
$metadata wp_get_attachment_metadata$att_id );

        
$image_sizes $this->get_intermediate_image_sizes_for_attachment$fullsizepath$is_pdf$metadata$att_id );

        
// First check if no applicable editor currently available (non-destructive - ie old thumbnails not removed).
        
if ( is_wp_error$image_sizes ) && 'image_no_editor' === $image_sizes->get_error_code() ) {
            
// Warn unless PDF or non-standard image.
            
if ( ! $is_pdf && is_array$metadata ) && ! empty( $metadata['sizes'] ) ) {
                
WP_CLI::warningsprintf'%s (ID %d)'$image_sizes->get_error_message(), $att_id ) );
            }
            
$skip_it true;
            return 
false;
        }

        
// If uploaded when applicable image editor such as Imagick unavailable, the metadata or sizes metadata may not exist.
        
if ( ! is_array$metadata ) ) {
            
$metadata = array();
        }
        
// If set `$metadata['sizes']` should be array but explicitly check as following code depends on it.
        
if ( ! isset( $metadata['sizes'] ) || ! is_array$metadata['sizes'] ) ) {
            
$metadata['sizes'] = array();
        }

        
// Remove any old thumbnails (so now destructive).
        
if ( ! $skip_delete ) {
            
$this->remove_old_images$metadata$fullsizepath$image_size );
        }

        
// Check for any other error (such as load error) apart from no editor available.
        
if ( is_wp_error$image_sizes ) ) {
            
// Warn but assume it may be possible to regenerate and allow processing to continue and possibly fail.
            
WP_CLI::warningsprintf'%s (ID %d)'$image_sizes->get_error_message(), $att_id ) );
            return 
true;
        }

        
// Have sizes - check whether there're new ones or they've changed. Note that an attachment can have no sizes if it's on or below the thumbnail threshold.

        
if ( $image_size ) {
            if ( empty( 
$image_sizes$image_size ] ) ) {
                return 
false;
            }
            if ( empty( 
$metadata['sizes'][ $image_size ] ) ) {
                return 
true;
            }
            
$metadata['sizes'] = array( $image_size => $metadata['sizes'][ $image_size ] );
        }

        if ( 
$this->image_sizes_differ$image_sizes$metadata['sizes'] ) ) {
            return 
true;
        }

        
$dir_path dirname$fullsizepath ) . '/';

        
// Check that the thumbnail files exist.
        
foreach ( $metadata['sizes'] as $size_info ) {
            
$intermediate_path $dir_path $size_info['file'];

            if ( 
$intermediate_path === $fullsizepath ) {
                continue;
            }

            if ( ! 
file_exists$intermediate_path ) ) {
                return 
true;
            }
        }
        return 
false;
    }

    
// Whether there's new image sizes or the width/height of existing image sizes have changed.
    
private function image_sizes_differ$image_sizes$meta_sizes ) {
        
// Check if have new image size(s).
        
if ( array_diffarray_keys$image_sizes ), array_keys$meta_sizes ) ) ) {
            return 
true;
        }
        
// Check if image sizes have changed.
        
foreach ( $image_sizes as $name => $image_size ) {
            if ( 
$image_size['width'] !== $meta_sizes$name ]['width'] || $image_size['height'] !== $meta_sizes$name ]['height'] ) {
                return 
true;
            }
        }
        return 
false;
    }

    
// Like WP's get_intermediate_image_sizes(), but removes sizes that won't be generated for a particular attachment due to its being on or below their thresholds,
    // and returns associative array with size name => width/height entries, resolved to crop values if applicable.
    
private function get_intermediate_image_sizes_for_attachment$fullsizepath$is_pdf$metadata$att_id ) {

        
// Need to get width, height of attachment for image_resize_dimensions().
        
$editor wp_get_image_editor$fullsizepath );
        if ( 
is_wp_error$editor ) ) {
            return 
$editor;
        }
        
$result $editor->load();
        if ( 
is_wp_error$result ) ) {
            unset( 
$editor );
            return 
$result;
        }
        list( 
$width$height ) = array_values$editor->get_size() );
        unset( 
$editor );

        
$sizes = array();
        foreach ( 
$this->get_intermediate_sizes$is_pdf$metadata$att_id ) as $name => $size ) {
            
// Need to check destination and original width or height differ before calling image_resize_dimensions(), otherwise it will return non-false.
            
$dims image_resize_dimensions$width$height$size['width'], $size['height'], $size['crop'] );
            if ( ( 
$width !== $size['width'] || $height !== $size['height'] ) && $dims ) {
                list( 
$dst_x$dst_y$src_x$src_y$dst_w$dst_h$src_w$src_h ) = $dims;
                
$sizes$name ] = array(
                    
'width'  => $dst_w,
                    
'height' => $dst_h,
                );
            }
        }
        return 
$sizes;
    }

    
// Like WP's get_intermediate_image_sizes(), but returns associative array with name => size info entries (and caters for PDFs also).
    
private function get_intermediate_sizes$is_pdf$metadata$att_id ) {
        if ( 
$is_pdf ) {
            
// Copied from wp_generate_attachment_metadata() in "wp-admin/includes/image.php".
            
$fallback_sizes = array(
                
'thumbnail',
                
'medium',
                
'large',
            );
            
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Calling native WordPress hook.
            
$intermediate_image_sizes apply_filters'fallback_intermediate_image_sizes'$fallback_sizes$metadata );
        } else {
            
$intermediate_image_sizes get_intermediate_image_sizes();
        }

        
// Adapted from wp_generate_attachment_metadata() in "wp-admin/includes/image.php".

        
if ( function_exists'wp_get_additional_image_sizes' ) ) {
            
$_wp_additional_image_sizes wp_get_additional_image_sizes();
        } else {
            
// For WP < 4.7.0.
            
global $_wp_additional_image_sizes;
            if ( ! 
$_wp_additional_image_sizes ) {
                
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Used as a fallback for WordPress version less than 4.7.0 as function wp_get_additional_image_sizes didn't exist then.
                
$_wp_additional_image_sizes = array();
            }
        }

        
$sizes = array();
        foreach ( 
$intermediate_image_sizes as $s ) {
            if ( isset( 
$_wp_additional_image_sizes$s ]['width'] ) ) {
                
$sizes$s ]['width'] = (int) $_wp_additional_image_sizes$s ]['width'];
            } else {
                
$sizes$s ]['width'] = (int) get_option"{$s}_size_w" );
            }

            if ( isset( 
$_wp_additional_image_sizes$s ]['height'] ) ) {
                
$sizes$s ]['height'] = (int) $_wp_additional_image_sizes$s ]['height'];
            } else {
                
$sizes$s ]['height'] = (int) get_option"{$s}_size_h" );
            }

            if ( isset( 
$_wp_additional_image_sizes$s ]['crop'] ) ) {
                
$sizes$s ]['crop'] = (bool) $_wp_additional_image_sizes$s ]['crop'];
            } else {
                
// Force PDF thumbnails to be soft crops.
                
if ( $is_pdf && 'thumbnail' === $s ) {
                    
$sizes$s ]['crop'] = false;
                } else {
                    
$sizes$s ]['crop'] = (bool) get_option"{$s}_crop" );
                }
            }
        }

        
// Check here that not PDF (as filter not applied in core if is) and `$metadata` is array (as may not be and filter only applied in core when is).
        
if ( ! $is_pdf && is_array$metadata ) ) {
            if ( 
Utils\wp_version_compare'5.3''>=' ) ) {
                
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Calling native WordPress hook.
                
$sizes apply_filters'intermediate_image_sizes_advanced'$sizes$metadata$att_id );
            } else {
                
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Calling native WordPress hook.
                
$sizes apply_filters'intermediate_image_sizes_advanced'$sizes$metadata );
            }
        }

        return 
$sizes;
    }

    
// Add filters to only process a particular intermediate image size in wp_generate_attachment_metadata().
    
private function add_image_size_filters$image_size ) {
        
$image_size_filters = array();

        
// For images.
        
$image_size_filters['intermediate_image_sizes_advanced'] = function ( $sizes ) use ( $image_size ) {
            
// $sizes is associative array of name => size info entries.
            
if ( isset( $sizes$image_size ] ) ) {
                return array( 
$image_size => $sizes$image_size ] );
            }
            return array();
        };

        
// For PDF previews.
        
$image_size_filters['fallback_intermediate_image_sizes'] = function ( $fallback_sizes ) use ( $image_size ) {
            
// $fallback_sizes is indexed array of size names.
            
if ( in_array$image_size$fallback_sizestrue ) ) {
                return array( 
$image_size );
            }
            return array();
        };

        foreach ( 
$image_size_filters as $name => $filter ) {
            
add_filter$name$filterPHP_INT_MAX );
        }

        return 
$image_size_filters;
    }

    
// Remove above intermediate image size filters.
    
private function remove_image_size_filters$image_size_filters ) {
        foreach ( 
$image_size_filters as $name => $filter ) {
            
remove_filter$name$filterPHP_INT_MAX );
        }
    }

    
// Update attachment sizes metadata just for a particular intermediate image size.
    
private function update_attachment_metadata_for_image_size$id$new_metadata$image_size ) {
        
$metadata wp_get_attachment_metadata$id );

        if ( ! 
is_array$metadata ) ) {
            return 
false;
        }

        
// If have metadata for image_size.
        
if ( ! empty( $new_metadata['sizes'][ $image_size ] ) ) {
            
$metadata['sizes'][ $image_size ] = $new_metadata['sizes'][ $image_size ];
            
wp_update_attachment_metadata$id$metadata );
            return 
true;
        }

        
// Else remove unused metadata if any.
        
if ( ! empty( $metadata['sizes'][ $image_size ] ) ) {
            unset( 
$metadata['sizes'][ $image_size ] );
            
wp_update_attachment_metadata$id$metadata );
            
// Treat removing unused metadata as no change.
        
}
        return 
false;
    }

    
/**
     * Get images from the installation.
     *
     * @param array $args                  The query arguments to use. Optional.
     * @param array $additional_mime_types The additional mime types to search for. Optional.
     *
     * @return WP_Query The query result.
     */
    
private function get_images$args = array(), $additional_mime_types = array() ) {
        
$mime_types array_merge( array( 'image' ), $additional_mime_types );

        
$query_args = array(
            
'post_type'      => 'attachment',
            
'post__in'       => $args,
            
'post_mime_type' => $mime_types,
            
'post_status'    => 'any',
            
'posts_per_page' => -1,
        );

        return new 
WP_Query$query_args );
    }

    
/**
     * Get the metadata for the passed intermediate image size.
     *
     * @param string $size The image size to get the metadata for.
     *
     * @return array The image size metadata.
     */
    
private function get_intermediate_size_metadata$size ) {
        
$width  intvalget_option"{$size}_size_w" ) );
        
$height intvalget_option"{$size}_size_h" ) );
        
$crop   get_option"{$size}_crop" );

        return array(
            
'name'   => $size,
            
'width'  => $width,
            
'height' => $height,
            
'crop'   => false !== $crop 'hard' 'soft',
            
'ratio'  => false !== $crop $this->get_ratio$width$height ) : 'N/A',
        );
    }

    
/**
     * Get all the registered image sizes along with their dimensions.
     *
     * @global array $_wp_additional_image_sizes The additional image sizes to parse.
     *
     * @link https://wordpress.stackexchange.com/a/251602 Original solution.
     *
     * @return array $image_sizes The image sizes
     */
    
private function get_registered_image_sizes() {
        global 
$_wp_additional_image_sizes;

        
$image_sizes         = array();
        
$default_image_sizes get_intermediate_image_sizes();

        foreach ( 
$default_image_sizes as $size ) {
            
$image_sizes[] = $this->get_intermediate_size_metadata$size );
        }

        if ( 
is_array$_wp_additional_image_sizes ) ) {
            foreach ( 
$_wp_additional_image_sizes as $size => $size_args ) {
                
$crop          filter_var$size_args['crop'], FILTER_VALIDATE_BOOLEAN );
                
$image_sizes[] = array(
                    
'name'   => $size,
                    
'width'  => $size_args['width'],
                    
'height' => $size_args['height'],
                    
'crop'   => empty( $crop ) || is_array$size_args['crop'] ) ? 'soft' 'hard',
                    
'ratio'  => empty( $crop ) || is_array$size_args['crop'] ) ? 'N/A' $this->get_ratio$size_args['width'], $size_args['height'] ),
                );
            }
        }

        return 
$image_sizes;
    }

    
/**
     * Fix image orientation for one or more attachments.
     *
     * ## OPTIONS
     *
     * [<attachment-id>...]
     * : One or more IDs of the attachments to regenerate.
     *
     * [--dry-run]
     * : Check images needing orientation without performing the operation.
     *
     * ## EXAMPLES
     *
     *     # Fix orientation for all images.
     *     $ wp media fix-orientation
     *     1/3 Fixing orientation for "Landscape_4" (ID 62).
     *     2/3 Fixing orientation for "Landscape_3" (ID 61).
     *     3/3 Fixing orientation for "Landscape_2" (ID 60).
     *     Success: Fixed 3 of 3 images.
     *
     *     # Fix orientation dry run.
     *     $ wp media fix-orientation 63 -dry run
     *     1/1 "Portrait_6" (ID 63) will be affected.
     *     Success: 1 of 1 image will be affected.
     *
     *     # Fix orientation for specific images.
     *     $ wp media fix-orientation 63
     *     1/1 Fixing orientation for "Portrait_6" (ID 63).
     *     Success: Fixed 1 of 1 images.
     *
     * @subcommand fix-orientation
     */
    
public function fix_orientation$args$assoc_args ) {

        
// EXIF is required to read image metadata for orientation.
        
if ( ! extension_loaded'exif' ) ) {
            
WP_CLI::error"'EXIF' extension is not loaded, it is required for this operation." );
        } elseif ( ! 
function_exists'exif_read_data' ) ) {
            
WP_CLI::error"Function 'exif_read_data' does not exist, it is required for this operation." );
        }

        
$images  $this->get_images$args );
        
$count   $images->post_count;
        
$dry_run Utils\get_flag_value$assoc_args'dry-run' );

        if ( ! 
$count ) {
            
WP_CLI::error'No images found.' );
        }

        
$number    0;
        
$successes 0;
        
$errors    0;
        foreach ( 
$images->posts as $post ) {
            
$number++;
            if ( 
=== $number self::WP_CLEAR_OBJECT_CACHE_INTERVAL ) {
                
Utils\wp_clear_object_cache();
            }
            
$this->process_orientation_fix$post->ID"{$number}/{$count}"$successes$errors$dry_run );
        }

        if ( 
Utils\get_flag_value$assoc_args'dry-run' ) ) {
            
WP_CLI::successsprintf'%s of %s %s will be affected.'$successes$countUtils\pluralize'image'$count ) ) );
        } else {
            
Utils\report_batch_operation_results'image''fix'$count$successes$errors );
        }
    }

    
/**
     * Perform orientation fix on attachments.
     *
     * @param int    $id        Attachment Id.
     * @param string $progress  Current progress string.
     * @param int    $successes Count of success in current operation.
     * @param int    $errors    Count of errors in current operation.
     * @param bool   $dry_run   Is this a dry run?
     */
    
private function process_orientation_fix$id$progress, &$successes, &$errors$dry_run ) {
        
$title get_the_title$id );
        if ( 
'' === $title ) {
            
// If audio or video cover art then the id is the sub attachment id, which has no title.
            
if ( metadata_exists'post'$id'_cover_hash' ) ) {
                
// Unfortunately the only way to get the attachment title would be to do a non-indexed query against the meta value of `_thumbnail_id`. So don't.
                
$att_desc sprintf'cover attachment (ID %d)'$id );
            } else {
                
$att_desc sprintf'"(no title)" (ID %d)'$id );
            }
        } else {
            
$att_desc sprintf'"%1$s" (ID %2$d)'$title$id );
        }

        
$full_size_path $this->get_attached_file$id );

        if ( 
false === $full_size_path || ! file_exists$full_size_path ) ) {
            
WP_CLI::warning"Can't find {$att_desc}." );
            
$errors++;
            return;
        }

        
// Get current metadata of the attachment.
        
$metadata   wp_generate_attachment_metadata$id$full_size_path );
        
$image_meta = ! empty( $metadata['image_meta'] ) ? $metadata['image_meta'] : [];

        if ( isset( 
$image_meta['orientation'] ) && absint$image_meta['orientation'] ) > ) {
            if ( ! 
$dry_run ) {
                
WP_CLI::log"{$progress} Fixing orientation for {$att_desc}." );
                if ( 
false !== $this->flip_rotate_image$id$metadata$image_meta$full_size_path ) ) {
                    
$successes++;
                } else {
                    
$errors++;
                    
WP_CLI::log"Couldn't fix orientation for {$att_desc}." );
                }
            } else {
                
WP_CLI::log"{$progress} {$att_desc} will be affected." );
                
$successes++;
            }
        } else {
            
WP_CLI::log"{$progress} No orientation fix required for {$att_desc}." );
        }
    }

    
/**
     * Perform image rotate operations on the image.
     *
     * @param int    $id             Attachment Id.
     * @param array  $metadata       Attachment Metadata.
     * @param array  $image_meta     `image_meta` information for the attachment.
     * @param string $full_size_path Path to original image.
     *
     * @return bool Whether the image rotation operation succeeded.
     */
    
private function flip_rotate_image$id$metadata$image_meta$full_size_path ) {
        
$editor wp_get_image_editor$full_size_path );

        if ( ! 
is_wp_error$editor ) ) {
            
$operations $this->calculate_transformation( (int) $image_meta['orientation'] );

            
// Rotate image if required.
            
if ( true === $operations['rotate'] ) {
                
$editor->rotate$operations['degree'] );
            }

            
// Flip image if required.
            
if ( false !== $operations['flip'] ) {
                
$editor->flip$operations['flip'][0], $operations['flip'][1] );
            }

            
// Save the image and generate metadata.
            
$editor->save$full_size_path );
            
$metadata   wp_generate_attachment_metadata$id$full_size_path );
            
$image_meta = empty( $metadata['image_meta'] ) ? [] : $metadata['image_meta'];

            
// Update attachment metadata with newly generated data.
            
wp_update_attachment_metadata$id$metadata );

            if ( isset( 
$image_meta['orientation'] ) && absint$image_meta['orientation'] ) === ) {
                return 
true;
            }
        }

        return 
false;
    }

    
/**
     * Return array of operations to be done for provided orientation value.
     *
     * @param int $orientation EXIF orientation value.
     *
     * @return array
     */
    
private function calculate_transformation$orientation ) {
        
$rotate false;
        
$flip   false;
        
$degree 0;
        switch ( 
$orientation ) {
            case 
2:
                
$flip = [ falsetrue ]; // $flip image along given axis [ horizontal, vertical ]
                
break;
            case 
3:
                
$flip = [ truetrue ];
                break;
            case 
4:
                
$flip = [ truefalse ];
                break;
            case 
5:
                
$degree = -90;
                
$rotate true;
                
$flip   = [ falsetrue ];
                break;
            case 
6:
                
$degree = -90;
                
$rotate true;
                break;
            case 
7:
                
$degree 90;
                
$rotate true;
                
$flip   = [ falsetrue ];
                break;
            case 
8:
                
$degree 90;
                
$rotate true;
                break;
            default:
                
$degree 0;
                
$rotate true;
                break;
        }

        return [
            
'flip'   => $flip,
            
'degree' => $degree,
            
'rotate' => $rotate,
        ];
    }

    
/**
     * Add compatibility indirection to get_attached_file().
     *
     * In WordPress 5.3, behavior changed to account for automatic resizing of
     * big image files.
     *
     * @see https://core.trac.wordpress.org/ticket/47873
     *
     * @param int $attachment_id ID of the attachment to get the filepath for.
     * @return string|false Filepath of the attachment, or false if not found.
     */
    
private function get_attached_file$attachment_id ) {
        if ( 
function_exists'wp_get_original_image_path' ) ) {
            
$filepath wp_get_original_image_path$attachment_id );

            if ( 
false !== $filepath ) {
                return 
$filepath;
            }
        }

        return 
get_attached_file$attachment_id );
    }
}
<?
php

if ( ! class_exists'WP_CLI' ) ) {
    return;
}

$wpcli_media_autoloader dirname__FILE__ ) . '/vendor/autoload.php';
if ( 
file_exists$wpcli_media_autoloader ) ) {
    require_once 
$wpcli_media_autoloader;
}

/**
 * Check for Imagick and GD extensions availability.
 *
 * @throws \WP_CLI\ExitException
 */
$wpcli_media_assert_image_editor_support = function () {
    if ( ! 
wp_image_editor_supports() ) {
        
WP_CLI::error(
            
'No support for generating images found. '
            
'Please install the Imagick or GD PHP extensions.'
        
);
    }
};

WP_CLI::add_command(
    
'media',
    
'Media_Command',
    [ 
'before_invoke' => $wpcli_media_assert_image_editor_support ]
);
<?
php

namespace WP_CLI\Fetchers;

/**
 * Fetch a WordPress plugin based on one of its attributes.
 *
 * This is a special version of the plugin fetcher. It doesn't use the
 * `all_plugins` filter, so that plugins cannot hide themselves from the
 * checks.
 *
 */
class UnfilteredPlugin extends Base {

    
/**
     * @var string $msg Error message to use when invalid data is provided
     */
    
protected $msg "The '%s' plugin could not be found.";

    
/**
     * Get a plugin object by name.
     *
     * @param string $name
     *
     * @return object|false
     */
    
public function get$name ) {
        foreach ( 
get_plugins() as $file => $_ ) {
            if ( 
"{$name}.php" === $file ||
                ( 
$name && $file === $name ) ||
                ( 
dirname$file ) === $name && '.' !== $name ) ) {
                return (object) 
compact'name''file' );
            }
        }

        return 
false;
    }
}

<?
php

use WP_CLI\Utils;

/**
 * Base command that all checksum commands rely on.
 *
 * @package wp-cli
 */
class Checksum_Base_Command extends WP_CLI_Command {

    
/**
     * Normalizes directory separators to slashes.
     *
     * @param string $path Path to convert.
     *
     * @return string Path with all backslashes replaced by slashes.
     */
    
public static function normalize_directory_separators$path ) {
        return 
str_replace'\\''/'$path );
    }

    
/**
     * Read a remote file and return its contents.
     *
     * @param string $url URL of the remote file to read.
     *
     * @return mixed
     */
    
protected static function _read$url ) { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore -- Could be used in classes extending this class.
        
$headers  = array( 'Accept' => 'application/json' );
        
$response Utils\http_request(
            
'GET',
            
$url,
            
null,
            
$headers,
            array( 
'timeout' => 30 )
        );
        if ( 
200 === $response->status_code ) {
            return 
$response->body;
        }
        
WP_CLI::error"Couldn't fetch response from {$url} (HTTP code {$response->status_code})." );
    }

    
/**
     * Recursively get the list of files for a given path.
     *
     * @param string $path Root path to start the recursive traversal in.
     *
     * @return array<string>
     */
    
protected function get_files$path ) {
        
$filtered_files = array();
        try {
            
$files = new RecursiveIteratorIterator(
                new 
RecursiveCallbackFilterIterator(
                    new 
RecursiveDirectoryIterator(
                        
$path,
                        
RecursiveDirectoryIterator::SKIP_DOTS
                    
),
                    function ( 
$current$key$iterator ) use ( $path ) {
                        return 
$this->filter_fileself::normalize_directory_separatorssubstr$current->getPathname(), strlen$path ) ) ) );
                    }
                ),
                
RecursiveIteratorIterator::CHILD_FIRST
            
);
            foreach ( 
$files as $file_info ) {
                if ( 
$file_info->isFile() ) {
                    
$filtered_files[] = self::normalize_directory_separatorssubstr$file_info->getPathname(), strlen$path ) ) );
                }
            }
        } catch ( 
Exception $e ) {
            
WP_CLI::error$e->getMessage() );
        }

        return 
$filtered_files;
    }

    
/**
     * Whether to include the file in the verification or not.
     *
     * Can be overridden in subclasses.
     *
     * @param string $filepath Path to a file.
     *
     * @return bool
     */
    
protected function filter_file$filepath ) {
        return 
true;
    }
}
<?
php

use WP_CLI\Utils;
use 
WP_CLI\WpOrgApi;

/**
 * Verifies core file integrity by comparing to published checksums.
 *
 * @package wp-cli
 */
class Checksum_Core_Command extends Checksum_Base_Command {

    
/**
     * Verifies WordPress files against WordPress.org's checksums.
     *
     * Downloads md5 checksums for the current version from WordPress.org, and
     * compares those checksums against the currently installed files.
     *
     * For security, avoids loading WordPress when verifying checksums.
     *
     * If you experience issues verifying from this command, ensure you are
     * passing the relevant `--locale` and `--version` arguments according to
     * the values from the `Dashboard->Updates` menu in the admin area of the
     * site.
     *
     * ## OPTIONS
     *
     * [--version=<version>]
     * : Verify checksums against a specific version of WordPress.
     *
     * [--locale=<locale>]
     * : Verify checksums against a specific locale of WordPress.
     *
     * [--insecure]
     * : Retry downloads without certificate validation if TLS handshake fails. Note: This makes the request vulnerable to a MITM attack.
     *
     * ## EXAMPLES
     *
     *     # Verify checksums
     *     $ wp core verify-checksums
     *     Success: WordPress installation verifies against checksums.
     *
     *     # Verify checksums for given WordPress version
     *     $ wp core verify-checksums --version=4.0
     *     Success: WordPress installation verifies against checksums.
     *
     *     # Verify checksums for given locale
     *     $ wp core verify-checksums --locale=en_US
     *     Success: WordPress installation verifies against checksums.
     *
     *     # Verify checksums for given locale
     *     $ wp core verify-checksums --locale=ja
     *     Warning: File doesn't verify against checksum: wp-includes/version.php
     *     Warning: File doesn't verify against checksum: readme.html
     *     Warning: File doesn't verify against checksum: wp-config-sample.php
     *     Error: WordPress installation doesn't verify against checksums.
     *
     * @when before_wp_load
     */
    
public function __invoke$args$assoc_args ) {
        
$wp_version '';
        
$locale     '';

        if ( ! empty( 
$assoc_args['version'] ) ) {
            
$wp_version $assoc_args['version'];
        }

        if ( ! empty( 
$assoc_args['locale'] ) ) {
            
$locale $assoc_args['locale'];
        }

        if ( empty( 
$wp_version ) ) {
            
$details    self::get_wp_details();
            
$wp_version $details['wp_version'];

            if ( empty( 
$locale ) ) {
                
$locale $details['wp_local_package'];
            }
        }

        
$insecure   = (bool) Utils\get_flag_value$assoc_args'insecure'false );
        
$wp_org_api = new WpOrgApi( [ 'insecure' => $insecure ] );

        try {
            
$checksums $wp_org_api->get_core_checksums$wp_version, empty( $locale ) ? 'en_US' $locale );
        } catch ( 
Exception $exception ) {
            
WP_CLI::error$exception );
        }

        if ( ! 
is_array$checksums ) ) {
            
WP_CLI::error"Couldn't get checksums from WordPress.org." );
        }

        
$has_errors false;
        foreach ( 
$checksums as $file => $checksum ) {
            
// Skip files which get updated
            
if ( 'wp-content' === substr$file010 ) ) {
                continue;
            }

            if ( ! 
file_existsABSPATH $file ) ) {
                
WP_CLI::warning"File doesn't exist: {$file});
                
$has_errors true;
                continue;
            }

            
$md5_file md5_fileABSPATH $file );
            if ( 
$md5_file !== $checksum ) {
                
WP_CLI::warning"File doesn't verify against checksum: {$file});
                
$has_errors true;
            }
        }

        
$core_checksums_files array_filterarray_keys$checksums ), [ $this'filter_file' ] );
        
$core_files           $this->get_filesABSPATH );
        
$additional_files     array_diff$core_files$core_checksums_files );

        if ( ! empty( 
$additional_files ) ) {
            foreach ( 
$additional_files as $additional_file ) {
                
WP_CLI::warning"File should not exist: {$additional_file});
            }
        }

        if ( ! 
$has_errors ) {
            
WP_CLI::success'WordPress installation verifies against checksums.' );
        } else {
            
WP_CLI::error"WordPress installation doesn't verify against checksums." );
        }
    }

    
/**
     * Whether to include the file in the verification or not.
     *
     * @param string $filepath Path to a file.
     *
     * @return bool
     */
    
protected function filter_file$filepath ) {
        return ( 
=== strpos$filepath'wp-admin/' )
            || 
=== strpos$filepath'wp-includes/' )
            || 
=== preg_match'/^wp-(?!config\.php)([^\/]*)$/'$filepath )
        );
    }

    
/**
     * Gets version information from `wp-includes/version.php`.
     *
     * @return array {
     *     @type string $wp_version The WordPress version.
     *     @type int $wp_db_version The WordPress DB revision.
     *     @type string $tinymce_version The TinyMCE version.
     *     @type string $wp_local_package The TinyMCE version.
     * }
     */
    
private static function get_wp_details() {
        
$versions_path ABSPATH 'wp-includes/version.php';

        if ( ! 
is_readable$versions_path ) ) {
            
WP_CLI::error(
                
"This does not seem to be a WordPress install.\n" .
                
'Pass --path=`path/to/wordpress` or run `wp core download`.'
            
);
        }

        
$version_content file_get_contents$versions_pathnullnull62048 );

        
$vars   = [ 'wp_version''wp_db_version''tinymce_version''wp_local_package' ];
        
$result = [];

        foreach ( 
$vars as $var_name ) {
            
$result$var_name ] = self::find_var$var_name$version_content );
        }

        return 
$result;
    }

    
/**
     * Searches for the value assigned to variable `$var_name` in PHP code `$code`.
     *
     * This is equivalent to matching the `\$VAR_NAME = ([^;]+)` regular expression and returning
     * the first match either as a `string` or as an `integer` (depending if it's surrounded by
     * quotes or not).
     *
     * @param string $var_name Variable name to search for.
     * @param string $code PHP code to search in.
     *
     * @return int|string|null
     */
    
private static function find_var$var_name$code ) {
        
$start strpos$code'$' $var_name ' = ' );

        if ( ! 
$start ) {
            return 
null;
        }

        
$start $start strlen$var_name ) + 3;
        
$end   strpos$code';'$start );

        
$value substr$code$start$end $start );

        return 
trim$value"'" );
    }
}
<?
php

use WP_CLI\Fetchers;
use 
WP_CLI\Formatter;
use 
WP_CLI\Utils;
use 
WP_CLI\WpOrgApi;

/**
 * Verifies plugin file integrity by comparing to published checksums.
 *
 * @package wp-cli
 */
class Checksum_Plugin_Command extends Checksum_Base_Command {

    
/**
     * URL template that points to the API endpoint to use.
     *
     * @var string
     */
    
private $url_template 'https://downloads.wordpress.org/plugin-checksums/{slug}/{version}.json';

    
/**
     * Cached plugin data for all installed plugins.
     *
     * @var array|null
     */
    
private $plugins_data;

    
/**
     * Array of detected errors.
     *
     * @var array
     */
    
private $errors = array();

    
/**
     * Verifies plugin files against WordPress.org's checksums.
     *
     * ## OPTIONS
     *
     * [<plugin>...]
     * : One or more plugins to verify.
     *
     * [--all]
     * : If set, all plugins will be verified.
     *
     * [--strict]
     * : If set, even "soft changes" like readme.txt changes will trigger
     * checksum errors.
     *
     * [--format=<format>]
     * : Render output in a specific format.
     * ---
     * default: table
     * options:
     *   - table
     *   - json
     *   - csv
     *   - yaml
     *   - count
     * ---
     *
     * [--insecure]
     * : Retry downloads without certificate validation if TLS handshake fails. Note: This makes the request vulnerable to a MITM attack.
     *
     * ## EXAMPLES
     *
     *     # Verify the checksums of all installed plugins
     *     $ wp plugin verify-checksums --all
     *     Success: Verified 8 of 8 plugins.
     *
     *     # Verify the checksums of a single plugin, Akismet in this case
     *     $ wp plugin verify-checksums akismet
     *     Success: Verified 1 of 1 plugins.
     */
    
public function __invoke$args$assoc_args ) {

        
$fetcher  = new Fetchers\UnfilteredPlugin();
        
$all      = (bool) Utils\get_flag_value$assoc_args'all'false );
        
$strict   = (bool) Utils\get_flag_value$assoc_args'strict'false );
        
$insecure = (bool) Utils\get_flag_value$assoc_args'insecure'false );
        
$plugins  $fetcher->get_many$all $this->get_all_plugin_names() : $args );

        if ( empty( 
$plugins ) && ! $all ) {
            
WP_CLI::error'You need to specify either one or more plugin slugs to check or use the --all flag to check all plugins.' );
        }

        
$skips 0;

        foreach ( 
$plugins as $plugin ) {
            
$version $this->get_plugin_version$plugin->file );

            if ( 
false === $version ) {
                
WP_CLI::warning"Could not retrieve the version for plugin {$plugin->name}, skipping." );
                
$skips++;
                continue;
            }

            
$wp_org_api = new WpOrgApi( [ 'insecure' => $insecure ] );

            try {
                
$checksums $wp_org_api->get_plugin_checksums$plugin->name$version );
            } catch ( 
Exception $exception ) {
                
$checksums false;
            }

            if ( 
false === $checksums ) {
                
WP_CLI::warning"Could not retrieve the checksums for version {$version} of plugin {$plugin->name}, skipping." );
                
$skips++;
                continue;
            }

            
$files $this->get_plugin_files$plugin->file );

            foreach ( 
$checksums as $file => $checksum_array ) {
                if ( ! 
in_array$file$filestrue ) ) {
                    
$this->add_error$plugin->name$file'File is missing' );
                }
            }

            foreach ( 
$files as $file ) {
                if ( ! 
array_key_exists$file$checksums ) ) {
                    
$this->add_error$plugin->name$file'File was added' );
                    continue;
                }

                if ( ! 
$strict && $this->is_soft_change_file$file ) ) {
                    continue;
                }

                
$result $this->check_file_checksumdirname$plugin->file ) . '/' $file$checksums$file ] );
                if ( 
true !== $result ) {
                    
$this->add_error$plugin->name$fileis_string$result ) ? $result 'Checksum does not match' );
                }
            }
        }

        if ( ! empty( 
$this->errors ) ) {
            
$formatter = new Formatter(
                
$assoc_args,
                array( 
'plugin_name''file''message' )
            );
            
$formatter->display_items$this->errors );
        }

        
$total     count$plugins );
        
$failures  countarray_uniquearray_column$this->errors'plugin_name' ) ) );
        
$successes $total $failures $skips;

        
Utils\report_batch_operation_results(
            
'plugin',
            
'verify',
            
$total,
            
$successes,
            
$failures,
            
$skips
        
);
    }

    
/**
     * Adds a new error to the array of detected errors.
     *
     * @param string $plugin_name Name of the plugin that had the error.
     * @param string $file Relative path to the file that had the error.
     * @param string $message Message explaining the error.
     */
    
private function add_error$plugin_name$file$message ) {
        
$error['plugin_name'] = $plugin_name;
        
$error['file']        = $file;
        
$error['message']     = $message;
        
$this->errors[]       = $error;
    }

    
/**
     * Gets the currently installed version for a given plugin.
     *
     * @param string $path Relative path to plugin file to get the version for.
     *
     * @return string|false Installed version of the plugin, or false if not
     *                      found.
     */
    
private function get_plugin_version$path ) {
        if ( ! isset( 
$this->plugins_data ) ) {
            
$this->plugins_data get_plugins();
        }

        if ( ! 
array_key_exists$path$this->plugins_data ) ) {
            return 
false;
        }

        return 
$this->plugins_data$path ]['Version'];
    }

    
/**
     * Gets the names of all installed plugins.
     *
     * @return array<string> Names of all installed plugins.
     */
    
private function get_all_plugin_names() {
        
$names = array();
        foreach ( 
get_plugins() as $file => $details ) {
            
$names[] = Utils\get_plugin_name$file );
        }

        return 
$names;
    }

    
/**
     * Gets the list of files that are part of the given plugin.
     *
     * @param string $path Relative path to the main plugin file.
     *
     * @return array<string> Array of files with their relative paths.
     */
    
private function get_plugin_files$path ) {
        
$folder dirname$this->get_absolute_path$path ) );

        
// Return single file plugins immediately, to avoid iterating over the
        // entire plugins folder.
        
if ( WP_PLUGIN_DIR === $folder ) {
            return (array) 
$path;
        }

        return 
$this->get_filestrailingslashit$folder ) );
    }

    
/**
     * Checks the integrity of a single plugin file by comparing it to the
     * officially provided checksum.
     *
     * @param string $path      Relative path to the plugin file to check the
     *                          integrity of.
     * @param array  $checksums Array of provided checksums to compare against.
     *
     * @return true|string
     */
    
private function check_file_checksum$path$checksums ) {
        if ( 
$this->supports_sha256()
            && 
array_key_exists'sha256'$checksums )
        ) {
            
$sha256 $this->get_sha256$this->get_absolute_path$path ) );

            return 
in_array$sha256, (array) $checksums['sha256'], true );
        }

        if ( ! 
array_key_exists'md5'$checksums ) ) {
            return 
'No matching checksum algorithm found';
        }

        
$md5 $this->get_md5$this->get_absolute_path$path ) );

        return 
in_array$md5, (array) $checksums['md5'], true );
    }

    
/**
     * Checks whether the current environment supports 256-bit SHA-2.
     *
     * Should be supported for PHP 5+, but we might find edge cases depending on
     * host.
     *
     * @return bool
     */
    
private function supports_sha256() {
        return 
true;
    }

    
/**
     * Gets the 256-bit SHA-2 of a given file.
     *
     * @param string $filepath Absolute path to the file to calculate the SHA-2
     *                         for.
     *
     * @return string
     */
    
private function get_sha256$filepath ) {
        return 
hash_file'sha256'$filepath );
    }

    
/**
     * Gets the MD5 of a given file.
     *
     * @param string $filepath Absolute path to the file to calculate the MD5
     *                         for.
     *
     * @return string
     */
    
private function get_md5$filepath ) {
        return 
hash_file'md5'$filepath );
    }

    
/**
     * Gets the absolute path to a relative plugin file.
     *
     * @param string $path Relative path to get the absolute path for.
     *
     * @return string
     */
    
private function get_absolute_path$path ) {
        return 
WP_PLUGIN_DIR '/' $path;
    }

    
/**
     * Returns a list of files that only trigger checksum errors in strict mode.
     *
     * @return array<string> Array of file names.
     */
    
private function get_soft_change_files() {
        static 
$files = array(
            
'readme.txt',
            
'readme.md',
        );

        return 
$files;
    }

    
/**
     * Checks whether a given file will only trigger checksum errors in strict
     * mode.
     *
     * @param string $file File to check.
     *
     * @return bool Whether the file only triggers checksum errors in strict
     * mode.
     */
    
private function is_soft_change_file$file ) {
        return 
in_arraystrtolower$file ), $this->get_soft_change_files(), true );
    }
}
<?
php

use WP_CLI\Dispatcher\CommandNamespace;

/**
 * Manages plugins, including installs, activations, and updates.
 */
final class Plugin_Command_Namespace extends CommandNamespace {

}
<?
php

use WP_CLI\Dispatcher\CommandNamespace;

/**
 * Downloads, installs, updates, and manages a WordPress installation.
 */
final class Core_Command_Namespace extends CommandNamespace {

}
<?
php

if ( ! class_exists'WP_CLI' ) ) {
    return;
}

$wpcli_checksum_autoloader dirname__FILE__ ) . '/vendor/autoload.php';
if ( 
file_exists$wpcli_checksum_autoloader ) ) {
    require_once 
$wpcli_checksum_autoloader;
}

WP_CLI::add_command'core''Core_Command_Namespace' );
WP_CLI::add_command'core verify-checksums''Checksum_Core_Command' );

WP_CLI::add_command'plugin''Plugin_Command_Namespace' );
WP_CLI::add_command'plugin verify-checksums''Checksum_Plugin_Command' );

<?
php namespace Mustangostang;
/**
   * Spyc -- A Simple PHP YAML Class
   * @version 0.6.2
   * @author Vlad Andersen <[email protected]>
   * @author Chris Wanstrath <[email protected]>
   * @link https://github.com/mustangostang/spyc/
   * @copyright Copyright 2005-2006 Chris Wanstrath, 2006-2011 Vlad Andersen
   * @license http://www.opensource.org/licenses/mit-license.php MIT License
   * @package Spyc
   */

/**
   * The Simple PHP YAML Class.
   *
   * This class can be used to read a YAML file and convert its contents
   * into a PHP array.  It currently supports a very limited subsection of
   * the YAML spec.
   *
   * Usage:
   * <code>
   *   $Spyc  = new Spyc;
   *   $array = $Spyc->load($file);
   * </code>
   * or:
   * <code>
   *   $array = Spyc::YAMLLoad($file);
   * </code>
   * or:
   * <code>
   *   $array = spyc_load_file($file);
   * </code>
   * @package Spyc
   */
class Spyc {

  
// SETTINGS

  
const REMPTY "\0\0\0\0\0";

  
/**
   * Setting this to true will force YAMLDump to enclose any string value in
   * quotes.  False by default.
   *
   * @var bool
   */
  
public $setting_dump_force_quotes false;

  
/**
   * Setting this to true will forse YAMLLoad to use syck_load function when
   * possible. False by default.
   * @var bool
   */
  
public $setting_use_syck_is_possible false;



  
/**#@+
  * @access private
  * @var mixed
  */
  
private $_dumpIndent;
  private 
$_dumpWordWrap;
  private 
$_containsGroupAnchor false;
  private 
$_containsGroupAlias false;
  private 
$path;
  private 
$result;
  private 
$LiteralPlaceHolder '___YAML_Literal_Block___';
  private 
$SavedGroups = array();
  private 
$indent;
  
/**
   * Path modifier that should be applied after adding current element.
   * @var array
   */
  
private $delayedPath = array();

  
/**#@+
  * @access public
  * @var mixed
  */
  
public $_nodeId;

/**
 * Load a valid YAML string to Spyc.
 * @param string $input
 * @return array
 */
  
public function load ($input) {
    return 
$this->_loadString($input);
  }

 
/**
 * Load a valid YAML file to Spyc.
 * @param string $file
 * @return array
 */
  
public function loadFile ($file) {
    return 
$this->_load($file);
  }

  
/**
     * Load YAML into a PHP array statically
     *
     * The load method, when supplied with a YAML stream (string or file),
     * will do its best to convert YAML in a file into a PHP array.  Pretty
     * simple.
     *  Usage:
     *  <code>
     *   $array = Spyc::YAMLLoad('lucky.yaml');
     *   print_r($array);
     *  </code>
     * @access public
     * @return array
     * @param string $input Path of YAML file or string containing YAML
     */
  
public static function YAMLLoad($input) {
    
$Spyc = new Spyc;
    return 
$Spyc->_load($input);
  }

  
/**
     * Load a string of YAML into a PHP array statically
     *
     * The load method, when supplied with a YAML string, will do its best
     * to convert YAML in a string into a PHP array.  Pretty simple.
     *
     * Note: use this function if you don't want files from the file system
     * loaded and processed as YAML.  This is of interest to people concerned
     * about security whose input is from a string.
     *
     *  Usage:
     *  <code>
     *   $array = Spyc::YAMLLoadString("---\n0: hello world\n");
     *   print_r($array);
     *  </code>
     * @access public
     * @return array
     * @param string $input String containing YAML
     */
  
public static function YAMLLoadString($input) {
    
$Spyc = new Spyc;
    return 
$Spyc->_loadString($input);
  }

  
/**
     * Dump YAML from PHP array statically
     *
     * The dump method, when supplied with an array, will do its best
     * to convert the array into friendly YAML.  Pretty simple.  Feel free to
     * save the returned string as nothing.yaml and pass it around.
     *
     * Oh, and you can decide how big the indent is and what the wordwrap
     * for folding is.  Pretty cool -- just pass in 'false' for either if
     * you want to use the default.
     *
     * Indent's default is 2 spaces, wordwrap's default is 40 characters.  And
     * you can turn off wordwrap by passing in 0.
     *
     * @access public
     * @return string
     * @param array|\stdClass $array PHP array
     * @param int $indent Pass in false to use the default, which is 2
     * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40)
     * @param bool $no_opening_dashes Do not start YAML file with "---\n"
     */
  
public static function YAMLDump($array$indent false$wordwrap false$no_opening_dashes false) {
    
$spyc = new Spyc;
    return 
$spyc->dump($array$indent$wordwrap$no_opening_dashes);
  }


  
/**
     * Dump PHP array to YAML
     *
     * The dump method, when supplied with an array, will do its best
     * to convert the array into friendly YAML.  Pretty simple.  Feel free to
     * save the returned string as tasteful.yaml and pass it around.
     *
     * Oh, and you can decide how big the indent is and what the wordwrap
     * for folding is.  Pretty cool -- just pass in 'false' for either if
     * you want to use the default.
     *
     * Indent's default is 2 spaces, wordwrap's default is 40 characters.  And
     * you can turn off wordwrap by passing in 0.
     *
     * @access public
     * @return string
     * @param array $array PHP array
     * @param int $indent Pass in false to use the default, which is 2
     * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40)
     */
  
public function dump($array,$indent false,$wordwrap false$no_opening_dashes false) {
    
// Dumps to some very clean YAML.  We'll have to add some more features
    // and options soon.  And better support for folding.

    // New features and options.
    
if ($indent === false or !is_numeric($indent)) {
      
$this->_dumpIndent 2;
    } else {
      
$this->_dumpIndent $indent;
    }

    if (
$wordwrap === false or !is_numeric($wordwrap)) {
      
$this->_dumpWordWrap 40;
    } else {
      
$this->_dumpWordWrap $wordwrap;
    }

    
// New YAML document
    
$string "";
    if (!
$no_opening_dashes$string "---\n";

    
// Start at the base of the array and move through it.
    
if ($array) {
      
$array = (array)$array;
      
$previous_key = -1;
      foreach (
$array as $key => $value) {
        if (!isset(
$first_key)) $first_key $key;
        
$string .= $this->_yamlize($key,$value,0,$previous_key$first_key$array);
        
$previous_key $key;
      }
    }
    return 
$string;
  }

  
/**
     * Attempts to convert a key / value array item to YAML
     * @access private
     * @return string
     * @param $key The name of the key
     * @param $value The value of the item
     * @param $indent The indent of the current node
     */
  
private function _yamlize($key,$value,$indent$previous_key = -1$first_key 0$source_array null) {
    if(
is_object($value)) $value = (array)$value;
    if (
is_array($value)) {
      if (empty (
$value))
        return 
$this->_dumpNode($key, array(), $indent$previous_key$first_key$source_array);
      
// It has children.  What to do?
      // Make it the right kind of item
      
$string $this->_dumpNode($keyself::REMPTY$indent$previous_key$first_key$source_array);
      
// Add the indent
      
$indent += $this->_dumpIndent;
      
// Yamlize the array
      
$string .= $this->_yamlizeArray($value,$indent);
    } elseif (!
is_array($value)) {
      
// It doesn't have children.  Yip.
      
$string $this->_dumpNode($key$value$indent$previous_key$first_key$source_array);
    }
    return 
$string;
  }

  
/**
     * Attempts to convert an array to YAML
     * @access private
     * @return string
     * @param $array The array you want to convert
     * @param $indent The indent of the current level
     */
  
private function _yamlizeArray($array,$indent) {
    if (
is_array($array)) {
      
$string '';
      
$previous_key = -1;
      foreach (
$array as $key => $value) {
        if (!isset(
$first_key)) $first_key $key;
        
$string .= $this->_yamlize($key$value$indent$previous_key$first_key$array);
        
$previous_key $key;
      }
      return 
$string;
    } else {
      return 
false;
    }
  }

  
/**
     * Returns YAML from a key and a value
     * @access private
     * @return string
     * @param $key The name of the key
     * @param $value The value of the item
     * @param $indent The indent of the current node
     */
  
private function _dumpNode($key$value$indent$previous_key = -1$first_key 0$source_array null) {
    
// do some folding here, for blocks
    
if (is_string ($value) && ((strpos($value,"\n") !== false || strpos($value,": ") !== false || strpos($value,"- ") !== false ||
      
strpos($value,"*") !== false || strpos($value,"#") !== false || strpos($value,"<") !== false || strpos($value,">") !== false || strpos ($value'%') !== false || strpos ($value'  ') !== false ||
      
strpos($value,"[") !== false || strpos($value,"]") !== false || strpos($value,"{") !== false || strpos($value,"}") !== false) || strpos($value,"&") !== false || strpos($value"'") !== false || strpos($value"!") === ||
      
substr ($value, -11) == ':')
    ) {
      
$value $this->_doLiteralBlock($value,$indent);
    } else {
      
$value  $this->_doFolding($value,$indent);
    }

    if (
$value === array()) $value '[ ]';
    if (
$value === ""$value '""';
    if (
self::isTranslationWord($value)) {
      
$value $this->_doLiteralBlock($value$indent);
    }
    if (
trim ($value) != $value)
       
$value $this->_doLiteralBlock($value,$indent);

    if (
is_bool($value)) {
       
$value $value "true" "false";
    }

    if (
$value === null$value 'null';
    if (
$value === "'" self::REMPTY "'"$value null;

    
$spaces str_repeat(' ',$indent);

    
//if (is_int($key) && $key - 1 == $previous_key && $first_key===0) {
    
if (is_array ($source_array) && array_keys($source_array) === range(0count($source_array) - 1)) {
      
// It's a sequence
      
$string $spaces.'- '.$value."\n";
    } else {
      
// if ($first_key===0)  throw new \Exception('Keys are all screwy.  The first one was zero, now it\'s "'. $key .'"');
      // It's mapped
      
if (strpos($key":") !== false || strpos($key"#") !== false) { $key '"' $key '"'; }
      
$string rtrim ($spaces.$key.': '.$value)."\n";
    }
    return 
$string;
  }

  
/**
     * Creates a literal block for dumping
     * @access private
     * @return string
     * @param $value
     * @param $indent int The value of the indent
     */
  
private function _doLiteralBlock($value,$indent) {
    if (
$value === "\n") return '\n';
    if (
strpos($value"\n") === false && strpos($value"'") === false) {
      return 
sprintf ("'%s'"$value);
    }
    if (
strpos($value"\n") === false && strpos($value'"') === false) {
      return 
sprintf ('"%s"'$value);
    }
    
$exploded explode("\n",$value);
    
$newValue '|';
    if (isset(
$exploded[0]) && ($exploded[0] == "|" || $exploded[0] == "|-" || $exploded[0] == ">")) {
        
$newValue $exploded[0];
        unset(
$exploded[0]);
    }
    
$indent += $this->_dumpIndent;
    
$spaces   str_repeat(' ',$indent);
    foreach (
$exploded as $line) {
      
$line trim($line);
      if (
strpos($line'"') === && strrpos($line'"') == (strlen($line)-1) || strpos($line"'") === && strrpos($line"'") == (strlen($line)-1)) {
        
$line substr($line1, -1);
      }
      
$newValue .= "\n" $spaces . ($line);
    }
    return 
$newValue;
  }

  
/**
     * Folds a string of text, if necessary
     * @access private
     * @return string
     * @param $value The string you wish to fold
     */
  
private function _doFolding($value,$indent) {
    
// Don't do anything if wordwrap is set to 0

    
if ($this->_dumpWordWrap !== && is_string ($value) && strlen($value) > $this->_dumpWordWrap) {
      
$indent += $this->_dumpIndent;
      
$indent str_repeat(' ',$indent);
      
$wrapped wordwrap($value,$this->_dumpWordWrap,"\n$indent");
      
$value   ">\n".$indent.$wrapped;
    } else {
      if (
$this->setting_dump_force_quotes && is_string ($value) && $value !== self::REMPTY)
        
$value '"' $value '"';
      if (
is_numeric($value) && is_string($value))
        
$value '"' $value '"';
    }


    return 
$value;
  }

  private function 
isTrueWord($value) {
    
$words self::getTranslations(array('true''on''yes''y'));
    return 
in_array($value$wordstrue);
  }

  private function 
isFalseWord($value) {
    
$words self::getTranslations(array('false''off''no''n'));
    return 
in_array($value$wordstrue);
  }

  private function 
isNullWord($value) {
    
$words self::getTranslations(array('null''~'));
    return 
in_array($value$wordstrue);
  }

  private function 
isTranslationWord($value) {
    return (
      
self::isTrueWord($value)  ||
      
self::isFalseWord($value) ||
      
self::isNullWord($value)
    );
  }

  
/**
     * Coerce a string into a native type
     * Reference: http://yaml.org/type/bool.html
     * TODO: Use only words from the YAML spec.
     * @access private
     * @param $value The value to coerce
     */
  
private function coerceValue(&$value) {
    if (
self::isTrueWord($value)) {
      
$value true;
    } else if (
self::isFalseWord($value)) {
      
$value false;
    } else if (
self::isNullWord($value)) {
      
$value null;
    }
  }

  
/**
     * Given a set of words, perform the appropriate translations on them to
     * match the YAML 1.1 specification for type coercing.
     * @param $words The words to translate
     * @access private
     */
  
private static function getTranslations(array $words) {
    
$result = array();
    foreach (
$words as $i) {
      
$result array_merge($result, array(ucfirst($i), strtoupper($i), strtolower($i)));
    }
    return 
$result;
  }

// LOADING FUNCTIONS

  
private function _load($input) {
    
$Source $this->loadFromSource($input);
    return 
$this->loadWithSource($Source);
  }

  private function 
_loadString($input) {
    
$Source $this->loadFromString($input);
    return 
$this->loadWithSource($Source);
  }

  private function 
loadWithSource($Source) {
    if (empty (
$Source)) return array();
    if (
$this->setting_use_syck_is_possible && function_exists ('syck_load')) {
      
$array syck_load (implode ("\n"$Source));
      return 
is_array($array) ? $array : array();
    }

    
$this->path = array();
    
$this->result = array();

    
$cnt count($Source);
    for (
$i 0$i $cnt$i++) {
      
$line $Source[$i];

      
$this->indent strlen($line) - strlen(ltrim($line));
      
$tempPath $this->getParentPathByIndent($this->indent);
      
$line self::stripIndent($line$this->indent);
      if (
self::isComment($line)) continue;
      if (
self::isEmpty($line)) continue;
      
$this->path $tempPath;

      
$literalBlockStyle self::startsLiteralBlock($line);
      if (
$literalBlockStyle) {
        
$line rtrim ($line$literalBlockStyle " \n");
        
$literalBlock '';
        
$line .= ' '.$this->LiteralPlaceHolder;
        
$literal_block_indent strlen($Source[$i+1]) - strlen(ltrim($Source[$i+1]));
        while (++
$i $cnt && $this->literalBlockContinues($Source[$i], $this->indent)) {
          
$literalBlock $this->addLiteralLine($literalBlock$Source[$i], $literalBlockStyle$literal_block_indent);
        }
        
$i--;
      }

      
// Strip out comments
      
if (strpos ($line'#')) {
          
$line preg_replace('/\s*#([^"\']+)$/','',$line);
      }

      while (++
$i $cnt && self::greedilyNeedNextLine($line)) {
        
$line rtrim ($line" \n\t\r") . ' ' ltrim ($Source[$i], " \t");
      }
      
$i--;

      
$lineArray $this->_parseLine($line);

      if (
$literalBlockStyle)
        
$lineArray $this->revertLiteralPlaceHolder ($lineArray$literalBlock);

      
$this->addArray($lineArray$this->indent);

      foreach (
$this->delayedPath as $indent => $delayedPath)
        
$this->path[$indent] = $delayedPath;

      
$this->delayedPath = array();

    }
    return 
$this->result;
  }

  private function 
loadFromSource ($input) {
    if (!empty(
$input) && strpos($input"\n") === false && file_exists($input))
      
$input file_get_contents($input);

    return 
$this->loadFromString($input);
  }

  private function 
loadFromString ($input) {
    
$lines explode("\n",$input);
    foreach (
$lines as $k => $_) {
      
$lines[$k] = rtrim ($_"\r");
    }
    return 
$lines;
  }

  
/**
     * Parses YAML code and returns an array for a node
     * @access private
     * @return array
     * @param string $line A line from the YAML file
     */
  
private function _parseLine($line) {
    if (!
$line) return array();
    
$line trim($line);
    if (!
$line) return array();

    
$array = array();

    
$group $this->nodeContainsGroup($line);
    if (
$group) {
      
$this->addGroup($line$group);
      
$line $this->stripGroup ($line$group);
    }

    if (
$this->startsMappedSequence($line))
      return 
$this->returnMappedSequence($line);

    if (
$this->startsMappedValue($line))
      return 
$this->returnMappedValue($line);

    if (
$this->isArrayElement($line))
     return 
$this->returnArrayElement($line);

    if (
$this->isPlainArray($line))
     return 
$this->returnPlainArray($line);


    return 
$this->returnKeyValuePair($line);

  }

  
/**
     * Finds the type of the passed value, returns the value as the new type.
     * @access private
     * @param string $value
     * @return mixed
     */
  
private function _toType($value) {
    if (
$value === '') return "";
    
$first_character $value[0];
    
$last_character substr($value, -11);

    
$is_quoted false;
    do {
      if (!
$value) break;
      if (
$first_character != '"' && $first_character != "'") break;
      if (
$last_character != '"' && $last_character != "'") break;
      
$is_quoted true;
    } while (
0);

    if (
$is_quoted) {
      
$value str_replace('\n'"\n"$value);
      if (
$first_character == "'")
        return 
strtr(substr ($value1, -1), array ('\'\'' => '\'''\\\''=> '\''));
      return 
strtr(substr ($value1, -1), array ('\\"' => '"''\\\''=> '\''));
    }

    if (
strpos($value' #') !== false && !$is_quoted)
      
$value preg_replace('/\s+#(.+)$/','',$value);

    if (
$first_character == '[' && $last_character == ']') {
      
// Take out strings sequences and mappings
      
$innerValue trim(substr ($value1, -1));
      if (
$innerValue === '') return array();
      
$explode $this->_inlineEscape($innerValue);
      
// Propagate value array
      
$value  = array();
      foreach (
$explode as $v) {
        
$value[] = $this->_toType($v);
      }
      return 
$value;
    }

    if (
strpos($value,': ')!==false && $first_character != '{') {
      
$array explode(': ',$value);
      
$key   trim($array[0]);
      
array_shift($array);
      
$value trim(implode(': ',$array));
      
$value $this->_toType($value);
      return array(
$key => $value);
    }

    if (
$first_character == '{' && $last_character == '}') {
      
$innerValue trim(substr ($value1, -1));
      if (
$innerValue === '') return array();
      
// Inline Mapping
      // Take out strings sequences and mappings
      
$explode $this->_inlineEscape($innerValue);
      
// Propagate value array
      
$array = array();
      foreach (
$explode as $v) {
        
$SubArr $this->_toType($v);
        if (empty(
$SubArr)) continue;
        if (
is_array ($SubArr)) {
          
$array[key($SubArr)] = $SubArr[key($SubArr)]; continue;
        }
        
$array[] = $SubArr;
      }
      return 
$array;
    }

    if (
$value == 'null' || $value == 'NULL' || $value == 'Null' || $value == '' || $value == '~') {
      return 
null;
    }

    if ( 
is_numeric($value) && preg_match ('/^(-|)[1-9]+[0-9]*$/'$value) ){
      
$intvalue = (int)$value;
      if (
$intvalue != PHP_INT_MAX && $intvalue != ~PHP_INT_MAX)
        
$value $intvalue;
      return 
$value;
    }

    if ( 
is_string($value) && preg_match('/^0[xX][0-9a-fA-F]+$/'$value)) {
      
// Hexadecimal value.
      
return hexdec($value);
    }

    
$this->coerceValue($value);

    if (
is_numeric($value)) {
      if (
$value === '0') return 0;
      if (
rtrim ($value0) === $value)
        
$value = (float)$value;
      return 
$value;
    }

    return 
$value;
  }

  
/**
     * Used in inlines to check for more inlines or quoted strings
     * @access private
     * @return array
     */
  
private function _inlineEscape($inline) {
    
// There's gotta be a cleaner way to do this...
    // While pure sequences seem to be nesting just fine,
    // pure mappings and mappings with sequences inside can't go very
    // deep.  This needs to be fixed.

    
$seqs = array();
    
$maps = array();
    
$saved_strings = array();
    
$saved_empties = array();

    
// Check for empty strings
    
$regex '/("")|(\'\')/';
    if (
preg_match_all($regex,$inline,$strings)) {
      
$saved_empties $strings[0];
      
$inline  preg_replace($regex,'YAMLEmpty',$inline);
    }
    unset(
$regex);

    
// Check for strings
    
$regex '/(?:(")|(?:\'))((?(1)[^"]+|[^\']+))(?(1)"|\')/';
    if (
preg_match_all($regex,$inline,$strings)) {
      
$saved_strings $strings[0];
      
$inline  preg_replace($regex,'YAMLString',$inline);
    }
    unset(
$regex);

    
// echo $inline;

    
$i 0;
    do {

    
// Check for sequences
    
while (preg_match('/\[([^{}\[\]]+)\]/U',$inline,$matchseqs)) {
      
$seqs[] = $matchseqs[0];
      
$inline preg_replace('/\[([^{}\[\]]+)\]/U', ('YAMLSeq' . (count($seqs) - 1) . 's'), $inline1);
    }

    
// Check for mappings
    
while (preg_match('/{([^\[\]{}]+)}/U',$inline,$matchmaps)) {
      
$maps[] = $matchmaps[0];
      
$inline preg_replace('/{([^\[\]{}]+)}/U', ('YAMLMap' . (count($maps) - 1) . 's'), $inline1);
    }

    if (
$i++ >= 10) break;

    } while (
strpos ($inline'[') !== false || strpos ($inline'{') !== false);

    
$explode explode(',',$inline);
    
$explode array_map('trim'$explode);
    
$stringi 0$i 0;

    while (
1) {

    
// Re-add the sequences
    
if (!empty($seqs)) {
      foreach (
$explode as $key => $value) {
        if (
strpos($value,'YAMLSeq') !== false) {
          foreach (
$seqs as $seqk => $seq) {
            
$explode[$key] = str_replace(('YAMLSeq'.$seqk.'s'),$seq,$value);
            
$value $explode[$key];
          }
        }
      }
    }

    
// Re-add the mappings
    
if (!empty($maps)) {
      foreach (
$explode as $key => $value) {
        if (
strpos($value,'YAMLMap') !== false) {
          foreach (
$maps as $mapk => $map) {
            
$explode[$key] = str_replace(('YAMLMap'.$mapk.'s'), $map$value);
            
$value $explode[$key];
          }
        }
      }
    }


    
// Re-add the strings
    
if (!empty($saved_strings)) {
      foreach (
$explode as $key => $value) {
        while (
strpos($value,'YAMLString') !== false) {
          
$explode[$key] = preg_replace('/YAMLString/',$saved_strings[$stringi],$value1);
          unset(
$saved_strings[$stringi]);
          ++
$stringi;
          
$value $explode[$key];
        }
      }
    }


    
// Re-add the empties
    
if (!empty($saved_empties)) {
      foreach (
$explode as $key => $value) {
        while (
strpos($value,'YAMLEmpty') !== false) {
          
$explode[$key] = preg_replace('/YAMLEmpty/'''$value1);
          
$value $explode[$key];
        }
      }
    }

    
$finished true;
    foreach (
$explode as $key => $value) {
      if (
strpos($value,'YAMLSeq') !== false) {
        
$finished false; break;
      }
      if (
strpos($value,'YAMLMap') !== false) {
        
$finished false; break;
      }
      if (
strpos($value,'YAMLString') !== false) {
        
$finished false; break;
      }
      if (
strpos($value,'YAMLEmpty') !== false) {
        
$finished false; break;
      }
    }
    if (
$finished) break;

    
$i++;
    if (
$i 10)
      break; 
// Prevent infinite loops.
    
}


    return 
$explode;
  }

  private function 
literalBlockContinues ($line$lineIndent) {
    if (!
trim($line)) return true;
    if (
strlen($line) - strlen(ltrim($line)) > $lineIndent) return true;
    return 
false;
  }

  private function 
referenceContentsByAlias ($alias) {
    do {
      if (!isset(
$this->SavedGroups[$alias])) { echo "Bad group name: $alias."; break; }
      
$groupPath $this->SavedGroups[$alias];
      
$value $this->result;
      foreach (
$groupPath as $k) {
        
$value $value[$k];
      }
    } while (
false);
    return 
$value;
  }

  private function 
addArrayInline ($array$indent) {
      
$CommonGroupPath $this->path;
      if (empty (
$array)) return false;

      foreach (
$array as $k => $_) {
        
$this->addArray(array($k => $_), $indent);
        
$this->path $CommonGroupPath;
      }
      return 
true;
  }

  private function 
addArray ($incoming_data$incoming_indent) {

   
// print_r ($incoming_data);

    
if (count ($incoming_data) > 1)
      return 
$this->addArrayInline ($incoming_data$incoming_indent);

    
$key key ($incoming_data);
    
$value = isset($incoming_data[$key]) ? $incoming_data[$key] : null;
    if (
$key === '__!YAMLZero'$key '0';

    if (
$incoming_indent == && !$this->_containsGroupAlias && !$this->_containsGroupAnchor) { // Shortcut for root-level values.
      
if ($key || $key === '' || $key === '0') {
        
$this->result[$key] = $value;
      } else {
        
$this->result[] = $valueend ($this->result); $key key ($this->result);
      }
      
$this->path[$incoming_indent] = $key;
      return;
    }



    
$history = array();
    
// Unfolding inner array tree.
    
$history[] = $_arr $this->result;
    foreach (
$this->path as $k) {
      
$history[] = $_arr $_arr[$k];
    }

    if (
$this->_containsGroupAlias) {
      
$value $this->referenceContentsByAlias($this->_containsGroupAlias);
      
$this->_containsGroupAlias false;
    }


    
// Adding string or numeric key to the innermost level or $this->arr.
    
if (is_string($key) && $key == '<<') {
      if (!
is_array ($_arr)) { $_arr = array (); }

      
$_arr array_merge ($_arr$value);
    } else if (
$key || $key === '' || $key === '0') {
      if (!
is_array ($_arr))
        
$_arr = array ($key=>$value);
      else
        
$_arr[$key] = $value;
    } else {
      if (!
is_array ($_arr)) { $_arr = array ($value); $key 0; }
      else { 
$_arr[] = $valueend ($_arr); $key key ($_arr); }
    }

    
$reverse_path array_reverse($this->path);
    
$reverse_history array_reverse ($history);
    
$reverse_history[0] = $_arr;
    
$cnt count($reverse_history) - 1;
    for (
$i 0$i $cnt$i++) {
      
$reverse_history[$i+1][$reverse_path[$i]] = $reverse_history[$i];
    }
    
$this->result $reverse_history[$cnt];

    
$this->path[$incoming_indent] = $key;

    if (
$this->_containsGroupAnchor) {
      
$this->SavedGroups[$this->_containsGroupAnchor] = $this->path;
      if (
is_array ($value)) {
        
$k key ($value);
        if (!
is_int ($k)) {
          
$this->SavedGroups[$this->_containsGroupAnchor][$incoming_indent 2] = $k;
        }
      }
      
$this->_containsGroupAnchor false;
    }

  }

  private static function 
startsLiteralBlock ($line) {
    
$lastChar substr (trim($line), -1);
    if (
$lastChar != '>' && $lastChar != '|') return false;
    if (
$lastChar == '|') return $lastChar;
    
// HTML tags should not be counted as literal blocks.
    
if (preg_match ('#<.*?>$#'$line)) return false;
    return 
$lastChar;
  }

  private static function 
greedilyNeedNextLine($line) {
    
$line trim ($line);
    if (!
strlen($line)) return false;
    if (
substr ($line, -11) == ']') return false;
    if (
$line[0] == '[') return true;
    if (
preg_match ('#^[^:]+?:\s*\[#'$line)) return true;
    return 
false;
  }

  private function 
addLiteralLine ($literalBlock$line$literalBlockStyle$indent = -1) {
    
$line self::stripIndent($line$indent);
    if (
$literalBlockStyle !== '|') {
        
$line self::stripIndent($line);
    }
    
$line rtrim ($line"\r\n\t ") . "\n";
    if (
$literalBlockStyle == '|') {
      return 
$literalBlock $line;
    }
    if (
strlen($line) == 0)
      return 
rtrim($literalBlock' ') . "\n";
    if (
$line == "\n" && $literalBlockStyle == '>') {
      return 
rtrim ($literalBlock" \t") . "\n";
    }
    if (
$line != "\n")
      
$line trim ($line"\r\n ") . " ";
    return 
$literalBlock $line;
  }

   function 
revertLiteralPlaceHolder ($lineArray$literalBlock) {
     foreach (
$lineArray as $k => $_) {
      if (
is_array($_))
        
$lineArray[$k] = $this->revertLiteralPlaceHolder ($_$literalBlock);
      else if (
substr($_, -strlen ($this->LiteralPlaceHolder)) == $this->LiteralPlaceHolder)
           
$lineArray[$k] = rtrim ($literalBlock" \r\n");
     }
     return 
$lineArray;
   }

  private static function 
stripIndent ($line$indent = -1) {
    if (
$indent == -1$indent strlen($line) - strlen(ltrim($line));
    return 
substr ($line$indent);
  }

  private function 
getParentPathByIndent ($indent) {
    if (
$indent == 0) return array();
    
$linePath $this->path;
    do {
      
end($linePath); $lastIndentInParentPath key($linePath);
      if (
$indent <= $lastIndentInParentPatharray_pop ($linePath);
    } while (
$indent <= $lastIndentInParentPath);
    return 
$linePath;
  }


  private function 
clearBiggerPathValues ($indent) {


    if (
$indent == 0$this->path = array();
    if (empty (
$this->path)) return true;

    foreach (
$this->path as $k => $_) {
      if (
$k $indent) unset ($this->path[$k]);
    }

    return 
true;
  }


  private static function 
isComment ($line) {
    if (!
$line) return false;
    if (
$line[0] == '#') return true;
    if (
trim($line" \r\n\t") == '---') return true;
    return 
false;
  }

  private static function 
isEmpty ($line) {
    return (
trim ($line) === '');
  }


  private function 
isArrayElement ($line) {
    if (!
$line || !is_scalar($line)) return false;
    if (
substr($line02) != '- ') return false;
    if (
strlen ($line) > 3)
      if (
substr($line,0,3) == '---') return false;

    return 
true;
  }

  private function 
isHashElement ($line) {
    return 
strpos($line':');
  }

  private function 
isLiteral ($line) {
    if (
$this->isArrayElement($line)) return false;
    if (
$this->isHashElement($line)) return false;
    return 
true;
  }


  private static function 
unquote ($value) {
    if (!
$value) return $value;
    if (!
is_string($value)) return $value;
    if (
$value[0] == '\'') return trim ($value'\'');
    if (
$value[0] == '"') return trim ($value'"');
    return 
$value;
  }

  private function 
startsMappedSequence ($line) {
    return (
substr($line02) == '- ' && substr ($line, -11) == ':');
  }

  private function 
returnMappedSequence ($line) {
    
$array = array();
    
$key         self::unquote(trim(substr($line,1,-1)));
    
$array[$key] = array();
    
$this->delayedPath = array(strpos ($line$key) + $this->indent => $key);
    return array(
$array);
  }

  private function 
checkKeysInValue($value) {
    if (
strchr('[{"\''$value[0]) === false) {
      if (
strchr($value': ') !== false) {
          throw new 
\Exception('Too many keys: '.$value);
      }
    }
  }

  private function 
returnMappedValue ($line) {
    
$this->checkKeysInValue($line);
    
$array = array();
    
$key         self::unquote (trim(substr($line,0,-1)));
    
$array[$key] = '';
    return 
$array;
  }

  private function 
startsMappedValue ($line) {
    return (
substr ($line, -11) == ':');
  }

  private function 
isPlainArray ($line) {
    return (
$line[0] == '[' && substr ($line, -11) == ']');
  }

  private function 
returnPlainArray ($line) {
    return 
$this->_toType($line);
  }

  private function 
returnKeyValuePair ($line) {
    
$array = array();
    
$key '';
    if (
strpos ($line': ')) {
      
// It's a key/value pair most likely
      // If the key is in double quotes pull it out
      
if (($line[0] == '"' || $line[0] == "'") && preg_match('/^(["\'](.*)["\'](\s)*:)/',$line,$matches)) {
        
$value trim(str_replace($matches[1],'',$line));
        
$key   $matches[2];
      } else {
        
// Do some guesswork as to the key and the value
        
$explode explode(': '$line);
        
$key     trim(array_shift($explode));
        
$value   trim(implode(': '$explode));
        
$this->checkKeysInValue($value);
      }
      
// Set the type of the value.  Int, string, etc
      
$value $this->_toType($value);
      if (
$key === '0'$key '__!YAMLZero';
      
$array[$key] = $value;
    } else {
      
$array = array ($line);
    }
    return 
$array;

  }


  private function 
returnArrayElement ($line) {
     if (
strlen($line) <= 1) return array(array()); // Weird %)
     
$array = array();
     
$value   trim(substr($line,1));
     
$value   $this->_toType($value);
     if (
$this->isArrayElement($value)) {
       
$value $this->returnArrayElement($value);
     }
     
$array[] = $value;
     return 
$array;
  }


  private function 
nodeContainsGroup ($line) {
    
$symbolsForReference 'A-z0-9_\-';
    if (
strpos($line'&') === false && strpos($line'*') === false) return false// Please die fast ;-)
    
if ($line[0] == '&' && preg_match('/^(&['.$symbolsForReference.']+)/'$line$matches)) return $matches[1];
    if (
$line[0] == '*' && preg_match('/^(\*['.$symbolsForReference.']+)/'$line$matches)) return $matches[1];
    if (
preg_match('/(&['.$symbolsForReference.']+)$/'$line$matches)) return $matches[1];
    if (
preg_match('/(\*['.$symbolsForReference.']+$)/'$line$matches)) return $matches[1];
    if (
preg_match ('#^\s*<<\s*:\s*(\*[^\s]+).*$#'$line$matches)) return $matches[1];
    return 
false;

  }

  private function 
addGroup ($line$group) {
    if (
$group[0] == '&'$this->_containsGroupAnchor substr ($group1);
    if (
$group[0] == '*'$this->_containsGroupAlias substr ($group1);
    
//print_r ($this->path);
  
}

  private function 
stripGroup ($line$group) {
    
$line trim(str_replace($group''$line));
    return 
$line;
  }
}
<?
php
/**
   * Spyc -- A Simple PHP YAML Class
   * @version 0.6.2
   * @author Vlad Andersen <[email protected]>
   * @author Chris Wanstrath <[email protected]>
   * @link https://github.com/mustangostang/spyc/
   * @copyright Copyright 2005-2006 Chris Wanstrath, 2006-2011 Vlad Andersen
   * @license http://www.opensource.org/licenses/mit-license.php MIT License
   * @package Spyc
   */

if (!class_exists('Mustangostang\Spyc')) {
  require_once 
dirname(__FILE__) . '/src/Spyc.php';
}

class_alias('Mustangostang\Spyc''Spyc');

require_once 
dirname(__FILE__) . '/includes/functions.php';

// Enable use of Spyc from command line
// The syntax is the following: php Spyc.php spyc.yaml
do {
  if (
PHP_SAPI != 'cli') break;
  if (empty (
$_SERVER['argc']) || $_SERVER['argc'] < 2) break;
  if (empty (
$_SERVER['PHP_SELF']) || FALSE === strpos ($_SERVER['PHP_SELF'], 'Spyc.php') ) break;
  
$file $argv[1];
  echo 
json_encode (spyc_load_file ($file));
} while (
0);
<?
php
/**
   * Spyc -- A Simple PHP YAML Class
   * @version 0.6.2
   * @author Vlad Andersen <[email protected]>
   * @author Chris Wanstrath <[email protected]>
   * @link https://github.com/mustangostang/spyc/
   * @copyright Copyright 2005-2006 Chris Wanstrath, 2006-2011 Vlad Andersen
   * @license http://www.opensource.org/licenses/mit-license.php MIT License
   * @package Spyc
   */

if (!function_exists('spyc_load')) {
  
/**
   * Parses YAML to array.
   * @param string $string YAML string.
   * @return array
   */
  
function spyc_load ($string) {
    return 
Spyc::YAMLLoadString($string);
  }
}

if (!
function_exists('spyc_load_file')) {
  
/**
   * Parses YAML to array.
   * @param string $file Path to YAML file.
   * @return array
   */
  
function spyc_load_file ($file) {
    return 
Spyc::YAMLLoad($file);
  }
}

if (!
function_exists('spyc_dump')) {
  
/**
   * Dumps array to YAML.
   * @param array $data Array.
   * @return string
   */
  
function spyc_dump ($data) {
    return 
Spyc::YAMLDump($datafalsefalsetrue);
  }
}
<?
php
/**
   * Spyc -- A Simple PHP YAML Class
   * @version 0.4.5
   * @author Vlad Andersen <[email protected]>
   * @author Chris Wanstrath <[email protected]>
   * @link http://code.google.com/p/spyc/
   * @copyright Copyright 2005-2006 Chris Wanstrath, 2006-2009 Vlad Andersen
   * @license http://www.opensource.org/licenses/mit-license.php MIT License
   * @package Spyc
   */

if (!function_exists('spyc_load')) {
  
/**
   * Parses YAML to array.
   * @param string $string YAML string.
   * @return array
   */
  
function spyc_load ($string) {
    return 
Spyc::YAMLLoadString($string);
  }
}

if (!
function_exists('spyc_load_file')) {
  
/**
   * Parses YAML to array.
   * @param string $file Path to YAML file.
   * @return array
   */
  
function spyc_load_file ($file) {
    return 
Spyc::YAMLLoad($file);
  }
}

/**
   * The Simple PHP YAML Class.
   *
   * This class can be used to read a YAML file and convert its contents
   * into a PHP array.  It currently supports a very limited subsection of
   * the YAML spec.
   *
   * Usage:
   * <code>
   *   $Spyc  = new Spyc;
   *   $array = $Spyc->load($file);
   * </code>
   * or:
   * <code>
   *   $array = Spyc::YAMLLoad($file);
   * </code>
   * or:
   * <code>
   *   $array = spyc_load_file($file);
   * </code>
   * @package Spyc
   */
class Spyc {

  
// SETTINGS

  /**
   * Setting this to true will force YAMLDump to enclose any string value in
   * quotes.  False by default.
   * 
   * @var bool
   */
  
var $setting_dump_force_quotes false;

  
/**
   * Setting this to true will forse YAMLLoad to use syck_load function when
   * possible. False by default.
   * @var bool
   */
  
var $setting_use_syck_is_possible false;



  
/**#@+
  * @access private
  * @var mixed
  */
  
var $_dumpIndent;
  var 
$_dumpWordWrap;
  var 
$_containsGroupAnchor false;
  var 
$_containsGroupAlias false;
  var 
$path;
  var 
$result;
  var 
$LiteralPlaceHolder '___YAML_Literal_Block___';
  var 
$SavedGroups = array();
  var 
$indent;
  
/**
   * Path modifier that should be applied after adding current element.
   * @var array
   */
  
var $delayedPath = array();

  
/**#@+
  * @access public
  * @var mixed
  */
  
var $_nodeId;

/**
 * Load a valid YAML string to Spyc.
 * @param string $input
 * @return array
 */
  
function load ($input) {
    return 
$this->__loadString($input);
  }

 
/**
 * Load a valid YAML file to Spyc.
 * @param string $file
 * @return array
 */
  
function loadFile ($file) {
    return 
$this->__load($file);
  }

  
/**
     * Load YAML into a PHP array statically
     *
     * The load method, when supplied with a YAML stream (string or file),
     * will do its best to convert YAML in a file into a PHP array.  Pretty
     * simple.
     *  Usage:
     *  <code>
     *   $array = Spyc::YAMLLoad('lucky.yaml');
     *   print_r($array);
     *  </code>
     * @access public
     * @return array
     * @param string $input Path of YAML file or string containing YAML
     */
  
function YAMLLoad($input) {
    
$Spyc = new Spyc;
    return 
$Spyc->__load($input);
  }

  
/**
     * Load a string of YAML into a PHP array statically
     *
     * The load method, when supplied with a YAML string, will do its best 
     * to convert YAML in a string into a PHP array.  Pretty simple.
     *
     * Note: use this function if you don't want files from the file system
     * loaded and processed as YAML.  This is of interest to people concerned
     * about security whose input is from a string.
     *
     *  Usage:
     *  <code>
     *   $array = Spyc::YAMLLoadString("---\n0: hello world\n");
     *   print_r($array);
     *  </code>
     * @access public
     * @return array
     * @param string $input String containing YAML
     */
  
function YAMLLoadString($input) {
    
$Spyc = new Spyc;
    return 
$Spyc->__loadString($input);
  }

  
/**
     * Dump YAML from PHP array statically
     *
     * The dump method, when supplied with an array, will do its best
     * to convert the array into friendly YAML.  Pretty simple.  Feel free to
     * save the returned string as nothing.yaml and pass it around.
     *
     * Oh, and you can decide how big the indent is and what the wordwrap
     * for folding is.  Pretty cool -- just pass in 'false' for either if
     * you want to use the default.
     *
     * Indent's default is 2 spaces, wordwrap's default is 40 characters.  And
     * you can turn off wordwrap by passing in 0.
     *
     * @access public
     * @return string
     * @param array $array PHP array
     * @param int $indent Pass in false to use the default, which is 2
     * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40)
     */
  
function YAMLDump($array,$indent false,$wordwrap false) {
    
$spyc = new Spyc;
    return 
$spyc->dump($array,$indent,$wordwrap);
  }


  
/**
     * Dump PHP array to YAML
     *
     * The dump method, when supplied with an array, will do its best
     * to convert the array into friendly YAML.  Pretty simple.  Feel free to
     * save the returned string as tasteful.yaml and pass it around.
     *
     * Oh, and you can decide how big the indent is and what the wordwrap
     * for folding is.  Pretty cool -- just pass in 'false' for either if
     * you want to use the default.
     *
     * Indent's default is 2 spaces, wordwrap's default is 40 characters.  And
     * you can turn off wordwrap by passing in 0.
     *
     * @access public
     * @return string
     * @param array $array PHP array
     * @param int $indent Pass in false to use the default, which is 2
     * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40)
     */
  
function dump($array,$indent false,$wordwrap false) {
    
// Dumps to some very clean YAML.  We'll have to add some more features
    // and options soon.  And better support for folding.

    // New features and options.
    
if ($indent === false or !is_numeric($indent)) {
      
$this->_dumpIndent 2;
    } else {
      
$this->_dumpIndent $indent;
    }

    if (
$wordwrap === false or !is_numeric($wordwrap)) {
      
$this->_dumpWordWrap 40;
    } else {
      
$this->_dumpWordWrap $wordwrap;
    }

    
// New YAML document
    
$string "---\n";

    
// Start at the base of the array and move through it.
    
if ($array) {
      
$array = (array)$array;
      
$first_key key($array);
      
      
$previous_key = -1;
      foreach (
$array as $key => $value) {
        
$string .= $this->_yamlize($key,$value,0,$previous_key$first_key);
        
$previous_key $key;
      }
    }
    return 
$string;
  }

  
/**
     * Attempts to convert a key / value array item to YAML
     * @access private
     * @return string
     * @param $key The name of the key
     * @param $value The value of the item
     * @param $indent The indent of the current node
     */
  
function _yamlize($key,$value,$indent$previous_key = -1$first_key 0) {
    if (
is_array($value)) {
      if (empty (
$value))
        return 
$this->_dumpNode($key, array(), $indent$previous_key$first_key);
      
// It has children.  What to do?
      // Make it the right kind of item
      
$string $this->_dumpNode($keyNULL$indent$previous_key$first_key);
      
// Add the indent
      
$indent += $this->_dumpIndent;
      
// Yamlize the array
      
$string .= $this->_yamlizeArray($value,$indent);
    } elseif (!
is_array($value)) {
      
// It doesn't have children.  Yip.
      
$string $this->_dumpNode($key$value$indent$previous_key$first_key);
    }
    return 
$string;
  }

  
/**
     * Attempts to convert an array to YAML
     * @access private
     * @return string
     * @param $array The array you want to convert
     * @param $indent The indent of the current level
     */
  
function _yamlizeArray($array,$indent) {
    if (
is_array($array)) {
      
$string '';
      
$previous_key = -1;
      
$first_key key($array);
      foreach (
$array as $key => $value) {
        
$string .= $this->_yamlize($key$value$indent$previous_key$first_key);
        
$previous_key $key;
      }
      return 
$string;
    } else {
      return 
false;
    }
  }

  
/**
     * Returns YAML from a key and a value
     * @access private
     * @return string
     * @param $key The name of the key
     * @param $value The value of the item
     * @param $indent The indent of the current node
     */
  
function _dumpNode($key$value$indent$previous_key = -1$first_key 0) {
    
// do some folding here, for blocks
    
if (is_string ($value) && ((strpos($value,"\n") !== false || strpos($value,": ") !== false || strpos($value,"- ") !== false ||
      
strpos($value,"*") !== false || strpos($value,"#") !== false || strpos($value,"<") !== false || strpos($value,">") !== false ||
      
strpos($value,"[") !== false || strpos($value,"]") !== false || strpos($value,"{") !== false || strpos($value,"}") !== false) || substr ($value, -11) == ':')) {
      
$value $this->_doLiteralBlock($value,$indent);
    } else {
      
$value  $this->_doFolding($value,$indent);
      if (
is_bool($value)) {
        
$value = ($value) ? "true" "false";
      }
    }

    if (
$value === array()) $value '[ ]';

    
$spaces str_repeat(' ',$indent);

    if (
is_int($key) && $key == $previous_key && $first_key===0) {
      
// It's a sequence
      
$string $spaces.'- '.$value."\n";
    } else {
      if (
$first_key===0)  throw new Exception('Keys are all screwy.  The first one was zero, now it\'s "'$key .'"');
      
// It's mapped
      
if (strpos($key":") !== false) { $key '"' $key '"'; }
      
$string $spaces.$key.': '.$value."\n";
    }
    return 
$string;
  }

  
/**
     * Creates a literal block for dumping
     * @access private
     * @return string
     * @param $value
     * @param $indent int The value of the indent
     */
  
function _doLiteralBlock($value,$indent) {
    if (
strpos($value"\n") === false && strpos($value"'") === false) {
      return 
sprintf ("'%s'"$value);
    }
    if (
strpos($value"\n") === false && strpos($value'"') === false) {
      return 
sprintf ('"%s"'$value);
    }
    
$exploded explode("\n",$value);
    
$newValue '|';
    
$indent  += $this->_dumpIndent;
    
$spaces   str_repeat(' ',$indent);
    foreach (
$exploded as $line) {
      
$newValue .= "\n" $spaces trim($line);
    }
    return 
$newValue;
  }

  
/**
     * Folds a string of text, if necessary
     * @access private
     * @return string
     * @param $value The string you wish to fold
     */
  
function _doFolding($value,$indent) {
    
// Don't do anything if wordwrap is set to 0

    
if ($this->_dumpWordWrap !== && is_string ($value) && strlen($value) > $this->_dumpWordWrap) {
      
$indent += $this->_dumpIndent;
      
$indent str_repeat(' ',$indent);
      
$wrapped wordwrap($value,$this->_dumpWordWrap,"\n$indent");
      
$value   ">\n".$indent.$wrapped;
    } else {
      if (
$this->setting_dump_force_quotes && is_string ($value))
        
$value '"' $value '"';
    }


    return 
$value;
  }

// LOADING FUNCTIONS

  
function __load($input) {
    
$Source $this->loadFromSource($input);
    return 
$this->loadWithSource($Source);
  }

  function 
__loadString($input) {
    
$Source $this->loadFromString($input);
    return 
$this->loadWithSource($Source);
  }

  function 
loadWithSource($Source) {
    if (empty (
$Source)) return array();
    if (
$this->setting_use_syck_is_possible && function_exists ('syck_load')) {
      
$array syck_load (implode (''$Source));
      return 
is_array($array) ? $array : array();
    }

    
$this->path = array();
    
$this->result = array();

    
$cnt count($Source);
    for (
$i 0$i $cnt$i++) {
      
$line $Source[$i];
      
      
$this->indent strlen($line) - strlen(ltrim($line));
      
$tempPath $this->getParentPathByIndent($this->indent);
      
$line $this->stripIndent($line$this->indent);
      if (
$this->isComment($line)) continue;
      if (
$this->isEmpty($line)) continue;
      
$this->path $tempPath;

      
$literalBlockStyle $this->startsLiteralBlock($line);
      if (
$literalBlockStyle) {
        
$line rtrim ($line$literalBlockStyle " \n");
        
$literalBlock '';
        
$line .= $this->LiteralPlaceHolder;

        while (++
$i $cnt && $this->literalBlockContinues($Source[$i], $this->indent)) {
          
$literalBlock $this->addLiteralLine($literalBlock$Source[$i], $literalBlockStyle);
        }
        
$i--;
      }

      while (++
$i $cnt && $this->greedilyNeedNextLine($line)) {
        
$line rtrim ($line" \n\t\r") . ' ' ltrim ($Source[$i], " \t");
      }
      
$i--;



      if (
strpos ($line'#')) {
        if (
strpos ($line'"') === false && strpos ($line"'") === false)
          
$line preg_replace('/\s+#(.+)$/','',$line);
      }

      
$lineArray $this->_parseLine($line);

      if (
$literalBlockStyle)
        
$lineArray $this->revertLiteralPlaceHolder ($lineArray$literalBlock);

      
$this->addArray($lineArray$this->indent);

      foreach (
$this->delayedPath as $indent => $delayedPath)
        
$this->path[$indent] = $delayedPath;

      
$this->delayedPath = array();

    }
    return 
$this->result;
  }

  function 
loadFromSource ($input) {
    if (!empty(
$input) && strpos($input"\n") === false && file_exists($input))
    return 
file($input);

    return 
$this->loadFromString($input);
  }

  function 
loadFromString ($input) {
    
$lines explode("\n",$input);
    foreach (
$lines as $k => $_) {
      
$lines[$k] = rtrim ($_"\r");
    }
    return 
$lines;
  }

  
/**
     * Parses YAML code and returns an array for a node
     * @access private
     * @return array
     * @param string $line A line from the YAML file
     */
  
function _parseLine($line) {
    if (!
$line) return array();
    
$line trim($line);

    if (!
$line) return array();
    
$array = array();

    
$group $this->nodeContainsGroup($line);
    if (
$group) {
      
$this->addGroup($line$group);
      
$line $this->stripGroup ($line$group);
    }

    if (
$this->startsMappedSequence($line))
      return 
$this->returnMappedSequence($line);

    if (
$this->startsMappedValue($line))
      return 
$this->returnMappedValue($line);

    if (
$this->isArrayElement($line))
     return 
$this->returnArrayElement($line);

    if (
$this->isPlainArray($line))
     return 
$this->returnPlainArray($line); 
     
     
    return 
$this->returnKeyValuePair($line);

  }

  
/**
     * Finds the type of the passed value, returns the value as the new type.
     * @access private
     * @param string $value
     * @return mixed
     */
  
function _toType($value) {
    if (
$value === '') return null;
    
$first_character $value[0];
    
$last_character substr($value, -11);

    
$is_quoted false;
    do {
      if (!
$value) break;
      if (
$first_character != '"' && $first_character != "'") break;
      if (
$last_character != '"' && $last_character != "'") break;
      
$is_quoted true;
    } while (
0);

    if (
$is_quoted)
      return 
strtr(substr ($value1, -1), array ('\\"' => '"''\'\'' => '\'''\\\'' => '\''));
    
    if (
strpos($value' #') !== false)
      
$value preg_replace('/\s+#(.+)$/','',$value);

    if (
$first_character == '[' && $last_character == ']') {
      
// Take out strings sequences and mappings
      
$innerValue trim(substr ($value1, -1));
      if (
$innerValue === '') return array();
      
$explode $this->_inlineEscape($innerValue);
      
// Propagate value array
      
$value  = array();
      foreach (
$explode as $v) {
        
$value[] = $this->_toType($v);
      }
      return 
$value;
    }

    if (
strpos($value,': ')!==false && $first_character != '{') {
      
$array explode(': ',$value);
      
$key   trim($array[0]);
      
array_shift($array);
      
$value trim(implode(': ',$array));
      
$value $this->_toType($value);
      return array(
$key => $value);
    }
    
    if (
$first_character == '{' && $last_character == '}') {
      
$innerValue trim(substr ($value1, -1));
      if (
$innerValue === '') return array();
      
// Inline Mapping
      // Take out strings sequences and mappings
      
$explode $this->_inlineEscape($innerValue);
      
// Propagate value array
      
$array = array();
      foreach (
$explode as $v) {
        
$SubArr $this->_toType($v);
        if (empty(
$SubArr)) continue;
        if (
is_array ($SubArr)) {
          
$array[key($SubArr)] = $SubArr[key($SubArr)]; continue;
        }
        
$array[] = $SubArr;
      }
      return 
$array;
    }

    if (
$value == 'null' || $value == 'NULL' || $value == 'Null' || $value == '' || $value == '~') {
      return 
null;
    }

    if (
intval($first_character) > && preg_match ('/^[1-9]+[0-9]*$/'$value)) {
      
$intvalue = (int)$value;
      if (
$intvalue != PHP_INT_MAX)
        
$value $intvalue;
      return 
$value;
    }

    if (
in_array($value,
                 array(
'true''on''+''yes''y''True''TRUE''On''ON''YES''Yes''Y'))) {
      return 
true;
    }

    if (
in_array(strtolower($value),
                 array(
'false''off''-''no''n'))) {
      return 
false;
    }

    if (
is_numeric($value)) {
      if (
$value === '0') return 0;
      if (
trim ($value0) === $value)
        
$value = (float)$value;
      return 
$value;
    }
    
    return 
$value;
  }

  
/**
     * Used in inlines to check for more inlines or quoted strings
     * @access private
     * @return array
     */
  
function _inlineEscape($inline) {
    
// There's gotta be a cleaner way to do this...
    // While pure sequences seem to be nesting just fine,
    // pure mappings and mappings with sequences inside can't go very
    // deep.  This needs to be fixed.

    
$seqs = array();
    
$maps = array();
    
$saved_strings = array();

    
// Check for strings
    
$regex '/(?:(")|(?:\'))((?(1)[^"]+|[^\']+))(?(1)"|\')/';
    if (
preg_match_all($regex,$inline,$strings)) {
      
$saved_strings $strings[0];
      
$inline  preg_replace($regex,'YAMLString',$inline);
    }
    unset(
$regex);

    
$i 0;
    do {

    
// Check for sequences
    
while (preg_match('/\[([^{}\[\]]+)\]/U',$inline,$matchseqs)) {
      
$seqs[] = $matchseqs[0];
      
$inline preg_replace('/\[([^{}\[\]]+)\]/U', ('YAMLSeq' . (count($seqs) - 1) . 's'), $inline1);
    }

    
// Check for mappings
    
while (preg_match('/{([^\[\]{}]+)}/U',$inline,$matchmaps)) {
      
$maps[] = $matchmaps[0];
      
$inline preg_replace('/{([^\[\]{}]+)}/U', ('YAMLMap' . (count($maps) - 1) . 's'), $inline1);
    }

    if (
$i++ >= 10) break;

    } while (
strpos ($inline'[') !== false || strpos ($inline'{') !== false);

    
$explode explode(', ',$inline);
    
$stringi 0$i 0;

    while (
1) {

    
// Re-add the sequences
    
if (!empty($seqs)) {
      foreach (
$explode as $key => $value) {
        if (
strpos($value,'YAMLSeq') !== false) {
          foreach (
$seqs as $seqk => $seq) {
            
$explode[$key] = str_replace(('YAMLSeq'.$seqk.'s'),$seq,$value);
            
$value $explode[$key];
          }
        }
      }
    }

    
// Re-add the mappings
    
if (!empty($maps)) {
      foreach (
$explode as $key => $value) {
        if (
strpos($value,'YAMLMap') !== false) {
          foreach (
$maps as $mapk => $map) {
            
$explode[$key] = str_replace(('YAMLMap'.$mapk.'s'), $map$value);
            
$value $explode[$key];
          }
        }
      }
    }


    
// Re-add the strings
    
if (!empty($saved_strings)) {
      foreach (
$explode as $key => $value) {
        while (
strpos($value,'YAMLString') !== false) {
          
$explode[$key] = preg_replace('/YAMLString/',$saved_strings[$stringi],$value1);
          unset(
$saved_strings[$stringi]);
          ++
$stringi;
          
$value $explode[$key];
        }
      }
    }

    
$finished true;
    foreach (
$explode as $key => $value) {
      if (
strpos($value,'YAMLSeq') !== false) {
        
$finished false; break;
      }
      if (
strpos($value,'YAMLMap') !== false) {
        
$finished false; break;
      }
      if (
strpos($value,'YAMLString') !== false) {
        
$finished false; break;
      }
    }
    if (
$finished) break;

    
$i++;
    if (
$i 10
      break; 
// Prevent infinite loops.
    
}

    return 
$explode;
  }

  function 
literalBlockContinues ($line$lineIndent) {
    if (!
trim($line)) return true;
    if (
strlen($line) - strlen(ltrim($line)) > $lineIndent) return true;
    return 
false;
  }

  function 
referenceContentsByAlias ($alias) {
    do {
      if (!isset(
$this->SavedGroups[$alias])) { echo "Bad group name: $alias."; break; }
      
$groupPath $this->SavedGroups[$alias];
      
$value $this->result;
      foreach (
$groupPath as $k) {
        
$value $value[$k];
      }
    } while (
false);
    return 
$value;
  }

  function 
addArrayInline ($array$indent) {
      
$CommonGroupPath $this->path;
      if (empty (
$array)) return false;
      
      foreach (
$array as $k => $_) {
        
$this->addArray(array($k => $_), $indent);
        
$this->path $CommonGroupPath;
      }
      return 
true;
  }

  function 
addArray ($incoming_data$incoming_indent) {

   
// print_r ($incoming_data);

    
if (count ($incoming_data) > 1)
      return 
$this->addArrayInline ($incoming_data$incoming_indent);
    
    
$key key ($incoming_data);
    
$value = isset($incoming_data[$key]) ? $incoming_data[$key] : null;
    if (
$key === '__!YAMLZero'$key '0';

    if (
$incoming_indent == && !$this->_containsGroupAlias && !$this->_containsGroupAnchor) { // Shortcut for root-level values.
      
if ($key || $key === '' || $key === '0') {
        
$this->result[$key] = $value;
      } else {
        
$this->result[] = $valueend ($this->result); $key key ($this->result);
      }
      
$this->path[$incoming_indent] = $key;
      return;
    }


    
    
$history = array();
    
// Unfolding inner array tree.
    
$history[] = $_arr $this->result;
    foreach (
$this->path as $k) {
      
$history[] = $_arr $_arr[$k];
    }

    if (
$this->_containsGroupAlias) {
      
$value $this->referenceContentsByAlias($this->_containsGroupAlias);
      
$this->_containsGroupAlias false;
    }


    
// Adding string or numeric key to the innermost level or $this->arr.
    
if (is_string($key) && $key == '<<') {
      if (!
is_array ($_arr)) { $_arr = array (); }
      
$_arr array_merge ($_arr$value);
    } else if (
$key || $key === '' || $key === '0') {
      
$_arr[$key] = $value;
    } else {
      if (!
is_array ($_arr)) { $_arr = array ($value); $key 0; }
      else { 
$_arr[] = $valueend ($_arr); $key key ($_arr); }
    }

    
$reverse_path array_reverse($this->path);
    
$reverse_history array_reverse ($history);
    
$reverse_history[0] = $_arr;
    
$cnt count($reverse_history) - 1;
    for (
$i 0$i $cnt$i++) {
      
$reverse_history[$i+1][$reverse_path[$i]] = $reverse_history[$i];
    }
    
$this->result $reverse_history[$cnt];

    
$this->path[$incoming_indent] = $key;

    if (
$this->_containsGroupAnchor) {
      
$this->SavedGroups[$this->_containsGroupAnchor] = $this->path;
      if (
is_array ($value)) {
        
$k key ($value);
        if (!
is_int ($k)) {
          
$this->SavedGroups[$this->_containsGroupAnchor][$incoming_indent 2] = $k;
        }
      }
      
$this->_containsGroupAnchor false;
    }

  }

  function 
startsLiteralBlock ($line) {
    
$lastChar substr (trim($line), -1);
    if (
$lastChar != '>' && $lastChar != '|') return false;
    if (
$lastChar == '|') return $lastChar;
    
// HTML tags should not be counted as literal blocks.
    
if (preg_match ('#<.*?>$#'$line)) return false;
    return 
$lastChar;
  }

  function 
greedilyNeedNextLine($line) {
    
$line trim ($line);
    if (!
strlen($line)) return false;
    if (
substr ($line, -11) == ']') return false;
    if (
$line[0] == '[') return true;
    if (
preg_match ('#^[^:]+?:\s*\[#'$line)) return true;
    return 
false;
  }

  function 
addLiteralLine ($literalBlock$line$literalBlockStyle) {
    
$line $this->stripIndent($line);
    
$line rtrim ($line"\r\n\t ") . "\n";
    if (
$literalBlockStyle == '|') {
      return 
$literalBlock $line;
    }
    if (
strlen($line) == 0)
      return 
rtrim($literalBlock' ') . "\n";
    if (
$line == "\n" && $literalBlockStyle == '>') {
      return 
rtrim ($literalBlock" \t") . "\n";
    }
    if (
$line != "\n")
      
$line trim ($line"\r\n ") . " ";
    return 
$literalBlock $line;
  }

   function 
revertLiteralPlaceHolder ($lineArray$literalBlock) {
     foreach (
$lineArray as $k => $_) {
      if (
is_array($_))
        
$lineArray[$k] = $this->revertLiteralPlaceHolder ($_$literalBlock);
      else if (
substr($_, -strlen ($this->LiteralPlaceHolder)) == $this->LiteralPlaceHolder)
           
$lineArray[$k] = rtrim ($literalBlock" \r\n");
     }
     return 
$lineArray;
   }

  function 
stripIndent ($line$indent = -1) {
    if (
$indent == -1$indent strlen($line) - strlen(ltrim($line));
    return 
substr ($line$indent);
  }

  function 
getParentPathByIndent ($indent) {
    if (
$indent == 0) return array();
    
$linePath $this->path;
    do {
      
end($linePath); $lastIndentInParentPath key($linePath);
      if (
$indent <= $lastIndentInParentPatharray_pop ($linePath);
    } while (
$indent <= $lastIndentInParentPath);
    return 
$linePath;
  }


  function 
clearBiggerPathValues ($indent) {


    if (
$indent == 0$this->path = array();
    if (empty (
$this->path)) return true;

    foreach (
$this->path as $k => $_) {
      if (
$k $indent) unset ($this->path[$k]);
    }

    return 
true;
  }


  function 
isComment ($line) {
    if (!
$line) return false;
    if (
$line[0] == '#') return true;
    if (
trim($line" \r\n\t") == '---') return true;
    return 
false;
  }

  function 
isEmpty ($line) {
    return (
trim ($line) === '');
  }


  function 
isArrayElement ($line) {
    if (!
$line) return false;
    if (
$line[0] != '-') return false;
    if (
strlen ($line) > 3)
      if (
substr($line,0,3) == '---') return false;
    
    return 
true;
  }

  function 
isHashElement ($line) {
    return 
strpos($line':');
  }

  function 
isLiteral ($line) {
    if (
$this->isArrayElement($line)) return false;
    if (
$this->isHashElement($line)) return false;
    return 
true;
  }


  function 
unquote ($value) {
    if (!
$value) return $value;
    if (!
is_string($value)) return $value;
    if (
$value[0] == '\'') return trim ($value'\'');
    if (
$value[0] == '"') return trim ($value'"');
    return 
$value;
  }

  function 
startsMappedSequence ($line) {
    return (
$line[0] == '-' && substr ($line, -11) == ':');
  }

  function 
returnMappedSequence ($line) {
    
$array = array();
    
$key         $this->unquote(trim(substr($line,1,-1)));
    
$array[$key] = array();
    
$this->delayedPath = array(strpos ($line$key) + $this->indent => $key);
    return array(
$array);
  }

  function 
returnMappedValue ($line) {
    
$array = array();
    
$key         $this->unquote (trim(substr($line,0,-1)));
    
$array[$key] = '';
    return 
$array;
  }

  function 
startsMappedValue ($line) {
    return (
substr ($line, -11) == ':');
  }
  
  function 
isPlainArray ($line) {
    return (
$line[0] == '[' && substr ($line, -11) == ']');
  }
  
  function 
returnPlainArray ($line) {
    return 
$this->_toType($line); 
  }  

  function 
returnKeyValuePair ($line) {
    
$array = array();
    
$key '';
    if (
strpos ($line':')) {
      
// It's a key/value pair most likely
      // If the key is in double quotes pull it out
      
if (($line[0] == '"' || $line[0] == "'") && preg_match('/^(["\'](.*)["\'](\s)*:)/',$line,$matches)) {
        
$value trim(str_replace($matches[1],'',$line));
        
$key   $matches[2];
      } else {
        
// Do some guesswork as to the key and the value
        
$explode explode(':',$line);
        
$key     trim($explode[0]);
        
array_shift($explode);
        
$value   trim(implode(':',$explode));
      }
      
// Set the type of the value.  Int, string, etc
      
$value $this->_toType($value);
      if (
$key === '0'$key '__!YAMLZero';
      
$array[$key] = $value;
    } else {
      
$array = array ($line);
    }
    return 
$array;

  }


  function 
returnArrayElement ($line) {
     if (
strlen($line) <= 1) return array(array()); // Weird %)
     
$array = array();
     
$value   trim(substr($line,1));
     
$value   $this->_toType($value);
     
$array[] = $value;
     return 
$array;
  }


  function 
nodeContainsGroup ($line) {    
    
$symbolsForReference 'A-z0-9_\-';
    if (
strpos($line'&') === false && strpos($line'*') === false) return false// Please die fast ;-)
    
if ($line[0] == '&' && preg_match('/^(&['.$symbolsForReference.']+)/'$line$matches)) return $matches[1];
    if (
$line[0] == '*' && preg_match('/^(\*['.$symbolsForReference.']+)/'$line$matches)) return $matches[1];
    if (
preg_match('/(&['.$symbolsForReference.']+)$/'$line$matches)) return $matches[1];
    if (
preg_match('/(\*['.$symbolsForReference.']+$)/'$line$matches)) return $matches[1];
    if (
preg_match ('#^\s*<<\s*:\s*(\*[^\s]+).*$#'$line$matches)) return $matches[1];
    return 
false;

  }

  function 
addGroup ($line$group) {
    if (
$group[0] == '&'$this->_containsGroupAnchor substr ($group1);
    if (
$group[0] == '*'$this->_containsGroupAlias substr ($group1);
    
//print_r ($this->path);
  
}

  function 
stripGroup ($line$group) {
    
$line trim(str_replace($group''$line));
    return 
$line;
  }
}

// Enable use of Spyc from command line
// The syntax is the following: php spyc.php spyc.yaml

define ('SPYC_FROM_COMMAND_LINE'false);

do {
  if (!
SPYC_FROM_COMMAND_LINE) break;
  if (empty (
$_SERVER['argc']) || $_SERVER['argc'] < 2) break;
  if (empty (
$_SERVER['PHP_SELF']) || $_SERVER['PHP_SELF'] != 'spyc.php') break;
  
$file $argv[1];
  
printf ("Spyc loading file: %s\n"$file);
  
print_r (spyc_load_file ($file));
} while (
0);<?php

php5to4 
("../spyc.php"'spyc-latest.php4');

function 
php5to4 ($src$dest) {
  
$code file_get_contents ($src);
  
$code preg_replace ('#(public|private|protected)\s+\$#i''var \$'$code);
  
$code preg_replace ('#(public|private|protected)\s+static\s+\$#i''var \$'$code);
  
$code preg_replace ('#(public|private|protected)\s+function#i''function'$code);
  
$code preg_replace ('#(public|private|protected)\s+static\s+function#i''function'$code);
  
$code preg_replace ('#throw new Exception\\(([^)]*)\\)#i''trigger_error($1,E_USER_ERROR)'$code);
  
$code str_replace ('self::''$this->'$code);
  
$f fopen ($dest'w');
  
fwrite($f$code);
  
fclose ($f);
  print 
"Written to $dest.\n";
}<?
php
#
#    S P Y C
#      a simple php yaml class
#   v0.3
#
# author: [chris wanstrath, [email protected]]
# websites: [http://www.yaml.org, http://spyc.sourceforge.net/]
# license: [MIT License, http://www.opensource.org/licenses/mit-license.php]
# copyright: (c) 2005-2006 Chris Wanstrath
#
# We're gonna load a file into memory and see if we get what we expect.
# If not, we're gonna complain.
#
# Pretty lo-fi.  Let's see if we can't get some unit testing going in the next,
# I dunno, 20 months?  Alright.  Go team.
#

error_reporting(E_ALL);

include(
'spyc.php4');

$yaml Spyc::YAMLLoad('../spyc.yaml');

// print_r ($yaml);

# Added in .2
if ($yaml[1040] != "Ooo, a numeric key!")
    die(
'Key: 1040 failed');

# Test mappings / types
if ($yaml['String'] != "Anyone's name, really.")
    die(
'Key: String failed');

if (
$yaml['Int'] !== 13)
    die(
'Key: Int failed');

if (
$yaml['True'] !== true)
    die(
'Key: True failed');

if (
$yaml['False'] !== false)
    die(
'Key: False failed');

if (
$yaml['Zero'] !== 0)
    die(
'Key: Zero failed');

if (isset(
$yaml['Null']))
    die(
'Key: Null failed');

if (
$yaml['Float'] !== 5.34)
    die(
'Key: Float failed');


# Test sequences
if ($yaml[0] != "PHP Class")
    die(
'Sequence 0 failed');

if (
$yaml[1] != "Basic YAML Loader")
    die(
'Sequence 1 failed');

if (
$yaml[2] != "Very Basic YAML Dumper")
    die(
'Sequence 2 failed');

# A sequence of a sequence
if ($yaml[3] != array("YAML is so easy to learn.",
                                            
"Your config files will never be the same."))
    die(
'Sequence 3 failed');

# Sequence of mappings
if ($yaml[4] != array("cpu" => "1.5ghz""ram" => "1 gig",
                                            
"os" => "os x 10.4.1"))
    die(
'Sequence 4 failed');

# Mapped sequence
if ($yaml['domains'] != array("yaml.org""php.net"))
    die(
"Key: 'domains' failed");

# A sequence like this.
if ($yaml[5] != array("program" => "Adium""platform" => "OS X",
                                            
"type" => "Chat Client"))
    die(
'Sequence 5 failed');

# A folded block as a mapped value
if ($yaml['no time'] != "There isn't any time for your tricks!\nDo you understand?")
    die(
"Key: 'no time' failed");

# A literal block as a mapped value
if ($yaml['some time'] != "There is nothing but time\nfor your tricks.")
    die(
"Key: 'some time' failed");

# Crazy combinations
if ($yaml['databases'] != array( array("name" => "spartan""notes" =>
                                                                            array( 
"Needs to be backed up",
                                                                                         
"Needs to be normalized" ),
                                                                             
"type" => "mysql" )))
  die(
"Key: 'databases' failed");

# You can be a bit tricky
if ($yaml["if: you'd"] != "like")
    die(
"Key: 'if: you\'d' failed");

# Inline sequences
if ($yaml[6] != array("One""Two""Three""Four"))
    die(
"Sequence 6 failed");

# Nested Inline Sequences
if ($yaml[7] != array("One", array("Two""And""Three"), "Four""Five"))
    die(
"Sequence 7 failed");

# Nested Nested Inline Sequences
if ($yaml[8] != array( "This", array("Is""Getting", array("Ridiculous""Guys")),
                                    
"Seriously", array("Show""Mercy")))
    die(
"Sequence 8 failed");

# Inline mappings
if ($yaml[9] != array("name" => "chris""age" => "young""brand" => "lucky strike"))
    die(
"Sequence 9 failed");

# Nested inline mappings
if ($yaml[10] != array("name" => "mark""age" => "older than chris",
                                             
"brand" => array("marlboro""lucky strike")))
    die(
"Sequence 10 failed");

# References -- they're shaky, but functional
if ($yaml['dynamic languages'] != array('Perl''Python''PHP''Ruby'))
    die(
"Key: 'dynamic languages' failed");

if (
$yaml['compiled languages'] != array('C/C++''Java'))
    die(
"Key: 'compiled languages' failed");

if (
$yaml['all languages'] != array(
                                                                        array(
'Perl''Python''PHP''Ruby'),
                                                                        array(
'C/C++''Java')
                                                                     ))
    die(
"Key: 'all languages' failed");

# Added in .2.2: Escaped quotes
if ($yaml[11] != "you know, this shouldn't work.  but it does.")
    die(
"Sequence 11 failed.");

if (
$yaml[12] != "that's my value.")
    die(
"Sequence 12 failed.");

if (
$yaml[13] != "again, that's my value.")
    die(
"Sequence 13 failed.");

if (
$yaml[14] != "here's to \"quotes\", boss.")
    die(
"Sequence 14 failed.");

if (
$yaml[15] != array( 'name' => "Foo, Bar's"'age' => 20))
    die(
"Sequence 15 failed.");

if (
$yaml[16] != array( => "a"=> array (=> 1=> 2), => "b"))
    die(
"Sequence 16 failed.");

if (
$yaml['endloop'] != "Does this line in the end indeed make Spyc go to an infinite loop?")
    die(
"[endloop] failed.");


print 
"spyc.yaml parsed correctly\n";

?><?php

/**
 * Transforms a wp-config.php file.
 */
class WPConfigTransformer {
    
/**
     * Append to end of file
     */
    
const ANCHOR_EOF 'EOF';

    
/**
     * Path to the wp-config.php file.
     *
     * @var string
     */
    
protected $wp_config_path;

    
/**
     * Original source of the wp-config.php file.
     *
     * @var string
     */
    
protected $wp_config_src;

    
/**
     * Array of parsed configs.
     *
     * @var array
     */
    
protected $wp_configs = array();

    
/**
     * Instantiates the class with a valid wp-config.php.
     *
     * @throws Exception If the wp-config.php file is missing.
     * @throws Exception If the wp-config.php file is not writable.
     *
     * @param string $wp_config_path Path to a wp-config.php file.
     */
    
public function __construct$wp_config_path ) {
        
$basename basename$wp_config_path );

        if ( ! 
file_exists$wp_config_path ) ) {
            throw new 
Exception"{$basename} does not exist." );
        }

        if ( ! 
is_writable$wp_config_path ) ) {
            throw new 
Exception"{$basename} is not writable." );
        }

        
$this->wp_config_path $wp_config_path;
    }

    
/**
     * Checks if a config exists in the wp-config.php file.
     *
     * @throws Exception If the wp-config.php file is empty.
     * @throws Exception If the requested config type is invalid.
     *
     * @param string $type Config type (constant or variable).
     * @param string $name Config name.
     *
     * @return bool
     */
    
public function exists$type$name ) {
        
$wp_config_src file_get_contents$this->wp_config_path );

        if ( ! 
trim$wp_config_src ) ) {
            throw new 
Exception'Config file is empty.' );
        }
        
// Normalize the newline to prevent an issue coming from OSX.
        
$this->wp_config_src str_replace( array( "\n\r""\r" ), "\n"$wp_config_src );
        
$this->wp_configs    $this->parse_wp_config$this->wp_config_src );

        if ( ! isset( 
$this->wp_configs$type ] ) ) {
            throw new 
Exception"Config type '{$type}' does not exist." );
        }

        return isset( 
$this->wp_configs$type ][ $name ] );
    }

    
/**
     * Get the value of a config in the wp-config.php file.
     *
     * @throws Exception If the wp-config.php file is empty.
     * @throws Exception If the requested config type is invalid.
     *
     * @param string $type Config type (constant or variable).
     * @param string $name Config name.
     *
     * @return array
     */
    
public function get_value$type$name ) {
        
$wp_config_src file_get_contents$this->wp_config_path );

        if ( ! 
trim$wp_config_src ) ) {
            throw new 
Exception'Config file is empty.' );
        }

        
$this->wp_config_src $wp_config_src;
        
$this->wp_configs    $this->parse_wp_config$this->wp_config_src );

        if ( ! isset( 
$this->wp_configs$type ] ) ) {
            throw new 
Exception"Config type '{$type}' does not exist." );
        }

        return 
$this->wp_configs$type ][ $name ]['value'];
    }

    
/**
     * Adds a config to the wp-config.php file.
     *
     * @throws Exception If the config value provided is not a string.
     * @throws Exception If the config placement anchor could not be located.
     *
     * @param string $type    Config type (constant or variable).
     * @param string $name    Config name.
     * @param string $value   Config value.
     * @param array  $options (optional) Array of special behavior options.
     *
     * @return bool
     */
    
public function add$type$name$value, array $options = array() ) {
        if ( ! 
is_string$value ) ) {
            throw new 
Exception'Config value must be a string.' );
        }

        if ( 
$this->exists$type$name ) ) {
            return 
false;
        }

        
$defaults = array(
            
'raw'       => false// Display value in raw format without quotes.
            
'anchor'    => "/* That's all, stop editing!"// Config placement anchor string.
            
'separator' => PHP_EOL// Separator between config definition and anchor string.
            
'placement' => 'before'// Config placement direction (insert before or after).
        
);

        list( 
$raw$anchor$separator$placement ) = array_valuesarray_merge$defaults$options ) );

        
$raw       = (bool) $raw;
        
$anchor    = (string) $anchor;
        
$separator = (string) $separator;
        
$placement = (string) $placement;

        if ( 
self::ANCHOR_EOF === $anchor ) {
            
$contents $this->wp_config_src $this->normalize$type$name$this->format_value$value$raw ) );
        } else {
            if ( 
false === strpos$this->wp_config_src$anchor ) ) {
                throw new 
Exception'Unable to locate placement anchor.' );
            }

            
$new_src  $this->normalize$type$name$this->format_value$value$raw ) );
            
$new_src  = ( 'after' === $placement ) ? $anchor $separator $new_src $new_src $separator $anchor;
            
$contents str_replace$anchor$new_src$this->wp_config_src );
        }

        return 
$this->save$contents );
    }

    
/**
     * Updates an existing config in the wp-config.php file.
     *
     * @throws Exception If the config value provided is not a string.
     *
     * @param string $type    Config type (constant or variable).
     * @param string $name    Config name.
     * @param string $value   Config value.
     * @param array  $options (optional) Array of special behavior options.
     *
     * @return bool
     */
    
public function update$type$name$value, array $options = array() ) {
        if ( ! 
is_string$value ) ) {
            throw new 
Exception'Config value must be a string.' );
        }

        
$defaults = array(
            
'add'       => true// Add the config if missing.
            
'raw'       => false// Display value in raw format without quotes.
            
'normalize' => false// Normalize config output using WP Coding Standards.
        
);

        list( 
$add$raw$normalize ) = array_valuesarray_merge$defaults$options ) );

        
$add       = (bool) $add;
        
$raw       = (bool) $raw;
        
$normalize = (bool) $normalize;

        if ( ! 
$this->exists$type$name ) ) {
            return ( 
$add ) ? $this->add$type$name$value$options ) : false;
        }

        
$old_src   $this->wp_configs$type ][ $name ]['src'];
        
$old_value $this->wp_configs$type ][ $name ]['value'];
        
$new_value $this->format_value$value$raw );

        if ( 
$normalize ) {
            
$new_src $this->normalize$type$name$new_value );
        } else {
            
$new_parts    $this->wp_configs$type ][ $name ]['parts'];
            
$new_parts[1] = str_replace$old_value$new_value$new_parts[1] ); // Only edit the value part.
            
$new_src      implode''$new_parts );
        }

        
$contents preg_replace(
            
sprintf'/(?<=^|;|<\?php\s|<\?\s)(\s*?)%s/m'preg_quotetrim$old_src ), '/' ) ),
            
'$1' str_replace'$''\$'trim$new_src ) ),
            
$this->wp_config_src
        
);

        return 
$this->save$contents );
    }

    
/**
     * Removes a config from the wp-config.php file.
     *
     * @param string $type Config type (constant or variable).
     * @param string $name Config name.
     *
     * @return bool
     */
    
public function remove$type$name ) {
        if ( ! 
$this->exists$type$name ) ) {
            return 
false;
        }

        
$pattern  sprintf'/(?<=^|;|<\?php\s|<\?\s)%s\s*(\S|$)/m'preg_quote$this->wp_configs$type ][ $name ]['src'], '/' ) );
        
$contents preg_replace$pattern'$1'$this->wp_config_src );

        return 
$this->save$contents );
    }

    
/**
     * Applies formatting to a config value.
     *
     * @throws Exception When a raw value is requested for an empty string.
     *
     * @param string $value Config value.
     * @param bool   $raw   Display value in raw format without quotes.
     *
     * @return mixed
     */
    
protected function format_value$value$raw ) {
        if ( 
$raw && '' === trim$value ) ) {
            throw new 
Exception'Raw value for empty string not supported.' );
        }

        return ( 
$raw ) ? $value var_export$valuetrue );
    }

    
/**
     * Normalizes the source output for a name/value pair.
     *
     * @throws Exception If the requested config type does not support normalization.
     *
     * @param string $type  Config type (constant or variable).
     * @param string $name  Config name.
     * @param mixed  $value Config value.
     *
     * @return string
     */
    
protected function normalize$type$name$value ) {
        if ( 
'constant' === $type ) {
            
$placeholder "define( '%s', %s );";
        } elseif ( 
'variable' === $type ) {
            
$placeholder '$%s = %s;';
        } else {
            throw new 
Exception"Unable to normalize config type '{$type}'." );
        }

        return 
sprintf$placeholder$name$value );
    }

    
/**
     * Parses the source of a wp-config.php file.
     *
     * @param string $src Config file source.
     *
     * @return array
     */
    
protected function parse_wp_config$src ) {
        
$configs             = array();
        
$configs['constant'] = array();
        
$configs['variable'] = array();

        
// Strip comments.
        
foreach ( token_get_all$src ) as $token ) {
            if ( 
in_array$token[0], array( T_COMMENTT_DOC_COMMENT ), true ) ) {
                
$src str_replace$token[1], ''$src );
            }
        }

        
preg_match_all'/(?<=^|;|<\?php\s|<\?\s)(\h*define\s*\(\s*[\'"](\w*?)[\'"]\s*)(,\s*(\'\'|""|\'.*?[^\\\\]\'|".*?[^\\\\]"|.*?)\s*)((?:,\s*(?:true|false)\s*)?\)\s*;)/ims'$src$constants );
        
preg_match_all'/(?<=^|;|<\?php\s|<\?\s)(\h*\$(\w+)\s*=)(\s*(\'\'|""|\'.*?[^\\\\]\'|".*?[^\\\\]"|.*?)\s*;)/ims'$src$variables );

        if ( ! empty( 
$constants[0] ) && ! empty( $constants[1] ) && ! empty( $constants[2] ) && ! empty( $constants[3] ) && ! empty( $constants[4] ) && ! empty( $constants[5] ) ) {
            foreach ( 
$constants[2] as $index => $name ) {
                
$configs['constant'][ $name ] = array(
                    
'src'   => $constants[0][ $index ],
                    
'value' => $constants[4][ $index ],
                    
'parts' => array(
                        
$constants[1][ $index ],
                        
$constants[3][ $index ],
                        
$constants[5][ $index ],
                    ),
                );
            }
        }

        if ( ! empty( 
$variables[0] ) && ! empty( $variables[1] ) && ! empty( $variables[2] ) && ! empty( $variables[3] ) && ! empty( $variables[4] ) ) {
            
// Remove duplicate(s), last definition wins.
            
$variables[2] = array_reversearray_uniquearray_reverse$variables[2], true ) ), true );
            foreach ( 
$variables[2] as $index => $name ) {
                
$configs['variable'][ $name ] = array(
                    
'src'   => $variables[0][ $index ],
                    
'value' => $variables[4][ $index ],
                    
'parts' => array(
                        
$variables[1][ $index ],
                        
$variables[3][ $index ],
                    ),
                );
            }
        }

        return 
$configs;
    }

    
/**
     * Saves new contents to the wp-config.php file.
     *
     * @throws Exception If the config file content provided is empty.
     * @throws Exception If there is a failure when saving the wp-config.php file.
     *
     * @param string $contents New config contents.
     *
     * @return bool
     */
    
protected function save$contents ) {
        if ( ! 
trim$contents ) ) {
            throw new 
Exception'Cannot save the config file with empty contents.' );
        }

        if ( 
$contents === $this->wp_config_src ) {
            return 
false;
        }

        
$result file_put_contents$this->wp_config_path$contentsLOCK_EX );

        if ( 
false === $result ) {
            throw new 
Exception'Failed to update the config file.' );
        }

        return 
true;
    }
}
<?
php

use WP_CLI\Utils;

/**
 * Schedules, runs, and deletes WP-Cron events.
 *
 * ## EXAMPLES
 *
 *     # Schedule a new cron event
 *     $ wp cron event schedule cron_test
 *     Success: Scheduled event with hook 'cron_test' for 2016-05-31 10:19:16 GMT.
 *
 *     # Run all cron events due right now
 *     $ wp cron event run --due-now
 *     Success: Executed a total of 2 cron events.
 *
 *     # Delete all scheduled cron events for the given hook
 *     $ wp cron event delete cron_test
 *     Success: Deleted 2 instances of the cron event 'cron_test'.
 *
 *     # List scheduled cron events in JSON
 *     $ wp cron event list --fields=hook,next_run --format=json
 *     [{"hook":"wp_version_check","next_run":"2016-05-31 10:15:13"},{"hook":"wp_update_plugins","next_run":"2016-05-31 10:15:13"},{"hook":"wp_update_themes","next_run":"2016-05-31 10:15:14"}]
 *
 * @package wp-cli
 */
class Cron_Event_Command extends WP_CLI_Command {

    private 
$fields = array(
        
'hook',
        
'next_run_gmt',
        
'next_run_relative',
        
'recurrence',
    );

    private static 
$time_format 'Y-m-d H:i:s';

    
/**
     * Lists scheduled cron events.
     *
     * ## OPTIONS
     *
     * [--fields=<fields>]
     * : Limit the output to specific object fields.
     *
     * [--<field>=<value>]
     * : Filter by one or more fields.
     *
     * [--field=<field>]
     * : Prints the value of a single field for each event.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - ids
     *   - json
     *   - count
     *   - yaml
     * ---
     *
     * ## AVAILABLE FIELDS
     *
     * These fields will be displayed by default for each cron event:
     * * hook
     * * next_run_gmt
     * * next_run_relative
     * * recurrence
     *
     * These fields are optionally available:
     * * time
     * * sig
     * * args
     * * schedule
     * * interval
     * * next_run
     *
     * ## EXAMPLES
     *
     *     # List scheduled cron events
     *     $ wp cron event list
     *     +-------------------+---------------------+---------------------+------------+
     *     | hook              | next_run_gmt        | next_run_relative   | recurrence |
     *     +-------------------+---------------------+---------------------+------------+
     *     | wp_version_check  | 2016-05-31 22:15:13 | 11 hours 57 minutes | 12 hours   |
     *     | wp_update_plugins | 2016-05-31 22:15:13 | 11 hours 57 minutes | 12 hours   |
     *     | wp_update_themes  | 2016-05-31 22:15:14 | 11 hours 57 minutes | 12 hours   |
     *     +-------------------+---------------------+---------------------+------------+
     *
     *     # List scheduled cron events in JSON
     *     $ wp cron event list --fields=hook,next_run --format=json
     *     [{"hook":"wp_version_check","next_run":"2016-05-31 10:15:13"},{"hook":"wp_update_plugins","next_run":"2016-05-31 10:15:13"},{"hook":"wp_update_themes","next_run":"2016-05-31 10:15:14"}]
     *
     * @subcommand list
     */
    
public function list_$args$assoc_args ) {
        
$formatter $this->get_formatter$assoc_args );

        
$events self::get_cron_events();

        if ( 
is_wp_error$events ) ) {
            
$events = array();
        }

        foreach ( 
$events as $key => $event ) {
            foreach ( 
$this->fields as $field ) {
                if ( ! empty( 
$assoc_args$field ] ) && $event->{$field} !== $assoc_args$field ] ) {
                    unset( 
$events$key ] );
                    break;
                }
            }
        }

        if ( 
'ids' === $formatter->format ) {
            echo 
implode' 'wp_list_pluck$events'hook' ) );
        } else {
            
$formatter->display_items$events );
        }

    }

    
/**
     * Schedules a new cron event.
     *
     * ## OPTIONS
     *
     * <hook>
     * : The hook name.
     *
     * [<next-run>]
     * : A Unix timestamp or an English textual datetime description compatible with `strtotime()`. Defaults to now.
     *
     * [<recurrence>]
     * : How often the event should recur. See `wp cron schedule list` for available schedule names. Defaults to no recurrence.
     *
     * [--<field>=<value>]
     * : Arguments to pass to the hook for the event. <field> should be a numeric key, not a string.
     *
     * ## EXAMPLES
     *
     *     # Schedule a new cron event
     *     $ wp cron event schedule cron_test
     *     Success: Scheduled event with hook 'cron_test' for 2016-05-31 10:19:16 GMT.
     *
     *     # Schedule new cron event with hourly recurrence
     *     $ wp cron event schedule cron_test now hourly
     *     Success: Scheduled event with hook 'cron_test' for 2016-05-31 10:20:32 GMT.
     *
     *     # Schedule new cron event and pass arguments
     *     $ wp cron event schedule cron_test '+1 hour' --0=first-argument --1=second-argument
     *     Success: Scheduled event with hook 'cron_test' for 2016-05-31 11:21:35 GMT.
     */
    
public function schedule$args$assoc_args ) {
        if ( 
count$assoc_args ) && countarray_filterarray_keys$assoc_args ), 'is_string' ) ) ) {
            
WP_CLI::warning'Numeric keys should be used for the hook arguments.' );
        }

        
$hook       $args[0];
        
$next_run   Utils\get_flag_value$args1'now' );
        
$recurrence Utils\get_flag_value$args2false );

        if ( empty( 
$next_run ) ) {
            
$timestamp time();
        } elseif ( 
is_numeric$next_run ) ) {
            
$timestamp absint$next_run );
        } else {
            
$timestamp strtotime$next_run );
        }

        if ( ! 
$timestamp ) {
            
WP_CLI::errorsprintf"'%s' is not a valid datetime."$next_run ) );
        }

        if ( ! empty( 
$recurrence ) ) {

            
$schedules wp_get_schedules();

            if ( ! isset( 
$schedules$recurrence ] ) ) {
                
WP_CLI::errorsprintf"'%s' is not a valid schedule name for recurrence."$recurrence ) );
            }

            
$event wp_schedule_event$timestamp$recurrence$hook$assoc_args );

        } else {

            
$event wp_schedule_single_event$timestamp$hook$assoc_args );

        }

        if ( 
false !== $event ) {
            
WP_CLI::successsprintf"Scheduled event with hook '%s' for %s GMT."$hookdateself::$time_format$timestamp ) ) ); //phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
        
} else {
            
WP_CLI::error'Event not scheduled.' );
        }

    }

    
/**
     * Runs the next scheduled cron event for the given hook.
     *
     * ## OPTIONS
     *
     * [<hook>...]
     * : One or more hooks to run.
     *
     * [--due-now]
     * : Run all hooks due right now.
     *
     * [--all]
     * : Run all hooks.
     *
     * ## EXAMPLES
     *
     *     # Run all cron events due right now
     *     $ wp cron event run --due-now
     *     Success: Executed a total of 2 cron events.
     */
    
public function run$args$assoc_args ) {

        if ( empty( 
$args ) && ! Utils\get_flag_value$assoc_args'due-now' ) && ! Utils\get_flag_value$assoc_args'all' ) ) {
            
WP_CLI::error'Please specify one or more cron events, or use --due-now/--all.' );
        }

        
$events self::get_cron_events();

        if ( 
is_wp_error$events ) ) {
            
WP_CLI::error$events );
        }

        
$hooks wp_list_pluck$events'hook' );
        foreach ( 
$args as $hook ) {
            if ( ! 
in_array$hook$hookstrue ) ) {
                
WP_CLI::errorsprintf"Invalid cron event '%s'"$hook ) );
            }
        }

        if ( 
Utils\get_flag_value$assoc_args'due-now' ) ) {
            
$due_events = array();
            foreach ( 
$events as $event ) {
                if ( ! empty( 
$args ) && ! in_array$event->hook$argstrue ) ) {
                    continue;
                }
                if ( 
time() >= $event->time ) {
                    
$due_events[] = $event;
                }
            }
            
$events $due_events;
        } elseif ( ! 
Utils\get_flag_value$assoc_args'all' ) ) {
            
$due_events = array();
            foreach ( 
$events as $event ) {
                if ( 
in_array$event->hook$argstrue ) ) {
                    
$due_events[] = $event;
                }
            }
            
$events $due_events;
        }

        
$executed 0;
        foreach ( 
$events as $event ) {
            
$start  microtimetrue );
            
$result self::run_event$event );
            
$total  roundmicrotimetrue ) - $start);
            
$executed++;
            
WP_CLI::logsprintf"Executed the cron event '%s' in %ss."$event->hook$total ) );
        }

        
$message = ( === $executed ) ? 'Executed a total of %d cron event.' 'Executed a total of %d cron events.';
        
WP_CLI::successsprintf$message$executed ) );
    }

    
/**
     * Unschedules all cron events for a given hook.
     *
     * ## OPTIONS
     *
     * <hook>
     * : Name of the hook for which all events should be unscheduled.
     *
     * ## EXAMPLES
     *
     *     # Unschedule a cron event on given hook.
     *     $ wp cron event unschedule cron_test
     *     Success: Unscheduled 2 events with hook 'cron_test'.
     */
    
public function unschedule$args$assoc_args ) {

        list( 
$hook ) = $args;

        if ( 
Utils\wp_version_compare'4.9.0''<' ) ) {
            
WP_CLI::error'Unscheduling events is only supported from WordPress 4.9.0 onwards.' );
        }

        
$unscheduled wp_unschedule_hook$hook );

        if ( empty( 
$unscheduled ) ) {
            
$message 'Failed to unschedule events for hook \'%1\$s.';

            
// If 0 event found on hook.
            
if ( === $unscheduled ) {
                
$message "No events found for hook '%1\$s'.";
            }

            
WP_CLI::errorsprintf$message$hook ) );

        } else {
            
WP_CLI::success(
                
sprintf(
                    
'Unscheduled %1$d %2$s for hook \'%3$s\'.',
                    
$unscheduled,
                    
Utils\pluralize'event'$unscheduled ),
                    
$hook
                
)
            );
        }

    }

    
/**
     * Executes an event immediately.
     *
     * @param stdClass $event The event
     * @return bool Whether the event was successfully executed or not.
     */
    
protected static function run_eventstdClass $event ) {

        if ( ! 
defined'DOING_CRON' ) ) {
            
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- Using native WordPress constant.
            
define'DOING_CRON'true );
        }

        if ( 
false !== $event->schedule ) {
            
$new_args = array( $event->time$event->schedule$event->hook$event->args );
            
call_user_func_array'wp_reschedule_event'$new_args );
        }

        
wp_unschedule_event$event->time$event->hook$event->args );

        
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Can't prefix dynamic hooks here, calling registered hooks.
        
do_action_ref_array$event->hook$event->args );

        return 
true;

    }

    
/**
     * Deletes all scheduled cron events for the given hook.
     *
     * ## OPTIONS
     *
     * <hook>
     * : The hook name.
     *
     * ## EXAMPLES
     *
     *     # Delete all scheduled cron events for the given hook
     *     $ wp cron event delete cron_test
     *     Success: Deleted 2 instances of the cron event 'cron_test'.
     */
    
public function delete$args$assoc_args ) {

        
$hook   $args[0];
        
$events self::get_cron_events();

        if ( 
is_wp_error$events ) ) {
            
WP_CLI::error$events );
        }

        
$deleted 0;
        foreach ( 
$events as $event ) {
            if ( 
$event->hook === $hook ) {
                
$result self::delete_event$event );
                if ( 
$result ) {
                    
$deleted++;
                } else {
                    
WP_CLI::warningsprintf"Failed to the delete the cron event '%s'."$hook ) );
                }
            }
        }

        if ( 
$deleted ) {
            
$message = ( === $deleted ) ? "Deleted the cron event '%2\$s'." "Deleted %1\$d instances of the cron event '%2\$s'.";
            
WP_CLI::successsprintf$message$deleted$hook ) );
        } else {
            
WP_CLI::errorsprintf"Invalid cron event '%s'."$hook ) );
        }

    }

    
/**
     * Deletes a cron event.
     *
     * @param stdClass $event The event
     * @return bool Whether the event was successfully deleted or not.
     */
    
protected static function delete_eventstdClass $event ) {
        
$crons _get_cron_array();

        if ( ! isset( 
$crons$event->time ][ $event->hook ][ $event->sig ] ) ) {
            return 
false;
        }

        
wp_unschedule_event$event->time$event->hook$event->args );
        return 
true;
    }

    
/**
     * Callback function to format a cron event.
     *
     * @param stdClass $event The event.
     * @return stdClass The formatted event object.
     */
    
protected static function format_eventstdClass $event ) {

        
$event->next_run          get_date_from_gmtdate'Y-m-d H:i:s'$event->time ), self::$time_format ); //phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
        
$event->next_run_gmt      dateself::$time_format$event->time ); //phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
        
$event->next_run_relative self::interval$event->time time() );
        
$event->recurrence        = ( $event->schedule ) ? self::interval$event->interval ) : 'Non-repeating';

        return 
$event;
    }

    
/**
     * Fetches an array of scheduled cron events.
     *
     * @return array|WP_Error An array of event objects, or a WP_Error object if there are no events scheduled.
     */
    
protected static function get_cron_events() {

        
$crons  _get_cron_array();
        
$events = array();

        if ( empty( 
$crons ) ) {
            return new 
WP_Error(
                
'no_events',
                
'You currently have no scheduled cron events.'
            
);
        }

        foreach ( 
$crons as $time => $hooks ) {
            foreach ( 
$hooks as $hook => $hook_events ) {
                foreach ( 
$hook_events as $sig => $data ) {

                    
$events[] = (object) array(
                        
'hook'     => $hook,
                        
'time'     => $time,
                        
'sig'      => $sig,
                        
'args'     => $data['args'],
                        
'schedule' => $data['schedule'],
                        
'interval' => Utils\get_flag_value$data'interval' ),
                    );

                }
            }
        }

        
$events array_map'Cron_Event_Command::format_event'$events );

        return 
$events;

    }

    
/**
     * Converts a time interval into human-readable format.
     *
     * Similar to WordPress' built-in `human_time_diff()` but returns two time period chunks instead of just one.
     *
     * @param int $since An interval of time in seconds
     * @return string The interval in human readable format
     */
    
private static function interval$since ) {
        if ( 
$since <= ) {
            return 
'now';
        }

        
$since absint$since );

        
// Array of time period chunks.
        
$chunks = array(
            array( 
60 60 24 365'year' ),
            array( 
60 60 24 30'month' ),
            array( 
60 60 24 7'week' ),
            array( 
60 60 24'day' ),
            array( 
60 60'hour' ),
            array( 
60'minute' ),
            array( 
1'second' ),
        );

        
// we only want to output two chunks of time here, eg:
        // x years, xx months
        // x days, xx hours
        // so there's only two bits of calculation below:

        // step one: the first chunk
        
for ( $i 0$j count$chunks ); $i $j$i++ ) {
            
$seconds $chunks$i ][0];
            
$name    $chunks$i ][1];

            
// Finding the biggest chunk (if the chunk fits, break).
            
$count floor$since $seconds );
            if ( 
floatval) !== $count ) {
                break;
            }
        }

        
// Set output var.
        
$output sprintf'%d %s'$countUtils\pluralize$nameabsint$count ) ) );

        
// Step two: the second chunk.
        
if ( $i $j ) {
            
$seconds2 $chunks$i ][0];
            
$name2    $chunks$i ][1];

            
$count2 floor( ( $since - ( $seconds $count ) ) / $seconds2 );
            if ( 
floatval) !== $count2 ) {
                
// Add to output var.
                
$output .= ' ' sprintf'%d %s'$count2Utils\pluralize$name2absint$count2 ) ) );
            }
        }

        return 
$output;
    }

    private function 
get_formatter( &$assoc_args ) {
        return new 
\WP_CLI\Formatter$assoc_args$this->fields'event' );
    }

}
<?
php

/**
 * Gets WP-Cron schedules.
 *
 * ## EXAMPLES
 *
 *     # List available cron schedules
 *     $ wp cron schedule list
 *     +------------+-------------+----------+
 *     | name       | display     | interval |
 *     +------------+-------------+----------+
 *     | hourly     | Once Hourly | 3600     |
 *     | twicedaily | Twice Daily | 43200    |
 *     | daily      | Once Daily  | 86400    |
 *     +------------+-------------+----------+
 */
class Cron_Schedule_Command extends WP_CLI_Command {

    private 
$fields = array(
        
'name',
        
'display',
        
'interval',
    );

    
/**
     * List available cron schedules.
     *
     * ## OPTIONS
     *
     * [--fields=<fields>]
     * : Limit the output to specific object fields.
     *
     * [--field=<field>]
     * : Prints the value of a single field for each schedule.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - ids
     *   - json
     *   - yaml
     * ---
     *
     * ## AVAILABLE FIELDS
     *
     * These fields will be displayed by default for each cron schedule:
     *
     * * name
     * * display
     * * interval
     *
     * There are no additional fields.
     *
     * ## EXAMPLES
     *
     *     # List available cron schedules
     *     $ wp cron schedule list
     *     +------------+-------------+----------+
     *     | name       | display     | interval |
     *     +------------+-------------+----------+
     *     | hourly     | Once Hourly | 3600     |
     *     | twicedaily | Twice Daily | 43200    |
     *     | daily      | Once Daily  | 86400    |
     *     +------------+-------------+----------+
     *
     *     # List id of available cron schedule
     *     $ wp cron schedule list --fields=name --format=ids
     *     hourly twicedaily daily
     *
     * @subcommand list
     */
    
public function list_$args$assoc_args ) {
        
$formatter $this->get_formatter$assoc_args );

        
$schedules self::get_schedules();

        if ( 
'ids' === $formatter->format ) {
            echo 
implode' 'wp_list_pluck$schedules'name' ) );
        } else {
            
$formatter->display_items$schedules );
        }

    }

    
/**
     * Callback function to format a cron schedule.
     *
     * @param array $schedule The schedule.
     * @param string $name The schedule name.
     * @return array The formatted schedule.
     */
    
protected static function format_schedule( array $schedule$name ) {
        
$schedule['name'] = $name;
        return 
$schedule;
    }

    
/**
    * Return a list of the cron schedules sorted according to interval.
    *
    * @return array The array of cron schedules. Each schedule is itself an array.
    */
    
protected static function get_schedules() {
        
$schedules wp_get_schedules();
        if ( ! empty( 
$schedules ) ) {
            
uasort$schedules'Cron_Schedule_Command::sort' );
            
$schedules array_map'Cron_Schedule_Command::format_schedule'$schedulesarray_keys$schedules ) );
        }
        return 
$schedules;
    }

    
/**
     * Callback function to sort the cron schedule array by interval.
     *
     */
    
protected static function sort( array $a, array $b ) {
        return 
$a['interval'] - $b['interval'];
    }

    private function 
get_formatter( &$assoc_args ) {
        return new 
\WP_CLI\Formatter$assoc_args$this->fields'schedule' );
    }

}
<?
php

/**
 * Tests, runs, and deletes WP-Cron events; manages WP-Cron schedules.
 *
 * ## EXAMPLES
 *
 *     # Test WP Cron spawning system
 *     $ wp cron test
 *     Success: WP-Cron spawning is working as expected.
 */
class Cron_Command extends WP_CLI_Command {

    
/**
     * Tests the WP Cron spawning system and reports back its status.
     *
     * This command tests the spawning system by performing the following steps:
     *
     * * Checks to see if the `DISABLE_WP_CRON` constant is set; errors if true
     * because WP-Cron is disabled.
     * * Checks to see if the `ALTERNATE_WP_CRON` constant is set; warns if true.
     * * Attempts to spawn WP-Cron over HTTP; warns if non 200 response code is
     * returned.
     *
     * ## EXAMPLES
     *
     *     # Cron test runs successfully.
     *     $ wp cron test
     *     Success: WP-Cron spawning is working as expected.
     */
    
public function test() {

        if ( 
defined'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) {
            
WP_CLI::error'The DISABLE_WP_CRON constant is set to true. WP-Cron spawning is disabled.' );
        }

        if ( 
defined'ALTERNATE_WP_CRON' ) && ALTERNATE_WP_CRON ) {
            
WP_CLI::warning'The ALTERNATE_WP_CRON constant is set to true. WP-Cron spawning is not asynchronous.' );
        }

        
$spawn self::get_cron_spawn();

        if ( 
is_wp_error$spawn ) ) {
            
WP_CLI::errorsprintf'WP-Cron spawn failed with error: %s'$spawn->get_error_message() ) );
        }

        
$code    wp_remote_retrieve_response_code$spawn );
        
$message wp_remote_retrieve_response_message$spawn );

        if ( 
200 === $code ) {
            
WP_CLI::success'WP-Cron spawning is working as expected.' );
        } else {
            
WP_CLI::errorsprintf'WP-Cron spawn returned HTTP status code: %1$s %2$s'$code$message ) );
        }

    }

    
/**
     * Spawns a request to `wp-cron.php` and return the response.
     *
     * This function is designed to mimic the functionality in `spawn_cron()`
     * with the addition of returning the result of the `wp_remote_post()`
     * request.
     *
     * @return WP_Error|array The response or WP_Error on failure.
     */
    
protected static function get_cron_spawn() {

        
$sslverify     \WP_CLI\Utils\wp_version_compare4.0'<' );
        
$doing_wp_cron sprintf'%.22F'microtimetrue ) );

        
$cron_request_array = array(
            
'url'  => site_url'wp-cron.php?doing_wp_cron=' $doing_wp_cron ),
            
'key'  => $doing_wp_cron,
            
'args' => array(
                
'timeout'   => 3,
                
'blocking'  => true,
                
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Calling native WordPress hook.
                
'sslverify' => apply_filters'https_local_ssl_verify'$sslverify ),
            ),
        );

        
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Calling native WordPress hook.
        
$cron_request apply_filters'cron_request'$cron_request_array );

        
# Enforce a blocking request in case something that's hooked onto the 'cron_request' filter sets it to false
        
$cron_request['args']['blocking'] = true;

        
$result wp_remote_post$cron_request['url'], $cron_request['args'] );

        return 
$result;

    }

}
<?
php

if ( ! class_exists'WP_CLI' ) ) {
    return;
}

$wpcli_cron_autoloader dirname__FILE__ ) . '/vendor/autoload.php';
if ( 
file_exists$wpcli_cron_autoloader ) ) {
    require_once 
$wpcli_cron_autoloader;
}

WP_CLI::add_command'cron''Cron_Command' );
WP_CLI::add_command'cron event''Cron_Event_Command' );
WP_CLI::add_command'cron schedule''Cron_Schedule_Command' );
<?
php

use WP_CLI\Utils;

/**
 * Lists or flushes the site's rewrite rules, updates the permalink structure.
 *
 * See the WordPress [Rewrite API](https://codex.wordpress.org/Rewrite_API) and
 * [WP Rewrite](https://codex.wordpress.org/Class_Reference/WP_Rewrite) class reference.
 *
 * ## EXAMPLES
 *
 *     # Flush rewrite rules
 *     $ wp rewrite flush
 *     Success: Rewrite rules flushed.
 *
 *     # Update permalink structure
 *     $ wp rewrite structure '/%year%/%monthnum%/%postname%'
 *     Success: Rewrite structure set.
 *
 *     # List rewrite rules
 *     $ wp rewrite list --format=csv
 *     match,query,source
 *     ^wp-json/?$,index.php?rest_route=/,other
 *     ^wp-json/(.*)?,index.php?rest_route=/$matches[1],other
 *     category/(.+?)/feed/(feed|rdf|rss|rss2|atom)/?$,index.php?category_name=$matches[1]&feed=$matches[2],category
 *     category/(.+?)/(feed|rdf|rss|rss2|atom)/?$,index.php?category_name=$matches[1]&feed=$matches[2],category
 *     category/(.+?)/embed/?$,index.php?category_name=$matches[1]&embed=true,category
 *
 * @package wp-cli
 */

class Rewrite_Command extends WP_CLI_Command {

    
/**
     * Flushes rewrite rules.
     *
     * Resets WordPress' rewrite rules based on registered post types, etc.
     *
     * To regenerate a .htaccess file with WP-CLI, you'll need to add the mod_rewrite module
     * to your wp-cli.yml or config.yml. For example:
     *
     * ```
     * apache_modules:
     *   - mod_rewrite
     * ```
     *
     * ## OPTIONS
     *
     * [--hard]
     * : Perform a hard flush - update `.htaccess` rules as well as rewrite rules in database. Works only on single site installs.
     *
     * ## EXAMPLES
     *
     *     $ wp rewrite flush
     *     Success: Rewrite rules flushed.
     */
    
public function flush$args$assoc_args ) {
        
// make sure we detect mod_rewrite if configured in apache_modules in config
        
self::apache_modules();

        if ( 
Utils\get_flag_value$assoc_args'hard' ) && ! in_array'mod_rewrite', (array) WP_CLI::get_config'apache_modules' ), true ) ) {
            
WP_CLI::warning'Regenerating a .htaccess file requires special configuration. See usage docs.' );
        }

        if ( 
Utils\get_flag_value$assoc_args'hard' ) && is_multisite() ) {
            
WP_CLI::warning"WordPress can't generate .htaccess file for a multisite install." );
        }

        
self::check_skip_plugins_themes();

        
flush_rewrite_rulesUtils\get_flag_value$assoc_args'hard' ) );

        if ( ! 
get_option'rewrite_rules' ) ) {
            
WP_CLI::warning"Rewrite rules are empty, possibly because of a missing permalink_structure option. Use 'wp rewrite list' to verify, or 'wp rewrite structure' to update permalink_structure." );
        } else {
            
WP_CLI::success'Rewrite rules flushed.' );
        }
    }

    
/**
     * Updates the permalink structure.
     *
     * Sets the post permalink structure to the specified pattern.
     *
     * To regenerate a .htaccess file with WP-CLI, you'll need to add
     * the mod_rewrite module to your [WP-CLI config](https://make.wordpress.org/cli/handbook/config/#config-files).
     * For example:
     *
     * ```
     * apache_modules:
     *   - mod_rewrite
     * ```
     *
     * ## OPTIONS
     *
     * <permastruct>
     * : The new permalink structure to apply.
     *
     * [--category-base=<base>]
     * : Set the base for category permalinks, i.e. '/category/'.
     *
     * [--tag-base=<base>]
     * : Set the base for tag permalinks, i.e. '/tag/'.
     *
     * [--hard]
     * : Perform a hard flush - update `.htaccess` rules as well as rewrite rules in database.
     *
     * ## EXAMPLES
     *
     *     $ wp rewrite structure '/%year%/%monthnum%/%postname%/'
     *     Success: Rewrite structure set.
     */
    
public function structure$args$assoc_args ) {
        global 
$wp_rewrite;

        
// copypasta from /wp-admin/options-permalink.php

        
$blog_prefix '';
        
$prefix      $blog_prefix;
        if ( 
is_multisite() && ! is_subdomain_install() && is_main_site() ) {
            
$blog_prefix '/blog';
        }

        
$permalink_structure = ( 'default' === $args[0] ) ? '' $args[0];

        if ( ! empty( 
$permalink_structure ) ) {
            
$permalink_structure preg_replace'#/+#''/''/' str_replace'#'''$permalink_structure ) );
            if ( 
$prefix && $blog_prefix ) {
                
$permalink_structure $prefix preg_replace'#^/?index\.php#'''$permalink_structure );
            } else {
                
$permalink_structure $blog_prefix $permalink_structure;
            }
        }
        
$wp_rewrite->set_permalink_structure$permalink_structure );

        
// Update category or tag bases
        
if ( isset( $assoc_args['category-base'] ) ) {

            
$category_base $assoc_args['category-base'];
            if ( ! empty( 
$category_base ) ) {
                
$category_base $blog_prefix preg_replace'#/+#''/''/' str_replace'#'''$category_base ) );
            }
            
$wp_rewrite->set_category_base$category_base );
        }

        if ( isset( 
$assoc_args['tag-base'] ) ) {

            
$tag_base $assoc_args['tag-base'];
            if ( ! empty( 
$tag_base ) ) {
                
$tag_base $blog_prefix preg_replace'#/+#''/''/' str_replace'#'''$tag_base ) );
            }
            
$wp_rewrite->set_tag_base$tag_base );
        }

        
// make sure we detect mod_rewrite if configured in apache_modules in config
        
self::apache_modules();

        
WP_CLI::success'Rewrite structure set.' );

        
// Launch a new process to flush rewrites because core expects flush
        // to happen after rewrites are set
        
$new_assoc_args = [];
        
$cmd            'rewrite flush';
        if ( 
Utils\get_flag_value$assoc_args'hard' ) ) {
            
$cmd                   .= ' --hard';
            
$new_assoc_args['hard'] = true;
            if ( ! 
in_array'mod_rewrite', (array) WP_CLI::get_config'apache_modules' ), true ) ) {
                
WP_CLI::warning'Regenerating a .htaccess file requires special configuration. See usage docs.' );
            }
        }

        
$process_run WP_CLI::runcommand$cmd );
        if ( ! empty( 
$process_run->stderr ) ) {
            
// Strip "Warning: "
            
WP_CLI::warningsubstr$process_run->stderr) );
        }
    }

    
/**
     * Gets a list of the current rewrite rules.
     *
     * ## OPTIONS
     *
     * [--match=<url>]
     * : Show rewrite rules matching a particular URL.
     *
     * [--source=<source>]
     * : Show rewrite rules from a particular source.
     *
     * [--fields=<fields>]
     * : Limit the output to specific fields. Defaults to match,query,source.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - count
     *   - yaml
     * ---
     *
     * ## EXAMPLES
     *
     *     $ wp rewrite list --format=csv
     *     match,query,source
     *     ^wp-json/?$,index.php?rest_route=/,other
     *     ^wp-json/(.*)?,index.php?rest_route=/$matches[1],other
     *     category/(.+?)/feed/(feed|rdf|rss|rss2|atom)/?$,index.php?category_name=$matches[1]&feed=$matches[2],category
     *     category/(.+?)/(feed|rdf|rss|rss2|atom)/?$,index.php?category_name=$matches[1]&feed=$matches[2],category
     *     category/(.+?)/embed/?$,index.php?category_name=$matches[1]&embed=true,category
     *
     * @subcommand list
     */
    
public function list_$args$assoc_args ) {
        global 
$wp_rewrite;

        
$rules get_option'rewrite_rules' );
        if ( ! 
$rules ) {
            
$rules = [];
            
WP_CLI::warning'No rewrite rules.' );
        }

        
self::check_skip_plugins_themes();

        
$defaults   = [
            
'source' => '',
            
'match'  => '',
            
'format' => 'table',
            
'fields' => 'match,query,source',
        ];
        
$assoc_args array_merge$defaults$assoc_args );

        
$rewrite_rules_by_source             = [];
        
$rewrite_rules_by_source['post']     = $wp_rewrite->generate_rewrite_rules$wp_rewrite->permalink_structureEP_PERMALINK );
        
$rewrite_rules_by_source['date']     = $wp_rewrite->generate_rewrite_rules$wp_rewrite->get_date_permastruct(), EP_DATE );
        
$rewrite_rules_by_source['root']     = $wp_rewrite->generate_rewrite_rules$wp_rewrite->root '/'EP_ROOT );
        
$rewrite_rules_by_source['comments'] = $wp_rewrite->generate_rewrite_rules$wp_rewrite->root $wp_rewrite->comments_baseEP_COMMENTStruetruetruefalse );
        
$rewrite_rules_by_source['search']   = $wp_rewrite->generate_rewrite_rules$wp_rewrite->get_search_permastruct(), EP_SEARCH );
        
$rewrite_rules_by_source['author']   = $wp_rewrite->generate_rewrite_rules$wp_rewrite->get_author_permastruct(), EP_AUTHORS );
        
$rewrite_rules_by_source['page']     = $wp_rewrite->page_rewrite_rules();

        
// Extra permastructs including tags, categories, etc.
        
foreach ( $wp_rewrite->extra_permastructs as $permastructname => $permastruct ) {
            if ( 
is_array$permastruct ) ) {
                
$rewrite_rules_by_source$permastructname ] = $wp_rewrite->generate_rewrite_rules$permastruct['struct'], $permastruct['ep_mask'], $permastruct['paged'], $permastruct['feed'], $permastruct['forcomments'], $permastruct['walk_dirs'], $permastruct['endpoints'] );
            } else {
                
$rewrite_rules_by_source$permastructname ] = $wp_rewrite->generate_rewrite_rules$permastructEP_NONE );
            }
        }

        
// Apply the filters used in core just in case
        
foreach ( $rewrite_rules_by_source as $source => $source_rules ) {
            
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.DynamicHooknameFound -- Can't prefix dynamic hooks here, calling hooks for custom permastructs.
            
$rewrite_rules_by_source$source ] = apply_filters$source '_rewrite_rules'$source_rules );
            if ( 
'post_tag' === $source ) {
                if ( 
Utils\wp_version_compare'3.1.0''>=' ) ) {
                    
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Calling native WordPress hook.
                    
$rewrite_rules_by_source$source ] = apply_filters'post_tag_rewrite_rules'$source_rules );
                } else {
                    
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Calling native WordPress hook.
                    
$rewrite_rules_by_source$source ] = apply_filters'tag_rewrite_rules'$source_rules );
                }
            }
        }

        
$rule_list = [];
        foreach ( 
$rules as $match => $query ) {

            if ( ! empty( 
$assoc_args['match'] ) && ! preg_match"!^$match!"trim$assoc_args['match'], '/' ) ) ) {
                continue;
            }

            
$source 'other';
            foreach ( 
$rewrite_rules_by_source as $rules_source => $source_rules ) {
                if ( 
array_key_exists$match$source_rules ) ) {
                    
$source $rules_source;
                }
            }

            if ( ! empty( 
$assoc_args['source'] ) && $source !== $assoc_args['source'] ) {
                continue;
            }

            
$rule_list[] = compact'match''query''source' );
        }

        
Utils\format_items$assoc_args['format'], $rule_listexplode','$assoc_args['fields'] ) );
    }

    
/**
     * Exposes apache modules if present in config
     *
     * Implementation Notes: This function exposes a global function
     * apache_get_modules and also sets the $is_apache global variable.
     *
     * This is so that flush_rewrite_rules will actually write out the
     * .htaccess file for apache WordPress installations. There is a check
     * to see:
     *
     * 1. if the $is_apache variable is set.
     * 2. if the mod_rewrite module is returned from the apache_get_modules
     *    function.
     *
     * To get this to work with wp-cli you'll need to add the mod_rewrite module
     * to your config.yml. For example
     *
     * ```
     * apache_modules:
     *   - mod_rewrite
     * ```
     *
     * If this isn't done then the .htaccess rewrite rules won't be flushed out
     * to disk.
     */
    
private static function apache_modules() {
        
$mods WP_CLI::get_config'apache_modules' );
        if ( ! empty( 
$mods ) && ! function_exists'apache_get_modules' ) ) {
            global 
$is_apache;
            
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
            
$is_apache true;

            
// needed for get_home_path() and .htaccess location
            
$_SERVER['SCRIPT_FILENAME'] = ABSPATH;

            function 
apache_get_modules() {
                return 
WP_CLI::get_config'apache_modules' );
            }
        }
    }

    
/**
     * Displays a warning if --skip-plugins or --skip-themes are in use.
     *
     * Skipping the loading of plugins or themes can mean some rewrite rules
     * are unregistered, which may cause erroneous behavior.
     */
    
private static function check_skip_plugins_themes() {
        
$skipped = [];
        if ( 
WP_CLI::get_config'skip-plugins' ) ) {
            
$skipped[] = 'plugins';
        }
        if ( 
WP_CLI::get_config'skip-themes' ) ) {
            
$skipped[] = 'themes';
        }
        if ( empty( 
$skipped ) ) {
            return;
        }
        
$skipped implode' and '$skipped );
        
WP_CLI::warningsprintf"Some rewrite rules may be missing because %s weren't loaded."$skipped ) );
    }
}

WP_CLI::add_command'rewrite''Rewrite_Command' );
<?
php

if ( ! class_exists'WP_CLI' ) ) {
    return;
}

$wpcli_rewrite_autoloader dirname__FILE__ ) . '/vendor/autoload.php';
if ( 
file_exists$wpcli_rewrite_autoloader ) ) {
    require_once 
$wpcli_rewrite_autoloader;
}

WP_CLI::add_command'rewrite''Rewrite_Command' );
<?
php

if ( ! class_exists'WP_CLI' ) ) {
    return;
}

$wpcli_eval_autoloader dirname__FILE__ ) . '/vendor/autoload.php';
if ( 
file_exists$wpcli_eval_autoloader ) ) {
    require_once 
$wpcli_eval_autoloader;
}

WP_CLI::add_command'eval''Eval_Command' );
WP_CLI::add_command'eval-file''EvalFile_Command' );
<?
php

use WP_CLI\Utils;

class 
EvalFile_Command extends WP_CLI_Command {

    
/**
     * Regular expression pattern to match the shell shebang.
     *
     * @var string
     */
    
const SHEBANG_PATTERN '/^(#!.*)$/m';

    
/**
     * Loads and executes a PHP file.
     *
     * Note: because code is executed within a method, global variables need
     * to be explicitly globalized.
     *
     * ## OPTIONS
     *
     * <file>
     * : The path to the PHP file to execute.  Use '-' to run code from STDIN.
     *
     * [<arg>...]
     * : One or more arguments to pass to the file. They are placed in the $args variable.
     *
     * [--skip-wordpress]
     * : Load and execute file without loading WordPress.
     *
     * @when before_wp_load
     *
     * ## EXAMPLES
     *
     *     wp eval-file my-code.php value1 value2
     */
    
public function __invoke$args$assoc_args ) {
        
$file array_shift$args );

        if ( 
'-' !== $file && ! file_exists$file ) ) {
            
WP_CLI::error"'$file' does not exist." );
        }

        if ( 
null === Utils\get_flag_value$assoc_args'skip-wordpress' ) ) {
            
WP_CLI::get_runner()->load_wordpress();
        }

        
self::execute_eval$file$args );
    }

    
/**
     * Evaluate a provided file.
     *
     * @param string $file Filepath to execute, or - for STDIN.
     * @param mixed  $args Array of positional arguments to pass to the file.
     */
    
private static function execute_eval$file$args ) {
        if ( 
'-' === $file ) {
            eval( 
'?>' file_get_contents'php://stdin' ) );
        } else {
            
$file_contents file_get_contents$file );

            
// Adjust for __FILE__ and __DIR__ magic constants.
            
$file_contents Utils\replace_path_consts$file_contents$file );

            
// Check for and remove she-bang.
            
if ( === strncmp$file_contents'#!') ) {
                
$file_contents preg_replace( static::SHEBANG_PATTERN''$file_contents );
            }

            eval( 
'?>' $file_contents );
        }
    }
}

<?
php

use WP_CLI\Utils;

class 
Eval_Command extends WP_CLI_Command {

    
/**
     * Executes arbitrary PHP code.
     *
     * Note: because code is executed within a method, global variables need
     * to be explicitly globalized.
     *
     * ## OPTIONS
     *
     * <php-code>
     * : The code to execute, as a string.
     *
     * [--skip-wordpress]
     * : Execute code without loading WordPress.
     *
     * ## EXAMPLES
     *
     *     # Display WordPress content directory.
     *     $ wp eval 'echo WP_CONTENT_DIR;'
     *     /var/www/wordpress/wp-content
     *
     *     # Generate a random number.
     *     $ wp eval 'echo rand();' --skip-wordpress
     *     479620423
     *
     * @when before_wp_load
     */
    
public function __invoke$args$assoc_args ) {

        if ( 
null === Utils\get_flag_value$assoc_args'skip-wordpress' ) ) {
            
WP_CLI::get_runner()->load_wordpress();
        }

        eval( 
$args[0] );
    }
}
<?
php

if ( ! class_exists'WP_CLI' ) ) {
    return;
}

$wpcli_scaffold_autoloader dirname__FILE__ ) . '/vendor/autoload.php';
if ( 
file_exists$wpcli_scaffold_autoloader ) ) {
    require_once 
$wpcli_scaffold_autoloader;
}

WP_CLI::add_command'scaffold''Scaffold_Command' );
<?
php

use WP_CLI\Utils;
use 
WP_CLI\Inflector;

/**
 * Generates code for post types, taxonomies, plugins, child themes, etc.
 *
 * ## EXAMPLES
 *
 *     # Generate a new plugin with unit tests
 *     $ wp scaffold plugin sample-plugin
 *     Success: Created plugin files.
 *     Success: Created test files.
 *
 *     # Generate theme based on _s
 *     $ wp scaffold _s sample-theme --theme_name="Sample Theme" --author="John Doe"
 *     Success: Created theme 'Sample Theme'.
 *
 *     # Generate code for post type registration in given theme
 *     $ wp scaffold post-type movie --label=Movie --theme=simple-life
 *     Success: Created /var/www/example.com/public_html/wp-content/themes/simple-life/post-types/movie.php
 *
 * @package wp-cli
 */
class Scaffold_Command extends WP_CLI_Command {

    
/**
     * Generates PHP code for registering a custom post type.
     *
     * ## OPTIONS
     *
     * <slug>
     * : The internal name of the post type.
     *
     * [--label=<label>]
     * : The text used to translate the update messages.
     *
     * [--textdomain=<textdomain>]
     * : The textdomain to use for the labels.
     *
     * [--dashicon=<dashicon>]
     * : The dashicon to use in the menu.
     *
     * [--theme]
     * : Create a file in the active theme directory, instead of sending to
     * STDOUT. Specify a theme with `--theme=<theme>` to have the file placed in that theme.
     *
     * [--plugin=<plugin>]
     * : Create a file in the given plugin's directory, instead of sending to STDOUT.
     *
     * [--raw]
     * : Just generate the `register_post_type()` call and nothing else.
     *
     * [--force]
     * : Overwrite files that already exist.
     *
     * ## EXAMPLES
     *
     *     # Generate a 'movie' post type for the 'simple-life' theme
     *     $ wp scaffold post-type movie --label=Movie --theme=simple-life
     *     Success: Created '/var/www/example.com/public_html/wp-content/themes/simple-life/post-types/movie.php'.
     *
     * @subcommand post-type
     *
     * @alias      cpt
     */
    
public function post_type$args$assoc_args ) {

        if ( 
strlen$args[0] ) > 20 ) {
            
WP_CLI::error'Post type slugs cannot exceed 20 characters in length.' );
        }

        
$defaults = [
            
'textdomain' => '',
            
'dashicon'   => 'admin-post',
        ];

        
$templates = [
            
'post_type.mustache',
            
'post_type_extended.mustache',
        ];

        
$this->scaffold$args[0], $assoc_args$defaults'/post-types/'$templates );
    }

    
/**
     * Generates PHP code for registering a custom taxonomy.
     *
     * ## OPTIONS
     *
     * <slug>
     * : The internal name of the taxonomy.
     *
     * [--post_types=<post-types>]
     * : Post types to register for use with the taxonomy.
     *
     * [--label=<label>]
     * : The text used to translate the update messages.
     *
     * [--textdomain=<textdomain>]
     * : The textdomain to use for the labels.
     *
     * [--theme]
     * : Create a file in the active theme directory, instead of sending to
     * STDOUT. Specify a theme with `--theme=<theme>` to have the file placed in that theme.
     *
     * [--plugin=<plugin>]
     * : Create a file in the given plugin's directory, instead of sending to STDOUT.
     *
     * [--raw]
     * : Just generate the `register_taxonomy()` call and nothing else.
     *
     * [--force]
     * : Overwrite files that already exist.
     *
     * ## EXAMPLES
     *
     *     # Generate PHP code for registering a custom taxonomy and save in a file
     *     $ wp scaffold taxonomy venue --post_types=event,presentation > taxonomy.php
     *
     * @subcommand taxonomy
     *
     * @alias      tax
     */
    
public function taxonomy$args$assoc_args ) {
        
$defaults = [
            
'textdomain' => '',
            
'post_types' => "'post'",
        ];

        if ( isset( 
$assoc_args['post_types'] ) ) {
            
$assoc_args['post_types'] = $this->quote_comma_list_elements$assoc_args['post_types'] );
        }

        
$templates = [
            
'taxonomy.mustache',
            
'taxonomy_extended.mustache',
        ];

        
$this->scaffold$args[0], $assoc_args$defaults'/taxonomies/'$templates );
    }

    private function 
scaffold$slug$assoc_args$defaults$subdir$templates ) {
        
$wp_filesystem $this->init_wp_filesystem();

        
$control_defaults = [
            
'label'  => preg_replace'/_|-/'' 'strtolower$slug ) ),
            
'theme'  => false,
            
'plugin' => false,
            
'raw'    => false,
        ];
        
$control_args     $this->extract_args$assoc_args$control_defaults );

        
$vars $this->extract_args$assoc_args$defaults );

        
$dashicon $this->extract_dashicon$assoc_args );
        if ( 
$dashicon ) {
            
$vars['dashicon'] = $dashicon;
        }

        
$vars['slug'] = $slug;

        
$vars['textdomain'] = $this->get_textdomain$vars['textdomain'], $control_args );

        
$vars['label'] = $control_args['label'];

        
$vars['label_ucfirst']        = ucfirst$vars['label'] );
        
$vars['label_plural']         = $this->pluralize$vars['label'] );
        
$vars['label_plural_ucfirst'] = ucfirst$vars['label_plural'] );

        
$machine_name        $this->generate_machine_name$slug );
        
$machine_name_plural $this->pluralize$slug );

        list( 
$raw_template$extended_template ) = $templates;

        
$raw_output self::mustache_render$raw_template$vars );

        if ( ! 
$control_args['raw'] ) {
            
$vars['machine_name'] = $machine_name;
            
$vars['output']       = $raw_output;

            
$final_output self::mustache_render$extended_template$vars );
        } else {
            
$final_output $raw_output;
        }

        
$path $this->get_output_path$control_args$subdir );
        if ( 
is_string$path ) && ! empty( $path ) ) {
            
$filename "{$path}{$slug}.php";

            
$force           Utils\get_flag_value$assoc_args'force' );
            
$files_written   $this->create_files( [ $filename => $final_output ], $force );
            
$skip_message    "Skipped creating '{$filename}'.";
            
$success_message "Created '{$filename}'.";
            
$this->log_whether_files_written$files_written$skip_message$success_message );

        } else {
            
// STDOUT
            
echo $final_output;
        }
    }

    
/**
     * Generates PHP, JS and CSS code for registering a Gutenberg block for a plugin or theme.
     *
     * Blocks are the fundamental element of the Gutenberg editor. They are the primary way in which plugins and themes can register their own functionality and extend the capabilities of the editor.
     *
     * Visit the [Gutenberg handbook](https://wordpress.org/gutenberg/handbook/block-api/) to learn more about Block API.
     *
     * When you scaffold a block you must use either the theme or plugin option. The latter is recommended.
     *
     * ## OPTIONS
     *
     * <slug>
     * : The internal name of the block.
     *
     * [--title=<title>]
     * : The display title for your block.
     *
     * [--dashicon=<dashicon>]
     * : The dashicon to make it easier to identify your block.
     *
     * [--category=<category>]
     * : The category name to help users browse and discover your block.
     * ---
     * default: widgets
     * options:
     *   - common
     *   - embed
     *   - formatting
     *   - layout
     *   - widgets
     * ---
     *
     * [--theme]
     * : Create files in the active theme directory. Specify a theme with `--theme=<theme>` to have the file placed in that theme.
     *
     * [--plugin=<plugin>]
     * : Create files in the given plugin's directory.
     *
     * [--force]
     * : Overwrite files that already exist.
     *
     * ## EXAMPLES
     *
     *     # Generate a 'movie' block for the 'movies' plugin
     *     $ wp scaffold block movie --title="Movie block" --plugin=movies
     *     Success: Created block 'Movie block'.
     *
     *     # Generate a 'movie' block for the 'simple-life' theme
     *     $ wp scaffold block movie --title="Movie block" --theme=simple-life
     *      Success: Created block 'Movie block'.
     *
     *     # Create a new plugin and add two blocks
     *     # Create plugin called books
     *     $ wp scaffold plugin books
     *     # Add a block called book to plugin books
     *     $ wp scaffold block book --title="Book" --plugin=books
     *     # Add a second block to plugin called books.
     *     $ wp scaffold block books --title="Book List" --plugin=books
     *
     * @subcommand block
     */
    
public function block$args$assoc_args ) {

        
$slug $args[0];
        if ( ! 
preg_match'/^[a-z][a-z0-9\-]*$/'$slug ) ) {
            
WP_CLI::error'Invalid block slug specified. Block slugs can contain only lowercase alphanumeric characters or dashes, and start with a letter.' );
        }

        
$defaults = [
            
'title'    => str_replace'-'' '$slug ),
            
'category' => 'widgets',
        ];
        
$data     $this->extract_args$assoc_args$defaults );

        
$data['slug']             = $slug;
        
$data['title_ucfirst']    = ucfirst$data['title'] );
        
$data['title_ucfirst_js'] = esc_js$data['title_ucfirst'] );

        
$dashicon $this->extract_dashicon$assoc_args );
        if ( 
$dashicon ) {
            
$data['dashicon'] = $dashicon;
        }

        
$control_defaults = [
            
'force'  => false,
            
'plugin' => false,
            
'theme'  => false,
        ];
        
$control_args     $this->extract_args$assoc_args$control_defaults );

        if ( isset( 
$control_args['plugin'] ) ) {
            if ( ! 
preg_match'/^[A-Za-z0-9\-]*$/'$control_args['plugin'] ) ) {
                
WP_CLI::error'Invalid plugin name specified. The block editor can only register blocks for plugins that have nothing but lowercase alphanumeric characters or dashes in their slug.' );
            }
        }

        
$data['namespace']    = $control_args['plugin'] ? $control_args['plugin'] : $this->get_theme_name$control_args['theme'] );
        
$data['machine_name'] = $this->generate_machine_name$slug );
        
$data['plugin']       = $control_args['plugin'] ? true false;
        
$data['theme']        = ! $data['plugin'];

        
$block_dir $this->get_output_path$control_args'/blocks' );
        if ( ! 
$block_dir ) {
            
WP_CLI::error'No plugin or theme selected.' );
        }

        
$files_to_create = [
            
"{$block_dir}/{$slug}.php"        => self::mustache_render'block-php.mustache'$data ),
            
"{$block_dir}/{$slug}/index.js"   => self::mustache_render'block-index-js.mustache'$data ),
            
"{$block_dir}/{$slug}/editor.css" => self::mustache_render'block-editor-css.mustache'$data ),
            
"{$block_dir}/{$slug}/style.css"  => self::mustache_render'block-style-css.mustache'$data ),
        ];
        
$files_written   $this->create_files$files_to_create$control_args['force'] );
        
$skip_message    'All block files were skipped.';
        
$success_message "Created block '{$data['title_ucfirst']}'.";
        
$this->log_whether_files_written$files_written$skip_message$success_message );
    }

    
/**
     * Generates starter code for a theme based on _s.
     *
     * See the [Underscores website](https://underscores.me/) for more details.
     *
     * ## OPTIONS
     *
     * <slug>
     * : The slug for the new theme, used for prefixing functions.
     *
     * [--activate]
     * : Activate the newly downloaded theme.
     *
     * [--enable-network]
     * : Enable the newly downloaded theme for the entire network.
     *
     * [--theme_name=<title>]
     * : What to put in the 'Theme Name:' header in 'style.css'.
     *
     * [--author=<full-name>]
     * : What to put in the 'Author:' header in 'style.css'.
     *
     * [--author_uri=<uri>]
     * : What to put in the 'Author URI:' header in 'style.css'.
     *
     * [--sassify]
     * : Include stylesheets as SASS.
     *
     * [--woocommerce]
     * : Include WooCommerce boilerplate files.
     *
     * [--force]
     * : Overwrite files that already exist.
     *
     * ## EXAMPLES
     *
     *     # Generate a theme with name "Sample Theme" and author "John Doe"
     *     $ wp scaffold _s sample-theme --theme_name="Sample Theme" --author="John Doe"
     *     Success: Created theme 'Sample Theme'.
     *
     * @alias _s
     */
    
public function underscores$args$assoc_args ) {

        
$theme_slug $args[0];
        
$theme_path WP_CONTENT_DIR '/themes';
        
$url        'https://underscores.me';
        
$timeout    30;

        if ( ! 
preg_match'/^[a-z_]\w+$/i'str_replace'-''_'$theme_slug ) ) ) {
            
WP_CLI::error'Invalid theme slug specified. Theme slugs can only contain letters, numbers, underscores and hyphens, and can only start with a letter or underscore.' );
        }

        
$defaults = [
            
'theme_name' => ucfirst$theme_slug ),
            
'author'     => 'Me',
            
'author_uri' => '',
        ];
        
$data     wp_parse_args$assoc_args$defaults );

        
$_s_theme_path "$theme_path/$data[theme_name]";

        
$error_msg $this->check_target_directory'theme'$_s_theme_path );
        if ( ! empty( 
$error_msg ) ) {
            
WP_CLI::error"Invalid theme slug specified. {$error_msg});
        }

        
$force             Utils\get_flag_value$assoc_args'force' );
        
$should_write_file $this->prompt_if_files_will_be_overwritten$_s_theme_path$force );
        if ( ! 
$should_write_file ) {
            
WP_CLI::log'No files created' );
            die;
        }

        
$theme_description "Custom theme: {$data['theme_name']}, developed by {$data['author']}";

        
$body                                  = [];
        
$body['underscoresme_name']            = $data['theme_name'];
        
$body['underscoresme_slug']            = $theme_slug;
        
$body['underscoresme_author']          = $data['author'];
        
$body['underscoresme_author_uri']      = $data['author_uri'];
        
$body['underscoresme_description']     = $theme_description;
        
$body['underscoresme_generate_submit'] = 'Generate';
        
$body['underscoresme_generate']        = '1';
        if ( 
Utils\get_flag_value$assoc_args'sassify' ) ) {
            
$body['underscoresme_sass'] = 1;
        }

        if ( 
Utils\get_flag_value$assoc_args'woocommerce' ) ) {
            
$body['underscoresme_woocommerce'] = 1;
        }

        
$tmpfname  wp_tempnam$url );
        
$post_args = [
            
'timeout'  => $timeout,
            
'body'     => $body,
            
'stream'   => true,
            
'filename' => $tmpfname,
        ];

        
$response wp_remote_post$url$post_args );

        
// Workaround to get scaffolding to work within Travis CI.
        // See https://github.com/wp-cli/scaffold-command/issues/181
        
if ( is_wp_error$response )
            && 
false !== strpos$response->get_error_message(), 'gnutls_handshake() failed' )
        ) {
            
// Certificate problem, falling back to unsecured request instead.
            
$alt_url str_replace'https://''http://'$url );
            
WP_CLI::warning"Secured request to {$url} failed, using {$alt_url} as a fallback." );
            
$response wp_remote_post$alt_url$post_args );
        }

        if ( 
is_wp_error$response ) ) {
            
WP_CLI::error$response );
        }

        
$response_code wp_remote_retrieve_response_code$response );
        if ( 
200 !== (int) $response_code ) {
            
WP_CLI::error"Couldn't create theme (received {$response_code} response)." );
        }

        
$this->maybe_create_themes_dir();

        
$this->init_wp_filesystem();

        
$unzip_result unzip_file$tmpfname$theme_path );
        
unlink$tmpfname );

        if ( 
true === $unzip_result ) {
            
$files_to_create = [
                
"{$theme_path}/{$theme_slug}/.editorconfig" => file_get_contentsself::get_template_path'.editorconfig' ) ),
            ];
            
$this->create_files$files_to_createfalse );
            
WP_CLI::success"Created theme '{$data['theme_name']}'." );
        } else {
            
WP_CLI::error"Could not decompress your theme files ('{$tmpfname}') at '{$theme_path}': {$unzip_result->get_error_message()});
        }

        if ( 
Utils\get_flag_value$assoc_args'activate' ) ) {
            
WP_CLI::run_command( [ 'theme''activate'$theme_slug ] );
        } elseif ( 
Utils\get_flag_value$assoc_args'enable-network' ) ) {
            
WP_CLI::run_command( [ 'theme''enable'$theme_slug ], [ 'network' => true ] );
        }
    }

    
/**
     * Generates child theme based on an existing theme.
     *
     * Creates a child theme folder with `functions.php` and `style.css` files.
     *
     * ## OPTIONS
     *
     * <slug>
     * : The slug for the new child theme.
     *
     * --parent_theme=<slug>
     * : What to put in the 'Template:' header in 'style.css'.
     *
     * [--theme_name=<title>]
     * : What to put in the 'Theme Name:' header in 'style.css'.
     *
     * [--author=<full-name>]
     * : What to put in the 'Author:' header in 'style.css'.
     *
     * [--author_uri=<uri>]
     * : What to put in the 'Author URI:' header in 'style.css'.
     *
     * [--theme_uri=<uri>]
     * : What to put in the 'Theme URI:' header in 'style.css'.
     *
     * [--activate]
     * : Activate the newly created child theme.
     *
     * [--enable-network]
     * : Enable the newly created child theme for the entire network.
     *
     * [--force]
     * : Overwrite files that already exist.
     *
     * ## EXAMPLES
     *
     *     # Generate a 'sample-theme' child theme based on TwentySixteen
     *     $ wp scaffold child-theme sample-theme --parent_theme=twentysixteen
     *     Success: Created '/var/www/example.com/public_html/wp-content/themes/sample-theme'.
     *
     * @subcommand child-theme
     */
    
public function child_theme$args$assoc_args ) {
        
$theme_slug $args[0];

        if ( 
in_array$theme_slug, [ '.''..' ], true ) ) {
            
WP_CLI::error"Invalid theme slug specified. The slug cannot be '.' or '..'." );
        }

        
$defaults = [
            
'theme_name' => ucfirst$theme_slug ),
            
'author'     => 'Me',
            
'author_uri' => '',
            
'theme_uri'  => '',
        ];

        
$data                               wp_parse_args$assoc_args$defaults );
        
$data['slug']                       = $theme_slug;
        
$data['parent_theme_function_safe'] = str_replace( [ ' ''-' ], '_'$data['parent_theme'] );
        
$data['description']                = ucfirst$data['parent_theme'] ) . ' child theme.';

        
$theme_dir WP_CONTENT_DIR "/themes/{$theme_slug}";

        
$error_msg $this->check_target_directory'theme'$theme_dir );
        if ( ! empty( 
$error_msg ) ) {
            
WP_CLI::error"Invalid theme slug specified. {$error_msg});
        }

        
$theme_style_path     "{$theme_dir}/style.css";
        
$theme_functions_path "{$theme_dir}/functions.php";

        
$this->maybe_create_themes_dir();

        
$files_to_create = [
            
$theme_style_path            => self::mustache_render'child_theme.mustache'$data ),
            
$theme_functions_path        => self::mustache_render'child_theme_functions.mustache'$data ),
            
"{$theme_dir}/.editorconfig" => file_get_contentsself::get_template_path'.editorconfig' ) ),
        ];
        
$force           Utils\get_flag_value$assoc_args'force' );
        
$files_written   $this->create_files$files_to_create$force );
        
$skip_message    'All theme files were skipped.';
        
$success_message "Created '{$theme_dir}'.";
        
$this->log_whether_files_written$files_written$skip_message$success_message );

        if ( 
Utils\get_flag_value$assoc_args'activate' ) ) {
            
WP_CLI::run_command( [ 'theme''activate'$theme_slug ] );
        } elseif ( 
Utils\get_flag_value$assoc_args'enable-network' ) ) {
            
WP_CLI::run_command( [ 'theme''enable'$theme_slug ], [ 'network' => true ] );
        }
    }

    private function 
get_output_path$assoc_args$subdir ) {
        if ( 
$assoc_args['theme'] ) {
            
$theme $assoc_args['theme'];
            if ( 
is_string$theme ) ) {
                
$path get_theme_root$theme ) . "/{$theme}";
            } else {
                
$path get_stylesheet_directory();
            }
            if ( ! 
is_dir$path ) ) {
                
WP_CLI::error"Can't find '{$theme}' theme." );
            }
        } elseif ( 
$assoc_args['plugin'] ) {
            
$plugin $assoc_args['plugin'];
            
$path   WP_PLUGIN_DIR "/{$plugin}";
            if ( ! 
is_dir$path ) ) {
                
WP_CLI::error"Can't find '{$plugin}' plugin." );
            }
        } else {
            return 
false;
        }

        
$path .= $subdir;

        return 
$path;
    }

    
/**
     * Generates starter code for a plugin.
     *
     * The following files are always generated:
     *
     * * `plugin-slug.php` is the main PHP plugin file.
     * * `readme.txt` is the readme file for the plugin.
     * * `package.json` needed by NPM holds various metadata relevant to the project. Packages: `grunt`, `grunt-wp-i18n` and `grunt-wp-readme-to-markdown`. Scripts: `start`, `readme`, `i18n`.
     * * `Gruntfile.js` is the JS file containing Grunt tasks. Tasks: `i18n` containing `addtextdomain` and `makepot`, `readme` containing `wp_readme_to_markdown`.
     * * `.editorconfig` is the configuration file for Editor.
     * * `.gitignore` tells which files (or patterns) git should ignore.
     * * `.distignore` tells which files and folders should be ignored in distribution.
     *
     * The following files are also included unless the `--skip-tests` is used:
     *
     * * `phpunit.xml.dist` is the configuration file for PHPUnit.
     * * `.travis.yml` is the configuration file for Travis CI. Use `--ci=<provider>` to select a different service.
     * * `bin/install-wp-tests.sh` configures the WordPress test suite and a test database.
     * * `tests/bootstrap.php` is the file that makes the current plugin active when running the test suite.
     * * `tests/test-sample.php` is a sample file containing test cases.
     * * `.phpcs.xml.dist` is a collection of PHP_CodeSniffer rules.
     *
     * ## OPTIONS
     *
     * <slug>
     * : The internal name of the plugin.
     *
     * [--dir=<dirname>]
     * : Put the new plugin in some arbitrary directory path. Plugin directory will be path plus supplied slug.
     *
     * [--plugin_name=<title>]
     * : What to put in the 'Plugin Name:' header.
     *
     * [--plugin_description=<description>]
     * : What to put in the 'Description:' header.
     *
     * [--plugin_author=<author>]
     * : What to put in the 'Author:' header.
     *
     * [--plugin_author_uri=<url>]
     * : What to put in the 'Author URI:' header.
     *
     * [--plugin_uri=<url>]
     * : What to put in the 'Plugin URI:' header.
     *
     * [--skip-tests]
     * : Don't generate files for unit testing.
     *
     * [--ci=<provider>]
     * : Choose a configuration file for a continuous integration provider.
     * ---
     * default: travis
     * options:
     *   - travis
     *   - circle
     *   - gitlab
     * ---
     *
     * [--activate]
     * : Activate the newly generated plugin.
     *
     * [--activate-network]
     * : Network activate the newly generated plugin.
     *
     * [--force]
     * : Overwrite files that already exist.
     *
     * ## EXAMPLES
     *
     *     $ wp scaffold plugin sample-plugin
     *     Success: Created plugin files.
     *     Success: Created test files.
     */
    
public function plugin$args$assoc_args ) {
        
$plugin_slug    $args[0];
        
$plugin_name    ucwordsstr_replace'-'' '$plugin_slug ) );
        
$plugin_package str_replace' ''_'$plugin_name );

        if ( 
in_array$plugin_slug, [ '.''..' ], true ) ) {
            
WP_CLI::error"Invalid plugin slug specified. The slug cannot be '.' or '..'." );
        }

        
$defaults = [
            
'plugin_slug'         => $plugin_slug,
            
'plugin_name'         => $plugin_name,
            
'plugin_package'      => $plugin_package,
            
'plugin_description'  => 'PLUGIN DESCRIPTION HERE',
            
'plugin_author'       => 'YOUR NAME HERE',
            
'plugin_author_uri'   => 'YOUR SITE HERE',
            
'plugin_uri'          => 'PLUGIN SITE HERE',
            
'plugin_tested_up_to' => get_bloginfo'version' ),
        ];
        
$data     wp_parse_args$assoc_args$defaults );

        
$data['textdomain'] = $plugin_slug;

        if ( ! empty( 
$assoc_args['dir'] ) ) {
            if ( ! 
is_dir$assoc_args['dir'] ) ) {
                
WP_CLI::error"Cannot create plugin in directory that doesn't exist." );
            }
            
$plugin_dir "{$assoc_args['dir']}/{$plugin_slug}";
        } else {
            
$plugin_dir WP_PLUGIN_DIR "/{$plugin_slug}";
            
$this->maybe_create_plugins_dir();

            
$error_msg $this->check_target_directory'plugin'$plugin_dir );
            if ( ! empty( 
$error_msg ) ) {
                
WP_CLI::error"Invalid plugin slug specified. {$error_msg});
            }
        }

        
$plugin_path        "{$plugin_dir}/{$plugin_slug}.php";
        
$plugin_readme_path "{$plugin_dir}/readme.txt";

        
$files_to_create = [
            
$plugin_path                  => self::mustache_render'plugin.mustache'$data ),
            
$plugin_readme_path           => self::mustache_render'plugin-readme.mustache'$data ),
            
"{$plugin_dir}/package.json"  => self::mustache_render'plugin-packages.mustache'$data ),
            
"{$plugin_dir}/Gruntfile.js"  => self::mustache_render'plugin-gruntfile.mustache'$data ),
            
"{$plugin_dir}/.gitignore"    => self::mustache_render'plugin-gitignore.mustache'$data ),
            
"{$plugin_dir}/.distignore"   => self::mustache_render'plugin-distignore.mustache'$data ),
            
"{$plugin_dir}/.editorconfig" => file_get_contentsself::get_template_path'.editorconfig' ) ),
        ];
        
$force           Utils\get_flag_value$assoc_args'force' );
        
$files_written   $this->create_files$files_to_create$force );

        
$skip_message    'All plugin files were skipped.';
        
$success_message 'Created plugin files.';
        
$this->log_whether_files_written$files_written$skip_message$success_message );

        if ( ! 
Utils\get_flag_value$assoc_args'skip-tests' ) ) {
            
$command_args = [
                
'dir'   => $plugin_dir,
                
'ci'    => empty( $assoc_args['ci'] ) ? '' $assoc_args['ci'],
                
'force' => $force,
            ];
            
WP_CLI::run_command( [ 'scaffold''plugin-tests'$plugin_slug ], $command_args );
        }

        if ( 
Utils\get_flag_value$assoc_args'activate' ) ) {
            
WP_CLI::run_command( [ 'plugin''activate'$plugin_slug ] );
        } elseif ( 
Utils\get_flag_value$assoc_args'activate-network' ) ) {
            
WP_CLI::run_command( [ 'plugin''activate'$plugin_slug ], [ 'network' => true ] );
        }
    }

    
/**
     * Generates files needed for running PHPUnit tests in a plugin.
     *
     * The following files are generated by default:
     *
     * * `phpunit.xml.dist` is the configuration file for PHPUnit.
     * * `.travis.yml` is the configuration file for Travis CI. Use `--ci=<provider>` to select a different service.
     * * `bin/install-wp-tests.sh` configures the WordPress test suite and a test database.
     * * `tests/bootstrap.php` is the file that makes the current plugin active when running the test suite.
     * * `tests/test-sample.php` is a sample file containing the actual tests.
     * * `.phpcs.xml.dist` is a collection of PHP_CodeSniffer rules.
     *
     * Learn more from the [plugin unit tests documentation](https://make.wordpress.org/cli/handbook/plugin-unit-tests/).
     *
     * ## ENVIRONMENT
     *
     * The `tests/bootstrap.php` file looks for the WP_TESTS_DIR environment
     * variable.
     *
     * ## OPTIONS
     *
     * [<plugin>]
     * : The name of the plugin to generate test files for.
     *
     * [--dir=<dirname>]
     * : Generate test files for a non-standard plugin path. If no plugin slug is specified, the directory name is used.
     *
     * [--ci=<provider>]
     * : Choose a configuration file for a continuous integration provider.
     * ---
     * default: travis
     * options:
     *   - travis
     *   - circle
     *   - gitlab
     *   - bitbucket
     * ---
     *
     * [--force]
     * : Overwrite files that already exist.
     *
     * ## EXAMPLES
     *
     *     # Generate unit test files for plugin 'sample-plugin'.
     *     $ wp scaffold plugin-tests sample-plugin
     *     Success: Created test files.
     *
     * @subcommand plugin-tests
     */
    
public function plugin_tests$args$assoc_args ) {
        
$this->scaffold_plugin_theme_tests$args$assoc_args'plugin' );
    }

    
/**
     * Generates files needed for running PHPUnit tests in a theme.
     *
     * The following files are generated by default:
     *
     * * `phpunit.xml.dist` is the configuration file for PHPUnit.
     * * `.travis.yml` is the configuration file for Travis CI. Use `--ci=<provider>` to select a different service.
     * * `bin/install-wp-tests.sh` configures the WordPress test suite and a test database.
     * * `tests/bootstrap.php` is the file that makes the current theme active when running the test suite.
     * * `tests/test-sample.php` is a sample file containing the actual tests.
     * * `.phpcs.xml.dist` is a collection of PHP_CodeSniffer rules.
     *
     * Learn more from the [plugin unit tests documentation](https://make.wordpress.org/cli/handbook/plugin-unit-tests/).
     *
     * ## ENVIRONMENT
     *
     * The `tests/bootstrap.php` file looks for the WP_TESTS_DIR environment
     * variable.
     *
     * ## OPTIONS
     *
     * [<theme>]
     * : The name of the theme to generate test files for.
     *
     * [--dir=<dirname>]
     * : Generate test files for a non-standard theme path. If no theme slug is specified, the directory name is used.
     *
     * [--ci=<provider>]
     * : Choose a configuration file for a continuous integration provider.
     * ---
     * default: travis
     * options:
     *   - travis
     *   - circle
     *   - gitlab
     *   - bitbucket
     * ---
     *
     * [--force]
     * : Overwrite files that already exist.
     *
     * ## EXAMPLES
     *
     *     # Generate unit test files for theme 'twentysixteenchild'.
     *     $ wp scaffold theme-tests twentysixteenchild
     *     Success: Created test files.
     *
     * @subcommand theme-tests
     */
    
public function theme_tests$args$assoc_args ) {
        
$this->scaffold_plugin_theme_tests$args$assoc_args'theme' );
    }

    private function 
scaffold_plugin_theme_tests$args$assoc_args$type ) {
        
$wp_filesystem $this->init_wp_filesystem();

        if ( ! empty( 
$args[0] ) ) {
            
$slug $args[0];
            if ( 
in_array$slug, [ '.''..' ], true ) ) {
                
WP_CLI::error"Invalid {$type} slug specified. The slug cannot be '.' or '..'." );
            }
            if ( 
'theme' === $type ) {
                
$theme wp_get_theme$slug );
                if ( 
$theme->exists() ) {
                    
$target_dir $theme->get_stylesheet_directory();
                } else {
                    
WP_CLI::error"Invalid {$type} slug specified. The theme '{$slug}' does not exist." );
                }
            } else {
                
$target_dir WP_PLUGIN_DIR "/{$slug}";
            }
            if ( empty( 
$assoc_args['dir'] ) && ! is_dir$target_dir ) ) {
                
WP_CLI::error"Invalid {$type} slug specified. No such target directory '{$target_dir}'." );
            }

            
$error_msg $this->check_target_directory$type$target_dir );
            if ( ! empty( 
$error_msg ) ) {
                
WP_CLI::error"Invalid {$type} slug specified. {$error_msg});
            }
        }

        if ( ! empty( 
$assoc_args['dir'] ) ) {
            
$target_dir $assoc_args['dir'];
            if ( ! 
is_dir$target_dir ) ) {
                
WP_CLI::error"Invalid {$type} directory specified. No such directory '{$target_dir}'." );
            }
            if ( empty( 
$slug ) ) {
                
$slug Utils\basename$target_dir );
            }
        }

        if ( empty( 
$slug ) || empty( $target_dir ) ) {
            
WP_CLI::error"Invalid {$type} specified." );
        }

        
$name    ucwordsstr_replace'-'' '$slug ) );
        
$package str_replace' ''_'$name );

        
$tests_dir "{$target_dir}/tests";
        
$bin_dir   "{$target_dir}/bin";

        
$wp_filesystem->mkdir$tests_dir );
        
$wp_filesystem->mkdir$bin_dir );

        
$wp_versions_to_test = [];
        
// Parse plugin readme.txt
        
if ( file_exists"{$target_dir}/readme.txt" ) ) {
            
$readme_content file_get_contents"{$target_dir}/readme.txt" );

            
preg_match'/Requires at least\:(.*)\n/m'$readme_content$matches );
            if ( isset( 
$matches[1] ) && $matches[1] ) {
                
$wp_versions_to_test[] = trim$matches[1] );
            }
        }
        
$wp_versions_to_test[] = 'latest';
        
$wp_versions_to_test[] = 'trunk';

        
$template_data = [
            
"{$type}_slug"    => $slug,
            
"{$type}_package" => $package,
        ];

        
$force           Utils\get_flag_value$assoc_args'force' );
        
$files_to_create = [
            
"{$tests_dir}/bootstrap.php"   => self::mustache_render"{$type}-bootstrap.mustache"$template_data ),
            
"{$tests_dir}/test-sample.php" => self::mustache_render"{$type}-test-sample.mustache"$template_data ),
        ];
        if ( 
'travis' === $assoc_args['ci'] ) {
            
$files_to_create"{$target_dir}/.travis.yml" ] = self::mustache_render'plugin-travis.mustache'compact'wp_versions_to_test' ) );
        } elseif ( 
'circle' === $assoc_args['ci'] ) {
            
$files_to_create"{$target_dir}/.circleci/config.yml" ] = self::mustache_render'plugin-circle.mustache'compact'wp_versions_to_test' ) );
        } elseif ( 
'gitlab' === $assoc_args['ci'] ) {
            
$files_to_create"{$target_dir}/.gitlab-ci.yml" ] = self::mustache_render'plugin-gitlab.mustache' );
        } elseif ( 
'bitbucket' === $assoc_args['ci'] ) {
            
$files_to_create"{$target_dir}/bitbucket-pipelines.yml" ] = self::mustache_render'plugin-bitbucket.mustache' );
        }

        
$files_written $this->create_files$files_to_create$force );

        
$to_copy = [
            
'install-wp-tests.sh' => $bin_dir,
            
'phpunit.xml.dist'    => $target_dir,
            
'.phpcs.xml.dist'     => $target_dir,
        ];

        foreach ( 
$to_copy as $file => $dir ) {
            
$file_name         "{$dir}/{$file}";
            
$force             Utils\get_flag_value$assoc_args'force' );
            
$should_write_file $this->prompt_if_files_will_be_overwritten$file_name$force );
            if ( ! 
$should_write_file ) {
                continue;
            }
            
$files_written[] = $file_name;

            
$wp_filesystem->copyself::get_template_path$file ), $file_nametrue );
            if ( 
'install-wp-tests.sh' === $file ) {
                if ( ! 
$wp_filesystem->chmod"{$dir}/{$file}"0755 ) ) {
                    
WP_CLI::warning"Couldn't mark 'install-wp-tests.sh' as executable." );
                }
            }
        }

        
$skip_message    'All test files were skipped.';
        
$success_message 'Created test files.';
        
$this->log_whether_files_written$files_written$skip_message$success_message );
    }

    
/**
     * Checks that the `$target_dir` is a child directory of the WP themes or plugins directory, depending on `$type`.
     *
     * @param string $type       "theme" or "plugin"
     * @param string $target_dir The theme/plugin directory to check.
     *
     * @return null|string Returns null on success, error message on error.
     */
    
private function check_target_directory$type$target_dir ) {
        
$parent_dir dirnameself::canonicalize_pathstr_replace'\\''/'$target_dir ) ) );

        if ( 
'theme' === $type && str_replace'\\''/'WP_CONTENT_DIR '/themes' ) !== $parent_dir ) {
            return 
sprintf'The target directory \'%1$s\' is not in \'%2$s\'.'$target_dirWP_CONTENT_DIR '/themes' );
        }

        if ( 
'plugin' === $type && str_replace'\\''/'WP_PLUGIN_DIR ) !== $parent_dir ) {
            return 
sprintf'The target directory \'%1$s\' is not in \'%2$s\'.'$target_dirWP_PLUGIN_DIR );
        }

        
// Success.
        
return null;
    }

    protected function 
create_files$files_and_contents$force ) {
        
$wp_filesystem $this->init_wp_filesystem();
        
$wrote_files   = [];

        foreach ( 
$files_and_contents as $filename => $contents ) {
            
$should_write_file $this->prompt_if_files_will_be_overwritten$filename$force );
            if ( ! 
$should_write_file ) {
                continue;
            }

            
$wp_filesystem->mkdirdirname$filename ) );

            if ( ! 
$wp_filesystem->put_contents$filename$contents ) ) {
                
WP_CLI::error"Error creating file: {$filename});
            } elseif ( 
$should_write_file ) {
                
$wrote_files[] = $filename;
            }
        }
        return 
$wrote_files;
    }

    protected function 
prompt_if_files_will_be_overwritten$filename$force ) {
        
$should_write_file true;
        if ( ! 
file_exists$filename ) ) {
            return 
true;
        }

        
WP_CLI::warning'File already exists.' );
        
WP_CLI::log$filename );
        if ( ! 
$force ) {
            do {
                
$answer      cli\prompt(
                    
'Skip this file, or replace it with scaffolding?',
                    
$default false,
                    
$marker  '[s/r]: '
                
);
            } while ( ! 
in_array$answer, [ 's''r' ], true ) );
            
$should_write_file 'r' === $answer;
        }

        
$outcome $should_write_file 'Replacing' 'Skipping';
        
WP_CLI::log$outcome PHP_EOL );

        return 
$should_write_file;
    }

    protected function 
log_whether_files_written$files_written$skip_message$success_message ) {
        if ( empty( 
$files_written ) ) {
            
WP_CLI::log$skip_message );
        } else {
            
WP_CLI::success$success_message );
        }
    }

    
/**
     * Extracts dashicon name when provided or return null otherwise.
     *
     * @param array $assoc_args
     * @return string|null
     */
    
private function extract_dashicon$assoc_args ) {
        
$dashicon Utils\get_flag_value$assoc_args'dashicon' );
        if ( ! 
$dashicon ) {
            return 
null;
        }
        return 
preg_replace'/dashicon(-|s-)/'''$dashicon );
    }

    
/**
     * If you're writing your files to your theme directory your textdomain also needs to be the same as your theme.
     * Same goes for when plugin is being used.
     */
    
private function get_textdomain$textdomain$args ) {
        if ( 
strlen$textdomain ) ) {
            return 
$textdomain;
        }

        if ( 
$args['theme'] ) {
            return 
$this->get_theme_name$args['theme'] );
        }

        if ( 
$args['plugin'] && true !== $args['plugin'] ) {
            return 
$args['plugin'];
        }

        return 
'YOUR-TEXTDOMAIN';
    }

    
/**
     * Generates the machine name for function declarations.
     *
     * @param string $slug Slug name to convert.
     * @return string
     */
    
private function generate_machine_name$slug ) {
        return 
str_replace'-''_'$slug );
    }

    
/**
     * Pluralizes a noun.
     *
     * @see    Inflector::pluralize()
     * @param  string $word Word to be pluralized.
     * @return string
     */
    
private function pluralize$word ) {
        return 
Inflector::pluralize$word );
    }

    protected function 
extract_args$assoc_args$defaults ) {
        
$out = [];

        foreach ( 
$defaults as $key => $value ) {
            
$out$key ] = Utils\get_flag_value$assoc_args$key$value );
        }

        return 
$out;
    }

    protected function 
quote_comma_list_elements$comma_list ) {
        return 
"'" implode"', '"explode','$comma_list ) ) . "'";
    }

    
/**
     * Creates the themes directory if it doesn't already exist.
     */
    
protected function maybe_create_themes_dir() {

        
$themes_dir WP_CONTENT_DIR '/themes';
        if ( ! 
is_dir$themes_dir ) ) {
            
wp_mkdir_p$themes_dir );
        }

    }

    
/**
     * Creates the plugins directory if it doesn't already exist.
     */
    
protected function maybe_create_plugins_dir() {

        if ( ! 
is_dirWP_PLUGIN_DIR ) ) {
            
wp_mkdir_pWP_PLUGIN_DIR );
        }

    }

    
/**
     * Initializes WP_Filesystem.
     */
    
protected function init_wp_filesystem() {
        global 
$wp_filesystem;
        
WP_Filesystem();

        return 
$wp_filesystem;
    }

    
/**
     * Localizes the template path.
     */
    
private static function mustache_render$template$data = [] ) {
        return 
Utils\mustache_renderdirnamedirname__FILE__ ) ) . "/templates/{$template}"$data );
    }

    
/**
     * Gets the template path based on installation type.
     */
    
private static function get_template_path$template ) {
        
$command_root  Utils\phar_safe_pathdirname__DIR__ ) );
        
$template_path "{$command_root}/templates/{$template}";

        if ( ! 
file_exists$template_path ) ) {
            
WP_CLI::error"Couldn't find {$template});
        }

        return 
$template_path;
    }

    
/*
     * Returns the canonicalized path, with dot and double dot segments resolved.
     *
     * Copied from Symfony\Component\DomCrawler\AbstractUriElement::canonicalizePath().
     * Implements RFC 3986, section 5.2.4.
     *
     * @param string $path The path to make canonical.
     *
     * @return string The canonicalized path.
     */
    
private static function canonicalize_path$path ) {
        if ( 
'' === $path || '/' === $path ) {
            return 
$path;
        }

        if ( 
'.' === substr$path, -) ) {
            
$path .= '/';
        }

        
$output = [];

        foreach ( 
explode'/'$path ) as $segment ) {
            if ( 
'..' === $segment ) {
                
array_pop$output );
            } elseif ( 
'.' !== $segment ) {
                
$output[] = $segment;
            }
        }

        return 
implode'/'$output );
    }

    
/**
     * Gets an active theme's name when true provided or the same name otherwise.
     *
     * @param string|bool $theme Theme name or true.
     * @return string
     */
    
private function get_theme_name$theme ) {
        if ( 
true === $theme ) {
            
$theme wp_get_theme()->template;
        }
        return 
strtolower$theme );
    }
}
<?
php
/**
 * Functions to register client-side assets (scripts and stylesheets) for the
 * Gutenberg block.
 *
 * @package {{namespace}}
 */

/**
 * Registers all block assets so that they can be enqueued through Gutenberg in
 * the corresponding context.
 *
 * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/tutorials/block-tutorial/applying-styles-with-stylesheets/
 */
function {{machine_name}}_block_init() {
    
// Skip block registration if Gutenberg is not enabled/merged.
    
if ( ! function_exists'register_block_type' ) ) {
        return;
    }
    {{
#plugin}}
    
$dir dirname__FILE__ );
    {{/
plugin}}
    {{
#theme}}
    
$dir get_stylesheet_directory() . '/blocks';
    {{/
theme}}

    
$index_js '{{slug}}/index.js';
    
wp_register_script(
        
'{{slug}}-block-editor',
        {{
#plugin}}
        
plugins_url$index_js__FILE__ ),
        {{/
plugin}}
        {{
#theme}}
        
get_stylesheet_directory_uri() . "/blocks/{$index_js}",
        {{/
theme}}
        [
            
'wp-blocks',
            
'wp-i18n',
            
'wp-element',
        ],
        
filemtime"{$dir}/{$index_js})
    );

    
$editor_css '{{slug}}/editor.css';
    
wp_register_style(
        
'{{slug}}-block-editor',
        {{
#plugin}}
        
plugins_url$editor_css__FILE__ ),
        {{/
plugin}}
        {{
#theme}}
        
get_stylesheet_directory_uri() . "/blocks/{$editor_css}",
        {{/
theme}}
        [],
        
filemtime"{$dir}/{$editor_css})
    );

    
$style_css '{{slug}}/style.css';
    
wp_register_style(
        
'{{slug}}-block',
        {{
#plugin}}
        
plugins_url$style_css__FILE__ ),
        {{/
plugin}}
        {{
#theme}}
        
get_stylesheet_directory_uri() . "/blocks/{$style_css}",
        {{/
theme}}
        [],
        
filemtime"{$dir}/{$style_css})
    );

    
register_block_type'{{namespace}}/{{slug}}', [
        
'editor_script' => '{{slug}}-block-editor',
        
'editor_style'  => '{{slug}}-block-editor',
        
'style'         => '{{slug}}-block',
    ] );
}

add_action'init''{{machine_name}}_block_init' );
<?
php

if ( ! class_exists'WP_CLI' ) ) {
    return;
}

$wpcli_super_admin_autoloader dirname__FILE__ ) . '/vendor/autoload.php';
if ( 
file_exists$wpcli_super_admin_autoloader ) ) {
    require_once 
$wpcli_super_admin_autoloader;
}

WP_CLI::add_command(
    
'super-admin',
    
'Super_Admin_Command',
    array(
        
'before_invoke' => function () {
            if ( ! 
is_multisite() ) {
                
WP_CLI::error'This is not a multisite installation.' );
            }
        },
    )
);
<?
php

use WP_CLI\Fetchers\User as UserFetcher;

/**
 * Lists, adds, or removes super admin users on a multisite installation.
 *
 * ## EXAMPLES
 *
 *     # List user with super-admin capabilities
 *     $ wp super-admin list
 *     supervisor
 *     administrator
 *
 *     # Grant super-admin privileges to the user.
 *     $ wp super-admin add superadmin2
 *     Success: Granted super-admin capabilities.
 *
 *     # Revoke super-admin privileges to the user.
 *     $ wp super-admin remove superadmin2
 *     Success: Revoked super-admin capabilities.
 *
 * @package wp-cli
 */
class Super_Admin_Command extends WP_CLI_Command {

    private 
$fields = [
        
'user_login',
    ];

    public function 
__construct() {
        
$this->fetcher = new UserFetcher();
    }

    
/**
     * Lists users with super admin capabilities.
     *
     * ## OPTIONS
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: list
     * options:
     *   - list
     *   - table
     *   - csv
     *   - json
     *   - count
     *   - yaml
     * ---
     *
     * ## EXAMPLES
     *
     *     # List user with super-admin capabilities
     *     $ wp super-admin list
     *     supervisor
     *     administrator
     *
     * @subcommand list
     */
    
public function list_subcommand$_$assoc_args ) {
        
$super_admins self::get_admins();

        if ( 
'list' === $assoc_args['format'] ) {
            foreach ( 
$super_admins as $user_login ) {
                
WP_CLI::line$user_login );
            }
        } else {
            
$output_users = [];
            foreach ( 
$super_admins as $user_login ) {
                
$output_user = new stdClass();

                
$output_user->user_login $user_login;

                
$output_users[] = $output_user;
            }
            
$formatter = new \WP_CLI\Formatter$assoc_args$this->fields );
            
$formatter->display_items$output_users );
        }
    }

    
/**
     * Grants super admin privileges to one or more users.
     *
     * ## OPTIONS
     *
     * <user>...
     * : One or more user IDs, user emails, or user logins.
     *
     * ## EXAMPLES
     *
     *     $ wp super-admin add superadmin2
     *     Success: Granted super-admin capabilities.
     */
    
public function add$args$_ ) {

        
$successes 0;
        
$errors    0;
        
$users     $this->fetcher->get_many$args );

        if ( 
count$users ) !== count$args ) ) {
            
$errors count$args ) - count$users );
        }

        
$super_admins     self::get_admins();
        
$num_super_admins count$super_admins );

        
$new_super_admins = [];
        foreach ( 
$users as $user ) {
            
do_action'grant_super_admin', (int) $user->ID ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound

            
if ( in_array$user->user_login$super_adminstrue ) ) {
                
WP_CLI::warning"User '{$user->user_login}' already has super-admin capabilities." );
                continue;
            }

            
$new_super_admins[] = $user->ID;
            
$super_admins[]     = $user->user_login;
            
$successes++;
        }

        if ( 
count$super_admins ) === $num_super_admins ) {
            if ( 
$errors ) {
                
$user_count count$args );
                
WP_CLI::error"Couldn't grant super-admin capabilities to {$errors} of {$user_count} users." );
            } else {
                
WP_CLI::success'Super admins remain unchanged.' );
            }
        } else {
            if ( 
update_site_option'site_admins'$super_admins ) ) {
                if ( 
$errors ) {
                    
$user_count count$args );
                    
WP_CLI::error"Only granted super-admin capabilities to {$successes} of {$user_count} users." );
                } else {
                    
$message $successes 'users' 'user';
                    
WP_CLI::success"Granted super-admin capabilities to {$successes} {$message}." );
                }
            } else {
                
WP_CLI::error'Site options update failed.' );
            }
        }

        foreach ( 
$new_super_admins as $user_id ) {
            
do_action'granted_super_admin', (int) $user_id ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
        
}
    }

    
/**
     * Removes super admin privileges from one or more users.
     *
     * ## OPTIONS
     *
     * <user>...
     * : One or more user IDs, user emails, or user logins.
     *
     * ## EXAMPLES
     *
     *     $ wp super-admin remove superadmin2
     *     Success: Revoked super-admin capabilities.
     */
    
public function remove$args$_ ) {
        
$users             $this->fetcher->get_many$args );
        
$user_logins       $users array_valuesarray_uniquewp_list_pluck$users'user_login' ) ) ) : [];
        
$user_logins_count count$user_logins );

        
$user_ids = [];
        foreach ( 
$users as $user ) {
            
$user_ids$user->user_login ] = $user->ID;

            
do_action'revoke_super_admin', (int) $user->ID ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
        
}

        
$super_admins self::get_admins();
        if ( ! 
$super_admins ) {
            
WP_CLI::error'No super admins to revoke super-admin privileges from.' );
        }

        if ( 
$user_logins_count count$args ) ) {
            
$flipped_user_logins array_flip$user_logins );
            
// Fetcher has already warned so don't bother here, but continue with any args that are possible login names to cater for invalid users in the site options meta.

            
$user_logins       array_merge(
                
$user_logins,
                
array_unique(
                    
array_filter(
                        
$args,
                        static function ( 
$v ) use ( $flipped_user_logins ) {
                            
// Exclude numeric and email-like logins (login names can be email-like but ignore this given the circumstances).
                            
return ! isset( $flipped_user_logins$v ] ) && ! is_numeric$v ) && ! is_email$v );
                        }
                    )
                )
            );
            
$user_logins_count count$user_logins );
        }
        if ( ! 
$user_logins ) {
            
WP_CLI::error'No valid user logins given to revoke super-admin privileges from.' );
        }

        
$update_super_admins array_diff$super_admins$user_logins );
        if ( 
$update_super_admins === $super_admins ) {
            
WP_CLI::error$user_logins_count 'None of the given users is a super admin.' 'The given user is not a super admin.' );
        }

        
update_site_option'site_admins'$update_super_admins );

        
$successes count$super_admins ) - count$update_super_admins );
        if ( 
$successes === $user_logins_count ) {
            
$message $user_logins_count 'users' 'user';
            
$msg     "Revoked super-admin capabilities from {$user_logins_count} {$message}.";
        } else {
            
$msg "Revoked super-admin capabilities from {$successes} of {$user_logins_count} users.";
        }
        if ( ! 
$update_super_admins ) {
            
$msg .= ' There are no remaining super admins.';
        }
        
WP_CLI::success$msg );

        
$removed_logins array_intersect$user_logins$super_admins );

        foreach ( 
$removed_logins as $user_login ) {
            
$user_id null;

            if ( 
array_key_exists$user_login$user_ids ) ) {
                
$user_id $user_ids$user_login ];
            } else {
                
$user get_user_by'login'$user_login );
                if ( 
$user instanceof WP_User ) {
                    
$user_id $user->ID;
                }
            }

            if ( 
null !== $user_id ) {
                
do_action'revoked_super_admin', (int) $user_ids$user_login ] ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
            
}
        }
    }

    private static function 
get_admins() {
        
// We don't use get_super_admins() because we don't want to mess with the global
        
return (array) get_site_option'site_admins', [ 'admin' ] );
    }
}
<?
php

namespace WP_CLI;

use 
WP_CLI;

trait 
ParsePluginNameInput {

    
/**
     * If have optional args ([<plugin>...]) and an all option, then check have something to do.
     *
     * @param array  $args Passed-in arguments.
     * @param bool   $all All flag.
     * @param string $verb Optional. Verb to use. Defaults to 'install'.
     * @return array Same as $args if not all, otherwise all slugs.
     * @throws ExitException If neither plugin name nor --all were provided.
     */
    
protected function check_optional_args_and_all$args$all$verb 'install' ) {
        if ( 
$all ) {
            
$args array_map(
                
'\WP_CLI\Utils\get_plugin_name',
                
array_keys$this->get_all_plugins() )
            );
        }

        if ( empty( 
$args ) ) {
            if ( ! 
$all ) {
                
WP_CLI::error'Please specify one or more plugins, or use --all.' );
            }

            
$past_tense_verb Utils\past_tense_verb$verb );
            
WP_CLI::success"No plugins {$past_tense_verb}." ); // Don't error if --all given for BC.
        
}

        return 
$args;
    }

    
/**
     * Gets all available plugins.
     *
     * Uses the same filter core uses in plugins.php to determine which plugins
     * should be available to manage through the WP_Plugins_List_Table class.
     *
     * @return array
     */
    
private function get_all_plugins() {
        
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Calling native WordPress hook.
        
return apply_filters'all_plugins'get_plugins() );
    }
}
<?
php

namespace WP_CLI;

/**
 * A plugin upgrader class that clears the destination directory.
 */
class DestructivePluginUpgrader extends \Plugin_Upgrader {

    public function 
install_package$args = array() ) {
        
parent::upgrade_strings(); // Needed for the 'remove_old' string.

        
$args['clear_destination']           = true;
        
$args['abort_if_destination_exists'] = false;
        return 
parent::install_package$args );
    }
}

<?
php

namespace WP_CLI;

/**
 * A theme upgrader class that clears the destination directory.
 */
class DestructiveThemeUpgrader extends \Theme_Upgrader {

    public function 
install_package$args = array() ) {
        
parent::upgrade_strings(); // Needed for the 'remove_old' string.

        
$args['clear_destination']           = true;
        
$args['abort_if_destination_exists'] = false;
        return 
parent::install_package$args );
    }
}

<?
php

namespace WP_CLI\Fetchers;

/**
 * Fetch a WordPress plugin based on one of its attributes.
 */
class Plugin extends Base {

    
/**
     * @var string $msg Error message to use when invalid data is provided
     */
    
protected $msg "The '%s' plugin could not be found.";

    
/**
     * Get a plugin object by name
     *
     * @param string $name
     * @return object|false
     */
    
public function get$name ) {
        
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Calling native WordPress hook.
        
foreach ( apply_filters'all_plugins'get_plugins() ) as $file => $_ ) {
            if ( 
"$name.php" === $file ||
                ( 
$name && $file === $name ) ||
                ( 
dirname$file ) === $name && '.' !== $name ) ) {
                return (object) 
compact'name''file' );
            }
        }

        return 
false;
    }
}

<?
php

namespace WP_CLI\Fetchers;

use 
WP_CLI\Utils;

/**
 * Fetch a WordPress theme based on one of its attributes.
 */
class Theme extends Base {

    
/**
     * @var string $msg Error message to use when invalid data is provided
     */
    
protected $msg "The '%s' theme could not be found.";

    
/**
     * Get a theme object by name
     *
     * @param string $name
     * @return object|false
     */
    
public function get$name ) {
        
// Workaround to equalize folder naming conventions across Win/Mac/Linux.
        // Returns false if theme stylesheet doesn't exactly match existing themes.
        
$existing_themes      wp_get_themes( array( 'errors' => null ) );
        
$existing_stylesheets array_keys$existing_themes );
        if ( ! 
in_array$name$existing_stylesheetstrue ) ) {
            
$inexact_match $this->find_inexact_match$name$existing_themes );
            if ( 
false !== $inexact_match ) {
                
$this->msg .= sprintf" Did you mean '%s'?"$inexact_match );
            }
            return 
false;
        }

        
$theme $existing_themes$name ];

        return 
$theme;
    }

    
/**
     * Find and return the key in $existing_themes that matches $name with
     * a case insensitive string comparison.
     *
     * @param string $name Name of theme received by command.
     * @param array  $existing_themes Key/value pair of existing themes, key is
     *                                a case sensitive name.
     * @return string|boolean Case sensitive name if match found, otherwise false.
     */
    
private function find_inexact_match$name$existing_themes ) {
        
$target strtolower$name );
        
$themes array_map'strtolower'array_keys$existing_themes ) );

        if ( 
in_array$target$themestrue ) ) {
            return 
$target;
        }

        
$suggestion Utils\get_suggestion$target$themes );

        if ( 
'' !== $suggestion ) {
            return 
$suggestion;
        }

        return 
false;
    }
}

<?
php

namespace WP_CLI;

use 
WP_CLI;

trait 
ParseThemeNameInput {

    
/**
     * If have optional args ([<theme>...]) and an all option, then check have something to do.
     *
     * @param array  $args Passed-in arguments.
     * @param bool   $all All flag.
     * @param string $verb Optional. Verb to use. Defaults to 'install'.
     * @return array Same as $args if not all, otherwise all slugs.
     * @throws ExitException If neither plugin name nor --all were provided.
     */
    
protected function check_optional_args_and_all$args$all$verb 'install' ) {
        if ( 
$all ) {
            
$args array_map(
                
'\WP_CLI\Utils\get_theme_name',
                
array_keys$this->get_all_themes() )
            );
        }

        if ( empty( 
$args ) ) {
            if ( ! 
$all ) {
                
WP_CLI::error'Please specify one or more themes, or use --all.' );
            }

            
$past_tense_verb Utils\past_tense_verb$verb );
            
WP_CLI::success"No themes {$past_tense_verb}." ); // Don't error if --all given for BC.
        
}

        return 
$args;
    }

    
/**
     * Gets all available themes.
     *
     * Uses the same filter core uses in themes.php to determine which themes
     * should be available to manage through the WP_Themes_List_Table class.
     *
     * @return array
     */
    
private function get_all_themes() {
        
$items              = array();
        
$theme_version_info = array();

        if ( 
is_multisite() ) {
            
$site_enabled get_option'allowedthemes' );
            if ( empty( 
$site_enabled ) ) {
                
$site_enabled = array();
            }

            
$network_enabled get_site_option'allowedthemes' );
            if ( empty( 
$network_enabled ) ) {
                
$network_enabled = array();
            }
        }

        
$all_update_info $this->get_update_info();
        
$checked_themes  = isset( $all_update_info->checked ) ? $all_update_info->checked : array();

        if ( ! empty( 
$checked_themes ) ) {
            foreach ( 
$checked_themes as $slug => $version ) {
                
$theme_version_info$slug ] = $this->is_theme_version_valid$slug$version );
            }
        }

        foreach ( 
wp_get_themes() as $key => $theme ) {
            
$file $theme->get_stylesheet_directory();

            
$update_info = ( isset( $all_update_info->response$theme->get_stylesheet() ] ) && null !== $all_update_info->response$theme->get_stylesheet() ] ) ? (array) $all_update_info->response$theme->get_stylesheet() ] : null;

            
$items$file ] = [
                
'name'           => $key,
                
'status'         => $this->get_status$theme ),
                
'update'         => (bool) $update_info,
                
'update_version' => isset( $update_info['new_version'] ) ? $update_info['new_version'] : null,
                
'update_package' => isset( $update_info['package'] ) ? $update_info['package'] : null,
                
'version'        => $theme->get'Version' ),
                
'update_id'      => $theme->get_stylesheet(),
                
'title'          => $theme->get'Name' ),
                
'description'    => wordwrap$theme->get'Description' ) ),
                
'author'         => $theme->get'Author' ),
            ];

            
// Compare version and update information in theme list.
            
if ( isset( $theme_version_info$key ] ) && false === $theme_version_info$key ] ) {
                
$items$file ]['update'] = 'version higher than expected';
            }

            if ( 
is_multisite() ) {
                if ( ! empty( 
$site_enabled$key ] ) && ! empty( $network_enabled$key ] ) ) {
                    
$items$file ]['enabled'] = 'network,site';
                } elseif ( ! empty( 
$network_enabled$key ] ) ) {
                    
$items$file ]['enabled'] = 'network';
                } elseif ( ! empty( 
$site_enabled$key ] ) ) {
                    
$items$file ]['enabled'] = 'site';
                } else {
                    
$items$file ]['enabled'] = 'no';
                }
            }
        }

        return 
$items;
    }

    
/**
     * Check if current version of the theme is higher than the one available at WP.org.
     *
     * @param string $slug Theme slug.
     * @param string $version Theme current version.
     *
     * @return bool|string
     */
    
protected function is_theme_version_valid$slug$version ) {
        
// Get Theme Info.
        
$theme_info themes_api'theme_information', array( 'slug' => $slug ) );

        
// Return empty string for themes not on WP.org.
        
if ( is_wp_error$theme_info ) ) {
            return 
'';
        }

        
// Compare theme version info.
        
return ! version_compare$version$theme_info->version'>' );
    }

    
/**
     * Get the status for a given theme.
     *
     * @param string $theme Theme to get the status for.
     *
     * @return string Status of the theme.
     */
    
protected function get_status$theme ) {
        if ( 
$this->is_active_theme$theme ) ) {
            return 
'active';
        }

        if ( 
$theme->get_stylesheet_directory() === get_template_directory() ) {
            return 
'parent';
        }

        return 
'inactive';
    }

    
/**
     * Check whether a given theme is the active theme.
     *
     * @param string $theme Theme to check.
     *
     * @return bool Whether the provided theme is the active theme.
     */
    
protected function is_active_theme$theme ) {
        return 
$theme->get_stylesheet_directory() === get_stylesheet_directory();
    }

    
/**
     * Get the available update info.
     *
     * @return mixed Available update info.
     */
    
protected function get_update_info() {
        return 
get_site_transient'update_themes' );
    }
}
<?
php

namespace WP_CLI;

use 
Composer\Semver\Comparator;
use 
Exception;
use 
WP_CLI;
use 
WP_CLI\Fetchers;
use 
WP_CLI\Loggers;
use 
WP_CLI\Utils;
use 
WP_Error;

abstract class 
CommandWithUpgrade extends \WP_CLI_Command {

    protected 
$item_type;
    protected 
$obj_fields;

    protected 
$upgrade_refresh;
    protected 
$upgrade_transient;

    protected 
$chained_command false;

    
// Invalid version message.
    
const INVALID_VERSION_MESSAGE 'version higher than expected';

    public function 
__construct() {

        
// Do not automatically check translations updates after updating plugins/themes.
        
add_action(
            
'upgrader_process_complete',
            function() {
                
remove_action'upgrader_process_complete', [ 'Language_Pack_Upgrader''async_upgrade' ], 20 );
            },
            
1
        
);

        
add_filter(
            
'http_request_timeout',
            function () {
                return 
MINUTE_IN_SECONDS;
            },
            
999
        
);

        
$this->fetcher = new Fetchers\Plugin();
    }

    abstract protected function 
get_upgrader_class$force );

    abstract protected function 
get_item_list();

    
/**
     * @param array List of update candidates
     * @param array List of item names
     * @return array List of update candidates
     */
    
abstract protected function filter_item_list$items$args );

    abstract protected function 
get_all_items();

    abstract protected function 
get_status$file );

    abstract protected function 
status_single$args );

    abstract protected function 
install_from_repo$slug$assoc_args );

    public function 
status$args ) {
        
// Force WordPress to check for updates.
        
call_user_func$this->upgrade_refresh );

        if ( empty( 
$args ) ) {
            
$this->status_all();
        } else {
            
$this->status_single$args );
        }
    }

    private function 
status_all() {
        
$items $this->get_all_items();

        
$n count$items );

        
WP_CLI::log(
            
sprintf'%d installed %s:'$nUtils\pluralize$this->item_typeabsint$n ) ) )
        );

        
$padding $this->get_padding$items );

        foreach ( 
$items as $file => $details ) {
            if ( 
$details['update'] ) {
                
$line ' %yU%n';
            } else {
                
$line '  ';
            }

            
$line .= $this->format_status$details['status'], 'short' );
            
$line .= ' ' str_pad$details['name'], $padding ) . '%n';
            if ( ! empty( 
$details['version'] ) ) {
                
$line .= ' ' $details['version'];
            }

            
WP_CLI::lineWP_CLI::colorize$line ) );
        }

        
WP_CLI::line();

        
$this->show_legend$items );
    }

    private function 
get_padding$items ) {
        
$max_len 0;

        foreach ( 
$items as $details ) {
            
$len strlen$details['name'] );

            if ( 
$len $max_len ) {
                
$max_len $len;
            }
        }

        return 
$max_len;
    }

    private function 
show_legend$items ) {
        
$statuses array_uniquewp_list_pluck$items'status' ) );

        
$legend_line = array();

        foreach ( 
$statuses as $status ) {
            
$legend_line[] = sprintf(
                
'%s%s = %s%%n',
                
$this->get_color$status ),
                
$this->map['short'][ $status ],
                
$this->map['long'][ $status ]
            );
        }

        if ( 
in_arraytruewp_list_pluck$items'update' ), true ) ) {
            
$legend_line[] = '%yU = Update Available%n';
        }

        
WP_CLI::line'Legend: ' WP_CLI::colorizeimplode', '$legend_line ) ) );
    }

    public function 
install$args$assoc_args ) {

        
$successes 0;
        
$errors    0;
        foreach ( 
$args as $slug ) {

            if ( empty( 
$slug ) ) {
                
WP_CLI::warning'Ignoring ambiguous empty slug value.' );
                continue;
            }

            
$result false;

            
$is_remote false !== strpos$slug'://' );

            
// Check if a URL to a remote or local zip has been specified.
            
if ( $is_remote || ( pathinfo$slugPATHINFO_EXTENSION ) === 'zip' && is_file$slug ) ) ) {
                
// Install from local or remote zip file.
                
$file_upgrader $this->get_upgrader$assoc_args );

                
$filter false;
                
// If a GitHub URL, do some guessing as to the correct plugin/theme directory.
                
if ( $is_remote && 'github.com' === $this->parse_url_host_component$slugPHP_URL_HOST )
                        
// Don't attempt to rename ZIPs uploaded to the releases page or coming from a raw source.
                        
&& ! preg_match'#github\.com/[^/]+/[^/]+/(?:releases/download|raw)/#'$slug ) ) {

                    
$filter = function( $source$remote_source$upgrader ) use ( $slug ) {

                        
$slug_dir Utils\basename$this->parse_url_host_component$slugPHP_URL_PATH ), '.zip' );

                        
// Don't use the zip name if archive attached to release, as name likely to contain version tag/branch.
                        
if ( preg_match'#github\.com/[^/]+/([^/]+)/archive/#'$slug$matches ) ) {
                            
// Note this will be wrong if the project name isn't the same as the plugin/theme slug name.
                            
$slug_dir $matches[1];
                        }

                        
$source_dir Utils\basename$source ); // `$source` is trailing-slashed path to the unzipped archive directory, so basename returns the unslashed directory.
                        
if ( $source_dir === $slug_dir ) {
                            return 
$source;
                        }
                        
$new_path substr_replace$source$slug_dirstrrpos$source$source_dir ), strlen$source_dir ) );

                        if ( 
$GLOBALS['wp_filesystem']->move$source$new_path ) ) {
                            
WP_CLI::logsprintf"Renamed Github-based project from '%s' to '%s'."$source_dir$slug_dir ) );
                            return 
$new_path;
                        }

                        return new 
WP_Error'wpcli_install_github'"Couldn't move Github-based project to appropriate directory." );
                    };
                    
add_filter'upgrader_source_selection'$filter10);
                }

                if ( 
$file_upgrader->install$slug ) ) {
                    
$slug   $file_upgrader->result['destination_name'];
                    
$result true;
                    if ( 
$filter ) {
                        
remove_filter'upgrader_source_selection'$filter10 );
                    }
                    
$successes++;
                } else {
                    
$errors++;
                }
            } else {
                
// Assume a plugin/theme slug from the WordPress.org repository has been specified.
                
$result $this->install_from_repo$slug$assoc_args );

                if ( 
is_null$result ) ) {
                    
$errors++;
                } elseif ( 
is_wp_error$result ) ) {
                    
$key $result->get_error_code();
                    if ( 
in_array$key, [ 'plugins_api_failed''themes_api_failed' ], true )
                        && ! empty( 
$result->error_data$key ] ) && in_array$result->error_data$key ], [ 'N;''b:0;' ], true ) ) {
                        
WP_CLI::warning"Couldn't find '$slug' in the WordPress.org {$this->item_type} directory." );
                        
$errors++;
                    } else {
                        
WP_CLI::warning"$slug: " $result->get_error_message() );
                        if ( 
'already_installed' !== $key ) {
                            
$errors++;
                        }
                    }
                } else {
                    
$successes++;
                }
            }

            
// Check extension is available or not.
            
$extension $this->fetcher->get_many( array( $slug ) );

            
// If installation goes well $result will be true.
            
$allow_activation $result;

            
// Allow installation for installed extension.
            
if ( is_wp_error$result ) && 'already_installed' === $result->get_error_code() ) {
                
$allow_activation true;
            }

            if ( 
true === $allow_activation && count$extension ) > ) {
                
$this->chained_command true;
                if ( 
Utils\get_flag_value$assoc_args'activate-network' ) ) {
                    
WP_CLI::log"Network-activating '$slug'..." );
                    
$this->activate( array( $slug ), array( 'network' => true ) );
                }

                if ( 
Utils\get_flag_value$assoc_args'activate' ) ) {
                    
WP_CLI::log"Activating '$slug'..." );
                    
$this->activate( array( $slug ) );
                }
                
$this->chained_command false;
            }
        }
        
Utils\report_batch_operation_results$this->item_type'install'count$args ), $successes$errors );
    }

    
/**
     * Prepare an API response for downloading a particular version of an item.
     *
     * @param object $response wordpress.org API response
     * @param string $version The desired version of the package
     */
    
protected static function alter_api_response$response$version ) {
        if ( 
$response->version === $version ) {
            return;
        }

        
// WordPress.org forces https, but still sometimes returns http
        // See https://twitter.com/nacin/status/512362694205140992
        
$response->download_link str_replace'http://''https://'$response->download_link );

        list( 
$link ) = explode$response->slug$response->download_link );

        if ( 
false !== strpos$response->download_link'/theme/' ) ) {
            
$download_type 'theme';
        } elseif ( 
false !== strpos$response->download_link'/plugin/' ) ) {
            
$download_type 'plugin';
        } else {
            
$download_type 'plugin/theme';
        }

        if ( 
'dev' === $version ) {
            
$response->download_link $link $response->slug '.zip';
            
$response->version       'Development Version';
        } else {
            
$response->download_link $link $response->slug '.' $version '.zip';
            
$response->version       $version;

            
// Check if the requested version exists.
            
$response      wp_remote_head$response->download_link );
            
$response_code wp_remote_retrieve_response_code$response );
            if ( 
200 !== (int) $response_code ) {
                if ( 
is_wp_error$response ) ) {
                    
$error_msg $response->get_error_message();
                } else {
                    
$error_msg sprintf'HTTP code %d'$response_code );
                }
                
WP_CLI::error(
                    
sprintf(
                        
"Can't find the requested %s's version %s in the WordPress.org %s repository (%s).",
                        
$download_type,
                        
$version,
                        
$download_type,
                        
$error_msg
                    
)
                );
            }
        }
    }

    protected function 
get_upgrader$assoc_args ) {
        
$force          = (bool) Utils\get_flag_value$assoc_args'force'false );
        
$insecure       = (bool) Utils\get_flag_value$assoc_args'insecure'false );
        
$upgrader_class $this->get_upgrader_class$force );
        return 
Utils\get_upgrader$upgrader_class$insecure );
    }

    protected function 
update_many$args$assoc_args ) {
        
call_user_func$this->upgrade_refresh );

        if ( ! empty( 
$assoc_args['format'] ) && in_array$assoc_args['format'], [ 'json''csv' ], true ) ) {
            
$logger = new Loggers\QuietWP_CLI::get_runner()->in_color() );
            
WP_CLI::set_logger$logger );
        }

        if ( ! 
Utils\get_flag_value$assoc_args'all' ) && empty( $args ) ) {
            
WP_CLI::error"Please specify one or more {$this->item_type}s, or use --all." );
        }

        if ( 
Utils\get_flag_value$assoc_args'minor' ) && Utils\get_flag_value$assoc_args'patch' ) ) {
            
WP_CLI::error'--minor and --patch cannot be used together.' );
        }

        
$items $this->get_item_list();

        
$errors 0;
        if ( ! 
Utils\get_flag_value$assoc_args'all' ) ) {
            
$items  $this->filter_item_list$items$args );
            
$errors count$args ) - count$items );
        }

        
$items_to_update wp_list_filter$items, [ 'update' => true ] );

        
$minor = (bool) Utils\get_flag_value$assoc_args'minor'false );
        
$patch = (bool) Utils\get_flag_value$assoc_args'patch'false );

        if ( 
'plugin' === $this->item_type
            
&& ( $minor || $patch ) ) {
            
$type     $minor 'minor' 'patch';
            
$insecure = (bool) Utils\get_flag_value$assoc_args'insecure'false );

            
$items_to_update self::get_minor_or_patch_updates$items_to_update$type$insecure );
        }

        
$exclude Utils\get_flag_value$assoc_args'exclude' );
        if ( isset( 
$exclude ) ) {
            
$exclude_items explode','trim$assoc_args['exclude'], ',' ) );
            unset( 
$assoc_args['exclude'] );
            foreach ( 
$exclude_items as $item ) {
                if ( 
'plugin' === $this->item_type ) {
                    
$plugin $this->fetcher->get$item );
                    unset( 
$items_to_update$plugin->file ] );
                } elseif ( 
'theme' === $this->item_type ) {
                    
$theme_root get_theme_root() . '/' $item;
                    unset( 
$items_to_update$theme_root ] );
                }
            }
        }

        
// Check for items to update and remove extensions that have version higher than expected.
        
foreach ( $items_to_update as $item_key => $item_info ) {
            if ( static::
INVALID_VERSION_MESSAGE === $item_info['update'] ) {
                
WP_CLI::warning"{$item_info['name']}: " . static::INVALID_VERSION_MESSAGE '.' );
                
$errors++;
                unset( 
$items_to_update$item_key ] );
            }
        }

        if ( 
Utils\get_flag_value$assoc_args'dry-run' ) ) {
            if ( empty( 
$items_to_update ) ) {
                
WP_CLI::log"No {$this->item_type} updates available." );

                if ( 
null !== $exclude ) {
                    
WP_CLI::log"Skipped updates for: $exclude);
                }

                return;
            }

            if ( ! empty( 
$assoc_args['format'] ) && in_array$assoc_args['format'], [ 'json''csv' ], true ) ) {
                
Utils\format_items$assoc_args['format'], $items_to_update, [ 'name''status''version''update_version' ] );
            } elseif ( ! empty( 
$assoc_args['format'] ) && 'summary' === $assoc_args['format'] ) {
                
WP_CLI::log"Available {$this->item_type} updates:" );
                foreach ( 
$items_to_update as $item_to_update => $info ) {
                    
WP_CLI::log"{$info['title']} update from version {$info['version']} to version {$info['update_version']});
                }
            } else {
                
WP_CLI::log"Available {$this->item_type} updates:" );
                
Utils\format_items'table'$items_to_update, [ 'name''status''version''update_version' ] );
            }

            if ( 
null !== $exclude ) {
                
WP_CLI::log"Skipped updates for: $exclude);
            }

            return;
        }

        
$result = array();

        
// Only attempt to update if there is something to update.
        
if ( ! empty( $items_to_update ) ) {
            
$cache_manager WP_CLI::get_http_cache_manager();
            foreach ( 
$items_to_update as $item ) {
                
$cache_manager->whitelist_package$item['update_package'], $this->item_type$item['name'], $item['update_version'] );
            }
            
$upgrader $this->get_upgrader$assoc_args );
            
// Ensure the upgrader uses the download offer present in each item.
            
$transient_filter = function( $transient ) use ( $items_to_update ) {
                foreach ( 
$items_to_update as $name => $item_data ) {
                    if ( isset( 
$transient->response$name ] ) ) {
                        
$transient->response$name ]->new_version $item_data['update_version'];
                        
$transient->response$name ]->package     $item_data['update_package'];
                    }
                }
                return 
$transient;
            };
            
add_filter'site_transient_' $this->upgrade_transient$transient_filter999 );
            
$result $upgrader->bulk_upgradewp_list_pluck$items_to_update'update_id' ) );
            
remove_filter'site_transient_' $this->upgrade_transient$transient_filter999 );
        }

        
// Let the user know the results.
        
$num_to_update count$items_to_update );
        
$num_updated   count(
            
array_filter(
                
$result,
                static function ( 
$result ) {
                    return 
$result && ! is_wp_error$result );
                }
            )
        );

        if ( 
$num_to_update ) {
            if ( ! empty( 
$assoc_args['format'] ) && 'summary' === $assoc_args['format'] ) {
                foreach ( 
$items_to_update as $item_to_update => $info ) {
                    
$message null !== $result$info['update_id'] ] ? 'updated successfully' 'did not update';
                    
WP_CLI::log"{$info['title']} {$message} from version {$info['version']} to version {$info['update_version']});
                }
            } else {
                
$status = array();
                foreach ( 
$items_to_update as $item_to_update => $info ) {
                    
$status$item_to_update ] = [
                        
'name'        => $info['name'],
                        
'old_version' => $info['version'],
                        
'new_version' => $info['update_version'],
                        
'status'      => ( null !== $result$info['update_id'] ] && ! is_wp_error$result$info['update_id'] ] ) ) ? 'Updated' 'Error',
                    ];
                    if ( 
null === $result$info['update_id'] ] || is_wp_error$result$info['update_id'] ] ) ) {
                        
$errors++;
                    }
                }

                
$format 'table';
                if ( ! empty( 
$assoc_args['format'] ) && in_array$assoc_args['format'], [ 'json''csv' ], true ) ) {
                    
$format $assoc_args['format'];
                }

                
Utils\format_items$format$status, [ 'name''old_version''new_version''status' ] );
            }
        }

        
$total_updated Utils\get_flag_value$assoc_args'all' ) ? $num_to_update count$args );
        
Utils\report_batch_operation_results$this->item_type'update'$total_updated$num_updated$errors );
        if ( 
null !== $exclude ) {
            
WP_CLI::log"Skipped updates for: $exclude);
        }
    }

    
// phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore -- Whitelisting to provide backward compatibility to classes possibly extending this class.
    
protected function _list$_$assoc_args ) {

        
// Force WordPress to check for updates if `--skip-update-check` is not passed.
        
if ( false === (bool) Utils\get_flag_value$assoc_args'skip-update-check'false ) ) {
            
call_user_func$this->upgrade_refresh );
        }

        
$all_items $this->get_all_items();

        if ( ! 
is_array$all_items ) ) {
            
WP_CLI::error"No {$this->item_type}s found." );
        }

        foreach ( 
$all_items as $key => &$item ) {

            if ( empty( 
$item['version'] ) ) {
                
$item['version'] = '';
            }

            foreach ( 
$item as $field => &$value ) {
                if ( 
true === $value ) {
                    
$value 'available';
                } elseif ( 
false === $value ) {
                    
$value 'none';
                }
            }

            foreach ( 
$this->obj_fields as $field ) {
                if ( ! 
array_key_exists$field$assoc_args ) ) {
                    continue;
                }

                
// This can be either a value to filter by or a comma-separated list of values.
                // Also, it is not forbidden for a value to contain a comma (in which case we can filter only by one).
                
$field_filter $assoc_args$field ];
                if (
                    
$item$field ] !== $field_filter
                    
&& ! in_array$item$field ], array_map'trim'explode','$field_filter ) ), true )
                ) {
                    unset( 
$all_items$key ] );
                }
            }
        }

        
$formatter $this->get_formatter$assoc_args );
        
$formatter->display_items$all_items );
    }

    
/**
     * Check whether an item has an update available or not.
     *
     * @param string $slug The plugin/theme slug
     *
     * @return bool
     */
    
protected function has_update$slug ) {
        
$update_list get_site_transient$this->upgrade_transient );

        return isset( 
$update_list->response$slug ] );
    }

    
/**
     * Get the available update info
     *
     * @return mixed
     */
    
protected function get_update_info() {
        return 
get_site_transient$this->upgrade_transient );
    }

    private 
$map = [
        
'short' => [
            
'inactive'       => 'I',
            
'active'         => 'A',
            
'active-network' => 'N',
            
'must-use'       => 'M',
            
'parent'         => 'P',
            
'dropin'         => 'D',
        ],
        
'long'  => [
            
'inactive'       => 'Inactive',
            
'active'         => 'Active',
            
'active-network' => 'Network Active',
            
'must-use'       => 'Must Use',
            
'parent'         => 'Parent',
            
'dropin'         => 'Drop-In',
        ],
    ];

    protected function 
format_status$status$format ) {
        return 
$this->get_color$status ) . $this->map$format ][ $status ];
    }

    private function 
get_color$status ) {
        static 
$colors = [
            
'inactive'       => '',
            
'active'         => '%g',
            
'active-network' => '%g',
            
'must-use'       => '%c',
            
'parent'         => '%p',
            
'dropin'         => '%B',
        ];

        return 
$colors$status ];
    }

    
/**
     * Get the minor or patch version for plugins with available updates
     *
     * @param array  $items    Plugins with updates.
     * @param string $type     Either 'minor' or 'patch'.
     * @param bool   $insecure Whether to retry without certificate validation on TLS handshake failure.
     * @return array
     */
    
private function get_minor_or_patch_updates$items$type$insecure ) {
        
$wp_org_api = new WpOrgApi( [ 'insecure' => $insecure ] );
        foreach ( 
$items as $i => $item ) {
            try {
                
$data $wp_org_api->get_plugin_info$item['name'] );
            } catch ( 
Exception $exception ) {
                unset( 
$items$i ] );
                continue;
            }
            
// No minor or patch versions to access.
            
if ( empty( $data['versions'] ) ) {
                unset( 
$items$i ] );
                continue;
            }
            
$update_version false;
            
$update_package false;
            foreach ( 
$data['versions'] as $version => $download_link ) {
                
$update_type Utils\get_named_sem_ver$version$item['version'] );
                
// Compared version must be older.
                
if ( ! $update_type ) {
                    continue;
                }
                
// Only permit 'patch' for 'patch'.
                
if ( 'patch' === $type && 'patch' !== $update_type ) {
                    continue;
                }
                
// Permit 'minor' or 'patch' for 'minor' phpcs:ignore Squiz.PHP.CommentedOutCode.Found -- False positive.
                
if ( 'minor' === $type && ! in_array$update_type, array( 'minor''patch' ), true ) ) {
                    continue;
                }
                if ( 
$update_version && ! Comparator::greaterThan$version$update_version ) ) {
                    continue;
                }
                
$update_version $version;
                
$update_package $download_link;
            }
            
// If there's not a matching version, bail on updates.
            
if ( ! $update_version ) {
                unset( 
$items$i ] );
                continue;
            }
            
$items$i ]['update_version'] = $update_version;
            
$items$i ]['update_package'] = $update_package;
        }
        return 
$items;
    }

    
/**
     * Search wordpress.org repo.
     *
     * @param  array $args       A arguments array containing the search term in the first element.
     * @param  array $assoc_args Data passed in from command.
     */
    // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore -- Whitelisting to provide backward compatibility to classes possibly extending this class.
    
protected function _search$args$assoc_args ) {
        
$term $args[0];

        
$defaults   = [
            
'per-page' => 10,
            
'page'     => 1,
            
'fields'   => implode',', [ 'name''slug''rating' ] ),
        ];
        
$assoc_args array_merge$defaults$assoc_args );
        
$fields     = array();
        foreach ( 
explode','$assoc_args['fields'] ) as $field ) {
            
$fields$field ] = true;
        }

        
$format    = ! empty( $assoc_args['format'] ) ? $assoc_args['format'] : 'table';
        
$formatter $this->get_formatter$assoc_args );

        
$api_args = [
            
'per_page' => (int) $assoc_args['per-page'],
            
'page'     => (int) $assoc_args['page'],
            
'search'   => $term,
            
'fields'   => $fields,
        ];

        if ( 
'plugin' === $this->item_type ) {
            
$api plugins_api'query_plugins'$api_args );
        } else {
            
$api themes_api'query_themes'$api_args );
        }

        if ( 
is_wp_error$api ) ) {
            
WP_CLI::error$api->get_error_message() . __' Try again' ) );
        }

        
$plural $this->item_type 's';

        if ( ! isset( 
$api->$plural ) ) {
            
WP_CLI::error__'API error. Try Again.' ) );
        }

        
$items $api->$plural;

        
// Add `url` for plugin or theme on wordpress.org.
        
foreach ( $items as $index => $item_object ) {
            if ( 
$item_object instanceof \stdClass ) {
                
$item_object->url "https://wordpress.org/{$plural}/{$item_object->slug}/";
            }
        }

        if ( 
'table' === $format ) {
            
$count Utils\get_flag_value$api->info'results''unknown' );
            
WP_CLI::successsprintf'Showing %s of %s %s.'count$items ), $count$plural ) );
        }

        
$formatter->display_items$items );
    }

    protected function 
get_formatter( &$assoc_args ) {
        return new 
\WP_CLI\Formatter$assoc_args$this->obj_fields$this->item_type );
    }

    
/**
     * Error handler to ignore failures on accessing SSL "https://api.wordpress.org/themes/update-check/1.1/" in `wp_update_themes()`
     * and "https://api.wordpress.org/plugins/update-check/1.1/" in `wp_update_plugins()` which seem to occur intermittently.
     */
    
public static function error_handler$errno$errstr$errfile$errline$errcontext null ) {
        
// If ignoring E_USER_WARNING | E_USER_NOTICE, default.
        
if ( ! ( error_reporting() & $errno ) ) {
            return 
false;
        }
        
// If not in "wp-includes/update.php", default.
        
$update_php 'wp-includes/update.php';
        if ( 
!== substr_compare$errfile$update_php, -strlen$update_php ) ) ) {
            return 
false;
        }
        
// Else assume it's in `wp_update_themes()` or `wp_update_plugins()` and just ignore it.
        
return true;
    }

    
/**
     * Retrieves PHP_URL_HOST component from URL.
     *
     * @param int $component The component to retrieve.
     *
     * @return string
     */
    
private function parse_url_host_component$url$component ) {
        
// phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url -- parse_url will only be used in absence of wp_parse_url.
        
return function_exists'wp_parse_url' ) ? wp_parse_url$url$component ) : parse_url$url$component );
    }

}
<?
php

/**
 * Sets, gets, and removes theme mods.
 *
 * ## EXAMPLES
 *
 *     # Set the 'background_color' theme mod to '000000'.
 *     $ wp theme mod set background_color 000000
 *     Success: Theme mod background_color set to 000000
 *
 *     # Get single theme mod in JSON format.
 *     $ wp theme mod get background_color --format=json
 *     [{"key":"background_color","value":"dd3333"}]
 *
 *     # Remove all theme mods.
 *     $ wp theme mod remove --all
 *     Success: Theme mods removed.
 */
class Theme_Mod_Command extends WP_CLI_Command {

    private 
$fields = [ 'key''value' ];

    
/**
     * Gets one or more theme mods.
     *
     * ## OPTIONS
     *
     * [<mod>...]
     * : One or more mods to get.
     *
     * [--field=<field>]
     * : Returns the value of a single field.
     *
     * [--all]
     * : List all theme mods
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - json
     *   - csv
     *   - yaml
     * ---
     *
     * ## EXAMPLES
     *
     *     # Get all theme mods.
     *     $ wp theme mod get --all
     *     +------------------+---------+
     *     | key              | value   |
     *     +------------------+---------+
     *     | background_color | dd3333  |
     *     | link_color       | #dd9933 |
     *     | main_text_color  | #8224e3 |
     *     +------------------+---------+
     *
     *     # Get single theme mod in JSON format.
     *     $ wp theme mod get background_color --format=json
     *     [{"key":"background_color","value":"dd3333"}]
     *
     *     # Get value of a single theme mod.
     *     $ wp theme mod get background_color --field=value
     *     dd3333
     *
     *     # Get multiple theme mods.
     *     $ wp theme mod get background_color header_textcolor
     *     +------------------+--------+
     *     | key              | value  |
     *     +------------------+--------+
     *     | background_color | dd3333 |
     *     | header_textcolor |        |
     *     +------------------+--------+
     */
    
public function get$args = array(), $assoc_args = array() ) {

        if ( ! 
\WP_CLI\Utils\get_flag_value$assoc_args'all' ) && empty( $args ) ) {
            
WP_CLI::error'You must specify at least one mod or use --all.' );
        }

        if ( 
\WP_CLI\Utils\get_flag_value$assoc_args'all' ) ) {
            
$args = array();
        }

        
$list = array();
        
$mods get_theme_mods();
        if ( ! 
is_array$mods ) ) {
            
// If no mods are set (perhaps new theme), make sure foreach still works.
            
$mods = array();
        }
        foreach ( 
$mods as $k => $v ) {
            
// If mods were given, skip the others.
            
if ( ! empty( $args ) && ! in_array$k$argstrue ) ) {
                continue;
            }

            if ( 
is_array$v ) ) {
                
$list[] = [
                    
'key'   => $k,
                    
'value' => '=>',
                ];
                foreach ( 
$v as $_k => $_v ) {
                    
$list[] = [
                        
'key'   => "    $_k",
                        
'value' => $_v,
                    ];
                }
            } else {
                
$list[] = [
                    
'key'   => $k,
                    
'value' => $v,
                ];
            }
        }

        
// For unset mods, show blank value.
        
foreach ( $args as $mod ) {
            if ( ! isset( 
$mods$mod ] ) ) {
                
$list[] = [
                    
'key'   => $mod,
                    
'value' => '',
                ];
            }
        }

        
$formatter = new \WP_CLI\Formatter$assoc_args$this->fields'thememods' );
        
$formatter->display_items$list );

    }

    
/**
     * Gets a list of theme mods.
     *
     * ## OPTIONS
     *
     * [--field=<field>]
     * : Returns the value of a single field.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - json
     *   - csv
     *   - yaml
     * ---
     *
     * ## EXAMPLES
     *
     *     # Gets a list of theme mods.
     *     $ wp theme mod list
     *     +------------------+---------+
     *     | key              | value   |
     *     +------------------+---------+
     *     | background_color | dd3333  |
     *     | link_color       | #dd9933 |
     *     | main_text_color  | #8224e3 |
     *     +------------------+---------+
     *
     * @subcommand list
     */
    
public function list_$args = array(), $assoc_args = array() ) {

        
$assoc_args['all'] = 1;

        
$this->get$args$assoc_args );
    }

    
/**
     * Removes one or more theme mods.
     *
     * ## OPTIONS
     *
     * [<mod>...]
     * : One or more mods to remove.
     *
     * [--all]
     * : Remove all theme mods.
     *
     * ## EXAMPLES
     *
     *     # Remove all theme mods.
     *     $ wp theme mod remove --all
     *     Success: Theme mods removed.
     *
     *     # Remove single theme mod.
     *     $ wp theme mod remove background_color
     *     Success: 1 mod removed.
     *
     *     # Remove multiple theme mods.
     *     $ wp theme mod remove background_color header_textcolor
     *     Success: 2 mods removed.
     */
    
public function remove$args = array(), $assoc_args = array() ) {

        if ( ! 
\WP_CLI\Utils\get_flag_value$assoc_args'all' ) && empty( $args ) ) {
            
WP_CLI::error'You must specify at least one mod or use --all.' );
        }

        if ( 
\WP_CLI\Utils\get_flag_value$assoc_args'all' ) ) {
            
remove_theme_mods();
            
WP_CLI::success'Theme mods removed.' );
            return;
        }

        foreach ( 
$args as $mod ) {
            
remove_theme_mod$mod );
        }

        
$count           count$args );
        
$success_message = ( === $count ) ? '%d mod removed.' '%d mods removed.';
        
WP_CLI::successsprintf$success_message$count ) );

    }

    
/**
     * Sets the value of a theme mod.
     *
     * ## OPTIONS
     *
     * <mod>
     * : The name of the theme mod to set or update.
     *
     * <value>
     * : The new value.
     *
     * ## EXAMPLES
     *
     *     # Set theme mod
     *     $ wp theme mod set background_color 000000
     *     Success: Theme mod background_color set to 000000
     */
    
public function set$args = array(), $assoc_args = array() ) {
        list( 
$mod$value ) = $args;

        
set_theme_mod$mod$value );

        if ( 
get_theme_mod$mod ) === $value ) {
            
WP_CLI::success"Theme mod {$mod} set to {$value}." );
        } else {
            
WP_CLI::success"Could not update theme mod {$mod}." );
        }
    }

}
<?
php

use WP_CLI\ParsePluginNameInput;
use 
WP_CLI\Utils;

/**
 * Manages plugins, including installs, activations, and updates.
 *
 * See the WordPress [Plugin Handbook](https://developer.wordpress.org/plugins/) developer resource for more information on plugins.
 *
 * ## EXAMPLES
 *
 *     # Activate plugin
 *     $ wp plugin activate hello
 *     Plugin 'hello' activated.
 *     Success: Activated 1 of 1 plugins.
 *
 *     # Deactivate plugin
 *     $ wp plugin deactivate hello
 *     Plugin 'hello' deactivated.
 *     Success: Deactivated 1 of 1 plugins.
 *
 *     # Delete plugin
 *     $ wp plugin delete hello
 *     Deleted 'hello' plugin.
 *     Success: Deleted 1 of 1 plugins.
 *
 *     # Install the latest version from wordpress.org and activate
 *     $ wp plugin install bbpress --activate
 *     Installing bbPress (2.5.9)
 *     Downloading install package from https://downloads.wordpress.org/plugin/bbpress.2.5.9.zip...
 *     Using cached file '/home/vagrant/.wp-cli/cache/plugin/bbpress-2.5.9.zip'...
 *     Unpacking the package...
 *     Installing the plugin...
 *     Plugin installed successfully.
 *     Activating 'bbpress'...
 *     Plugin 'bbpress' activated.
 *     Success: Installed 1 of 1 plugins.
 *
 * @package wp-cli
 */
class Plugin_Command extends \WP_CLI\CommandWithUpgrade {

    use 
ParsePluginNameInput;

    protected 
$item_type         'plugin';
    protected 
$upgrade_refresh   'wp_update_plugins';
    protected 
$upgrade_transient 'update_plugins';

    protected 
$obj_fields = array(
        
'name',
        
'status',
        
'update',
        
'version',
    );

    
/**
     * Plugin fetcher instance.
     *
     * @var \WP_CLI\Fetchers\Plugin
     */
    
protected $fetcher;

    public function 
__construct() {
        require_once 
ABSPATH 'wp-admin/includes/plugin.php';
        require_once 
ABSPATH 'wp-admin/includes/plugin-install.php';

        
parent::__construct();

        
$this->fetcher = new WP_CLI\Fetchers\Plugin();
    }

    protected function 
get_upgrader_class$force ) {
        return 
$force '\\WP_CLI\\DestructivePluginUpgrader' 'Plugin_Upgrader';
    }

    
/**
     * Reveals the status of one or all plugins.
     *
     * ## OPTIONS
     *
     * [<plugin>]
     * : A particular plugin to show the status for.
     *
     * ## EXAMPLES
     *
     *     # Displays status of all plugins
     *     $ wp plugin status
     *     5 installed plugins:
     *       I akismet                3.1.11
     *       I easy-digital-downloads 2.5.16
     *       A theme-check            20160523.1
     *       I wen-logo-slider        2.0.3
     *       M ns-pack                1.0.0
     *     Legend: I = Inactive, A = Active, M = Must Use
     *
     *     # Displays status of a plugin
     *     $ wp plugin status theme-check
     *     Plugin theme-check details:
     *         Name: Theme Check
     *         Status: Active
     *         Version: 20160523.1
     *         Author: Otto42, pross
     *         Description: A simple and easy way to test your theme for all the latest WordPress standards and practices. A great theme development tool!
     */
    
public function status$args ) {
        
parent::status$args );
    }

    
/**
     * Searches the WordPress.org plugin directory.
     *
     * Displays plugins in the WordPress.org plugin directory matching a given
     * search query.
     *
     * ## OPTIONS
     *
     * <search>
     * : The string to search for.
     *
     * [--page=<page>]
     * : Optional page to display.
     * ---
     * default: 1
     * ---
     *
     * [--per-page=<per-page>]
     * : Optional number of results to display.
     * ---
     * default: 10
     * ---
     *
     * [--field=<field>]
     * : Prints the value of a single field for each plugin.
     *
     * [--fields=<fields>]
     * : Ask for specific fields from the API. Defaults to name,slug,author_profile,rating. Acceptable values:
     *
     *     **name**: Plugin Name
     *     **slug**: Plugin Slug
     *     **version**: Current Version Number
     *     **author**: Plugin Author
     *     **author_profile**: Plugin Author Profile
     *     **contributors**: Plugin Contributors
     *     **requires**: Plugin Minimum Requirements
     *     **tested**: Plugin Tested Up To
     *     **compatibility**: Plugin Compatible With
     *     **rating**: Plugin Rating in Percent and Total Number
     *     **ratings**: Plugin Ratings for each star (1-5)
     *     **num_ratings**: Number of Plugin Ratings
     *     **homepage**: Plugin Author's Homepage
     *     **description**: Plugin's Description
     *     **short_description**: Plugin's Short Description
     *     **sections**: Plugin Readme Sections: description, installation, FAQ, screenshots, other notes, and changelog
     *     **downloaded**: Plugin Download Count
     *     **last_updated**: Plugin's Last Update
     *     **added**: Plugin's Date Added to wordpress.org Repository
     *     **tags**: Plugin's Tags
     *     **versions**: Plugin's Available Versions with D/L Link
     *     **donate_link**: Plugin's Donation Link
     *     **banners**: Plugin's Banner Image Link
     *     **icons**: Plugin's Icon Image Link
     *     **active_installs**: Plugin's Number of Active Installs
     *     **contributors**: Plugin's List of Contributors
     *     **url**: Plugin's URL on wordpress.org
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - count
     *   - json
     *   - yaml
     * ---
     *
     * ## EXAMPLES
     *
     *     $ wp plugin search dsgnwrks --per-page=20 --format=json
     *     Success: Showing 3 of 3 plugins.
     *     [{"name":"DsgnWrks Instagram Importer Debug","slug":"dsgnwrks-instagram-importer-debug","rating":0},{"name":"DsgnWrks Instagram Importer","slug":"dsgnwrks-instagram-importer","rating":84},{"name":"DsgnWrks Twitter Importer","slug":"dsgnwrks-twitter-importer","rating":80}]
     *
     *     $ wp plugin search dsgnwrks --fields=name,version,slug,rating,num_ratings
     *     Success: Showing 3 of 3 plugins.
     *     +-----------------------------------+---------+-----------------------------------+--------+-------------+
     *     | name                              | version | slug                              | rating | num_ratings |
     *     +-----------------------------------+---------+-----------------------------------+--------+-------------+
     *     | DsgnWrks Instagram Importer Debug | 0.1.6   | dsgnwrks-instagram-importer-debug | 0      | 0           |
     *     | DsgnWrks Instagram Importer       | 1.3.7   | dsgnwrks-instagram-importer       | 84     | 23          |
     *     | DsgnWrks Twitter Importer         | 1.1.1   | dsgnwrks-twitter-importer         | 80     | 1           |
     *     +-----------------------------------+---------+-----------------------------------+--------+-------------+
     */
    
public function search$args$assoc_args ) {
        
parent::_search$args$assoc_args );
    }

    protected function 
status_single$args ) {
        
$plugin $this->fetcher->get_check$args[0] );
        
$file   $plugin->file;

        
$details $this->get_details$file );

        
$status $this->format_status$this->get_status$file ), 'long' );

        
$version $details['Version'];

        if ( 
$this->has_update$file ) ) {
            
$version .= ' (%gUpdate available%n)';
        }

        echo 
WP_CLI::colorize(
            
Utils\mustache_render(
                
self::get_template_path'plugin-status.mustache' ),
                [
                    
'slug'        => Utils\get_plugin_name$file ),
                    
'status'      => $status,
                    
'version'     => $version,
                    
'name'        => $details['Name'],
                    
'author'      => $details['Author'],
                    
'description' => $details['Description'],
                ]
            )
        );
    }

    protected function 
get_all_items() {
        
$items $this->get_item_list();

        foreach ( 
get_mu_plugins() as $file => $mu_plugin ) {
            
$mu_version '';
            if ( ! empty( 
$mu_plugin['Version'] ) ) {
                
$mu_version $mu_plugin['Version'];
            }

            
$items$file ] = array(
                
'name'           => Utils\get_plugin_name$file ),
                
'status'         => 'must-use',
                
'update'         => false,
                
'update_version' => null,
                
'update_package' => null,
                
'version'        => $mu_version,
                
'update_id'      => '',
                
'title'          => '',
                
'description'    => '',
                
'file'           => $file,
            );
        }

        
$raw_items get_dropins();
        
$raw_data  _get_dropins();
        foreach ( 
$raw_items as $name => $item_data ) {
            
$description    = ! empty( $raw_data$name ][0] ) ? $raw_data$name ][0] : '';
            
$items$name ] = [
                
'name'           => $name,
                
'title'          => $item_data['Title'],
                
'description'    => $description,
                
'status'         => 'dropin',
                
'update'         => false,
                
'update_version' => null,
                
'update_package' => null,
                
'update_id'      => '',
                
'file'           => $name,
            ];
        }

        return 
$items;
    }

    
/**
     * Activates one or more plugins.
     *
     * ## OPTIONS
     *
     * [<plugin>...]
     * : One or more plugins to activate.
     *
     * [--all]
     * : If set, all plugins will be activated.
     *
     * [--network]
     * : If set, the plugin will be activated for the entire multisite network.
     *
     * ## EXAMPLES
     *
     *     # Activate plugin
     *     $ wp plugin activate hello
     *     Plugin 'hello' activated.
     *     Success: Activated 1 of 1 plugins.
     *
     *     # Activate plugin in entire multisite network
     *     $ wp plugin activate hello --network
     *     Plugin 'hello' network activated.
     *     Success: Network activated 1 of 1 plugins.
     */
    
public function activate$args$assoc_args = array() ) {
        
$network_wide Utils\get_flag_value$assoc_args'network' );
        
$all          Utils\get_flag_value$assoc_args'all'false );

        
$args $this->check_optional_args_and_all$args$all );
        if ( ! 
$args ) {
            return;
        }

        
$successes 0;
        
$errors    0;
        
$plugins   $this->fetcher->get_many$args );
        if ( 
count$plugins ) < count$args ) ) {
            
$errors count$args ) - count$plugins );
        }
        foreach ( 
$plugins as $plugin ) {
            
$status $this->get_status$plugin->file );
            if ( 
$all && in_array$status, [ 'active''active-network' ], true ) ) {
                continue;
            }
            
// Network-active is the highest level of activation status.
            
if ( 'active-network' === $status ) {
                
WP_CLI::warning"Plugin '{$plugin->name}' is already network active." );
                continue;
            }
            
// Don't reactivate active plugins, but do let them become network-active.
            
if ( ! $network_wide && 'active' === $status ) {
                
WP_CLI::warning"Plugin '{$plugin->name}' is already active." );
                continue;
            }

            
// Plugins need to be deactivated before being network activated.
            
if ( $network_wide && 'active' === $status ) {
                
deactivate_plugins$plugin->filefalsefalse );
            }

            
$result activate_plugin$plugin->file''$network_wide );

            if ( 
is_wp_error$result ) ) {
                
$message $result->get_error_message();
                
$message preg_replace'/<a\s[^>]+>.*<\/a>/im'''$message );
                
$message wp_strip_all_tags$message );
                
$message str_replace'Error: '''$message );
                
WP_CLI::warning"Failed to activate plugin. {$message});
            } else {
                
$this->active_output$plugin->name$plugin->file$network_wide'activate' );
                
$successes++;
            }
        }

        if ( ! 
$this->chained_command ) {
            
$verb $network_wide 'network activate' 'activate';
            
Utils\report_batch_operation_results'plugin'$verbcount$args ), $successes$errors );
        }

    }

    
/**
     * Deactivates one or more plugins.
     *
     * ## OPTIONS
     *
     * [<plugin>...]
     * : One or more plugins to deactivate.
     *
     * [--uninstall]
     * : Uninstall the plugin after deactivation.
     *
     * [--all]
     * : If set, all plugins will be deactivated.
     *
     * [--network]
     * : If set, the plugin will be deactivated for the entire multisite network.
     *
     * ## EXAMPLES
     *
     *     # Deactivate plugin
     *     $ wp plugin deactivate hello
     *     Plugin 'hello' deactivated.
     *     Success: Deactivated 1 of 1 plugins.
     */
    
public function deactivate$args$assoc_args = array() ) {
        
$network_wide Utils\get_flag_value$assoc_args'network' );
        
$disable_all  Utils\get_flag_value$assoc_args'all' );

        
$args $this->check_optional_args_and_all$args$disable_all );
        if ( ! 
$args ) {
            return;
        }

        
$successes 0;
        
$errors    0;
        
$plugins   $this->fetcher->get_many$args );
        if ( 
count$plugins ) < count$args ) ) {
            
$errors count$args ) - count$plugins );
        }

        foreach ( 
$plugins as $plugin ) {

            
$status $this->get_status$plugin->file );
            if ( 
$disable_all && ! in_array$status, [ 'active''active-network' ], true ) ) {
                continue;
            }

            
// Network active plugins must be explicitly deactivated.
            
if ( ! $network_wide && 'active-network' === $status ) {
                
WP_CLI::warning"Plugin '{$plugin->name}' is network active and must be deactivated with --network flag." );
                
$errors++;
                continue;
            }

            if ( ! 
in_array$status, [ 'active''active-network' ], true ) ) {
                
WP_CLI::warning"Plugin '{$plugin->name}' isn't active." );
                continue;
            }

            
deactivate_plugins$plugin->filefalse$network_wide );

            if ( ! 
is_network_admin() ) {
                
update_option(
                    
'recently_activated',
                    array( 
$plugin->file => time() ) + (array) get_option'recently_activated' )
                );
            } else {
                
update_site_option(
                    
'recently_activated',
                    array( 
$plugin->file => time() ) + (array) get_site_option'recently_activated' )
                );
            }

            
$this->active_output$plugin->name$plugin->file$network_wide'deactivate' );
            
$successes++;

            if ( 
Utils\get_flag_value$assoc_args'uninstall' ) ) {
                
WP_CLI::log"Uninstalling '{$plugin->name}'..." );
                
$this->chained_command true;
                
$this->uninstall( array( $plugin->name ) );
                
$this->chained_command false;
            }
        }

        if ( ! 
$this->chained_command ) {
            
$verb $network_wide 'network deactivate' 'deactivate';
            
Utils\report_batch_operation_results'plugin'$verbcount$args ), $successes$errors );
        }

    }

    
/**
     * Toggles a plugin's activation state.
     *
     * If the plugin is active, then it will be deactivated. If the plugin is
     * inactive, then it will be activated.
     *
     * ## OPTIONS
     *
     * <plugin>...
     * : One or more plugins to toggle.
     *
     * [--network]
     * : If set, the plugin will be toggled for the entire multisite network.
     *
     * ## EXAMPLES
     *
     *     # Akismet is currently activated
     *     $ wp plugin toggle akismet
     *     Plugin 'akismet' deactivated.
     *     Success: Toggled 1 of 1 plugins.
     *
     *     # Akismet is currently deactivated
     *     $ wp plugin toggle akismet
     *     Plugin 'akismet' activated.
     *     Success: Toggled 1 of 1 plugins.
     */
    
public function toggle$args$assoc_args = array() ) {
        
$network_wide Utils\get_flag_value$assoc_args'network' );

        
$successes 0;
        
$errors    0;
        
$plugins   $this->fetcher->get_many$args );
        if ( 
count$plugins ) < count$args ) ) {
            
$errors count$args ) - count$plugins );
        }
        
$this->chained_command true;
        foreach ( 
$plugins as $plugin ) {
            if ( 
$this->check_active$plugin->file$network_wide ) ) {
                
$this->deactivate( array( $plugin->name ), $assoc_args );
            } else {
                
$this->activate( array( $plugin->name ), $assoc_args );
            }
            
$successes++;
        }
        
$this->chained_command false;
        
Utils\report_batch_operation_results'plugin''toggle'count$args ), $successes$errors );
    }

    
/**
     * Gets the path to a plugin or to the plugin directory.
     *
     * ## OPTIONS
     *
     * [<plugin>]
     * : The plugin to get the path to. If not set, will return the path to the
     * plugins directory.
     *
     * [--dir]
     * : If set, get the path to the closest parent directory, instead of the
     * plugin file.
     *
     * ## EXAMPLES
     *
     *     $ cd $(wp plugin path) && pwd
     *     /var/www/wordpress/wp-content/plugins
     */
    
public function path$args$assoc_args ) {
        
$path untrailingslashitWP_PLUGIN_DIR );

        if ( ! empty( 
$args ) ) {
            
$plugin $this->fetcher->get_check$args[0] );
            
$path  .= '/' $plugin->file;

            if ( 
Utils\get_flag_value$assoc_args'dir' ) ) {
                
$path dirname$path );
            }
        }

        
WP_CLI::line$path );
    }

    protected function 
install_from_repo$slug$assoc_args ) {
        
$api plugins_api'plugin_information', array( 'slug' => $slug ) );

        if ( 
is_wp_error$api ) ) {
            return 
$api;
        }

        if ( isset( 
$assoc_args['version'] ) ) {
            
self::alter_api_response$api$assoc_args['version'] );
        }

        
$status install_plugin_install_status$api );

        if ( ! 
Utils\get_flag_value$assoc_args'force' ) && 'install' !== $status['status'] ) {
            
// We know this will fail, so avoid a needless download of the package.
            
return new WP_Error'already_installed''Plugin already installed.' );
        }

        
WP_CLI::logsprintf'Installing %s (%s)'html_entity_decode$api->nameENT_QUOTES ), $api->version ) );
        if ( 
Utils\get_flag_value$assoc_args'version' ) !== 'dev' ) {
            
WP_CLI::get_http_cache_manager()->whitelist_package$api->download_link$this->item_type$api->slug$api->version );
        }

        
// Ignore failures on accessing SSL "https://api.wordpress.org/plugins/update-check/1.1/" in `wp_update_plugins()` which seem to occur intermittently.
        
set_error_handler( array( __CLASS__'error_handler' ), E_USER_WARNING E_USER_NOTICE );

        
$result $this->get_upgrader$assoc_args )->install$api->download_link );

        
restore_error_handler();

        return 
$result;
    }

    
/**
     * Updates one or more plugins.
     *
     * ## OPTIONS
     *
     * [<plugin>...]
     * : One or more plugins to update.
     *
     * [--all]
     * : If set, all plugins that have updates will be updated.
     *
     * [--exclude=<name>]
     * : Comma separated list of plugin names that should be excluded from updating.
     *
     * [--minor]
     * : Only perform updates for minor releases (e.g. from 1.3 to 1.4 instead of 2.0)
     *
     * [--patch]
     * : Only perform updates for patch releases (e.g. from 1.3 to 1.3.3 instead of 1.4)
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - summary
     * ---
     *
     * [--version=<version>]
     * : If set, the plugin will be updated to the specified version.
     *
     * [--dry-run]
     * : Preview which plugins would be updated.
     *
     * [--insecure]
     * : Retry downloads without certificate validation if TLS handshake fails. Note: This makes the request vulnerable to a MITM attack.
     *
     * ## EXAMPLES
     *
     *     $ wp plugin update bbpress --version=dev
     *     Installing bbPress (Development Version)
     *     Downloading install package from https://downloads.wordpress.org/plugin/bbpress.zip...
     *     Unpacking the package...
     *     Installing the plugin...
     *     Removing the old version of the plugin...
     *     Plugin updated successfully.
     *     Success: Updated 1 of 2 plugins.
     *
     *     $ wp plugin update --all
     *     Enabling Maintenance mode...
     *     Downloading update from https://downloads.wordpress.org/plugin/akismet.3.1.11.zip...
     *     Unpacking the update...
     *     Installing the latest version...
     *     Removing the old version of the plugin...
     *     Plugin updated successfully.
     *     Downloading update from https://downloads.wordpress.org/plugin/nginx-champuru.3.2.0.zip...
     *     Unpacking the update...
     *     Installing the latest version...
     *     Removing the old version of the plugin...
     *     Plugin updated successfully.
     *     Disabling Maintenance mode...
     *     +------------------------+-------------+-------------+---------+
     *     | name                   | old_version | new_version | status  |
     *     +------------------------+-------------+-------------+---------+
     *     | akismet                | 3.1.3       | 3.1.11      | Updated |
     *     | nginx-cache-controller | 3.1.1       | 3.2.0       | Updated |
     *     +------------------------+-------------+-------------+---------+
     *     Success: Updated 2 of 2 plugins.
     *
     *     $ wp plugin update --all --exclude=akismet
     *     Enabling Maintenance mode...
     *     Downloading update from https://downloads.wordpress.org/plugin/nginx-champuru.3.2.0.zip...
     *     Unpacking the update...
     *     Installing the latest version...
     *     Removing the old version of the plugin...
     *     Plugin updated successfully.
     *     Disabling Maintenance mode...
     *     +------------------------+-------------+-------------+---------+
     *     | name                   | old_version | new_version | status  |
     *     +------------------------+-------------+-------------+---------+
     *     | nginx-cache-controller | 3.1.1       | 3.2.0       | Updated |
     *     +------------------------+-------------+-------------+---------+
     *
     * @alias upgrade
     */
    
public function update$args$assoc_args ) {
        
$all Utils\get_flag_value$assoc_args'all'false );

        
$args $this->check_optional_args_and_all$args$all );
        if ( ! 
$args ) {
            return;
        }

        if ( isset( 
$assoc_args['version'] ) ) {
            foreach ( 
$this->fetcher->get_many$args ) as $plugin ) {
                
$assoc_args['force'] = 1;
                
$this->install( array( $plugin->name ), $assoc_args );
            }
        } else {
            
parent::update_many$args$assoc_args );
        }
    }

    protected function 
get_item_list() {
        
$items           = [];
        
$duplicate_names = [];

        foreach ( 
$this->get_all_plugins() as $file => $details ) {
            
$all_update_info $this->get_update_info();
            
$update_info     = ( isset( $all_update_info->response$file ] ) && null !== $all_update_info->response$file ] ) ? (array) $all_update_info->response$file ] : null;
            
$name            Utils\get_plugin_name$file );

            if ( ! isset( 
$duplicate_names$name ] ) ) {
                
$duplicate_names$name ] = array();
            }

            
$duplicate_names$name ][] = $file;
            
$items$file ]             = [
                
'name'           => $name,
                
'status'         => $this->get_status$file ),
                
'update'         => (bool) $update_info,
                
'update_version' => isset( $update_info ) && isset( $update_info['new_version'] ) ? $update_info['new_version'] : null,
                
'update_package' => isset( $update_info ) && isset( $update_info['package'] ) ? $update_info['package'] : null,
                
'version'        => $details['Version'],
                
'update_id'      => $file,
                
'title'          => $details['Name'],
                
'description'    => wordwrap$details['Description'] ),
                
'file'           => $file,
            ];

            if ( 
null === $update_info ) {

                
// Get info for all plugins that don't have an update.
                
$plugin_update_info = isset( $all_update_info->no_update$file ] ) ? $all_update_info->no_update$file ] : null;

                
// Compare version and update information in plugin list.
                
if ( null !== $plugin_update_info && version_compare$details['Version'], $plugin_update_info->new_version'>' ) ) {
                    
$items$file ]['update'] = static::INVALID_VERSION_MESSAGE;
                }
            }
        }

        foreach ( 
$duplicate_names as $name => $files ) {
            if ( 
count$files ) <= ) {
                continue;
            }
            foreach ( 
$files as $file ) {
                
$items$file ]['name'] = str_replace'.' pathinfo$filePATHINFO_EXTENSION ), ''$file );
            }
        }

        return 
$items;
    }

    protected function 
filter_item_list$items$args ) {
        
$basenames wp_list_pluck$this->fetcher->get_many$args ), 'file' );
        return 
Utils\pick_fields$items$basenames );
    }

    
/**
     * Installs one or more plugins.
     *
     * ## OPTIONS
     *
     * <plugin|zip|url>...
     * : One or more plugins to install. Accepts a plugin slug, the path to a local zip file, or a URL to a remote zip file.
     *
     * [--version=<version>]
     * : If set, get that particular version from wordpress.org, instead of the
     * stable version.
     *
     * [--force]
     * : If set, the command will overwrite any installed version of the plugin, without prompting
     * for confirmation.
     *
     * [--activate]
     * : If set, the plugin will be activated immediately after install.
     *
     * [--activate-network]
     * : If set, the plugin will be network activated immediately after install
     *
     * [--insecure]
     * : Retry downloads without certificate validation if TLS handshake fails. Note: This makes the request vulnerable to a MITM attack.
     *
     * ## EXAMPLES
     *
     *     # Install the latest version from wordpress.org and activate
     *     $ wp plugin install bbpress --activate
     *     Installing bbPress (2.5.9)
     *     Downloading install package from https://downloads.wordpress.org/plugin/bbpress.2.5.9.zip...
     *     Using cached file '/home/vagrant/.wp-cli/cache/plugin/bbpress-2.5.9.zip'...
     *     Unpacking the package...
     *     Installing the plugin...
     *     Plugin installed successfully.
     *     Activating 'bbpress'...
     *     Plugin 'bbpress' activated.
     *     Success: Installed 1 of 1 plugins.
     *
     *     # Install the development version from wordpress.org
     *     $ wp plugin install bbpress --version=dev
     *     Installing bbPress (Development Version)
     *     Downloading install package from https://downloads.wordpress.org/plugin/bbpress.zip...
     *     Unpacking the package...
     *     Installing the plugin...
     *     Plugin installed successfully.
     *     Success: Installed 1 of 1 plugins.
     *
     *     # Install from a local zip file
     *     $ wp plugin install ../my-plugin.zip
     *     Unpacking the package...
     *     Installing the plugin...
     *     Plugin installed successfully.
     *     Success: Installed 1 of 1 plugins.
     *
     *     # Install from a remote zip file
     *     $ wp plugin install http://s3.amazonaws.com/bucketname/my-plugin.zip?AWSAccessKeyId=123&Expires=456&Signature=abcdef
     *     Downloading install package from http://s3.amazonaws.com/bucketname/my-plugin.zip?AWSAccessKeyId=123&Expires=456&Signature=abcdef
     *     Unpacking the package...
     *     Installing the plugin...
     *     Plugin installed successfully.
     *     Success: Installed 1 of 1 plugins.
     *
     *     # Update from a remote zip file
     *     $ wp plugin install https://github.com/envato/wp-envato-market/archive/master.zip --force
     *     Downloading install package from https://github.com/envato/wp-envato-market/archive/master.zip
     *     Unpacking the package...
     *     Installing the plugin...
     *     Renamed Github-based project from 'wp-envato-market-master' to 'wp-envato-market'.
     *     Plugin updated successfully
     *     Success: Installed 1 of 1 plugins.
     *
     *     # Forcefully re-install all installed plugins
     *     $ wp plugin install $(wp plugin list --field=name) --force
     *     Installing Akismet (3.1.11)
     *     Downloading install package from https://downloads.wordpress.org/plugin/akismet.3.1.11.zip...
     *     Unpacking the package...
     *     Installing the plugin...
     *     Removing the old version of the plugin...
     *     Plugin updated successfully
     *     Success: Installed 1 of 1 plugins.
     */
    
public function install$args$assoc_args ) {

        if ( ! 
is_dirWP_PLUGIN_DIR ) ) {
            
wp_mkdir_pWP_PLUGIN_DIR );
        }

        
parent::install$args$assoc_args );
    }

    
/**
     * Gets details about an installed plugin.
     *
     * ## OPTIONS
     *
     * <plugin>
     * : The plugin to get.
     *
     * [--field=<field>]
     * : Instead of returning the whole plugin, returns the value of a single field.
     *
     * [--fields=<fields>]
     * : Limit the output to specific fields. Defaults to all fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - yaml
     * ---
     *
     * ## EXAMPLES
     *
     *     $ wp plugin get bbpress --format=json
     *     {"name":"bbpress","title":"bbPress","author":"The bbPress Contributors","version":"2.6-alpha","description":"bbPress is forum software with a twist from the creators of WordPress.","status":"active"}
     */
    
public function get$args$assoc_args ) {
        
$plugin $this->fetcher->get_check$args[0] );
        
$file   $plugin->file;

        
$plugin_data get_plugin_dataWP_PLUGIN_DIR '/' $filefalsefalse );

        
$plugin_obj = (object) [
            
'name'        => Utils\get_plugin_name$file ),
            
'title'       => $plugin_data['Name'],
            
'author'      => $plugin_data['Author'],
            
'version'     => $plugin_data['Version'],
            
'description' => wordwrap$plugin_data['Description'] ),
            
'status'      => $this->get_status$file ),
        ];

        if ( empty( 
$assoc_args['fields'] ) ) {
            
$plugin_array         get_object_vars$plugin_obj );
            
$assoc_args['fields'] = array_keys$plugin_array );
        }

        
$formatter $this->get_formatter$assoc_args );
        
$formatter->display_item$plugin_obj );
    }

    
/**
     * Uninstalls one or more plugins.
     *
     * ## OPTIONS
     *
     * [<plugin>...]
     * : One or more plugins to uninstall.
     *
     * [--deactivate]
     * : Deactivate the plugin before uninstalling. Default behavior is to warn and skip if the plugin is active.
     *
     * [--skip-delete]
     * : If set, the plugin files will not be deleted. Only the uninstall procedure
     * will be run.
     *
     * [--all]
     * : If set, all plugins will be uninstalled.
     *
     * ## EXAMPLES
     *
     *     $ wp plugin uninstall hello
     *     Uninstalled and deleted 'hello' plugin.
     *     Success: Uninstalled 1 of 1 plugins.
     */
    
public function uninstall$args$assoc_args = array() ) {

        
$all Utils\get_flag_value$assoc_args'all'false );

        
// Check if plugin names or --all is passed.
        
$args $this->check_optional_args_and_all$args$all'uninstall' );
        if ( ! 
$args ) {
            return;
        }

        
$successes 0;
        
$errors    0;
        
$plugins   $this->fetcher->get_many$args );
        if ( 
count$plugins ) < count$args ) ) {
            
$errors count$args ) - count$plugins );
        }

        foreach ( 
$plugins as $plugin ) {
            if ( 
is_plugin_active$plugin->file ) && ! WP_CLI\Utils\get_flag_value$assoc_args'deactivate' ) ) {
                
WP_CLI::warning"The '{$plugin->name}' plugin is active." );
                
$errors++;
                continue;
            }

            if ( 
Utils\get_flag_value$assoc_args'deactivate' ) ) {
                
WP_CLI::log"Deactivating '{$plugin->name}'..." );
                
$this->chained_command true;
                
$this->deactivate( array( $plugin->name ) );
                
$this->chained_command false;
            }

            
uninstall_plugin$plugin->file );

            if ( ! 
Utils\get_flag_value$assoc_args'skip-delete' ) && $this->delete_plugin$plugin ) ) {
                
WP_CLI::log"Uninstalled and deleted '$plugin->name' plugin." );
            } else {
                
WP_CLI::log"Ran uninstall procedure for '$plugin->name' plugin without deleting." );
            }
            
$successes++;
        }
        if ( ! 
$this->chained_command ) {
            
Utils\report_batch_operation_results'plugin''uninstall'count$args ), $successes$errors );
        }
    }

    
/**
     * Checks if a given plugin is installed.
     *
     * Returns exit code 0 when installed, 1 when uninstalled.
     *
     * ## OPTIONS
     *
     * <plugin>
     * : The plugin to check.
     *
     * ## EXAMPLES
     *
     *     # Check whether plugin is installed; exit status 0 if installed, otherwise 1
     *     $ wp plugin is-installed hello
     *     $ echo $?
     *     1
     *
     * @subcommand is-installed
     */
    
public function is_installed$args$assoc_args = array() ) {
        if ( 
$this->fetcher->get$args[0] ) ) {
            
WP_CLI::halt);
        } else {
            
WP_CLI::halt);
        }
    }

    
/**
     * Checks if a given plugin is active.
     *
     * Returns exit code 0 when active, 1 when not active.
     *
     * ## OPTIONS
     *
     * <plugin>
     * : The plugin to check.
     *
     * [--network]
     * : If set, check if plugin is network-activated.
     *
     * ## EXAMPLES
     *
     *     # Check whether plugin is Active; exit status 0 if active, otherwise 1
     *     $ wp plugin is-active hello
     *     $ echo $?
     *     1
     *
     * @subcommand is-active
     */
    
public function is_active$args$assoc_args = array() ) {
        
$network_wide Utils\get_flag_value$assoc_args'network' );

        
$plugin $this->fetcher->get$args[0] );

        if ( ! 
$plugin ) {
            
WP_CLI::halt);
        }

        
$this->check_active$plugin->file$network_wide ) ? WP_CLI::halt) : WP_CLI::halt);
    }

    
/**
     * Deletes plugin files without deactivating or uninstalling.
     *
     * ## OPTIONS
     *
     * [<plugin>...]
     * : One or more plugins to delete.
     *
     * [--all]
     * : If set, all plugins will be deleted.
     *
     * ## EXAMPLES
     *
     *     # Delete plugin
     *     $ wp plugin delete hello
     *     Deleted 'hello' plugin.
     *     Success: Deleted 1 of 1 plugins.
     *
     *     # Delete inactive plugins
     *     $ wp plugin delete $(wp plugin list --status=inactive --field=name)
     *     Deleted 'tinymce-templates' plugin.
     *     Success: Deleted 1 of 1 plugins.
     */
    
public function delete$args$assoc_args = array() ) {
        
$all Utils\get_flag_value$assoc_args'all'false );

        
// Check if plugin names or --all is passed.
        
$args $this->check_optional_args_and_all$args$all'delete' );
        if ( ! 
$args ) {
            return;
        }

        
$successes 0;
        
$errors    0;

        foreach ( 
$this->fetcher->get_many$args ) as $plugin ) {
            if ( 
$this->delete_plugin$plugin ) ) {
                
WP_CLI::log"Deleted '{$plugin->name}' plugin." );
                
$successes++;
            } else {
                
$errors++;
            }
        }
        if ( ! 
$this->chained_command ) {
            
Utils\report_batch_operation_results'plugin''delete'count$args ), $successes$errors );
        }
    }

    
/**
     * Gets a list of plugins.
     *
     * Displays a list of the plugins installed on the site with activation
     * status, whether or not there's an update available, etc.
     *
     * Use `--status=dropin` to list installed dropins (e.g. `object-cache.php`).
     *
     * ## OPTIONS
     *
     * [--<field>=<value>]
     * : Filter results based on the value of a field.
     *
     * [--field=<field>]
     * : Prints the value of a single field for each plugin.
     *
     * [--fields=<fields>]
     * : Limit the output to specific object fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - count
     *   - json
     *   - yaml
     * ---
     *
     * [--status=<status>]
     * : Filter the output by plugin status.
     * ---
     * options:
     *   - active
     *   - active-network
     *   - dropin
     *   - inactive
     *   - must-use
     * ---
     *
     * [--skip-update-check]
     * : If set, the plugin update check will be skipped.
     *
     * ## AVAILABLE FIELDS
     *
     * These fields will be displayed by default for each plugin:
     *
     * * name
     * * status
     * * update
     * * version
     *
     * These fields are optionally available:
     *
     * * update_version
     * * update_package
     * * update_id
     * * title
     * * description
     * * file
     *
     * ## EXAMPLES
     *
     *     # List active plugins on the site.
     *     $ wp plugin list --status=active --format=json
     *     [{"name":"dynamic-hostname","status":"active","update":"none","version":"0.4.2"},{"name":"tinymce-templates","status":"active","update":"none","version":"4.4.3"},{"name":"wp-multibyte-patch","status":"active","update":"none","version":"2.4"},{"name":"wp-total-hacks","status":"active","update":"none","version":"2.0.1"}]
     *
     *     # List plugins on each site in a network.
     *     $ wp site list --field=url | xargs -I % wp plugin list --url=%
     *     +---------+----------------+--------+---------+
     *     | name    | status         | update | version |
     *     +---------+----------------+--------+---------+
     *     | akismet | active-network | none   | 3.1.11  |
     *     | hello   | inactive       | none   | 1.6     |
     *     +---------+----------------+--------+---------+
     *     +---------+----------------+--------+---------+
     *     | name    | status         | update | version |
     *     +---------+----------------+--------+---------+
     *     | akismet | active-network | none   | 3.1.11  |
     *     | hello   | inactive       | none   | 1.6     |
     *     +---------+----------------+--------+---------+
     *
     * @subcommand list
     */
    
public function list_$_$assoc_args ) {
        
parent::_list$_$assoc_args );
    }

    
/* PRIVATES */

    
private function check_active$file$network_wide ) {
        
$required $network_wide 'active-network' 'active';

        return 
$required === $this->get_status$file );
    }

    private function 
active_output$name$file$network_wide$action ) {
        
$network_wide $network_wide || ( is_multisite() && is_network_only_plugin$file ) );

        
$check $this->check_active$file$network_wide );

        if ( ( 
'activate' === $action ) ? $check : ! $check ) {
            if ( 
$network_wide ) {
                
WP_CLI::log"Plugin '{$name}' network {$action}d." );
            } else {
                
WP_CLI::log"Plugin '{$name}{$action}d." );
            }
        } else {
            
WP_CLI::warning"Could not {$action} the '{$name}' plugin." );
        }
    }

    protected function 
get_status$file ) {
        if ( 
is_plugin_active_for_network$file ) ) {
            return 
'active-network';
        }

        if ( 
is_plugin_active$file ) ) {
            return 
'active';
        }

        return 
'inactive';
    }

    
/**
     * Gets the template path based on installation type.
     */
    
private static function get_template_path$template ) {
        
$command_root  Utils\phar_safe_pathdirname__DIR__ ) );
        
$template_path "{$command_root}/templates/{$template}";

        if ( ! 
file_exists$template_path ) ) {
            
WP_CLI::error"Couldn't find {$template});
        }

        return 
$template_path;
    }

    
/**
     * Gets the details of a plugin.
     *
     * @param object
     * @return array
     */
    
private function get_details$file ) {
        
$plugin_folder get_plugins'/' plugin_basenamedirname$file ) ) );
        
$plugin_file   Utils\basename$file );

        return 
$plugin_folder$plugin_file ];
    }

    private function 
delete_plugin$plugin ) {
        
$plugin_dir dirname$plugin->file );
        if ( 
'.' === $plugin_dir ) {
            
$plugin_dir $plugin->file;
        }

        
$path path_joinWP_PLUGIN_DIR$plugin_dir );

        if ( 
Utils\is_windows() ) {
            
// Handles plugins that are not in own folders
            // e.g. Hello Dolly -> plugins/hello.php
            
if ( is_file$path ) ) {
                
$command 'del /f /q ';
            } else {
                
$command 'rd /s /q ';
            }
            
$path str_replace'/''\\'$path );
        } else {
            
$command 'rm -rf ';
        }

        return ! 
WP_CLI::launch$command escapeshellarg$path ) );
    }
}
<?
php

use WP_CLI\Fetchers\Plugin;
use 
WP_CLI\Formatter;
use 
WP_CLI\ParsePluginNameInput;
use 
WP_CLI\Utils;

/**
 * Manages plugin auto-updates.
 *
 * ## EXAMPLES
 *
 *     # Enable the auto-updates for a plugin
 *     $ wp plugin auto-updates enable hello
 *     Plugin auto-updates for 'hello' enabled.
 *     Success: Enabled 1 of 1 plugin auto-updates.
 *
 *     # Disable the auto-updates for a plugin
 *     $ wp plugin auto-updates disable hello
 *     Plugin auto-updates for 'hello' disabled.
 *     Success: Disabled 1 of 1 plugin auto-updates.
 *
 *     # Get the status of plugin auto-updates
 *     $ wp plugin auto-updates status hello
 *     Auto-updates for plugin 'hello' are disabled.
 *
 * @package wp-cli
 */
class Plugin_AutoUpdates_Command {

    use 
ParsePluginNameInput;

    
/**
     * Site option that stores the status of plugin auto-updates.
     *
     * @var string
     */
    
const SITE_OPTION 'auto_update_plugins';

    
/**
     * Plugin fetcher instance.
     *
     * @var Plugin
     */
    
protected $fetcher;

    
/**
     * Plugin_AutoUpdates_Command constructor.
     */
    
public function __construct() {
        
$this->fetcher = new Plugin();
    }

    
/**
     * Enables the auto-updates for a plugin.
     *
     * ## OPTIONS
     *
     * [<plugin>...]
     * : One or more plugins to enable auto-updates for.
     *
     * [--all]
     * : If set, auto-updates will be enabled for all plugins.
     *
     * [--disabled-only]
     * : If set, filters list of plugins to only include the ones that have
     * auto-updates disabled.
     *
     * ## EXAMPLES
     *
     *     # Enable the auto-updates for a plugin
     *     $ wp plugin auto-updates enable hello
     *     Plugin auto-updates for 'hello' enabled.
     *     Success: Enabled 1 of 1 plugin auto-updates.
     */
    
public function enable$args$assoc_args ) {
        
$all           Utils\get_flag_value$assoc_args'all'false );
        
$disabled_only Utils\get_flag_value$assoc_args'disabled-only'false );

        
$args $this->check_optional_args_and_all$args$all );
        if ( ! 
$args ) {
            return;
        }

        
$plugins      $this->fetcher->get_many$args );
        
$auto_updates get_site_option( static::SITE_OPTION );

        if ( 
false === $auto_updates ) {
            
$auto_updates = [];
        }

        
$count     0;
        
$successes 0;

        foreach ( 
$plugins as $plugin ) {
            
$enabled in_array$plugin->file$auto_updatestrue );

            if ( 
$disabled_only && $enabled ) {
                continue;
            }

            
$count++;

            if ( 
$enabled ) {
                
WP_CLI::warning(
                    
"Auto-updates already enabled for plugin {$plugin->name}."
                
);
            } else {
                
$auto_updates[] = $plugin->file;
                
$successes++;
            }
        }

        if ( 
=== $count ) {
            
WP_CLI::error(
                
'No plugins provided to enable auto-updates for.'
            
);
        }

        
update_site_option( static::SITE_OPTION$auto_updates );

        
Utils\report_batch_operation_results(
            
'plugin auto-update',
            
'enable',
            
$count,
            
$successes,
            
$count $successes
        
);
    }

    
/**
     * Disables the auto-updates for a plugin.
     *
     * ## OPTIONS
     *
     * [<plugin>...]
     * : One or more plugins to disable auto-updates for.
     *
     * [--all]
     * : If set, auto-updates will be disabled for all plugins.
     *
     * [--enabled-only]
     * : If set, filters list of plugins to only include the ones that have
     * auto-updates enabled.
     *
     * ## EXAMPLES
     *
     *     # Disable the auto-updates for a plugin
     *     $ wp plugin auto-updates disable hello
     *     Plugin auto-updates for 'hello' disabled.
     *     Success: Disabled 1 of 1 plugin auto-updates.
     */
    
public function disable$args$assoc_args ) {
        
$all          Utils\get_flag_value$assoc_args'all'false );
        
$enabled_only Utils\get_flag_value$assoc_args'enabled-only'false );

        
$args $this->check_optional_args_and_all$args$all );
        if ( ! 
$args ) {
            return;
        }

        
$plugins      $this->fetcher->get_many$args );
        
$auto_updates get_site_option( static::SITE_OPTION );

        if ( 
false === $auto_updates ) {
            
$auto_updates = [];
        }

        
$count     0;
        
$successes 0;

        foreach ( 
$plugins as $plugin ) {
            
$enabled in_array$plugin->file$auto_updatestrue );

            if ( 
$enabled_only && ! $enabled ) {
                continue;
            }

            
$count++;

            if ( ! 
$enabled ) {
                
WP_CLI::warning(
                    
"Auto-updates already disabled for plugin {$plugin->name}."
                
);
            } else {
                
$auto_updates array_diff$auto_updates, [ $plugin->file ] );
                
$successes++;
            }
        }

        if ( 
=== $count ) {
            
WP_CLI::error(
                
'No plugins provided to disable auto-updates for.'
            
);
        }

        if ( 
count$auto_updates ) > ) {
            
update_site_option( static::SITE_OPTION$auto_updates );
        } else {
            
delete_site_option( static::SITE_OPTION );
        }

        
Utils\report_batch_operation_results(
            
'plugin auto-update',
            
'disable',
            
$count,
            
$successes,
            
$count $successes
        
);
    }

    
/**
     * Shows the status of auto-updates for a plugin.
     *
     * ## OPTIONS
     *
     * [<plugin>...]
     * : One or more plugins to show the status of the auto-updates of.
     *
     * [--all]
     * : If set, the status of auto-updates for all plugins will be shown.
     *
     * [--enabled-only]
     * : If set, filters list of plugins to only include the ones that have
     * auto-updates enabled.
     *
     * [--disabled-only]
     * : If set, filters list of plugins to only include the ones that have
     * auto-updates disabled.
     *
     * [--field=<field>]
     * : Only show the provided field.
     *
     * ## EXAMPLES
     *
     *     # Get the status of plugin auto-updates
     *     $ wp plugin auto-updates status hello
     *     +-------+----------+
     *     | name  | status   |
     *     +-------+----------+
     *     | hello | disabled |
     *     +-------+----------+
     *
     *     # Get the list of plugins that have auto-updates enabled
     *     $ wp plugin auto-updates status --all --enabled-only --field=name
     *     akismet
     *     duplicate-post
     */
    
public function status$args$assoc_args ) {
        
$all           Utils\get_flag_value$assoc_args'all'false );
        
$enabled_only  Utils\get_flag_value$assoc_args'enabled-only'false );
        
$disabled_only Utils\get_flag_value$assoc_args'disabled-only'false );

        if ( 
$enabled_only && $disabled_only ) {
            
WP_CLI::error(
                
'--enabled-only and --disabled-only are mutually exclusive and '
                
'cannot be used at the same time.'
            
);
        }

        
$args $this->check_optional_args_and_all$args$all );
        if ( ! 
$args ) {
            return;
        }

        
$plugins      $this->fetcher->get_many$args );
        
$auto_updates get_site_option( static::SITE_OPTION );

        if ( 
false === $auto_updates ) {
            
$auto_updates = [];
        }

        
$results = [];

        foreach ( 
$plugins as $plugin ) {
            
$enabled in_array$plugin->file$auto_updatestrue );

            if ( 
$enabled_only && ! $enabled ) {
                continue;
            }

            if ( 
$disabled_only && $enabled ) {
                continue;
            }

            
$results[] = [
                
'name'   => $plugin->name,
                
'file'   => $plugin->file,
                
'status' => $enabled 'enabled' 'disabled',
            ];
        }

        
$formatter = new Formatter$assoc_args, [ 'name''status' ], 'plugin' );
        
$formatter->display_items$results );
    }
}
<?
php

use WP_CLI\CommandWithUpgrade;
use 
WP_CLI\ParseThemeNameInput;
use 
WP_CLI\Utils;

/**
 * Manages themes, including installs, activations, and updates.
 *
 * See the WordPress [Theme Handbook](https://developer.wordpress.org/themes/) developer resource for more information on themes.
 *
 * ## EXAMPLES
 *
 *     # Install the latest version of a theme from wordpress.org and activate
 *     $ wp theme install twentysixteen --activate
 *     Installing Twenty Sixteen (1.2)
 *     Downloading install package from http://downloads.wordpress.org/theme/twentysixteen.1.2.zip...
 *     Unpacking the package...
 *     Installing the theme...
 *     Theme installed successfully.
 *     Activating 'twentysixteen'...
 *     Success: Switched to 'Twenty Sixteen' theme.
 *
 *     # Get details of an installed theme
 *     $ wp theme get twentysixteen --fields=name,title,version
 *     +---------+----------------+
 *     | Field   | Value          |
 *     +---------+----------------+
 *     | name    | Twenty Sixteen |
 *     | title   | Twenty Sixteen |
 *     | version | 1.2            |
 *     +---------+----------------+
 *
 *     # Get status of theme
 *     $ wp theme status twentysixteen
 *     Theme twentysixteen details:
 *          Name: Twenty Sixteen
 *          Status: Active
 *          Version: 1.2
 *          Author: the WordPress team
 *
 * @package wp-cli
 */
class Theme_Command extends CommandWithUpgrade {

    use 
ParseThemeNameInput;

    protected 
$item_type         'theme';
    protected 
$upgrade_refresh   'wp_update_themes';
    protected 
$upgrade_transient 'update_themes';

    protected 
$obj_fields = [ 'name''status''update''version' ];

    public function 
__construct() {
        if ( 
is_multisite() ) {
            
$this->obj_fields[] = 'enabled';
        }
        
parent::__construct();

        
$this->fetcher = new WP_CLI\Fetchers\Theme();
    }

    protected function 
get_upgrader_class$force ) {
        return 
$force '\\WP_CLI\\DestructiveThemeUpgrader' 'Theme_Upgrader';
    }

    
/**
     * Reveals the status of one or all themes.
     *
     * ## OPTIONS
     *
     * [<theme>]
     * : A particular theme to show the status for.
     *
     * ## EXAMPLES
     *
     *     $ wp theme status twentysixteen
     *     Theme twentysixteen details:
     *          Name: Twenty Sixteen
     *          Status: Inactive
     *          Version: 1.2
     *          Author: the WordPress team
     */
    
public function status$args ) {
        if ( isset( 
$args[0] ) ) {
            
$theme  $this->fetcher->get_check$args[0] );
            
$errors $theme->errors();
            if ( 
is_wp_error$errors ) ) {
                
$message $errors->get_error_message();
                
WP_CLI::error$message );
            }
        }

        
parent::status$args );
    }

    
/**
     * Searches the WordPress.org theme directory.
     *
     * Displays themes in the WordPress.org theme directory matching a given
     * search query.
     *
     * ## OPTIONS
     *
     * <search>
     * : The string to search for.
     *
     * [--page=<page>]
     * : Optional page to display.
     * ---
     * default: 1
     * ---
     *
     * [--per-page=<per-page>]
     * : Optional number of results to display. Defaults to 10.
     *
     * [--field=<field>]
     * : Prints the value of a single field for each theme.
     *
     * [--fields=<fields>]
     * : Ask for specific fields from the API. Defaults to name,slug,author,rating. Acceptable values:
     *
     *     **name**: Theme Name
     *     **slug**: Theme Slug
     *     **version**: Current Version Number
     *     **author**: Theme Author
     *     **preview_url**: Theme Preview URL
     *     **screenshot_url**: Theme Screenshot URL
     *     **rating**: Theme Rating
     *     **num_ratings**: Number of Theme Ratings
     *     **homepage**: Theme Author's Homepage
     *     **description**: Theme Description
     *     **url**: Theme's URL on wordpress.org
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - count
     *   - yaml
     * ---
     *
     * ## EXAMPLES
     *
     *     $ wp theme search photo --per-page=6
     *     Success: Showing 6 of 203 themes.
     *     +----------------------+----------------------+--------+
     *     | name                 | slug                 | rating |
     *     +----------------------+----------------------+--------+
     *     | Photos               | photos               | 100    |
     *     | Infinite Photography | infinite-photography | 100    |
     *     | PhotoBook            | photobook            | 100    |
     *     | BG Photo Frame       | bg-photo-frame       | 0      |
     *     | fPhotography         | fphotography         | 0      |
     *     | Photo Perfect        | photo-perfect        | 98     |
     *     +----------------------+----------------------+--------+
     */
    
public function search$args$assoc_args ) {
        
parent::_search$args$assoc_args );
    }

    protected function 
status_single$args ) {
        
$theme $this->fetcher->get_check$args[0] );

        
$status $this->format_status$this->get_status$theme ), 'long' );

        
$version $theme->get'Version' );
        if ( 
$this->has_update$theme->get_stylesheet() ) ) {
            
$version .= ' (%gUpdate available%n)';
        }

        echo 
WP_CLI::colorize(
            
Utils\mustache_render(
                
self::get_template_path'theme-status.mustache' ),
                [
                    
'slug'    => $theme->get_stylesheet(),
                    
'status'  => $status,
                    
'version' => $version,
                    
'name'    => $theme->get'Name' ),
                    
'author'  => $theme->get'Author' ),
                ]
            )
        );
    }

    protected function 
get_all_items() {
        return 
$this->get_item_list();
    }

    
/**
     * Activates a theme.
     *
     * ## OPTIONS
     *
     * <theme>
     * : The theme to activate.
     *
     * ## EXAMPLES
     *
     *     $ wp theme activate twentysixteen
     *     Success: Switched to 'Twenty Sixteen' theme.
     */
    
public function activate$args = array() ) {
        
$theme $this->fetcher->get_check$args[0] );

        
$errors $theme->errors();
        if ( 
is_wp_error$errors ) ) {
            
$message $errors->get_error_message();
            
WP_CLI::error$message );
        }

        
$name $theme->get'Name' );

        if ( 
'active' === $this->get_status$theme ) ) {
            
WP_CLI::warning"The '$name' theme is already active." );
            return;
        }

        if ( 
$theme->get_stylesheet() !== $theme->get_template() && ! $this->fetcher->get$theme->get_template() ) ) {
            
WP_CLI::error"The '{$theme->get_stylesheet()}' theme cannot be activated without its parent, '{$theme->get_template()}'." );
        }

        
switch_theme$theme->get_template(), $theme->get_stylesheet() );

        if ( 
$this->is_active_theme$theme ) ) {
            
WP_CLI::success"Switched to '$name' theme." );
        } else {
            
WP_CLI::error"Could not switch to '$name' theme." );
        }
    }

    
/**
     * Enables a theme on a WordPress multisite install.
     *
     * Permits theme to be activated from the dashboard of a site on a WordPress
     * multisite install.
     *
     * ## OPTIONS
     *
     * <theme>
     * : The theme to enable.
     *
     * [--network]
     * : If set, the theme is enabled for the entire network
     *
     * [--activate]
     * : If set, the theme is activated for the current site. Note that
     * the "network" flag has no influence on this.
     *
     * ## EXAMPLES
     *
     *     # Enable theme
     *     $ wp theme enable twentysixteen
     *     Success: Enabled the 'Twenty Sixteen' theme.
     *
     *     # Network enable theme
     *     $ wp theme enable twentysixteen --network
     *     Success: Network enabled the 'Twenty Sixteen' theme.
     *
     *     # Network enable and activate theme for current site
     *     $ wp theme enable twentysixteen --activate
     *     Success: Enabled the 'Twenty Sixteen' theme.
     *     Success: Switched to 'Twenty Sixteen' theme.
     */
    
public function enable$args$assoc_args ) {
        if ( ! 
is_multisite() ) {
            
WP_CLI::error'This is not a multisite installation.' );
        }

        
$theme $this->fetcher->get_check$args[0] );
        
$name  $theme->get'Name' );

        
# If the --network flag is set, we'll be calling the (get|update)_site_option functions
        
$_site = ! empty( $assoc_args['network'] ) ? '_site' '';

        
# Add the current theme to the allowed themes option or site option
        
$allowed_themes call_user_func"get{$_site}_option"'allowedthemes' );
        if ( empty( 
$allowed_themes ) ) {
            
$allowed_themes = array();
        }
        
$allowed_themes$theme->get_stylesheet() ] = true;
        
call_user_func"update{$_site}_option"'allowedthemes'$allowed_themes );

        if ( ! empty( 
$assoc_args['network'] ) ) {
            
WP_CLI::success"Network enabled the '$name' theme." );
        } else {
            
WP_CLI::success"Enabled the '$name' theme." );
        }

        
# If the --activate flag is set, activate the theme for the current site
        
if ( ! empty( $assoc_args['activate'] ) ) {
            
$this->activate$args );
        }
    }

    
/**
     * Disables a theme on a WordPress multisite install.
     *
     * Removes ability for a theme to be activated from the dashboard of a site
     * on a WordPress multisite install.
     *
     * ## OPTIONS
     *
     * <theme>
     * : The theme to disable.
     *
     * [--network]
     * : If set, the theme is disabled on the network level. Note that
     * individual sites may still have this theme enabled if it was
     * enabled for them independently.
     *
     * ## EXAMPLES
     *
     *     # Disable theme
     *     $ wp theme disable twentysixteen
     *     Success: Disabled the 'Twenty Sixteen' theme.
     *
     *     # Disable theme in network level
     *     $ wp theme disable twentysixteen --network
     *     Success: Network disabled the 'Twenty Sixteen' theme.
     */
    
public function disable$args$assoc_args ) {
        if ( ! 
is_multisite() ) {
            
WP_CLI::error'This is not a multisite installation.' );
        }

        
$theme $this->fetcher->get_check$args[0] );
        
$name  $theme->get'Name' );

        
# If the --network flag is set, we'll be calling the (get|update)_site_option functions
        
$_site = ! empty( $assoc_args['network'] ) ? '_site' '';

        
# Add the current theme to the allowed themes option or site option
        
$allowed_themes call_user_func"get{$_site}_option"'allowedthemes' );
        if ( ! empty( 
$allowed_themes$theme->get_stylesheet() ] ) ) {
            unset( 
$allowed_themes$theme->get_stylesheet() ] );
        }
        
call_user_func"update{$_site}_option"'allowedthemes'$allowed_themes );

        if ( ! empty( 
$assoc_args['network'] ) ) {
            
WP_CLI::success"Network disabled the '$name' theme." );
        } else {
            
WP_CLI::success"Disabled the '$name' theme." );
        }
    }

    
/**
     * Gets the path to a theme or to the theme directory.
     *
     * ## OPTIONS
     *
     * [<theme>]
     * : The theme to get the path to. Path includes "style.css" file.
     * If not set, will return the path to the themes directory.
     *
     * [--dir]
     * : If set, get the path to the closest parent directory, instead of the
     * theme's "style.css" file.
     *
     * ## EXAMPLES
     *
     *     # Get theme path
     *     $ wp theme path
     *     /var/www/example.com/public_html/wp-content/themes
     *
     *     # Change directory to theme path
     *     $ cd $(wp theme path)
     */
    
public function path$args$assoc_args ) {
        if ( empty( 
$args ) ) {
            
$path WP_CONTENT_DIR '/themes';
        } else {
            
$theme $this->fetcher->get_check$args[0] );

            
$path $theme->get_stylesheet_directory();

            if ( ! 
Utils\get_flag_value$assoc_args'dir' ) ) {
                
$path .= '/style.css';
            }
        }

        
WP_CLI::line$path );
    }

    protected function 
install_from_repo$slug$assoc_args ) {
        
$api themes_api'theme_information', array( 'slug' => $slug ) );

        if ( 
is_wp_error$api ) ) {
            return 
$api;
        }

        if ( isset( 
$assoc_args['version'] ) ) {
            
self::alter_api_response$api$assoc_args['version'] );
        }

        if ( ! 
Utils\get_flag_value$assoc_args'force' ) ) {
            
$theme wp_get_theme$slug );
            if ( 
$theme->exists() ) {
                
// We know this will fail, so avoid a needless download of the package.
                
return new WP_Error'already_installed''Theme already installed.' );
            }
            
// Clear cache so WP_Theme doesn't create a "missing theme" object.
            
$cache_hash md5$theme->theme_root '/' $theme->stylesheet );
            foreach ( [ 
'theme''screenshot''headers''page_templates' ] as $key ) {
                
wp_cache_delete$key '-' $cache_hash'themes' );
            }
        }

        
WP_CLI::logsprintf'Installing %s (%s)'html_entity_decode$api->nameENT_QUOTES ), $api->version ) );
        if ( 
Utils\get_flag_value$assoc_args'version' ) !== 'dev' ) {
            
WP_CLI::get_http_cache_manager()->whitelist_package$api->download_link$this->item_type$api->slug$api->version );
        }

        
// Ignore failures on accessing SSL "https://api.wordpress.org/themes/update-check/1.1/" in `wp_update_themes()` which seem to occur intermittently.
        
set_error_handler( array( __CLASS__'error_handler' ), E_USER_WARNING E_USER_NOTICE );

        
$result $this->get_upgrader$assoc_args )->install$api->download_link );

        
restore_error_handler();

        return 
$result;
    }

    protected function 
get_item_list() {
        return 
$this->get_all_themes();
    }

    protected function 
filter_item_list$items$args ) {
        
$theme_files = array();
        foreach ( 
$args as $arg ) {
            
$theme_files[] = $this->fetcher->get_check$arg )->get_stylesheet_directory();
        }

        return 
Utils\pick_fields$items$theme_files );
    }

    
/**
     * Installs one or more themes.
     *
     * ## OPTIONS
     *
     * <theme|zip|url>...
     * : One or more themes to install. Accepts a theme slug, the path to a local zip file, or a URL to a remote zip file.
     *
     * [--version=<version>]
     * : If set, get that particular version from wordpress.org, instead of the
     * stable version.
     *
     * [--force]
     * : If set, the command will overwrite any installed version of the theme, without prompting
     * for confirmation.
     *
     * [--activate]
     * : If set, the theme will be activated immediately after install.
     *
     * [--insecure]
     * : Retry downloads without certificate validation if TLS handshake fails. Note: This makes the request vulnerable to a MITM attack.
     *
     * ## EXAMPLES
     *
     *     # Install the latest version from wordpress.org and activate
     *     $ wp theme install twentysixteen --activate
     *     Installing Twenty Sixteen (1.2)
     *     Downloading install package from http://downloads.wordpress.org/theme/twentysixteen.1.2.zip...
     *     Unpacking the package...
     *     Installing the theme...
     *     Theme installed successfully.
     *     Activating 'twentysixteen'...
     *     Success: Switched to 'Twenty Sixteen' theme.
     *
     *     # Install from a local zip file
     *     $ wp theme install ../my-theme.zip
     *
     *     # Install from a remote zip file
     *     $ wp theme install http://s3.amazonaws.com/bucketname/my-theme.zip?AWSAccessKeyId=123&Expires=456&Signature=abcdef
     */
    
public function install$args$assoc_args ) {

        
$theme_root get_theme_root();
        if ( 
$theme_root && ! is_dir$theme_root ) ) {
            
wp_mkdir_p$theme_root );
            
register_theme_directory$theme_root );
        }

        
parent::install$args$assoc_args );
    }

    
/**
     * Gets details about a theme.
     *
     * ## OPTIONS
     *
     * <theme>
     * : The theme to get.
     *
     * [--field=<field>]
     * : Instead of returning the whole theme, returns the value of a single field.
     *
     * [--fields=<fields>]
     * : Limit the output to specific fields. Defaults to all fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - yaml
     * ---
     *
     * ## EXAMPLES
     *
     *     $ wp theme get twentysixteen --fields=name,title,version
     *     +---------+----------------+
     *     | Field   | Value          |
     *     +---------+----------------+
     *     | name    | Twenty Sixteen |
     *     | title   | Twenty Sixteen |
     *     | version | 1.2            |
     *     +---------+----------------+
     */
    
public function get$args$assoc_args ) {
        
$theme $this->fetcher->get_check$args[0] );

        
$errors $theme->errors();
        if ( 
is_wp_error$errors ) ) {
            
$message $errors->get_error_message();
            
WP_CLI::error$message );
        }

        
// WP_Theme object employs magic getter, unfortunately.
        
$theme_vars = [
            
'name',
            
'title',
            
'version',
            
'status',
            
'parent_theme',
            
'template_dir',
            
'stylesheet_dir',
            
'template',
            
'stylesheet',
            
'screenshot',
            
'description',
            
'author',
            
'tags',
            
'theme_root',
            
'theme_root_uri',
        ];
        
$theme_obj  = new stdClass();
        foreach ( 
$theme_vars as $var ) {
            
$theme_obj->$var $theme->$var;
        }

        
$theme_obj->status      $this->get_status$theme );
        
$theme_obj->description wordwrap$theme_obj->description );

        if ( empty( 
$assoc_args['fields'] ) ) {
            
$assoc_args['fields'] = $theme_vars;
        }

        
$formatter $this->get_formatter$assoc_args );
        
$formatter->display_item$theme_obj );
    }

    
/**
     * Updates one or more themes.
     *
     * ## OPTIONS
     *
     * [<theme>...]
     * : One or more themes to update.
     *
     * [--all]
     * : If set, all themes that have updates will be updated.
     *
     * [--exclude=<theme-names>]
     * : Comma separated list of theme names that should be excluded from updating.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - summary
     * ---
     *
     * [--version=<version>]
     * : If set, the theme will be updated to the specified version.
     *
     * [--dry-run]
     * : Preview which themes would be updated.
     *
     * [--insecure]
     * : Retry downloads without certificate validation if TLS handshake fails. Note: This makes the request vulnerable to a MITM attack.
     *
     * ## EXAMPLES
     *
     *     # Update multiple themes
     *     $ wp theme update twentyfifteen twentysixteen
     *     Downloading update from https://downloads.wordpress.org/theme/twentyfifteen.1.5.zip...
     *     Unpacking the update...
     *     Installing the latest version...
     *     Removing the old version of the theme...
     *     Theme updated successfully.
     *     Downloading update from https://downloads.wordpress.org/theme/twentysixteen.1.2.zip...
     *     Unpacking the update...
     *     Installing the latest version...
     *     Removing the old version of the theme...
     *     Theme updated successfully.
     *     +---------------+-------------+-------------+---------+
     *     | name          | old_version | new_version | status  |
     *     +---------------+-------------+-------------+---------+
     *     | twentyfifteen | 1.4         | 1.5         | Updated |
     *     | twentysixteen | 1.1         | 1.2         | Updated |
     *     +---------------+-------------+-------------+---------+
     *     Success: Updated 2 of 2 themes.
     *
     *     # Exclude themes updates when bulk updating the themes
     *     $ wp theme update --all --exclude=twentyfifteen
     *     Downloading update from https://downloads.wordpress.org/theme/astra.1.0.5.1.zip...
     *     Unpacking the update...
     *     Installing the latest version...
     *     Removing the old version of the theme...
     *     Theme updated successfully.
     *     Downloading update from https://downloads.wordpress.org/theme/twentyseventeen.1.2.zip...
     *     Unpacking the update...
     *     Installing the latest version...
     *     Removing the old version of the theme...
     *     Theme updated successfully.
     *     +-----------------+----------+---------+----------------+
     *     | name            | status   | version | update_version |
     *     +-----------------+----------+---------+----------------+
     *     | astra           | inactive | 1.0.1   | 1.0.5.1        |
     *     | twentyseventeen | inactive | 1.1     | 1.2            |
     *     +-----------------+----------+---------+----------------+
     *     Success: Updated 2 of 2 themes.
     *
     *     # Update all themes
     *     $ wp theme update --all
     *
     * @alias upgrade
     */
    
public function update$args$assoc_args ) {
        
$all Utils\get_flag_value$assoc_args'all'false );

        
$args $this->check_optional_args_and_all$args$all );
        if ( ! 
$args ) {
            return;
        }

        if ( isset( 
$assoc_args['version'] ) && isset( $assoc_args['dry-run'] ) ) {
            
WP_CLI::error'--dry-run cannot be used together with --version.' );
        }

        if ( isset( 
$assoc_args['version'] ) ) {
            foreach ( 
$this->fetcher->get_many$args ) as $theme ) {
                
$r delete_theme$theme->stylesheet );
                if ( 
is_wp_error$r ) ) {
                    
WP_CLI::warning$r );
                } else {
                    
$assoc_args['force'] = true;
                    
$this->install( array( $theme->stylesheet ), $assoc_args );
                }
            }
        } else {
            
parent::update_many$args$assoc_args );
        }
    }

    
/**
     * Checks if a given theme is installed.
     *
     * Returns exit code 0 when installed, 1 when uninstalled.
     *
     * ## OPTIONS
     *
     * <theme>
     * : The theme to check.
     *
     * ## EXAMPLES
     *
     *     # Check whether theme is installed; exit status 0 if installed, otherwise 1
     *     $ wp theme is-installed hello
     *     $ echo $?
     *     1
     *
     * @subcommand is-installed
     */
    
public function is_installed$args$assoc_args = array() ) {
        
$theme wp_get_theme$args[0] );

        if ( 
$theme->exists() ) {
            
WP_CLI::halt);
        } else {
            
WP_CLI::halt);
        }
    }

    
/**
     * Checks if a given theme is active.
     *
     * Returns exit code 0 when active, 1 when not active.
     *
     * ## OPTIONS
     *
     * <theme>
     * : The plugin to check.
     *
     * ## EXAMPLES
     *
     *     # Check whether theme is Active; exit status 0 if active, otherwise 1
     *     $ wp theme is-active twentyfifteen
     *     $ echo $?
     *     1
     *
     * @subcommand is-active
     */
    
public function is_active$args$assoc_args = array() ) {
        
$theme wp_get_theme$args[0] );

        if ( ! 
$theme->exists() ) {
            
WP_CLI::halt);
        }

        
$this->is_active_theme$theme ) ? WP_CLI::halt) : WP_CLI::halt);
    }

    
/**
     * Deletes one or more themes.
     *
     * Removes the theme or themes from the filesystem.
     *
     * ## OPTIONS
     *
     * [<theme>...]
     * : One or more themes to delete.
     *
     * [--all]
     * : If set, all themes will be deleted except active theme.
     *
     * [--force]
     * : To delete active theme use this.
     *
     * ## EXAMPLES
     *
     *     $ wp theme delete twentytwelve
     *     Deleted 'twentytwelve' theme.
     *     Success: Deleted 1 of 1 themes.
     *
     * @alias uninstall
     */
    
public function delete$args$assoc_args ) {

        
$all Utils\get_flag_value$assoc_args'all'false );

        
$args $this->check_optional_args_and_all$args$all'delete' );
        if ( ! 
$args ) {
            return;
        }

        
$force Utils\get_flag_value$assoc_args'force'false );

        
$successes 0;
        
$errors    0;
        foreach ( 
$this->fetcher->get_many$args ) as $theme ) {
            
$theme_slug $theme->get_stylesheet();

            if ( 
$this->is_active_theme$theme ) && ! $force ) {
                if ( ! 
$all ) {
                    
WP_CLI::warning"Can't delete the currently active theme: $theme_slug);
                    
$errors++;
                }
                continue;
            }

            
$r delete_theme$theme_slug );

            if ( 
is_wp_error$r ) ) {
                
WP_CLI::warning$r );
                
$errors++;
            } else {
                
WP_CLI::log"Deleted '$theme_slug' theme." );
                
$successes++;
            }
        }
        if ( ! 
$this->chained_command ) {
            
Utils\report_batch_operation_results'theme''delete'count$args ), $successes$errors );
        }
    }

    
/**
     * Gets a list of themes.
     *
     * ## OPTIONS
     *
     * [--<field>=<value>]
     * : Filter results based on the value of a field.
     *
     * [--field=<field>]
     * : Prints the value of a single field for each theme.
     *
     * [--fields=<fields>]
     * : Limit the output to specific object fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - count
     *   - yaml
     * ---
     *
     * [--status=<status>]
     * : Filter the output by theme status.
     * ---
     * options:
     *   - active
     *   - parent
     *   - inactive
     * ---
     *
     * [--skip-update-check]
     * : If set, the theme update check will be skipped.
     *
     * ## AVAILABLE FIELDS
     *
     * These fields will be displayed by default for each theme:
     *
     * * name
     * * status
     * * update
     * * version
     *
     * These fields are optionally available:
     *
     * * update_version
     * * update_package
     * * update_id
     * * title
     * * description
     *
     * ## EXAMPLES
     *
     *     # List themes
     *     $ wp theme list --status=inactive --format=csv
     *     name,status,update,version
     *     twentyfourteen,inactive,none,1.7
     *     twentysixteen,inactive,available,1.1
     *
     * @subcommand list
     */
    
public function list_$_$assoc_args ) {
        
parent::_list$_$assoc_args );
    }

    
/**
     * Gets the template path based on installation type.
     */
    
private static function get_template_path$template ) {
        
$command_root  Utils\phar_safe_pathdirname__DIR__ ) );
        
$template_path "{$command_root}/templates/{$template}";

        if ( ! 
file_exists$template_path ) ) {
            
WP_CLI::error"Couldn't find {$template});
        }

        return 
$template_path;
    }
}
<?
php

use WP_CLI\Fetchers\Theme;
use 
WP_CLI\Formatter;
use 
WP_CLI\ParseThemeNameInput;
use 
WP_CLI\Utils;

/**
 * Manages theme auto-updates.
 *
 * ## EXAMPLES
 *
 *     # Enable the auto-updates for a theme
 *     $ wp theme auto-updates enable twentysixteen
 *     Theme auto-updates for 'twentysixteen' enabled.
 *     Success: Enabled 1 of 1 theme auto-updates.
 *
 *     # Disable the auto-updates for a theme
 *     $ wp theme auto-updates disable twentysixteen
 *     Theme auto-updates for 'twentysixteen' disabled.
 *     Success: Disabled 1 of 1 theme auto-updates.
 *
 *     # Get the status of theme auto-updates
 *     $ wp theme auto-updates status twentysixteen
 *     Auto-updates for theme 'twentysixteen' are disabled.
 *
 * @package wp-cli
 */
class Theme_AutoUpdates_Command {

    use 
ParseThemeNameInput;

    
/**
     * Site option that stores the status of theme auto-updates.
     *
     * @var string
     */
    
const SITE_OPTION 'auto_update_themes';

    
/**
     * Theme fetcher instance.
     *
     * @var Theme
     */
    
protected $fetcher;

    
/**
     * Theme_AutoUpdates_Command constructor.
     */
    
public function __construct() {
        
$this->fetcher = new Theme();
    }

    
/**
     * Enables the auto-updates for a theme.
     *
     * ## OPTIONS
     *
     * [<theme>...]
     * : One or more themes to enable auto-updates for.
     *
     * [--all]
     * : If set, auto-updates will be enabled for all themes.
     *
     * [--disabled-only]
     * : If set, filters list of themes to only include the ones that have
     * auto-updates disabled.
     *
     * ## EXAMPLES
     *
     *     # Enable the auto-updates for a theme
     *     $ wp theme auto-updates enable twentysixteen
     *     Theme auto-updates for 'twentysixteen' enabled.
     *     Success: Enabled 1 of 1 theme auto-updates.
     */
    
public function enable$args$assoc_args ) {
        
$all           Utils\get_flag_value$assoc_args'all'false );
        
$disabled_only Utils\get_flag_value$assoc_args'disabled-only'false );

        
$args $this->check_optional_args_and_all$args$all );
        if ( ! 
$args ) {
            return;
        }

        
$themes       $this->fetcher->get_many$args );
        
$auto_updates get_site_option( static::SITE_OPTION );

        if ( 
false === $auto_updates ) {
            
$auto_updates = [];
        }

        
$count     0;
        
$successes 0;

        foreach ( 
$themes as $theme ) {
            
$enabled in_array$theme->stylesheet$auto_updatestrue );

            if ( 
$disabled_only && $enabled ) {
                continue;
            }

            
$count++;

            if ( 
$enabled ) {
                
WP_CLI::warning(
                    
"Auto-updates already enabled for theme {$theme->stylesheet}."
                
);
            } else {
                
$auto_updates[] = $theme->stylesheet;
                
$successes++;
            }
        }

        if ( 
=== $count ) {
            
WP_CLI::error(
                
'No themes provided to enable auto-updates for.'
            
);
        }

        
update_site_option( static::SITE_OPTION$auto_updates );

        
Utils\report_batch_operation_results(
            
'theme auto-update',
            
'enable',
            
$count,
            
$successes,
            
$count $successes
        
);
    }

    
/**
     * Disables the auto-updates for a theme.
     *
     * ## OPTIONS
     *
     * [<theme>...]
     * : One or more themes to disable auto-updates for.
     *
     * [--all]
     * : If set, auto-updates will be disabled for all themes.
     *
     * [--enabled-only]
     * : If set, filters list of themes to only include the ones that have
     * auto-updates enabled.
     *
     * ## EXAMPLES
     *
     *     # Disable the auto-updates for a theme
     *     $ wp theme auto-updates disable twentysixteen
     *     Theme auto-updates for 'twentysixteen' disabled.
     *     Success: Disabled 1 of 1 theme auto-updates.
     */
    
public function disable$args$assoc_args ) {
        
$all          Utils\get_flag_value$assoc_args'all'false );
        
$enabled_only Utils\get_flag_value$assoc_args'enabled-only'false );

        
$args $this->check_optional_args_and_all$args$all );
        if ( ! 
$args ) {
            return;
        }

        
$themes       $this->fetcher->get_many$args );
        
$auto_updates get_site_option( static::SITE_OPTION );

        if ( 
false === $auto_updates ) {
            
$auto_updates = [];
        }

        
$count     0;
        
$successes 0;

        foreach ( 
$themes as $theme ) {
            
$enabled in_array$theme->stylesheet$auto_updatestrue );

            if ( 
$enabled_only && ! $enabled ) {
                continue;
            }

            
$count++;

            if ( ! 
$enabled ) {
                
WP_CLI::warning(
                    
"Auto-updates already disabled for theme {$theme->stylesheet}."
                
);
            } else {
                
$auto_updates array_diff$auto_updates, [ $theme->stylesheet ] );
                
$successes++;
            }
        }

        if ( 
=== $count ) {
            
WP_CLI::error(
                
'No themes provided to disable auto-updates for.'
            
);
        }

        if ( 
count$auto_updates ) > ) {
            
update_site_option( static::SITE_OPTION$auto_updates );
        } else {
            
delete_site_option( static::SITE_OPTION );
        }

        
Utils\report_batch_operation_results(
            
'theme auto-update',
            
'disable',
            
$count,
            
$successes,
            
$count $successes
        
);
    }

    
/**
     * Shows the status of auto-updates for a theme.
     *
     * ## OPTIONS
     *
     * [<theme>...]
     * : One or more themes to show the status of the auto-updates of.
     *
     * [--all]
     * : If set, the status of auto-updates for all themes will be shown.
     *
     * [--enabled-only]
     * : If set, filters list of themes to only include the ones that have
     * auto-updates enabled.
     *
     * [--disabled-only]
     * : If set, filters list of themes to only include the ones that have
     * auto-updates disabled.
     *
     * [--field=<field>]
     * : Only show the provided field.
     *
     * ## EXAMPLES
     *
     *     # Get the status of theme auto-updates
     *     $ wp theme auto-updates status twentysixteen
     *     +---------------+----------+
     *     | name          | status   |
     *     +---------------+----------+
     *     | twentysixteen | disabled |
     *     +---------------+----------+
     *
     *     # Get the list of themes that have auto-updates enabled
     *     $ wp theme auto-updates status --all --enabled-only --field=name
     *     twentysixteen
     *     twentyseventeen
     */
    
public function status$args$assoc_args ) {
        
$all           Utils\get_flag_value$assoc_args'all'false );
        
$enabled_only  Utils\get_flag_value$assoc_args'enabled-only'false );
        
$disabled_only Utils\get_flag_value$assoc_args'disabled-only'false );

        if ( 
$enabled_only && $disabled_only ) {
            
WP_CLI::error(
                
'--enabled-only and --disabled-only are mutually exclusive and '
                
'cannot be used at the same time.'
            
);
        }

        
$args $this->check_optional_args_and_all$args$all );
        if ( ! 
$args ) {
            return;
        }

        
$themes       $this->fetcher->get_many$args );
        
$auto_updates get_site_option( static::SITE_OPTION );

        if ( 
false === $auto_updates ) {
            
$auto_updates = [];
        }

        
$results = [];

        foreach ( 
$themes as $theme ) {
            
$enabled in_array$theme->stylesheet$auto_updatestrue );

            if ( 
$enabled_only && ! $enabled ) {
                continue;
            }

            if ( 
$disabled_only && $enabled ) {
                continue;
            }

            
$results[] = [
                
'name'   => $theme->stylesheet,
                
'name
            '            
=> $theme->stylesheet,
                
'status' => $enabled 'enabled' 'disabled',
            ];
        }

        
$formatter = new Formatter$assoc_args, [ 'name''status' ], 'theme' );
        
$formatter->display_items$results );
    }
}
<?
php

if ( ! class_exists'WP_CLI' ) ) {
    return;
}

$wpcli_extension_autoloader dirname__FILE__ ) . '/vendor/autoload.php';
if ( 
file_exists$wpcli_extension_autoloader ) ) {
    require_once 
$wpcli_extension_autoloader;
}

$wpcli_extension_requires_wp_5_5 = [
    
'before_invoke' => static function () {
        if ( 
WP_CLI\Utils\wp_version_compare'5.5''<' ) ) {
            
WP_CLI::error'Requires WordPress 5.5 or greater.' );
        }
    },
];

WP_CLI::add_command'plugin''Plugin_Command' );
WP_CLI::add_command'plugin auto-updates''Plugin_AutoUpdates_Command'$wpcli_extension_requires_wp_5_5 );
WP_CLI::add_command'theme''Theme_Command' );
WP_CLI::add_command'theme auto-updates''Theme_AutoUpdates_Command'$wpcli_extension_requires_wp_5_5 );
WP_CLI::add_command'theme mod''Theme_Mod_Command' );
<?
php

class WP_Map_Iterator extends IteratorIterator {
    public function 
__construct$iterator$callback ) {
        
$this->callback $callback;
        
parent::__construct$iterator );
    }

    public function 
current() {
        
$original_current parent::current();
        return 
call_user_func$this->callback$original_current );
    }
}
<?
php

use WP_CLI\Utils;

// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- Changing breaks Phar compat.
define'WP_CLI_EXPORT_COMMAND_NO_SPLIT''-1' );

/**
 * Exports WordPress content to a WXR file.
 *
 * ## EXAMPLES
 *
 *     # Export posts published by the user between given start and end date
 *     $ wp export --dir=/tmp/ --user=admin --post_type=post --start_date=2011-01-01 --end_date=2011-12-31
 *     Starting export process...
 *     Writing to file /tmp/staging.wordpress.2016-05-24.000.xml
 *     Success: All done with export.
 *
 * @package wp-cli
 */
class Export_Command extends WP_CLI_Command {

    
/**
    * Initialize the array of arguments that will be eventually be passed to export_wp.
    *
    * @var array
    */
    
public $export_args = [];

    private 
$stdout;
    private 
$max_file_size;
    private 
$wxr_path;

    
/**
     * Exports WordPress content to a WXR file.
     *
     * Generates one or more WXR files containing authors, terms, posts,
     * comments, and attachments. WXR files do not include site configuration
     * (options) or the attachment files themselves.
     *
     * ## OPTIONS
     *
     * [--dir=<dirname>]
     * : Full path to directory where WXR export files should be stored. Defaults
     * to current working directory.
     *
     * [--stdout]
     * : Output the whole XML using standard output (incompatible with --dir=)
     *
     * [--skip_comments]
     * : Don't include comments in the WXR export file.
     *
     * [--max_file_size=<MB>]
     * : A single export file should have this many megabytes. -1 for unlimited.
     * ---
     * default: 15
     * ---
     *
     * ## FILTERS
     *
     * [--start_date=<date>]
     * : Export only posts published after this date, in format YYYY-MM-DD.
     *
     * [--end_date=<date>]
     * : Export only posts published before this date, in format YYYY-MM-DD.
     *
     * [--post_type=<post-type>]
     * : Export only posts with this post_type. Separate multiple post types with a
     * comma.
     * ---
     * default: any
     * ---
     *
     * [--post_type__not_in=<post-type>]
     * : Export all post types except those identified. Separate multiple post types
     * with a comma. Defaults to none.
     *
     * [--post__in=<pid>]
     * : Export all posts specified as a comma- or space-separated list of IDs.
     * Post's attachments won't be exported unless --with_attachments is specified.
     *
     * [--with_attachments]
     * : Force including attachments in case --post__in has been specified.
     *
     * [--start_id=<pid>]
     * : Export only posts with IDs greater than or equal to this post ID.
     *
     * [--max_num_posts=<num>]
     * : Export no more than <num> posts (excluding attachments).
     *
     * [--author=<author>]
     * : Export only posts by this author. Can be either user login or user ID.
     *
     * [--category=<name|id>]
     * : Export only posts in this category.
     *
     * [--post_status=<status>]
     * : Export only posts with this status.
     *
     * [--filename_format=<format>]
     * : Use a custom format for export filenames. Defaults to '{site}.wordpress.{date}.{n}.xml'.
     *
     * ## EXAMPLES
     *
     *     # Export posts published by the user between given start and end date
     *     $ wp export --dir=/tmp/ --user=admin --post_type=post --start_date=2011-01-01 --end_date=2011-12-31
     *     Starting export process...
     *     Writing to file /tmp/staging.wordpress.2016-05-24.000.xml
     *     Success: All done with export.
     *
     *     # Export posts by IDs
     *     $ wp export --dir=/tmp/ --post__in=123,124,125
     *     Starting export process...
     *     Writing to file /tmp/staging.wordpress.2016-05-24.000.xml
     *     Success: All done with export.
     *
     *     # Export a random subset of content
     *     $ wp export --post__in="$(wp post list --post_type=post --orderby=rand --posts_per_page=8 --format=ids)"
     *     Starting export process...
     *     Writing to file /var/www/example.com/public_html/staging.wordpress.2016-05-24.000.xml
     *     Success: All done with export.
     */
    
public function __invoke$_$assoc_args ) {
        
$defaults = [
            
'dir'               => null,
            
'stdout'            => false,
            
'start_date'        => null,
            
'end_date'          => null,
            
'post_type'         => null,
            
'post_type__not_in' => null,
            
'max_num_posts'     => null,
            
'author'            => null,
            
'category'          => null,
            
'post_status'       => null,
            
'post__in'          => null,
            
'with_attachments'  => true// or FALSE if user requested some post__in
            
'start_id'          => null,
            
'skip_comments'     => null,
            
'max_file_size'     => 15,
            
'filename_format'   => '{site}.wordpress.{date}.{n}.xml',
        ];

        if ( ! empty( 
$assoc_args['stdout'] ) && ( ! empty( $assoc_args['dir'] ) || ! empty( $assoc_args['filename_format'] ) ) ) {
            
WP_CLI::error'--stdout and --dir cannot be used together.' );
        }

        if ( ! empty( 
$assoc_args['post__in'] ) && empty( $assoc_args['with_attachments'] ) ) {
            
$defaults['with_attachments'] = false;
        }

        
$assoc_args wp_parse_args$assoc_args$defaults );

        
$this->validate_args$assoc_args );

        
$this->export_args['with_attachments'] = Utils\get_flag_value(
            
$assoc_args,
            
'with_attachments',
            
$defaults['with_attachments']
        );

        if ( ! 
function_exists'wp_export' ) ) {
            
self::load_export_api();
        }

        if ( ! 
$this->stdout ) {
            
WP_CLI::log'Starting export process...' );
        }

        
add_action(
            
'wp_export_new_file',
            static function ( 
$file_path ) {
                
WP_CLI::logsprintf'Writing to file %s'$file_path ) );
                
Utils\wp_clear_object_cache();
            }
        );

        try {
            if ( 
$this->stdout ) {
                
wp_export(
                    [
                        
'filters'     => $this->export_args,
                        
'writer'      => 'WP_Export_File_Writer',
                        
'writer_args' => 'php://output',
                    ]
                );
            } else {
                
wp_export(
                    [
                        
'filters'     => $this->export_args,
                        
'writer'      => 'WP_Export_Split_Files_Writer',
                        
'writer_args' => [
                            
'max_file_size'         => $this->max_file_size,
                            
'destination_directory' => $this->wxr_path,
                            
'filename_template'     => self::get_filename_template$assoc_args['filename_format'] ),
                        ],
                    ]
                );
            }
        } catch ( 
Exception $e ) {
            
WP_CLI::error$e->getMessage() );
        }

        if ( ! 
$this->stdout ) {
            
WP_CLI::success'All done with export.' );
        }
    }

    private static function 
get_filename_template$filename_format ) {
        
$sitename sanitize_keyget_bloginfo'name' ) );
        if ( empty( 
$sitename ) ) {
            
$sitename 'site';
        }
        return 
str_replace( [ '{site}''{date}''{n}' ], [ $sitenamedate'Y-m-d' ), '%03d' ], $filename_format ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
    
}

    public static function 
load_export_api() {
        require 
dirname__DIR__ ) . '/functions.php';
    }

    private function 
validate_args$args ) {
        
$has_errors false;

        foreach ( 
$args as $key => $value ) {
            if ( 
is_callable( [ $this'check_' $key ] ) ) {
                
$result call_user_func( [ $this'check_' $key ], $value );
                if ( 
false === $result ) {
                    
$has_errors true;
                }
            }
        }

        if ( 
$args['stdout'] ) {
            
$this->wxr_path null;
            
$this->stdout   true;
        }

        if ( 
$has_errors ) {
            
WP_CLI::halt);
        }
    }

    private function 
check_dir$path ) {
        if ( empty( 
$path ) ) {
            
$path getcwd();
        } elseif ( ! 
is_dir$path ) ) {
            
WP_CLI::errorsprintf"The directory '%s' does not exist."$path ) );
        } elseif ( ! 
is_writable$path ) ) {
            
WP_CLI::errorsprintf"The directory '%s' is not writable."$path ) );
        }

        
$this->wxr_path trailingslashit$path );

        return 
true;
    }

    private function 
check_start_date$date ) {
        if ( 
null === $date ) {
            return 
true;
        }

        
$time strtotime$date );
        if ( ! empty( 
$date ) && ! $time ) {
            
WP_CLI::warningsprintf'The start_date %s is invalid.'$date ) );
            return 
false;
        }
        
$this->export_args['start_date'] = date'Y-m-d'$time ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
        
return true;
    }

    private function 
check_end_date$date ) {
        if ( 
null === $date ) {
            return 
true;
        }

        
$time strtotime$date );
        if ( ! empty( 
$date ) && ! $time ) {
            
WP_CLI::warningsprintf'The end_date %s is invalid.'$date ) );
            return 
false;
        }
        
$this->export_args['end_date'] = date'Y-m-d'$time ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
        
return true;
    }

    private function 
check_post_type$post_type ) {
        if ( 
null === $post_type || 'any' === $post_type ) {
            return 
true;
        }

        
$post_type  array_uniquearray_filterexplode','$post_type ) ) );
        
$post_types get_post_types();

        foreach ( 
$post_type as $type ) {
            if ( ! 
in_array$type$post_typestrue ) ) {
                
WP_CLI::warning(
                    
sprintf(
                        
'The post type %s does not exist. Choose "any" or any of these existing post types instead: %s',
                        
$type,
                        
implode', '$post_types )
                    )
                );
                return 
false;
            }
        }
        
$this->export_args['post_type'] = $post_type;
        return 
true;
    }

    private function 
check_post_type__not_in$post_type ) {
        if ( 
null === $post_type ) {
            return 
true;
        }

        
$post_type  array_uniquearray_filterexplode','$post_type ) ) );
        
$post_types get_post_types();

        foreach ( 
$post_type as $type ) {
            if ( ! 
in_array$type$post_typestrue ) ) {
                
WP_CLI::warning(
                    
sprintf(
                        
'The post type %s does not exist. Use any of these existing post types instead: %s',
                        
$type,
                        
implode', '$post_types )
                    )
                );
                return 
false;
            }
        }
        
$this->export_args['post_type'] = array_diff$post_types$post_type );
        return 
true;
    }

    private function 
check_post__in$post__in ) {
        if ( 
null === $post__in ) {
            return 
true;
        }

        
$separator false !== stripos$post__in' ' ) ? ' ' ',';
        
$post__in  array_filterarray_uniquearray_map'intval'explode$separator$post__in ) ) ) );
        if ( empty( 
$post__in ) ) {
            
WP_CLI::warning'post__in should be comma-separated post IDs.' );
            return 
false;
        }
        
// New exporter uses a different argument.
        
$this->export_args['post_ids'] = $post__in;
        return 
true;
    }

    private function 
check_start_id$start_id ) {
        if ( 
null === $start_id ) {
            return 
true;
        }

        
$start_id = (int) $start_id;

        
// Post IDs must be greater than 0.
        
if ( >= $start_id ) {
            
WP_CLI::warning"Invalid start ID: {$start_id});
            return 
false;
        }

        
$this->export_args['start_id'] = $start_id;
        return 
true;
    }

    private function 
check_author$author ) {
        if ( 
null === $author ) {
            return 
true;
        }

        
// phpcs:ignore WordPress.WP.DeprecatedFunctions.get_users_of_blogFound -- Fallback.
        
$authors function_exists'get_users' ) ? get_users() : get_users_of_blog();
        if ( empty( 
$authors ) || is_wp_error$authors ) ) {
            
WP_CLI::warning'Could not find any authors in this blog.' );
            return 
false;
        }
        
$hit false;
        foreach ( 
$authors as $user ) {
            if ( 
$hit ) {
                break;
            }
            if ( (int) 
$author === $user->ID || $author === $user->user_login ) {
                
$hit $user->ID;
            }
        }
        if ( 
false === $hit ) {
            
$authors_nice = [];
            foreach ( 
$authors as $_author ) {
                
$authors_nice[] = sprintf'%s (%s)'$_author->user_login$_author->display_name );
            }
            
WP_CLI::warningsprintf'Could not find a matching author for %s. The following authors exist: %s'$authorimplode', '$authors_nice ) ) );
            return 
false;
        }

        
$this->export_args['author'] = $hit;
        return 
true;
    }

    private function 
check_max_num_posts$num ) {
        if ( 
null !== $num && ( ! is_numeric$num ) || $num <= ) ) {
            
WP_CLI::warning'max_num_posts should be a positive integer.' );
            return 
false;
        }

        
$this->export_args['max_num_posts'] = (int) $num;

        return 
true;
    }

    private function 
check_category$category ) {
        if ( 
null === $category ) {
            return 
true;
        }

        if ( 
is_numeric$category ) ) {
            
$category = (int) $category;
        }

        
$term category_exists$category );
        if ( empty( 
$term ) || is_wp_error$term ) ) {
            
WP_CLI::warningsprintf'Could not find a category matching %s.'$category ) );
            return 
false;
        }
        
$this->export_args['category'] = $category;
        return 
true;
    }

    private function 
check_post_status$status ) {
        if ( 
null === $status ) {
            return 
true;
        }

        
$stati get_post_statuses();
        if ( empty( 
$stati ) || is_wp_error$stati ) ) {
            
WP_CLI::warning'Could not find any post stati.' );
            return 
false;
        }

        if ( ! isset( 
$stati$status ] ) ) {
            
WP_CLI::warningsprintf'Could not find a post_status matching %s. Here is a list of available stati: %s'$statusimplode', 'array_keys$stati ) ) ) );
            return 
false;
        }
        
$this->export_args['status'] = $status;
        return 
true;
    }

    private function 
check_skip_comments$skip ) {
        if ( 
null === $skip ) {
            return 
true;
        }

        if ( 
!== (int) $skip && !== (int) $skip ) {
            
WP_CLI::warning'skip_comments needs to be 0 (no) or 1 (yes).' );
            return 
false;
        }
        
$this->export_args['skip_comments'] = $skip;
        return 
true;
    }

    private function 
check_max_file_size$size ) {
        if ( ! 
is_numeric$size ) ) {
            
WP_CLI::warning'max_file_size should be numeric.' );
            return 
false;
        }

        
$this->max_file_size $size;

        return 
true;
    }
}
<?
php

use WP_CLI\Utils;

/**
 * Represents a set of posts and other site data to be exported.
 *
 * An immutable object, which gathers all data needed for the export.
 */
class WP_Export_Query {
    const 
QUERY_CHUNK 100;

    private static 
$defaults = [
        
'post_ids'      => null,
        
'post_type'     => null,
        
'status'        => null,
        
'author'        => null,
        
'start_date'    => null,
        
'end_date'      => null,
        
'start_id'      => null,
        
'max_num_posts' => null,
        
'category'      => null,
    ];

    private 
$post_ids;
    private 
$filters;

    private 
$wheres = [];
    private 
$joins  = [];

    private 
$author;
    private 
$category;

    public 
$missing_parents false;

    public function 
__construct$filters = [] ) {
        
$this->filters wp_parse_args$filtersself::$defaults );

        
$user $this->find_user_from_any_object$this->filters['author'] );
        if ( 
$user && ! is_wp_error$user ) ) {
            
$this->author $user;
        }

        
$this->post_ids $this->calculate_post_ids();
    }

    public function 
post_ids() {
        return 
$this->post_ids;
    }

    public function 
charset() {
        return 
get_bloginfo'charset' );
    }

    public function 
site_metadata() {
        
$metadata = [
            
'name'        => $this->bloginfo_rss'name' ),
            
'url'         => $this->bloginfo_rss'url' ),
            
'language'    => $this->bloginfo_rss'language' ),
            
'description' => $this->bloginfo_rss'description' ),
            
'pubDate'     => date'D, d M Y H:i:s +0000' ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
            
'site_url'    => is_multisite() ? network_home_url() : $this->bloginfo_rss'url' ),
            
'blog_url'    => $this->bloginfo_rss'url' ),
        ];
        return 
$metadata;
    }

    public function 
wp_generator_tag() {
        
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Calling native WordPress hook.
        
return apply_filters'the_generator'get_the_generator'export' ), 'export' );
    }

    public function 
authors() {
        global 
$wpdb;

        
// If we're filtering by a specific author, we only need to include that
        // author's user object, and no other users.
        
if ( is_object$this->author ) && property_exists$this->author'ID' ) ) {
            return [ 
$this->author ];
        }

        
$authors    = [];
        
$author_ids = (array) $wpdb->get_col"SELECT DISTINCT post_author FROM $wpdb->posts WHERE post_status != 'auto-draft'" );
        foreach ( 
$author_ids as $author_id ) {
            
$authors[] = get_userdata$author_id );
        }
        
$authors array_filter$authors );
        return 
$authors;
    }

    public function 
categories() {
        if ( 
$this->category ) {
            return [ 
$this->category ];
        }
        if ( 
$this->filters['post_type'] ) {
            return [];
        }
        
$categories = (array) get_categories( [ 'get' => 'all' ] );

        
$this->check_for_orphaned_terms$categories );

        
$categories self::topologically_sort_terms$categories );

        return 
$categories;
    }

    public function 
tags() {
        if ( 
$this->filters['post_type'] ) {
            return [];
        }
        
$tags = (array) get_tags( [ 'get' => 'all' ] );

        
$this->check_for_orphaned_terms$tags );

        return 
$tags;
    }

    public function 
custom_taxonomies_terms() {
        if ( 
$this->filters['post_type'] ) {
            return [];
        }
        
$custom_taxonomies get_taxonomies( [ '_builtin' => false ] );
        
$custom_terms      = (array) get_terms$custom_taxonomies, [ 'get' => 'all' ] );
        
$this->check_for_orphaned_terms$custom_terms );
        
$custom_terms self::topologically_sort_terms$custom_terms );
        return 
$custom_terms;
    }

    public function 
nav_menu_terms() {
        
$nav_menus wp_get_nav_menus();
        foreach ( 
$nav_menus as $term ) {
            
$term->description '';
        }
        return 
$nav_menus;
    }

    public function 
exportify_post$post ) {
        
$GLOBALS['wp_query']->in_the_loop true;
        
$previous_global_post             Utils\get_flag_value$GLOBALS'post' );
        
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Temporary override.
        
$GLOBALS['post'] = $post;
        
setup_postdata$post );
        
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Calling native WordPress hook.
        
$post->post_content apply_filters'the_content_export'$post->post_content );
        
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Calling native WordPress hook.
        
$post->post_excerpt apply_filters'the_excerpt_export'$post->post_excerpt );
        
$post->is_sticky    is_sticky$post->ID ) ? 0;
        
$post->terms        self::get_terms_for_post$post );
        
$post->meta         self::get_meta_for_post$post );
        
$post->comments     $this->get_comments_for_post$post );
        
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Revert back to original.
        
$GLOBALS['post'] = $previous_global_post;
        return 
$post;
    }

    public function 
posts() {
        
$posts_iterator = new WP_Post_IDs_Iterator$this->post_idsself::QUERY_CHUNK );
        return new 
WP_Map_Iterator$posts_iterator, [ $this'exportify_post' ] );
    }

    private function 
calculate_post_ids() {
        global 
$wpdb;
        if ( 
is_array$this->filters['post_ids'] ) ) {
            if ( 
$this->filters['with_attachments'] ) {
                
$attachment_post_ids       $this->include_attachment_ids$this->filters['post_ids'] );
                
$this->filters['post_ids'] = array_merge$this->filters['post_ids'], $attachment_post_ids );
            }
            return 
$this->filters['post_ids'];
        }
        
$this->post_type_where();
        
$this->status_where();
        
$this->author_where();
        
$this->start_date_where();
        
$this->end_date_where();
        
$this->start_id_where();
        
$this->category_where();

        
$where implode' AND 'array_filter$this->wheres ) );
        if ( 
$where ) {
            
$where "WHERE $where";
        }
        
$join implode' 'array_filter$this->joins ) );

        
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Individual where clauses run through $wpdb->prepare().
        
$post_ids $wpdb->get_col"SELECT ID FROM {$wpdb->posts} AS p {$join} {$where} {$this->max_num_posts()});
        if ( 
$this->filters['post_type'] ) {
            
$post_ids array_merge$post_ids$this->include_attachment_ids$post_ids ) );
        }
        return 
$post_ids;
    }

    private function 
post_type_where() {
        
$post_types_filters = [ 'can_export' => true ];
        if ( 
$this->filters['post_type'] ) {
            
$post_types $this->filters['post_type'];

            
// Flatten single post types
            
if ( is_array$post_types ) && === count$post_types ) ) {
                
$post_types array_shift$post_types );
            }
            
$post_types_filters array_merge$post_types_filters, [ 'name' => $post_types ] );
        }

        
// Multiple post types
        
if ( isset( $post_types_filters['name'] ) && is_array$post_types_filters['name'] ) ) {
            
$post_types = [];
            foreach ( 
$post_types_filters['name'] as $post_type ) {
                if ( 
post_type_exists$post_type ) ) {
                    
$post_types[] = $post_type;
                }
            }
        } else {
            
$post_types get_post_types$post_types_filters );
        }

        if ( ! 
$post_types ) {
            
$this->wheres[] = 'p.post_type IS NULL';
            return;
        }

        if ( 
false === $this->filters['with_attachments'] && ( ! $this->filters['post_type'] || ! in_array'attachment'$this->filters['post_type'], true ) ) ) {
            unset( 
$post_types['attachment'] );
        }

        
$this->wheres[] = _wp_export_build_IN_condition'p.post_type'$post_types );
    }

    private function 
status_where() {
        global 
$wpdb;
        if ( ! 
$this->filters['status'] ) {
            
$this->wheres[] = "p.post_status != 'auto-draft'";
            return;
        }
        
$this->wheres[] = $wpdb->prepare'p.post_status = %s'$this->filters['status'] );
    }

    private function 
author_where() {
        global 
$wpdb;
        if ( 
is_object$this->author ) && property_exists$this->author'ID' ) ) {
            
$this->wheres[] = $wpdb->prepare'p.post_author = %d'$this->author->ID );
        }
    }

    private function 
start_date_where() {
        global 
$wpdb;
        
$timestamp strtotime$this->filters['start_date'] );
        if ( ! 
$timestamp ) {
            return;
        }
        
$this->wheres[] = $wpdb->prepare'p.post_date >= %s'date'Y-m-d 00:00:00'$timestamp ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
    
}

    private function 
end_date_where() {
        global 
$wpdb;
        if ( 
preg_match'/^\d{4}-\d{2}$/'$this->filters['end_date'] ) ) {
            
$timestamp $this->get_timestamp_for_the_last_day_of_a_month$this->filters['end_date'] );
        } else {
            
$timestamp strtotime$this->filters['end_date'] );
        }
        if ( ! 
$timestamp ) {
            return;
        }
        
$this->wheres[] = $wpdb->prepare'p.post_date <= %s'date'Y-m-d 23:59:59'$timestamp ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
    
}

    private function 
start_id_where() {
        global 
$wpdb;

        
$start_id absint$this->filters['start_id'] );
        if ( 
=== $start_id ) {
            return;
        }
        
$this->wheres[] = $wpdb->prepare'p.ID >= %d'$start_id );
    }

    private function 
get_timestamp_for_the_last_day_of_a_month$yyyy_mm ) {
        return 
strtotime"$yyyy_mm +1month -1day" );
    }

    private function 
category_where() {
        global 
$wpdb;
        if ( 
'post' !== $this->filters['post_type'] && ! in_array'post', (array) $this->filters['post_type'], true ) ) {
            return;
        }
        
$category $this->find_category_from_any_object$this->filters['category'] );
        if ( ! 
$category ) {
            return;
        }
        
$this->category $category;
        
$this->joins[]  = "INNER JOIN {$wpdb->term_relationships} AS tr ON (p.ID = tr.object_id)";
        
$this->wheres[] = $wpdb->prepare'tr.term_taxonomy_id = %d'$category->term_taxonomy_id );
    }

    private function 
max_num_posts() {
        if ( 
$this->filters['max_num_posts'] > ) {
            return 
"LIMIT {$this->filters['max_num_posts']}";
        } else {
            return 
'';
        }
    }

    private function 
include_attachment_ids$post_ids ) {
        global 
$wpdb;
        if ( ! 
$post_ids ) {
            return [];
        }
        
$attachment_ids = [];
        
// phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition -- Assigment is part of the break condition.
        
while ( $batch_of_post_ids array_splice$post_ids0self::QUERY_CHUNK ) ) {
            
$post_parent_condition _wp_export_build_IN_condition'post_parent'$batch_of_post_ids );
            
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Escaped in wpcli_export_build_in_condition() function.
            
$attachment_ids array_merge$attachment_ids, (array) $wpdb->get_col"SELECT ID FROM {$wpdb->posts} WHERE post_type = 'attachment' AND {$post_parent_condition}) );
        }
        return 
array_map'intval'$attachment_ids );
    }

    private function 
bloginfo_rss$section ) {
        
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Calling native WordPress hook.
        
return apply_filters'bloginfo_rss'get_bloginfo_rss$section ), $section );
    }

    private function 
find_user_from_any_object$user ) {
        if ( 
is_numeric$user ) ) {
            return 
get_user_by'id'$user );
        } elseif ( 
is_string$user ) ) {
            return 
get_user_by'login'$user );
        } elseif ( isset( 
$user->ID ) ) {
            return 
get_user_by'id'$user->ID );
        }
        return 
false;
    }

    private function 
find_category_from_any_object$category ) {
        if ( 
is_numeric$category ) ) {
            return 
get_term$category'category' );
        } elseif ( 
is_string$category ) ) {
            
$term term_exists$category'category' );
            return isset( 
$term['term_id'] ) ? get_term$term['term_id'], 'category' ) : false;
        } elseif ( isset( 
$category->term_id ) ) {
            return 
get_term$category->term_id'category' );
        }
        return 
false;
    }

    private static function 
topologically_sort_terms$terms ) {
        
$sorted = [];
        
// phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition -- assignment is used as break condition.
        
while ( $term array_shift$terms ) ) {
            if ( 
=== (int) $term->parent || isset( $sorted$term->parent ] ) ) {
                
$sorted$term->term_id ] = $term;
            } else {
                
$terms[] = $term;
            }
        }
        return 
$sorted;
    }

    private function 
check_for_orphaned_terms$terms ) {
        
$term_ids    = [];
        
$have_parent = [];

        foreach ( 
$terms as $term ) {
            
$term_ids$term->term_id ] = true;
            if ( 
!== (int) $term->parent ) {
                
$have_parent[] = $term;
            }
        }

        foreach ( 
$have_parent as $has_parent ) {
            if ( ! isset( 
$term_ids$has_parent->parent ] ) ) {
                
$this->missing_parents $has_parent;
                throw new 
WP_Export_Term_Exception"Term is missing a parent: {$has_parent->slug} ({$has_parent->term_taxonomy_id})" );
            }
        }
    }

    private static function 
get_terms_for_post$post ) {
        
$taxonomies get_object_taxonomies$post->post_type );
        if ( empty( 
$taxonomies ) ) {
            return [];
        }
        return 
wp_get_object_terms$post->ID$taxonomies ) ?: [];
    }

    private static function 
get_meta_for_post$post ) {
        global 
$wpdb;
        
$meta_for_export = [];
        
$meta_from_db    $wpdb->get_results$wpdb->prepare"SELECT * FROM $wpdb->postmeta WHERE post_id = %d"$post->ID ) );
        foreach ( 
$meta_from_db as $meta ) {
            if ( 
'_edit_lock' === $meta->meta_key ) {
                continue;
            }
            
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Calling native WordPress hook.
            
if ( apply_filters'wxr_export_skip_postmeta'false$meta->meta_key$meta ) ) {
                continue;
            }
            
$meta_for_export[] = $meta;
        }
        return 
$meta_for_export;
    }

    private function 
get_comments_for_post$post ) {
        global 
$wpdb;

        if ( isset( 
$this->filters['skip_comments'] ) ) {
            return [];
        }

        
$comments $wpdb->get_results$wpdb->prepare"SELECT * FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_approved <> 'spam'"$post->ID ) );
        foreach ( 
$comments as $comment ) {
            
$comment->meta $wpdb->get_results$wpdb->prepare"SELECT * FROM $wpdb->commentmeta WHERE comment_id = %d"$comment->comment_ID ) ) ?: [];
        }
        return 
$comments;
    }
}

<?
php

class WP_Export_Returner extends WP_Export_Base_Writer {
    private 
$result '';

    public function 
export() {
        
$this->private '';
        try {
            
parent::export();
        } catch ( 
WP_Export_Exception $e ) {
            
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Possibly used by third party extension.
            
$message apply_filters'export_error_message'$e->getMessage() );
            return new 
WP_Error'wp-export-error'$message );

        } catch ( 
WP_Export_Term_Exception $e ) {
            
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Possibly used by third party extensions.
            
do_action'export_term_orphaned'$this->formatter->export->missing_parents );
            
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Possibly used by third party extension.
            
$message apply_filters'export_term_error_message'$e->getMessage() );
            return new 
WP_Error'wp-export-error'$message );
        }
        return 
$this->result;
    }
    protected function 
write$xml ) {
        
$this->result .= $xml;
    }
}
<?
php

class WP_Export_Term_Exception extends RuntimeException {
}
<?
php

class WP_Export_Split_Files_Writer extends WP_Export_Base_Writer {
    private 
$max_file_size;
    private 
$destination_directory;
    private 
$filename_template;
    private 
$before_posts_xml;
    private 
$after_posts_xml;

    private 
$f;
    private 
$next_file_number  0;
    private 
$current_file_size 0;

    public function 
__construct$formatter$writer_args = [] ) {
        
parent::__construct$formatter );

        if ( ! 
defined'MB_IN_BYTES' ) ) {
            
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- WordPress native constants.
            
define'MB_IN_BYTES'1024 1024 );
        }

        
//TODO: check if args are not missing
        
if ( is_null$writer_args['max_file_size'] ) ) {
            
$this->max_file_size 15 MB_IN_BYTES;
        } elseif ( 
WP_CLI_EXPORT_COMMAND_NO_SPLIT === (string) $writer_args['max_file_size'] ) {
            
$this->max_file_size WP_CLI_EXPORT_COMMAND_NO_SPLIT;
        } else {
            
$this->max_file_size $writer_args['max_file_size'] * MB_IN_BYTES;
        }

        
$this->destination_directory $writer_args['destination_directory'];
        
$this->filename_template     $writer_args['filename_template'];
        
$this->before_posts_xml      $this->formatter->before_posts();
        
$this->after_posts_xml       $this->formatter->after_posts();
    }

    public function 
export() {
        
$this->start_new_file();
        foreach ( 
$this->formatter->posts() as $post_xml ) {
            if ( 
WP_CLI_EXPORT_COMMAND_NO_SPLIT !== $this->max_file_size && $this->current_file_size strlen$post_xml ) > $this->max_file_size ) {
                
$this->start_new_file();
            }
            
$this->write$post_xml );
        }
        
$this->close_current_file();
    }

    protected function 
write$xml ) {
        
$res fwrite$this->f$xml );
        if ( 
false === $res ) {
            throw new 
WP_Export_Exception'WP Export: error writing to export file.' );
        }
        
$this->current_file_size += strlen$xml );
    }

    private function 
start_new_file() {
        if ( 
$this->) {
            
$this->close_current_file();
        }
        
$file_path $this->next_file_path();
        
$this->f   fopen$file_path'w' );
        if ( ! 
$this->) {
            throw new 
WP_Export_Exception"WP Export: error opening {$file_path} for writing." );
        }
        
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Possibly used by third party extension.
        
do_action'wp_export_new_file'$file_path );
        
$this->current_file_size 0;
        
$this->write$this->before_posts_xml );
    }

    private function 
close_current_file() {
        if ( ! 
$this->) {
            return;
        }
        
$this->write$this->after_posts_xml );
        
fclose$this->);
    }

    private function 
next_file_name() {
        
$next_file_name sprintf$this->filename_template$this->next_file_number );
        
$this->next_file_number++;
        return 
$next_file_name;
    }

    private function 
next_file_path() {
        return 
untrailingslashit$this->destination_directory ) . DIRECTORY_SEPARATOR $this->next_file_name();
    }

}
<?
php

class WP_Export_File_Writer extends WP_Export_Base_Writer {
    private 
$f;
    private 
$file_name;

    public function 
__construct$formatter$file_name ) {
        
parent::__construct$formatter );
        
$this->file_name $file_name;
    }

    public function 
export() {
        
$this->fopen$this->file_name'w' );
        if ( ! 
$this->) {
            throw new 
WP_Export_Exception"WP Export: error opening {$this->file_name} for writing." );
        }

        try {
            
parent::export();
        } catch ( 
WP_Export_Exception $e ) {
            
fclose$this->);
            throw 
$e;
        } catch ( 
WP_Export_Term_Exception $e ) {
            
fclose$this->);
            throw 
$e;
        }

        
fclose$this->);
    }

    protected function 
write$xml ) {
        
$res fwrite$this->f$xml );
        if ( 
false === $res ) {
            throw new 
WP_Export_Exception'WP Export: error writing to export file.' );
        }
    }
}
<?
php

class WP_Export_Oxymel extends Oxymel {
    public function 
optional$tag_name$contents ) {
        if ( 
$contents ) {
            
$this->$tag_name$contents );
        }
        return 
$this;
    }

    public function 
optional_cdata$tag_name$contents ) {
        if ( 
$contents ) {
            
$this->$tag_name->contains->cdata$contents )->end;
        }
        return 
$this;
    }

    public function 
cdata$text ) {
        if ( 
is_string$text ) && ! seems_utf8$text ) ) {
            
$text utf8_encode$text );
        }
        return 
parent::cdata$text );
    }
}

<?
php

abstract class WP_Export_Base_Writer {
    protected 
$formatter;

    public function 
__construct$formatter ) {
        
$this->formatter $formatter;
    }

    public function 
export() {
        
$this->write$this->formatter->before_posts() );
        foreach ( 
$this->formatter->posts() as $post_in_wxr ) {
            
$this->write$post_in_wxr );
        }
        
$this->write$this->formatter->after_posts() );
    }

    abstract protected function 
write$xml );
}
<?
php
/**
 * Version number for the export format.
 *
 * Bump this when something changes that might affect compatibility.
 *
 * @since 2.5.0
 */
define'WXR_VERSION''1.2' ); //phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- WordPress constant.

/**
 * Responsible for formatting the data in WP_Export_Query to WXR
 */
class WP_Export_WXR_Formatter {
    public function 
__construct$export ) {
        
$this->export      $export;
        
$this->wxr_version WXR_VERSION;
    }

    public function 
before_posts$requested_sections = [] ) {
        
$available_sections = [
            
'header',
            
'site_metadata',
            
'authors',
            
'categories',
            
'tags',
            
'nav_menu_terms',
            
'custom_taxonomies_terms',
            
'rss2_head_action',
        ];

        
$before_posts_xml '';

        foreach ( 
$available_sections as $section ) {
            if ( ! 
$requested_sections || in_array$section$requested_sectionstrue ) ) {
                
$before_posts_xml .= $this->$section();
            }
        }

        return 
$before_posts_xml;
    }

    public function 
posts() {
        return new 
WP_Map_Iterator$this->export->posts(), [ $this'post' ] );
    }

    public function 
after_posts() {
        return 
$this->footer();
    }

    public function 
header() {
        
$oxymel           = new Oxymel();
        
$wp_generator_tag $this->export->wp_generator_tag();
        
$comment          = <<<COMMENT

 This is a WordPress eXtended RSS file generated by WordPress as an export of your site.
 It contains information about your site's posts, pages, comments, categories, and other content.
 You may use this file to transfer that content from one site to another.
 This file is not intended to serve as a complete backup of your site.

 To import this information into a WordPress site follow these steps:
 1. Log in to that site as an administrator.
 2. Go to Tools: Import in the WordPress admin panel.
 3. Install the "WordPress" importer from the list.
 4. Activate & Run Importer.
 5. Upload this file using the form provided on that page.
 6. You will first be asked to map the authors in this export file to users
    on the site. For each author, you may choose to map to an
    existing user on the site or to create a new user.
 7. WordPress will then import each of the posts, pages, comments, categories, etc.
    contained in this file into your site.

COMMENT;
        return 
$oxymel
            
->xml
            
->comment$comment )
            ->
raw$wp_generator_tag )
            ->
open_rss(
                [
                    
'version'       => '2.0',
                    
'xmlns:excerpt' => "http://wordpress.org/export/{$this->wxr_version}/excerpt/",
                    
'xmlns:content' => 'http://purl.org/rss/1.0/modules/content/',
                    
'xmlns:wfw'     => 'http://wellformedweb.org/CommentAPI/',
                    
'xmlns:dc'      => 'http://purl.org/dc/elements/1.1/',
                    
'xmlns:wp'      => "http://wordpress.org/export/{$this->wxr_version}/",
                ]
            )
                ->
open_channel
                
->to_string();

    }

    public function 
site_metadata() {
        
$oxymel   = new Oxymel();
        
$metadata $this->export->site_metadata();
        return 
$oxymel
            
->title$metadata['name'] )
            ->
link$metadata['url'] )
            ->
description$metadata['description'] )
            ->
pubDate$metadata['pubDate'] )
            ->
language$metadata['language'] )
            ->
tag'wp:wxr_version'$this->wxr_version )
            ->
tag'wp:base_site_url'$metadata['site_url'] )
            ->
tag'wp:base_blog_url'$metadata['blog_url'] )
            ->
to_string();
    }

    public function 
authors() {
        
$oxymel  = new Oxymel();
        
$authors $this->export->authors();
        foreach ( 
$authors as $author ) {
            
$oxymel
                
->tag'wp:author' )->contains
                    
->tag'wp:author_id'$author->ID )
                    ->
tag'wp:author_login'$author->user_login )
                    ->
tag'wp:author_email'$author->user_email )
                    ->
tag'wp:author_display_name' )->contains->cdata$author->display_name )->end
                    
->tag'wp:author_first_name' )->contains->cdata$author->user_firstname )->end
                    
->tag'wp:author_last_name' )->contains->cdata$author->user_lastname )->end
                    
->end;
        }
        return 
$oxymel->to_string();
    }

    public function 
categories() {
        
$oxymel     = new WP_Export_Oxymel();
        
$categories $this->export->categories();
        foreach ( 
$categories as $category ) {
            
$category->parent_slug $category->parent $categories$category->parent ]->slug '';
            
$oxymel->tag'wp:category' )->contains
                
->tag'wp:term_id'$category->term_id )
                ->
tag'wp:category_nicename'$category->slug )
                ->
tag'wp:category_parent'$category->parent_slug )
                ->
optional_cdata'wp:cat_name'$category->name )
                ->
optional_cdata'wp:category_description'$category->description )
                ->
end;
        }
        return 
$oxymel->to_string();
    }

    public function 
tags() {
        
$oxymel = new WP_Export_Oxymel();
        
$tags   $this->export->tags();
        foreach ( 
$tags as $tag ) {
            
$oxymel->tag'wp:tag' )->contains
                
->tag'wp:term_id'$tag->term_id )
                ->
tag'wp:tag_slug'$tag->slug )
                ->
optional_cdata'wp:tag_name'$tag->name )
                ->
optional_cdata'wp:tag_description'$tag->description )
                ->
end;
        }
        return 
$oxymel->to_string();
    }

    public function 
nav_menu_terms() {
        return 
$this->terms$this->export->nav_menu_terms() );
    }

    public function 
custom_taxonomies_terms() {
        return 
$this->terms$this->export->custom_taxonomies_terms() );
    }

    public function 
rss2_head_action() {
        
ob_start();
        
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WordPress native hook.
        
do_action'rss2_head' );
        return 
ob_get_clean();
    }

    public function 
post$post ) {
        
$oxymel                           = new WP_Export_Oxymel();
        
$GLOBALS['wp_query']->in_the_loop true;
        
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Intentional.
        
$GLOBALS['post'] = $post;
        
setup_postdata$post );

        
$oxymel->item->contains
            
->tag'title' )->contains->cdataapply_filters'the_title_export'$post->post_title ) )->end // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WordPress native hook.
            
->linkesc_urlapply_filters'the_permalink_rss'get_permalink() ) ) ) // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WordPress native hook.
            
->pubDatemysql2date'D, d M Y H:i:s +0000'get_post_time'Y-m-d H:i:s'true ), false ) )
            ->
tag'dc:creator'get_the_author_meta'login' ) )
            ->
guidesc_urlget_the_guid() ), [ 'isPermaLink' => 'false' ] )
            ->
description'' )
            ->
tag'content:encoded' )->contains->cdata$post->post_content )->end
            
->tag'excerpt:encoded' )->contains->cdata$post->post_excerpt )->end
            
->tag'wp:post_id'$post->ID )
            ->
tag'wp:post_date'$post->post_date )
            ->
tag'wp:post_date_gmt'$post->post_date_gmt )
            ->
tag'wp:post_modified'$post->post_modified )
            ->
tag'wp:post_modified_gmt'$post->post_modified_gmt )
            ->
tag'wp:comment_status'$post->comment_status )
            ->
tag'wp:ping_status'$post->ping_status )
            ->
tag'wp:post_name'$post->post_name )
            ->
tag'wp:status'$post->post_status )
            ->
tag'wp:post_parent'$post->post_parent )
            ->
tag'wp:menu_order'$post->menu_order )
            ->
tag'wp:post_type'$post->post_type )
            ->
tag'wp:post_password'$post->post_password )
            ->
tag'wp:is_sticky'$post->is_sticky )
            ->
optional'wp:attachment_url'wp_get_attachment_url$post->ID ) );
        foreach ( 
$post->terms as $term ) {
            
$oxymel
            
->category(
                [
                    
'domain'   => $term->taxonomy,
                    
'nicename' => $term->slug,
                ]
            )->
contains->cdata$term->name )->end;
        }
        foreach ( 
$post->meta as $meta ) {
            
$oxymel
            
->tag'wp:postmeta' )->contains
                
->tag'wp:meta_key'$meta->meta_key )
                ->
tag'wp:meta_value' )->contains->cdata$meta->meta_value )->end
                
->end;
        }
        foreach ( 
$post->comments as $comment ) {
            
$oxymel
            
->tag'wp:comment' )->contains
                
->tag'wp:comment_id'$comment->comment_ID )
                ->
tag'wp:comment_author' )->contains->cdata$comment->comment_author )->end
                
->tag'wp:comment_author_email'$comment->comment_author_email )
                ->
tag'wp:comment_author_url'esc_url$comment->comment_author_url ) )
                ->
tag'wp:comment_author_IP'$comment->comment_author_IP )
                ->
tag'wp:comment_date'$comment->comment_date )
                ->
tag'wp:comment_date_gmt'$comment->comment_date_gmt )
                ->
tag'wp:comment_content' )->contains->cdata$comment->comment_content )->end
                
->tag'wp:comment_approved'$comment->comment_approved )
                ->
tag'wp:comment_type'$comment->comment_type )
                ->
tag'wp:comment_parent'$comment->comment_parent )
                ->
tag'wp:comment_user_id'$comment->user_id )
                ->
oxymel$this->comment_meta$comment ) )
                ->
end;
        }
        
$oxymel
            
->end;
        return 
$oxymel->to_string();
    }

    public function 
footer() {
        
$oxymel = new Oxymel();
        return 
$oxymel->close_channel->close_rss->to_string();
    }

    protected function 
terms$terms ) {
        
$oxymel = new WP_Export_Oxymel();
        foreach ( 
$terms as $term ) {
            
$term->parent_slug $term->parent $terms$term->parent ]->slug '';
            
$oxymel->tag'wp:term' )->contains
                
->tag'wp:term_id'$term->term_id )
                ->
tag'wp:term_taxonomy'$term->taxonomy )
                ->
tag'wp:term_slug'$term->slug );
            if ( 
'nav_menu' !== $term->taxonomy ) {
                
$oxymel
                
->tag'wp:term_parent'$term->parent_slug );
            }
                
$oxymel
                
->optional_cdata'wp:term_name'$term->name )
                ->
optional_cdata'wp:term_description'$term->description )
                ->
end;
        }
        return 
$oxymel->to_string();
    }

    protected function 
comment_meta$comment ) {
        global 
$wpdb;
        
$metas $wpdb->get_results$wpdb->prepare"SELECT * FROM $wpdb->commentmeta WHERE comment_id = %d"$comment->comment_ID ) );
        if ( ! 
$metas ) {
            return new 
Oxymel();
        }
        
$oxymel = new WP_Export_Oxymel();
        foreach ( 
$metas as $meta ) {
            
$oxymel->tag'wp:commentmeta' )->contains
                
->tag'wp:meta_key'$meta->meta_key )
                ->
tag'wp:meta_value' )->contains->cdata$meta->meta_value )->end
            
->end;
        }
        return 
$oxymel;
    }
}
<?
php

class WP_Iterator_Exception extends Exception {
}
<?
php

class WP_Post_IDs_Iterator implements Iterator {
    private 
$limit 100;
    private 
$post_ids;
    private 
$ids_left;
    private 
$results = array();

    public function 
__construct$post_ids$limit null ) {
        
$this->db       $GLOBALS['wpdb'];
        
$this->post_ids $post_ids;
        
$this->ids_left $post_ids;
        if ( ! 
is_null$limit ) ) {
            
$this->limit $limit;
        }
    }

    public function 
current() {
        return 
$this->results$this->index_in_results ];
    }

    public function 
key() {
        return 
$this->global_index;
    }

    public function 
next() {
        
$this->index_in_results++;
        
$this->global_index++;
    }

    public function 
rewind() {
        
$this->results          = array();
        
$this->global_index     0;
        
$this->index_in_results 0;
        
$this->ids_left         $this->post_ids;
    }

    public function 
valid() {
        if ( isset( 
$this->results$this->index_in_results ] ) ) {
            return 
true;
        }
        if ( empty( 
$this->ids_left ) ) {
            return 
false;
        }
        
$has_posts $this->load_next_posts_from_db();
        if ( ! 
$has_posts ) {
            return 
false;
        }
        
$this->index_in_results 0;
        return 
true;
    }

    private function 
load_next_posts_from_db() {
        
$next_batch_post_ids array_splice$this->ids_left0$this->limit );
        
$in_post_ids_sql     _wp_export_build_IN_condition'ID'$next_batch_post_ids );
        
$this->results       $this->db->get_results"SELECT * FROM {$this->db->posts} WHERE {$in_post_ids_sql});
        if ( ! 
$this->results ) {
            if ( 
$this->db->last_error ) {
                throw new 
WP_Iterator_Exception"Database error: {$this->db->last_error});
            } else {
                return 
false;
            }
        }
        return 
true;
    }
}
<?
php

class WP_Export_Exception extends RuntimeException {
}
<?
php


class WP_Export_XML_Over_HTTP extends WP_Export_Base_Writer {
    private 
$file_name;

    public function 
__construct$formatter$file_name ) {
        
parent::__construct$formatter );
        
$this->file_name $file_name;
    }

    public function 
export() {
        try {
            
$export $this->get_export();
            
$this->send_headers();
            echo 
$export;
        } catch ( 
WP_Export_Exception $e ) {
            
// phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Possibly used by third party extension.
            
$message apply_filters'export_error_message'$e->getMessage() );
            
wp_die$message__'Export Error' ), [ 'back_link' => true ] );
        } catch ( 
WP_Export_Term_Exception $e ) {
            
do_action'export_term_orphaned'$this->formatter->export->missing_parents );
            
$message apply_filters'export_term_error_message'$e->getMessage() );
            
// phpcs:enable
            
wp_die$message__'Export Error' ), [ 'back_link' => true ] );
        }
    }

    protected function 
write$xml ) {
        
$this->result .= $xml;
    }

    protected function 
get_export() {
        
$this->result '';
        
parent::export();
        return 
$this->result;
    }

    protected function 
send_headers() {
        
header'Content-Description: File Transfer' );
        
header'Content-Disposition: attachment; filename=' $this->file_name );
        
header'Content-Type: text/xml; charset=' get_option'blog_charset' ), true );
    }
}
<?
php

// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- Renaming breaks Phar compat.
function wp_export$args = array() ) {
    
$defaults     = array(
        
'filters'     => array(),
        
'format'      => 'WP_Export_WXR_Formatter',
        
'writer'      => 'WP_Export_Returner',
        
'writer_args' => null,
    );
    
$args         wp_parse_args$args$defaults );
    
$export_query = new WP_Export_Query$args['filters'] );
    
$formatter    = new $args['format']( $export_query );
    
$writer       = new $args['writer']( $formatter$args['writer_args'] );
    try {
        return 
$writer->export();
    } catch ( 
WP_Export_Exception $e ) {
        return new 
WP_Error'wp-export-error'$e->getMessage() );
    }
}

// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound,WordPress.NamingConventions.ValidFunctionName.FunctionNameInvalid -- Renaming breaks Phar compat.
function _wp_export_build_IN_condition$column_name$values$format '%s' ) {
    global 
$wpdb;

    if ( ! 
is_array$values ) || empty( $values ) ) {
        return 
'';
    }
    
$formats implode', 'array_fill0count$values ), $format ) );
    
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare -- $column_name_sql escaped as ident, $formats hardcoded value.
    
return $wpdb->prepare"{$column_name} IN ({$formats})"$values );
}
<?
php

if ( ! class_exists'WP_CLI' ) ) {
    return;
}

$wpcli_export_autoloader dirname__FILE__ ) . '/vendor/autoload.php';
if ( 
file_exists$wpcli_export_autoloader ) ) {
    require_once 
$wpcli_export_autoloader;
}

WP_CLI::add_command'export''Export_Command' );
<?
php

use WP_CLI\Utils;

class 
Server_Command extends WP_CLI_Command {

    
/**
     * Launches PHP's built-in web server for a specific WordPress installation.
     *
     * Uses `php -S` to launch a web server serving the WordPress webroot.
     * <http://php.net/manual/en/features.commandline.webserver.php>
     *
     * Importantly, PHP's built-in web server doesn't support `.htaccess` files.
     * If this is a requirement, please use a more advanced web server.
     *
     * ## OPTIONS
     *
     * [--host=<host>]
     * : The hostname to bind the server to.
     * ---
     * default: localhost
     * ---
     *
     * [--port=<port>]
     * : The port number to bind the server to.
     * ---
     * default: 8080
     * ---
     *
     * [--docroot=<path>]
     * : The path to use as the document root. If the path global parameter is
     * set, the default value is it.
     *
     * [--config=<file>]
     * : Configure the server with a specific .ini file.
     *
     * ## EXAMPLES
     *
     *     # Make the instance available on any address (with port 8080)
     *     $ wp server --host=0.0.0.0
     *     PHP 5.6.9 Development Server started at Tue May 24 01:27:11 2016
     *     Listening on http://0.0.0.0:8080
     *     Document root is /
     *     Press Ctrl-C to quit.
     *
     *     # Run on port 80 (for multisite)
     *     $ wp server --host=localhost.localdomain --port=80
     *     PHP 5.6.9 Development Server started at Tue May 24 01:30:06 2016
     *     Listening on http://localhost1.localdomain1:80
     *     Document root is /
     *     Press Ctrl-C to quit.
     *
     *     # Configure the server with a specific .ini file
     *     $ wp server --config=development.ini
     *     PHP 7.0.9 Development Server started at Mon Aug 22 12:09:04 2016
     *     Listening on http://localhost:8080
     *     Document root is /
     *     Press Ctrl-C to quit.
     *
     * @when before_wp_load
     */
    
public function __invoke$_$assoc_args ) {
        
$defaults   = array(
            
'host'    => 'localhost',
            
'port'    => 8080,
            
'docroot' => ! is_nullWP_CLI::get_runner()->config['path'] ) ? WP_CLI::get_runner()->config['path'] : false,
            
'config'  => get_cfg_var'cfg_file_path' ),
        );
        
$assoc_args array_merge$defaults$assoc_args );

        
$docroot $assoc_args['docroot'];

        if ( ! 
$docroot ) {
            
$config_path WP_CLI::get_runner()->project_config_path;

            if ( ! 
$config_path ) {
                
$docroot ABSPATH;
            } else {
                
$docroot dirname$config_path );
            }
        }

        
// Get the path to the router file
        
$command_root Utils\phar_safe_pathdirname__DIR__ ) );
        
$router_path  $command_root '/router.php';
        if ( ! 
file_exists$router_path ) ) {
            
WP_CLI::error"Couldn't find router.php" );
        }
        
$cmd Utils\esc_cmd(
            
'%s -S %s -t %s -c %s %s',
            
WP_CLI::get_php_binary(),
            
$assoc_args['host'] . ':' $assoc_args['port'],
            
$docroot,
            
$assoc_args['config'],
            
Utils\extract_from_phar$router_path )
        );

        
$descriptors = array( STDINSTDOUTSTDERR );

        
// https://bugs.php.net/bug.php?id=60181
        
$options = array();
        if ( 
Utils\is_windows() ) {
            
$options['bypass_shell'] = true;
        }

        exit( 
proc_closeproc_open$cmd$descriptors$pipesnullnull$options ) ) );
    }
}
<?
php
// Used by `wp server` to route requests.

namespace WP_CLI\Router;

/**
 * This is a copy of WordPress's add_filter() function.
 *
 * We duplicate it because WordPress is not loaded yet.
 */
function add_filter$tag$function_to_add$priority 10$accepted_args ) {
    global 
$wp_filter$merged_filters;

    
$idx _wp_filter_build_unique_id$tag$function_to_add$priority );

    
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
    
$wp_filter$tag ][ $priority ][ $idx ] = array(
        
'function'      => $function_to_add,
        
'accepted_args' => $accepted_args,
    );
    unset( 
$merged_filters$tag ] );
    return 
true;
}

/**
 * This is a copy of WordPress's _wp_filter_build_unique_id() function.
 *
 * We duplicate it because WordPress is not loaded yet.
 */
function _wp_filter_build_unique_id$tag$function$priority ) {
    global 
$wp_filter;
    static 
$filter_id_count 0;

    if ( 
is_string$function ) ) {
        return 
$function;
    }

    if ( 
is_object$function ) ) {
        
// Closures are currently implemented as objects
        
$function = array( $function'' );
    } else {
        
$function = (array) $function;
    }

    if ( 
is_object$function[0] ) ) {
        
// Object Class Calling
        
if ( function_exists'spl_object_hash' ) ) {
            return 
spl_object_hash$function[0] ) . $function[1];
        } else {
            
$obj_idx get_class$function[0] ) . $function[1];
            if ( ! isset( 
$function[0]->wp_filter_id ) ) {
                if ( 
false === $priority ) {
                    return 
false;
                }
                
$obj_idx .= isset( $wp_filter$tag ][ $priority ] )
                    ? 
count( (array) $wp_filter$tag ][ $priority ] )
                    : 
$filter_id_count;

                
$function[0]->wp_filter_id $filter_id_count;
                ++
$filter_id_count;
            } else {
                
$obj_idx .= $function[0]->wp_filter_id;
            }

            return 
$obj_idx;
        }
    } elseif ( 
is_string$function[0] ) ) {
        
// Static Calling
        
return $function[0] . '::' $function[1];
    }
}

function 
_get_full_host$url ) {
    
// phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url
    
$parsed_url parse_url$url );

    
$host $parsed_url['host'];
    if ( isset( 
$parsed_url['port'] ) && 80 !== $parsed_url['port'] ) {
        
$host .= ':' $parsed_url['port'];
    }

    return 
$host;
}

// We need to trick WordPress into using the URL set by `wp server`, especially on multisite.
add_filter(
    
'option_home',
    function ( 
$url ) {
        
$GLOBALS['wpcli_server_original_url'] = $url;

        return 
'http://' $_SERVER['HTTP_HOST'];
    },
    
20
);

add_filter(
    
'option_siteurl',
    function ( 
$url ) {
        if ( ! isset( 
$GLOBALS['wpcli_server_original_url'] ) ) {
            
get_option'home' );  // trigger the option_home filter
        
}

        
$home_url_host _get_full_host$GLOBALS['wpcli_server_original_url'] );
        
$site_url_host _get_full_host$url );

        if ( 
$site_url_host === $home_url_host ) {
            
$url str_replace$site_url_host$_SERVER['HTTP_HOST'], $url );
        }

        return 
$url;
    },
    
20
);

$_SERVER['SERVER_ADDR'] = gethostbyname$_SERVER['SERVER_NAME'] );
$wpcli_server_root      $_SERVER['DOCUMENT_ROOT'];
// phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url
$wpcli_server_path '/' ltrimparse_urlurldecode$_SERVER['REQUEST_URI'] ) )['path'], '/' );

if ( 
file_exists$wpcli_server_root $wpcli_server_path ) ) {
    if ( 
is_dir$wpcli_server_root $wpcli_server_path ) && substr$wpcli_server_path, -) !== '/' ) {
        
header"Location: $wpcli_server_path/" );
        exit;
    }

    if ( 
strpos$wpcli_server_path'.php' ) !== false ) {
        
chdirdirname$wpcli_server_root $wpcli_server_path ) );
        require_once 
$wpcli_server_root $wpcli_server_path;
    } else {
        return 
false;
    }
} else {
    
chdir$wpcli_server_root );
    require_once 
'index.php';
}
<?
php

if ( ! class_exists'WP_CLI' ) ) {
    return;
}

$wpcli_server_autoloader dirname__FILE__ ) . '/vendor/autoload.php';
if ( 
file_exists$wpcli_server_autoloader ) ) {
    require_once 
$wpcli_server_autoloader;
}

WP_CLI::add_command(
    
'server',
    
'Server_Command',
    array(
        
'before_invoke' => function() {
            
$min_version '5.4';
            if ( 
version_comparePHP_VERSION$min_version'<' ) ) {
                
WP_CLI::error"The `wp server` command requires PHP {$min_version} or newer." );
            }
        },
    )
);
<?
php

namespace WP_CLI;

use 
WP_CLI;
use 
WP_CLI_Command;

/**
 * Base class for WP-CLI commands that deal with translations
 *
 * @package wp-cli
 */
abstract class CommandWithTranslation extends WP_CLI_Command {
    protected 
$obj_type;

    protected 
$obj_fields;

    
/**
     * Callback to sort array by a 'language' key.
     */
    
protected function sort_translations_callback$a$b ) {
        return 
strnatcasecmp$a['language'], $b['language'] );
    }

    
/**
     * Updates installed languages for the current object type.
     */
    
public function update$args$assoc_args ) {
        
$updates $this->get_translation_updates();

        if ( empty( 
$updates ) ) {
            
WP_CLI::success'Translations are up to date.' );

            return;
        }

        if ( empty( 
$args ) ) {
            
$args = array( null ); // Used for core.
        
}

        
$upgrader      'WP_CLI\\LanguagePackUpgrader';
        
$results       = array();
        
$num_to_update 0;

        foreach ( 
$args as $slug ) {
            
// Gets a list of all languages.
            
$all_languages $this->get_all_languages$slug );

            
$updates_per_type = array();

            
// Formats the updates list.
            
foreach ( $updates as $update ) {
                if ( 
null !== $slug && $update->slug !== $slug ) {
                    continue;
                }

                
$name 'WordPress'// Core.

                
if ( 'plugin' === $update->type ) {
                    
$plugins     get_plugins'/' $update->slug );
                    
$plugin_data array_shift$plugins );
                    
$name        $plugin_data['Name'];
                } elseif ( 
'theme' === $update->type ) {
                    
$theme_data wp_get_theme$update->slug );
                    
$name       $theme_data['Name'];
                }

                
// Gets the translation data.
                
$translation wp_list_filter$all_languages, array( 'language' => $update->language ) );
                
$translation = (object) reset$translation );

                
$update->Type    ucfirst$update->type );
                
$update->Name    $name;
                
$update->Version $update->version;

                if ( isset( 
$translation->english_name ) ) {
                    
$update->Language $translation->english_name;
                }

                if ( ! isset( 
$updates_per_type$update->type ] ) ) {
                    
$updates_per_type$update->type ] = array();
                }
                
$updates_per_type$update->type ][] = $update;
            }

            
$obj_type          rtrim$this->obj_type's' );
            
$available_updates = isset( $updates_per_type$obj_type ] ) ? $updates_per_type$obj_type ] : null;

            if ( ! 
is_array$available_updates ) ) {
                continue;
            }

            
$num_to_update += count$available_updates );

            if ( ! 
Utils\get_flag_value$assoc_args'dry-run' ) ) {
                
// Update translations.
                
foreach ( $available_updates as $update ) {
                    
WP_CLI::line"Updating '{$update->Language}' translation for {$update->Name} {$update->Version}..." );

                    
$result Utils\get_upgrader$upgrader )->upgrade$update );

                    
$results[] = $result;
                }
            }
        }

        
// Only preview which translations would be updated.
        
if ( Utils\get_flag_value$assoc_args'dry-run' ) ) {
            
$update_count count$updates );

            
WP_CLI::line(
                
sprintf(
                    
'Found %d translation %s that would be processed:',
                    
$update_count,
                    
WP_CLI\Utils\pluralize'update'$update_count )
                )
            );

            
Utils\format_items'table'$updates, array( 'Type''Name''Version''Language' ) );

            return;
        }

        
$num_updated countarray_filter$results ) );

        
$line sprintf"Updated $num_updated/$num_to_update %s."WP_CLI\Utils\pluralize'translation'$num_updated ) );

        if ( 
$num_to_update === $num_updated ) {
            
WP_CLI::success$line );
        } elseif ( 
$num_updated ) {
            
WP_CLI::warning$line );
        } else {
            
WP_CLI::error$line );
        }
    }

    
/**
     * Get all updates available for all translations.
     *
     * @see wp_get_translation_updates()
     *
     * @return array
     */
    
protected function get_translation_updates() {
        
$available $this->get_installed_languages();

        
$func = function() use ( $available ) {
            return 
$available;
        };

        switch ( 
$this->obj_type ) {
            case 
'plugins':
                
add_filter'plugins_update_check_locales'$func );

                
wp_clean_plugins_cache();
                
// Check for Plugin translation updates.
                
wp_update_plugins();

                
remove_filter'plugins_update_check_locales'$func );

                
$transient 'update_plugins';
                break;
            case 
'themes':
                
add_filter'themes_update_check_locales'$func );

                
wp_clean_themes_cache();
                
// Check for Theme translation updates.
                
wp_update_themes();

                
remove_filter'themes_update_check_locales'$func );

                
$transient 'update_themes';
                break;
            default:
                
delete_site_transient'update_core' );

                
// Check for Core translation updates.
                
wp_version_check();

                
$transient 'update_core';
                break;
        }

        
$updates   = array();
        
$transient get_site_transient$transient );

        if ( empty( 
$transient->translations ) ) {
            return 
$updates;
        }

        foreach ( 
$transient->translations as $translation ) {
            
$updates[] = (object) $translation;
        }

        return 
$updates;
    }

    
/**
     * Download a language pack.
     *
     * @see wp_download_language_pack()
     *
     * @param string $download Language code to download.
     * @param string $slug Plugin or theme slug. Not used for core.
     * @return string|\WP_Error Returns the language code if successfully downloaded, or a WP_Error object on failure.
     */
    
protected function download_language_pack$download$slug null ) {
        
$translations        $this->get_all_languages$slug );
        
$translation_to_load null;

        foreach ( 
$translations as $translation ) {
            if ( 
$translation['language'] === $download ) {
                
$translation_to_load $translation;
                break;
            }
        }

        if ( ! 
$translation_to_load ) {
            return new 
\WP_Error'not_found'"Language '{$download}' not available." );
        }
        
$translation = (object) $translation;

        
$translation->type rtrim$this->obj_type's' );

        
// Make sure caching in LanguagePackUpgrader works.
        
if ( ! isset( $translation->slug ) ) {
            
$translation->slug $slug;
        }

        
$upgrader 'WP_CLI\\LanguagePackUpgrader';
        
$result   Utils\get_upgrader$upgrader )->upgrade$translation, array( 'clear_update_cache' => false ) );

        if ( 
is_wp_error$result ) ) {
            return 
$result;
        }

        if ( ! 
$result ) {
            return new 
\WP_Error'not_installed'"Could not install language '{$download}'." );
        }

        return 
$translation->language;
    }

    
/**
     * Return a list of installed languages.
     *
     * @param string $slug Optional. Plugin or theme slug. Defaults to 'default' for core.
     *
     * @return array
     */
    
protected function get_installed_languages$slug 'default' ) {
        
$available   wp_get_installed_translations$this->obj_type );
        
$available   = ! empty( $available$slug ] ) ? array_keys$available$slug ] ) : array();
        
$available[] = 'en_US';

        return 
$available;
    }

    
/**
     * Return a list of all languages.
     *
     * @param string $slug Optional. Plugin or theme slug. Not used for core.
     *
     * @return array
     */
    
protected function get_all_languages$slug null ) {
        require_once 
ABSPATH '/wp-admin/includes/translation-install.php';
        require 
ABSPATH WPINC '/version.php'// Include an unmodified $wp_version

        
$args = array(
            
'version' => $wp_version,
        );

        if ( 
$slug ) {
            
$args['slug'] = $slug;

            if ( 
'plugins' === $this->obj_type ) {
                
$plugins     get_plugins'/' $slug );
                
$plugin_data array_shift$plugins );
                if ( isset( 
$plugin_data['Version'] ) ) {
                    
$args['version'] = $plugin_data['Version'];
                }
            } elseif ( 
'themes' === $this->obj_type ) {
                
$theme_data wp_get_theme$slug );
                if ( isset( 
$theme_data['Version'] ) ) {
                    
$args['version'] = $theme_data['Version'];
                }
            }
        }

        
$response translations_api$this->obj_type$args );

        if ( 
is_wp_error$response ) ) {
            
WP_CLI::error$response );
        }

        
$translations = ! empty( $response['translations'] ) ? $response['translations'] : array();

        
$en_us = array(
            
'language'     => 'en_US',
            
'english_name' => 'English (United States)',
            
'native_name'  => 'English (United States)',
            
'updated'      => '',
        );

        
$translations[] = $en_us;

        
uasort$translations, array( $this'sort_translations_callback' ) );

        return 
$translations;
    }

    
/**
     * Get Formatter object based on supplied parameters.
     *
     * @param array $assoc_args Parameters passed to command. Determines formatting.
     * @return Formatter
     */
    
protected function get_formatter( &$assoc_args ) {
        return new 
Formatter$assoc_args$this->obj_fields$this->obj_type );
    }
}
<?
php

namespace WP_CLI;

use 
WP_CLI;

/**
 * A Language Pack Upgrader class that caches the download, and uses cached if available
 *
 * @package wp-cli
 */
class LanguagePackUpgrader extends \Language_Pack_Upgrader {
    
/**
     * Initialize the upgrade strings.
     *
     * Makes sure that the strings are always in English.
     */
    
public function upgrade_strings() {
        
$switched_locale function_exists'switch_to_locale' ) && switch_to_locale'en_US' );

        
parent::upgrade_strings();

        if ( 
$switched_locale ) {
            
restore_previous_locale();
        }
    }

    
/**
     * Initialize the generic strings.
     *
     * Makes sure that the strings are always in English.
     */
    
public function generic_strings() {
        
$switched_locale function_exists'switch_to_locale' ) && switch_to_locale'en_US' );

        
parent::generic_strings();

        if ( 
$switched_locale ) {
            
restore_previous_locale();
        }
    }

    
/**
     * Caches the download, and uses cached if available.
     *
     * @param string $package          The URI of the package. If this is the full path to an
     *                                 existing local file, it will be returned untouched.
     * @param bool   $check_signatures Whether to validate file signatures. Default false.
     * @param array  $hook_extra       Extra arguments to pass to the filter hooks. Default empty array.
     * @return string|\WP_Error The full path to the downloaded package file, or a WP_Error object.
     */
    
public function download_package$package$check_signatures false$hook_extra = [] ) {

        
/**
         * Filter whether to return the package.
         *
         * @since 3.7.0
         * @since 5.5.0 Added the `$hook_extra` parameter.
         *
         * @param bool          $reply      Whether to bail without returning the package. Default is false.
         * @param string        $package    The package file name.
         * @param \WP_Upgrader  $this       The WP_Upgrader instance.
         * @param array         $hook_extra Extra arguments passed to hooked filters.
         *
         * @phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Using WP native hook.
         */
        
$reply apply_filters'upgrader_pre_download'false$package$this$hook_extra );
        
// phpcs:enable

        
if ( false !== $reply ) {
            return 
$reply;
        }

        
// Check if package is a local or remote file. Bail if it's local.
        
if ( ! preg_match'!^(http|https|ftp)://!i'$package ) && file_exists$package ) ) {
            return 
$package;
        }

        if ( empty( 
$package ) ) {
            return new 
\WP_Error'no_package'$this->strings['no_package'] );
        }

        
$language_update $this->skin->language_update;
        
$type            $language_update->type;
        
$slug            = empty( $language_update->slug ) ? 'default' $language_update->slug;
        
$updated         strtotime$language_update->updated );
        
$version         $language_update->version;
        
$language        $language_update->language;
        
$ext             pathinfo$packagePATHINFO_EXTENSION );

        
$temp \WP_CLI\Utils\get_temp_dir() . uniqid'wp_' ) . '.' $ext;

        
$cache      WP_CLI::get_cache();
        
$cache_key  "translation/{$type}-{$slug}-{$version}-{$language}-{$updated}.{$ext}";
        
$cache_file $cache->has$cache_key );

        if ( 
$cache_file ) {
            
WP_CLI::log"Using cached file '$cache_file'..." );
            
copy$cache_file$temp );
            return 
$temp;
        }

        
$this->skin->feedback'downloading_package'$package );

        
$temp download_url$package600 ); // 10 minutes ought to be enough for everybody.

        
if ( is_wp_error$temp ) ) {
            return 
$temp;
        }

        
$cache->import$cache_key$temp );

        return 
$temp;
    }
}
<?
php

/**
 * Installs, activates, and manages core language packs.
 *
 * ## EXAMPLES
 *
 *     # Install the Dutch core language pack.
 *     $ wp language core install nl_NL
 *     Success: Language installed.
 *
 *     # Activate the Dutch core language pack.
 *     $ wp language core activate nl_NL
 *     Success: Language activated.
 *
 *     # Uninstall the Dutch core language pack.
 *     $ wp language core uninstall nl_NL
 *     Success: Language uninstalled.
 *
 *     # List installed core language packages.
 *     $ wp language core list --status=installed
 *     +----------+--------------+-------------+-----------+-----------+---------------------+
 *     | language | english_name | native_name | status    | update    | updated             |
 *     +----------+--------------+-------------+-----------+-----------+---------------------+
 *     | nl_NL    | Dutch        | Nederlands  | installed | available | 2016-05-13 08:12:50 |
 *     +----------+--------------+-------------+-----------+-----------+---------------------+
 */
class Core_Language_Command extends WP_CLI\CommandWithTranslation {
    protected 
$obj_type 'core';

    protected 
$obj_fields = array(
        
'language',
        
'english_name',
        
'native_name',
        
'status',
        
'update',
        
'updated',
    );

    
/**
     * Lists all available languages.
     *
     * ## OPTIONS
     *
     * [--field=<field>]
     * : Display the value of a single field
     *
     * [--<field>=<value>]
     * : Filter results by key=value pairs.
     *
     * [--fields=<fields>]
     * : Limit the output to specific fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     * ---
     *
     * ## AVAILABLE FIELDS
     *
     * These fields will be displayed by default for each translation:
     *
     * * language
     * * english_name
     * * native_name
     * * status
     * * update
     * * updated
     *
     * ## EXAMPLES
     *
     *     # List language,english_name,status fields of available languages.
     *     $ wp language core list --fields=language,english_name,status
     *     +----------------+-------------------------+-------------+
     *     | language       | english_name            | status      |
     *     +----------------+-------------------------+-------------+
     *     | ar             | Arabic                  | uninstalled |
     *     | ary            | Moroccan Arabic         | uninstalled |
     *     | az             | Azerbaijani             | uninstalled |
     *
     * @subcommand list
     */
    
public function list_$args$assoc_args ) {
        
$translations $this->get_all_languages();
        
$available    $this->get_installed_languages();
        
$updates      $this->get_translation_updates();

        
$current_locale get_locale();

        
$translations array_map(
            function( 
$translation ) use ( $available$current_locale$updates ) {
                
$translation['status'] = 'uninstalled';
                if ( 
in_array$translation['language'], $availabletrue ) ) {
                    
$translation['status'] = 'installed';
                }

                if ( 
$current_locale === $translation['language'] ) {
                    
$translation['status'] = 'active';
                }

                
$update wp_list_filter$updates, array( 'language' => $translation['language'] ) );
                if ( 
$update ) {
                    
$translation['update'] = 'available';
                } else {
                    
$translation['update'] = 'none';
                }

                return 
$translation;
            },
            
$translations
        
);

        foreach ( 
$translations as $key => $translation ) {
            foreach ( 
array_keys$translation ) as $field ) {
                if ( isset( 
$assoc_args$field ] ) && $assoc_args$field ] !== $translation$field ] ) {
                    unset( 
$translations$key ] );
                }
            }
        }

        
$formatter $this->get_formatter$assoc_args );
        
$formatter->display_items$translations );
    }

    
/**
     * Checks if a given language is installed.
     *
     * Returns exit code 0 when installed, 1 when uninstalled.
     *
     * ## OPTIONS
     *
     * <language>
     * : The language code to check.
     *
     * ## EXAMPLES
     *
     *     # Check whether the German language is installed; exit status 0 if installed, otherwise 1.
     *     $ wp language core is-installed de_DE
     *     $ echo $?
     *     1
     *
     * @subcommand is-installed
     */
    
public function is_installed$args$assoc_args = array() ) {
        list( 
$language_code ) = $args;
        
$available             $this->get_installed_languages();
        if ( 
in_array$language_code$availabletrue ) ) {
            
\WP_CLI::halt);
        } else {
            
\WP_CLI::halt);
        }
    }

    
/**
     * Installs a given language.
     *
     * Downloads the language pack from WordPress.org.
     *
     * ## OPTIONS
     *
     * <language>...
     * : Language code to install.
     *
     * [--activate]
     * : If set, the language will be activated immediately after install.
     *
     * ## EXAMPLES
     *
     *     # Install the Japanese language.
     *     $ wp language core install ja
     *     Downloading translation from https://downloads.wordpress.org/translation/core/4.9.8/ja.zip...
     *     Unpacking the update...
     *     Installing the latest version...
     *     Translation updated successfully.
     *     Language 'ja' installed.
     *     Success: Installed 1 of 1 languages.
     *
     * @subcommand install
     */
    
public function install$args$assoc_args ) {
        
$language_codes = (array) $args;
        
$count          count$language_codes );

        if ( 
$count && in_arraytrue$assoc_argstrue ) ) {
            
WP_CLI::error'Only a single language can be active.' );
        }

        
$available $this->get_installed_languages();

        
$successes 0;
        
$errors    0;
        
$skips     0;
        foreach ( 
$language_codes as $language_code ) {

            if ( 
in_array$language_code$availabletrue ) ) {
                
\WP_CLI::log"Language '{$language_code}' already installed." );
                
$skips++;
            } else {
                
$response $this->download_language_pack$language_code );

                if ( 
is_wp_error$response ) ) {
                    
\WP_CLI::warning$response );
                    
\WP_CLI::log"Language '{$language_code}' not installed." );

                    
// Skip if translation is not yet available.
                    
if ( 'not_found' === $response->get_error_code() ) {
                        
$skips++;
                    } else {
                        
$errors++;
                    }
                } else {
                    
\WP_CLI::log"Language '{$language_code}' installed." );
                    
$successes++;
                }
            }

            if ( 
WP_CLI\Utils\get_flag_value$assoc_args'activate' ) ) {
                
$this->activate_language$language_code );
            }
        }

        
\WP_CLI\Utils\report_batch_operation_results'language''install'$count$successes$errors$skips );
    }

    
/**
     * Uninstalls a given language.
     *
     * ## OPTIONS
     *
     * <language>...
     * : Language code to uninstall.
     *
     * ## EXAMPLES
     *
     *     $ wp language core uninstall ja
     *     Success: Language uninstalled.
     *
     * @subcommand uninstall
     * @throws WP_CLI\ExitException
     */
    
public function uninstall$args$assoc_args ) {
        global 
$wp_filesystem;

        
$language_codes = (array) $args;

        
$available $this->get_installed_languages();

        foreach ( 
$language_codes as $language_code ) {

            if ( ! 
in_array$language_code$availabletrue ) ) {
                
WP_CLI::error'Language not installed.' );
            }

            
$dir   'core' === $this->obj_type '' "/$this->obj_type";
            
$files scandirWP_LANG_DIR $dir );
            if ( ! 
$files ) {
                
WP_CLI::error'No files found in language directory.' );
            }

            
$current_locale get_locale();
            if ( 
$language_code === $current_locale ) {
                
WP_CLI::warning"The '{$language_code}' language is active." );
                exit;
            }

            
// As of WP 4.0, no API for deleting a language pack
            
WP_Filesystem();
            
$deleted false;
            foreach ( 
$files as $file ) {
                if ( 
'.' === $file[0] || is_dir$file ) ) {
                    continue;
                }
                
$extension_length strlen$language_code ) + 4;
                
$ending           substr$file, -$extension_length );
                if ( ! 
in_array$file, array( $language_code '.po'$language_code '.mo' ), true ) && ! in_array$ending, array( '-' $language_code '.po''-' $language_code '.mo' ), true ) ) {
                    continue;
                }

                
/** @var WP_Filesystem_Base $wp_filesystem */
                
$deleted $wp_filesystem->deleteWP_LANG_DIR $dir '/' $file );
            }

            if ( 
$deleted ) {
                
WP_CLI::success'Language uninstalled.' );
            } else {
                
WP_CLI::error"Couldn't uninstall language." );
            }
        }
    }

    
/**
     * Updates installed languages for core.
     *
     * ## OPTIONS
     *
     * [--dry-run]
     * : Preview which translations would be updated.
     *
     * ## EXAMPLES
     *
     *     $ wp language core update
     *     Updating 'Japanese' translation for WordPress 4.9.2...
     *     Downloading translation from https://downloads.wordpress.org/translation/core/4.9.2/ja.zip...
     *     Translation updated successfully.
     *     Success: Updated 1/1 translation.
     *
     * @subcommand update
     */
    
public function update$args$assoc_args ) { // phpcs:ignore Generic.CodeAnalysis.UselessOverridingMethod.Found -- Overruling the documentation, so not useless ;-).
        
parent::update$args$assoc_args );
    }

    
/**
     * Activates a given language.
     *
     * ## OPTIONS
     *
     * <language>
     * : Language code to activate.
     *
     * ## EXAMPLES
     *
     *     $ wp language core activate ja
     *     Success: Language activated.
     *
     * @subcommand activate
     * @throws WP_CLI\ExitException
     */
    
public function activate$args$assoc_args ) {
        
\WP_CLI::warning'This command is deprecated. use wp site switch-language instead' );

        list( 
$language_code ) = $args;

        
$this->activate_language$language_code );
    }

    private function 
activate_language$language_code ) {
        
$available $this->get_installed_languages();

        if ( ! 
in_array$language_code$availabletrue ) ) {
            
WP_CLI::error'Language not installed.' );
        }

        if ( 
'en_US' === $language_code ) {
            
$language_code '';
        }

        if ( 
get_locale() === $language_code ) {
            
WP_CLI::warning"Language '{$language_code}' already active." );

            return;
        }

        
update_option'WPLANG'$language_code );

        
WP_CLI::success'Language activated.' );
    }
}
<?
php

/**
 * Installs, activates, and manages plugin language packs.
 *
 * ## EXAMPLES
 *
 *     # Install the Dutch plugin language pack.
 *     $ wp language plugin install hello-dolly nl_NL
 *     Success: Language installed.
 *
 *     # Uninstall the Dutch plugin language pack.
 *     $ wp language plugin uninstall hello-dolly nl_NL
 *     Success: Language uninstalled.
 *
 *     # List installed plugin language packages.
 *     $ wp language plugin list --status=installed
 *     +----------+--------------+-------------+-----------+-----------+---------------------+
 *     | language | english_name | native_name | status    | update    | updated             |
 *     +----------+--------------+-------------+-----------+-----------+---------------------+
 *     | nl_NL    | Dutch        | Nederlands  | installed | available | 2016-05-13 08:12:50 |
 *     +----------+--------------+-------------+-----------+-----------+---------------------+
 */
class Plugin_Language_Command extends WP_CLI\CommandWithTranslation {
    protected 
$obj_type 'plugins';

    protected 
$obj_fields = array(
        
'plugin',
        
'language',
        
'english_name',
        
'native_name',
        
'status',
        
'update',
        
'updated',
    );

    
/**
     * Lists all available languages for one or more plugins.
     *
     * ## OPTIONS
     *
     * [<plugin>...]
     * : One or more plugins to list languages for.
     *
     * [--all]
     * : If set, available languages for all plugins will be listed.
     *
     * [--field=<field>]
     * : Display the value of a single field.
     *
     * [--<field>=<value>]
     * : Filter results by key=value pairs.
     *
     * [--fields=<fields>]
     * : Limit the output to specific fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     * ---
     *
     * ## AVAILABLE FIELDS
     *
     * These fields will be displayed by default for each translation:
     *
     * * plugin
     * * language
     * * english_name
     * * native_name
     * * status
     * * update
     * * updated
     *
     * ## EXAMPLES
     *
     *     # List language,english_name,status fields of available languages.
     *     $ wp language plugin list --fields=language,english_name,status
     *     +----------------+-------------------------+-------------+
     *     | language       | english_name            | status      |
     *     +----------------+-------------------------+-------------+
     *     | ar             | Arabic                  | uninstalled |
     *     | ary            | Moroccan Arabic         | uninstalled |
     *     | az             | Azerbaijani             | uninstalled |
     *
     * @subcommand list
     */
    
public function list_$args$assoc_args ) {
        
$all \WP_CLI\Utils\get_flag_value$assoc_args'all'false );

        if ( ! 
$all && empty( $args ) ) {
            
WP_CLI::error'Please specify one or more plugins, or use --all.' );
        }

        if ( 
$all ) {
            
$args array_map'\WP_CLI\Utils\get_plugin_name'array_keys$this->get_all_plugins() ) );

            if ( empty( 
$args ) ) {
                
WP_CLI::success'No plugins installed.' );
                return;
            }
        }

        
$updates        $this->get_translation_updates();
        
$current_locale get_locale();

        
$translations = array();

        foreach ( 
$args as $plugin ) {
            
$installed_translations $this->get_installed_languages$plugin );
            
$available_translations $this->get_all_languages$plugin );

            foreach ( 
$available_translations as $translation ) {
                
$translation['plugin'] = $plugin;
                
$translation['status'] = in_array$translation['language'], $installed_translationstrue ) ? 'installed' 'uninstalled';

                if ( 
$current_locale === $translation['language'] ) {
                    
$translation['status'] = 'active';
                }

                
$filter_args = array(
                    
'language' => $translation['language'],
                    
'type'     => 'plugin',
                    
'slug'     => $plugin,
                );
                
$update      wp_list_filter$updates$filter_args );

                
$translation['update'] = $update 'available' 'none';

                
// Support features like --status=active.
                
foreach ( array_keys$translation ) as $field ) {
                    if ( isset( 
$assoc_args$field ] ) && $assoc_args$field ] !== $translation$field ] ) {
                        continue 
2;
                    }
                }

                
$translations[] = $translation;
            }
        }

        
$formatter $this->get_formatter$assoc_args );
        
$formatter->display_items$translations );
    }

    
/**
     * Checks if a given language is installed.
     *
     * Returns exit code 0 when installed, 1 when uninstalled.
     *
     * ## OPTIONS
     *
     * <plugin>
     * : Plugin to check for.
     *
     * <language>...
     * : The language code to check.
     *
     * ## EXAMPLES
     *
     *     # Check whether the German language is installed for Akismet; exit status 0 if installed, otherwise 1.
     *     $ wp language plugin is-installed akismet de_DE
     *     $ echo $?
     *     1
     *
     * @subcommand is-installed
     */
    
public function is_installed$args$assoc_args = array() ) {
        
$plugin         array_shift$args );
        
$language_codes = (array) $args;

        
$available $this->get_installed_languages$plugin );

        foreach ( 
$language_codes as $language_code ) {
            if ( ! 
in_array$language_code$availabletrue ) ) {
                
\WP_CLI::halt);
            }
        }

        
\WP_CLI::halt);
    }

    
/**
     * Installs a given language for a plugin.
     *
     * Downloads the language pack from WordPress.org.
     *
     * ## OPTIONS
     *
     * [<plugin>]
     * : Plugin to install language for.
     *
     * [--all]
     * : If set, languages for all plugins will be installed.
     *
     * <language>...
     * : Language code to install.
     *
     * [--format=<format>]
     * : Render output in a particular format. Used when installing languages for all plugins.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - summary
     * ---
     *
     * ## EXAMPLES
     *
     *     # Install the Japanese language for Akismet.
     *     $ wp language plugin install akismet ja
     *     Downloading translation from https://downloads.wordpress.org/translation/plugin/akismet/4.0.3/ja.zip...
     *     Unpacking the update...
     *     Installing the latest version...
     *     Translation updated successfully.
     *     Language 'ja' installed.
     *     Success: Installed 1 of 1 languages.
     *
     * @subcommand install
     */
    
public function install$args$assoc_args ) {
        
$all \WP_CLI\Utils\get_flag_value$assoc_args'all'false );

        if ( ! 
$all && count$args ) < ) {
            
\WP_CLI::error'Please specify a plugin, or use --all.' );
        }

        if ( 
$all ) {
            
$this->install_many$args$assoc_args );
        } else {
            
$this->install_one$args$assoc_args );
        }
    }

    
/**
     * Installs translations for a plugin.
     *
     * @param array $args       Runtime arguments.
     * @param array $assoc_args Runtime arguments.
     */
    
private function install_one$args$assoc_args ) {
        
$plugin         array_shift$args );
        
$language_codes = (array) $args;
        
$count          count$language_codes );

        
$available $this->get_installed_languages$plugin );

        
$successes 0;
        
$errors    0;
        
$skips     0;
        foreach ( 
$language_codes as $language_code ) {

            if ( 
in_array$language_code$availabletrue ) ) {
                
\WP_CLI::log"Language '{$language_code}' already installed." );
                
$skips++;
            } else {
                
$response $this->download_language_pack$language_code$plugin );

                if ( 
is_wp_error$response ) ) {
                    
\WP_CLI::warning$response );
                    
\WP_CLI::log"Language '{$language_code}' not installed." );

                    
// Skip if translation is not yet available.
                    
if ( 'not_found' === $response->get_error_code() ) {
                        
$skips++;
                    } else {
                        
$errors++;
                    }
                } else {
                    
\WP_CLI::log"Language '{$language_code}' installed." );
                    
$successes++;
                }
            }
        }

        
\WP_CLI\Utils\report_batch_operation_results'language''install'$count$successes$errors$skips );
    }

    
/**
     * Installs translations for all installed plugins.
     *
     * @param array $args       Runtime arguments.
     * @param array $assoc_args Runtime arguments.
     */
    
private function install_many$args$assoc_args ) {
        
$language_codes = (array) $args;
        
$plugins        $this->get_all_plugins();

        if ( empty( 
$assoc_args['format'] ) ) {
            
$assoc_args['format'] = 'table';
        }

        if ( 
in_array$assoc_args['format'], array( 'json''csv' ), true ) ) {
            
$logger = new \WP_CLI\Loggers\Quiet();
            
\WP_CLI::set_logger$logger );
        }

        if ( empty( 
$plugins ) ) {
            
\WP_CLI::success'No plugins installed.' );
            return;
        }

        
$count count$plugins ) * count$language_codes );

        
$results = array();

        
$successes 0;
        
$errors    0;
        
$skips     0;
        foreach ( 
$plugins as $plugin_path => $plugin_details ) {
            
$plugin_name \WP_CLI\Utils\get_plugin_name$plugin_path );

            
$available $this->get_installed_languages$plugin_name );

            foreach ( 
$language_codes as $language_code ) {
                
$result = [
                    
'name'   => $plugin_name,
                    
'locale' => $language_code,
                ];

                if ( 
in_array$language_code$availabletrue ) ) {
                    
\WP_CLI::log"Language '{$language_code}' for '{$plugin_details['Name']}' already installed." );
                    
$result['status'] = 'already installed';
                    
$skips++;
                } else {
                    
$response $this->download_language_pack$language_code$plugin_name );

                    if ( 
is_wp_error$response ) ) {
                        
\WP_CLI::warning$response );
                        
\WP_CLI::log"Language '{$language_code}' for '{$plugin_details['Name']}' not installed." );

                        if ( 
'not_found' === $response->get_error_code() ) {
                            
$result['status'] = 'not available';
                            
$skips++;
                        } else {
                            
$result['status'] = 'not installed';
                            
$errors++;
                        }
                    } else {
                        
\WP_CLI::log"Language '{$language_code}' for '{$plugin_details['Name']}' installed." );
                        
$result['status'] = 'installed';
                        
$successes++;
                    }
                }

                
$results[] = (object) $result;
            }
        }

        if ( 
'summary' !== $assoc_args['format'] ) {
            
\WP_CLI\Utils\format_items$assoc_args['format'], $results, array( 'name''locale''status' ) );
        }

        
\WP_CLI\Utils\report_batch_operation_results'language''install'$count$successes$errors$skips );
    }

    
/**
     * Uninstalls a given language for a plugin.
     *
     * ## OPTIONS
     *
     * <plugin>
     * : Plugin to uninstall language for.
     *
     * <language>...
     * : Language code to uninstall.
     *
     * ## EXAMPLES
     *
     *     $ wp language plugin uninstall hello-dolly ja
     *     Success: Language uninstalled.
     *
     * @subcommand uninstall
     */
    
public function uninstall$args$assoc_args ) {
        
/** @var WP_Filesystem_Base $wp_filesystem */
        
global $wp_filesystem;

        
$plugin         array_shift$args );
        
$language_codes = (array) $args;
        
$current_locale get_locale();

        
$dir   WP_LANG_DIR "/$this->obj_type";
        
$files scandir$dir );

        if ( ! 
$files ) {
            
\WP_CLI::error'No files found in language directory.' );
        }

        
// As of WP 4.0, no API for deleting a language pack
        
WP_Filesystem();
        
$available $this->get_installed_languages$plugin );

        foreach ( 
$language_codes as $language_code ) {
            if ( ! 
in_array$language_code$availabletrue ) ) {
                
\WP_CLI::error'Language not installed.' );
            }

            if ( 
$language_code === $current_locale ) {
                
\WP_CLI::warning"The '{$language_code}' language is active." );
                exit;
            }

            if ( 
$wp_filesystem->delete"{$dir}/{$plugin}-{$language_code}.po" ) && $wp_filesystem->delete"{$dir}/{$plugin}-{$language_code}.mo" ) ) {
                
\WP_CLI::success'Language uninstalled.' );
            } else {
                
\WP_CLI::error"Couldn't uninstall language." );
            }
        }
    }

    
/**
     * Updates installed languages for one or more plugins.
     *
     * ## OPTIONS
     *
     * [<plugin>...]
     * : One or more plugins to update languages for.
     *
     * [--all]
     * : If set, languages for all plugins will be updated.
     *
     * [--dry-run]
     * : Preview which translations would be updated.
     *
     * ## EXAMPLES
     *
     *     $ wp language plugin update --all
     *     Updating 'Japanese' translation for Akismet 3.1.11...
     *     Downloading translation from https://downloads.wordpress.org/translation/plugin/akismet/3.1.11/ja.zip...
     *     Translation updated successfully.
     *     Success: Updated 1/1 translation.
     *
     * @subcommand update
     */
    
public function update$args$assoc_args ) {
        
$all \WP_CLI\Utils\get_flag_value$assoc_args'all'false );

        if ( ! 
$all && empty( $args ) ) {
            
WP_CLI::error'Please specify one or more plugins, or use --all.' );
        }

        if ( 
$all ) {
            
$args array_map'\WP_CLI\Utils\get_plugin_name'array_keys$this->get_all_plugins() ) );
            if ( empty( 
$args ) ) {
                
WP_CLI::success'No plugins installed.' );

                return;
            }
        }

        
parent::update$args$assoc_args );
    }

    
/**
     * Gets all available plugins.
     *
     * Uses the same filter core uses in plugins.php to determine which plugins
     * should be available to manage through the WP_Plugins_List_Table class.
     *
     * @return array
     */
    
private function get_all_plugins() {
        
// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Using WP native hook.
        
return apply_filters'all_plugins'get_plugins() );
    }
}
<?
php

class Site_Switch_Language_Command extends WP_CLI\CommandWithTranslation {
    protected 
$obj_type 'core';

    
/**
     * Activates a given language.
     *
     * ## OPTIONS
     *
     * <language>
     * : Language code to activate.
     *
     * ## EXAMPLES
     *
     *     $ wp site switch-language ja
     *     Success: Language activated.
     *
     * @throws WP_CLI\ExitException
     */
    
public function __invoke$args$assoc_args ) {
        list( 
$language_code ) = $args;

        
$available $this->get_installed_languages();

        if ( ! 
in_array$language_code$availabletrue ) ) {
            
WP_CLI::error'Language not installed.' );
        }

        if ( 
'en_US' === $language_code ) {
            
$language_code '';
        }

        if ( 
get_locale() === $language_code ) {
            
WP_CLI::warning"Language '{$language_code}' already active." );

            return;
        }

        
update_option'WPLANG'$language_code );

        
WP_CLI::success'Language activated.' );
    }
}
<?
php

/**
 * Installs, activates, and manages theme language packs.
 *
 * ## EXAMPLES
 *
 *     # Install the Dutch theme language pack.
 *     $ wp language theme install twentyten nl_NL
 *     Success: Language installed.
 *
 *     # Uninstall the Dutch theme language pack.
 *     $ wp language theme uninstall twentyten nl_NL
 *     Success: Language uninstalled.
 *
 *     # List installed theme language packages.
 *     $ wp language theme list --status=installed
 *     +----------+--------------+-------------+-----------+-----------+---------------------+
 *     | language | english_name | native_name | status    | update    | updated             |
 *     +----------+--------------+-------------+-----------+-----------+---------------------+
 *     | nl_NL    | Dutch        | Nederlands  | installed | available | 2016-05-13 08:12:50 |
 *     +----------+--------------+-------------+-----------+-----------+---------------------+
 */
class Theme_Language_Command extends WP_CLI\CommandWithTranslation {
    protected 
$obj_type 'themes';

    protected 
$obj_fields = array(
        
'theme',
        
'language',
        
'english_name',
        
'native_name',
        
'status',
        
'update',
        
'updated',
    );

    
/**
     * Lists all available languages for one or more themes.
     *
     * ## OPTIONS
     *
     * [<theme>...]
     * : One or more themes to list languages for.
     *
     * [--all]
     * : If set, available languages for all themes will be listed.
     *
     * [--field=<field>]
     * : Display the value of a single field.
     *
     * [--<field>=<value>]
     * : Filter results by key=value pairs.
     *
     * [--fields=<fields>]
     * : Limit the output to specific fields.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     * ---
     *
     * ## AVAILABLE FIELDS
     *
     * These fields will be displayed by default for each translation:
     *
     * * theme
     * * language
     * * english_name
     * * native_name
     * * status
     * * update
     * * updated
     *
     * ## EXAMPLES
     *
     *     # List language,english_name,status fields of available languages.
     *     $ wp language theme list --fields=language,english_name,status
     *     +----------------+-------------------------+-------------+
     *     | language       | english_name            | status      |
     *     +----------------+-------------------------+-------------+
     *     | ar             | Arabic                  | uninstalled |
     *     | ary            | Moroccan Arabic         | uninstalled |
     *     | az             | Azerbaijani             | uninstalled |
     *
     * @subcommand list
     */
    
public function list_$args$assoc_args ) {
        
$all \WP_CLI\Utils\get_flag_value$assoc_args'all'false );

        if ( ! 
$all && empty( $args ) ) {
            
WP_CLI::error'Please specify one or more themes, or use --all.' );
        }

        if ( 
$all ) {
            
$args array_map(
                function( 
$file ) {
                    return 
\WP_CLI\Utils\get_theme_name$file );
                },
                
array_keyswp_get_themes() )
            );

            if ( empty( 
$args ) ) {
                
WP_CLI::success'No themes installed.' );
                return;
            }
        }

        
$updates        $this->get_translation_updates();
        
$current_locale get_locale();

        
$translations = array();

        foreach ( 
$args as $theme ) {
            
$installed_translations $this->get_installed_languages$theme );
            
$available_translations $this->get_all_languages$theme );

            foreach ( 
$available_translations as $translation ) {
                
$translation['theme']  = $theme;
                
$translation['status'] = in_array$translation['language'], $installed_translationstrue ) ? 'installed' 'uninstalled';

                if ( 
$current_locale === $translation['language'] ) {
                    
$translation['status'] = 'active';
                }

                
$filter_args = array(
                    
'language' => $translation['language'],
                    
'type'     => 'theme',
                    
'slug'     => $theme,
                );
                
$update      wp_list_filter$updates$filter_args );

                
$translation['update'] = $update 'available' 'none';

                
// Support features like --status=active.
                
foreach ( array_keys$translation ) as $field ) {
                    if ( isset( 
$assoc_args$field ] ) && $assoc_args$field ] !== $translation$field ] ) {
                        continue 
2;
                    }
                }

                
$translations[] = $translation;
            }
        }

        
$formatter $this->get_formatter$assoc_args );
        
$formatter->display_items$translations );
    }

    
/**
     * Checks if a given language is installed.
     *
     * Returns exit code 0 when installed, 1 when uninstalled.
     *
     * ## OPTIONS
     *
     * <theme>
     * : Theme to check for.
     *
     * <language>...
     * : The language code to check.
     *
     * ## EXAMPLES
     *
     *     # Check whether the German language is installed for Twenty Seventeen; exit status 0 if installed, otherwise 1.
     *     $ wp language theme is-installed twentyseventeen de_DE
     *     $ echo $?
     *     1
     *
     * @subcommand is-installed
     */
    
public function is_installed$args$assoc_args = array() ) {
        
$theme          array_shift$args );
        
$language_codes = (array) $args;

        
$available $this->get_installed_languages$theme );

        foreach ( 
$language_codes as $language_code ) {
            if ( ! 
in_array$language_code$availabletrue ) ) {
                
\WP_CLI::halt);
            }
        }

        
\WP_CLI::halt);
    }

    
/**
     * Installs a given language for a theme.
     *
     * Downloads the language pack from WordPress.org.
     *
     * ## OPTIONS
     *
     * [<theme>]
     * : Theme to install language for.
     *
     * [--all]
     * : If set, languages for all themes will be installed.
     *
     * <language>...
     * : Language code to install.
     *
     * [--format=<format>]
     * : Render output in a particular format. Used when installing languages for all themes.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - summary
     * ---
     *
     * ## EXAMPLES
     *
     *     # Install the Japanese language for Twenty Seventeen.
     *     $ wp language theme install twentyseventeen ja
     *     Downloading translation from https://downloads.wordpress.org/translation/theme/twentyseventeen/1.3/ja.zip...
     *     Unpacking the update...
     *     Installing the latest version...
     *     Translation updated successfully.
     *     Language 'ja' installed.
     *     Success: Installed 1 of 1 languages.
     *
     * @subcommand install
     */
    
public function install$args$assoc_args ) {
        
$all \WP_CLI\Utils\get_flag_value$assoc_args'all'false );

        if ( ! 
$all && count$args ) < ) {
            
\WP_CLI::error'Please specify a theme, or use --all.' );
        }

        if ( 
$all ) {
            
$this->install_many$args$assoc_args );
        } else {
            
$this->install_one$args$assoc_args );
        }
    }

    
/**
     * Installs translations for a theme.
     *
     * @param array $args       Runtime arguments.
     * @param array $assoc_args Runtime arguments.
     */
    
private function install_one$args$assoc_args ) {
        
$theme          array_shift$args );
        
$language_codes = (array) $args;
        
$count          count$language_codes );

        
$available $this->get_installed_languages$theme );

        
$successes 0;
        
$errors    0;
        
$skips     0;
        foreach ( 
$language_codes as $language_code ) {

            if ( 
in_array$language_code$availabletrue ) ) {
                
\WP_CLI::log"Language '{$language_code}' already installed." );
                
$skips++;
            } else {
                
$response $this->download_language_pack$language_code$theme );

                if ( 
is_wp_error$response ) ) {
                    
\WP_CLI::warning$response );
                    
\WP_CLI::log"Language '{$language_code}' not installed." );

                    
// Skip if translation is not yet available.
                    
if ( 'not_found' === $response->get_error_code() ) {
                        
$skips++;
                    } else {
                        
$errors++;
                    }
                } else {
                    
\WP_CLI::log"Language '{$language_code}' installed." );
                    
$successes++;
                }
            }
        }

        
\WP_CLI\Utils\report_batch_operation_results'language''install'$count$successes$errors$skips );
    }

    
/**
     * Installs translations for all installed themes.
     *
     * @param array $args       Runtime arguments.
     * @param array $assoc_args Runtime arguments.
     */
    
private function install_many$args$assoc_args ) {
        
$language_codes = (array) $args;
        
$themes         wp_get_themes();

        if ( empty( 
$assoc_args['format'] ) ) {
            
$assoc_args['format'] = 'table';
        }

        if ( 
in_array$assoc_args['format'], array( 'json''csv' ), true ) ) {
            
$logger = new \WP_CLI\Loggers\Quiet();
            
\WP_CLI::set_logger$logger );
        }

        if ( empty( 
$themes ) ) {
            
\WP_CLI::success'No themes installed.' );
            return;
        }

        
$count count$themes ) * count$language_codes );

        
$results = array();

        
$successes 0;
        
$errors    0;
        
$skips     0;
        foreach ( 
$themes as $theme_path => $theme_details ) {
            
$theme_name \WP_CLI\Utils\get_theme_name$theme_path );

            
$available $this->get_installed_languages$theme_name );

            foreach ( 
$language_codes as $language_code ) {
                
$result = [
                    
'name'   => $theme_name,
                    
'locale' => $language_code,
                ];

                if ( 
in_array$language_code$availabletrue ) ) {
                    
\WP_CLI::log"Language '{$language_code}' for '{$theme_details['Name']}' already installed." );
                    
$result['status'] = 'already installed';
                    
$skips++;
                } else {
                    
$response $this->download_language_pack$language_code$theme_name );

                    if ( 
is_wp_error$response ) ) {
                        
\WP_CLI::warning$response );
                        
\WP_CLI::log"Language '{$language_code}' for '{$theme_details['Name']}' not installed." );

                        if ( 
'not_found' === $response->get_error_code() ) {
                            
$result['status'] = 'not available';
                            
$skips++;
                        } else {
                            
$result['status'] = 'not installed';
                            
$errors++;
                        }
                    } else {
                        
\WP_CLI::log"Language '{$language_code}' for '{$theme_details['Name']}' installed." );
                        
$result['status'] = 'installed';
                        
$successes++;
                    }
                }

                
$results[] = (object) $result;
            }
        }

        if ( 
'summary' !== $assoc_args['format'] ) {
            
\WP_CLI\Utils\format_items$assoc_args['format'], $results, array( 'name''locale''status' ) );
        }

        
\WP_CLI\Utils\report_batch_operation_results'language''install'$count$successes$errors$skips );
    }

    
/**
     * Uninstalls a given language for a theme.
     *
     * ## OPTIONS
     *
     * <theme>
     * : Theme to uninstall language for.
     *
     * <language>...
     * : Language code to uninstall.
     *
     * ## EXAMPLES
     *
     *     $ wp language theme uninstall twentyten ja
     *     Success: Language uninstalled.
     *
     * @subcommand uninstall
     */
    
public function uninstall$args$assoc_args ) {
        
/** @var WP_Filesystem_Base $wp_filesystem */
        
global $wp_filesystem;

        
$theme          array_shift$args );
        
$language_codes = (array) $args;
        
$current_locale get_locale();

        
$dir   WP_LANG_DIR "/$this->obj_type";
        
$files scandir$dir );

        if ( ! 
$files ) {
            
\WP_CLI::error'No files found in language directory.' );
        }

        
// As of WP 4.0, no API for deleting a language pack
        
WP_Filesystem();
        
$available $this->get_installed_languages$theme );

        foreach ( 
$language_codes as $language_code ) {
            if ( ! 
in_array$language_code$availabletrue ) ) {
                
WP_CLI::error'Language not installed.' );
            }

            if ( 
$language_code === $current_locale ) {
                
WP_CLI::warning"The '{$language_code}' language is active." );
                exit;
            }

            if ( 
$wp_filesystem->delete"{$dir}/{$theme}-{$language_code}.po" ) && $wp_filesystem->delete"{$dir}/{$theme}-{$language_code}.mo" ) ) {
                
WP_CLI::success'Language uninstalled.' );
            } else {
                
WP_CLI::error"Couldn't uninstall language." );
            }
        }
    }

    
/**
     * Updates installed languages for one or more themes.
     *
     * ## OPTIONS
     *
     * [<theme>...]
     * : One or more themes to update languages for.
     *
     * [--all]
     * : If set, languages for all themes will be updated.
     *
     * [--dry-run]
     * : Preview which translations would be updated.
     *
     * ## EXAMPLES
     *
     *     $ wp language theme update --all
     *     Updating 'Japanese' translation for Twenty Fifteen 1.5...
     *     Downloading translation from https://downloads.wordpress.org/translation/theme/twentyfifteen/1.5/ja.zip...
     *     Translation updated successfully.
     *     Success: Updated 1/1 translation.
     *
     * @subcommand update
     */
    
public function update$args$assoc_args ) {
        
$all \WP_CLI\Utils\get_flag_value$assoc_args'all'false );

        if ( ! 
$all && empty( $args ) ) {
            
WP_CLI::error'Please specify one or more themes, or use --all.' );
        }

        if ( 
$all ) {
            
$args array_map'\WP_CLI\Utils\get_theme_name'array_keyswp_get_themes() ) );
            if ( empty( 
$args ) ) {
                
WP_CLI::success'No themes installed.' );

                return;
            }
        }

        
parent::update$args$assoc_args );
    }
}
<?
php

use WP_CLI\Dispatcher\CommandNamespace;

/**
 * Installs, activates, and manages language packs.
 *
 * ## EXAMPLES
 *
 *     # Install the Dutch core language pack.
 *     $ wp language core install nl_NL
 *     Success: Language installed.
 *
 *     # Activate the Dutch core language pack.
 *     $ wp language core activate nl_NL
 *     Success: Language activated.
 *
 *     # Install the Dutch language pack for Twenty Seventeen.
 *     $ wp language theme install twentyseventeen nl_NL
 *     Success: Language installed.
 *
 *     # Install the Dutch language pack for Akismet.
 *     $ wp language plugin install akismet nl_NL
 *     Success: Language installed.
 */
class Language_Namespace extends CommandNamespace {

}
<?
php

if ( ! class_exists'WP_CLI' ) ) {
    return;
}

$wpcli_language_autoloader dirname__FILE__ ) . '/vendor/autoload.php';

if ( 
file_exists$wpcli_language_autoloader ) ) {
    require_once 
$wpcli_language_autoloader;
}

$wpcli_language_check_requirements = function() {
    if ( 
\WP_CLI\Utils\wp_version_compare'4.0''<' ) ) {
        
WP_CLI::error'Requires WordPress 4.0 or greater.' );
    }
};

WP_CLI::add_command(
    
'language core',
    
'Core_Language_Command',
    array( 
'before_invoke' => $wpcli_language_check_requirements )
);

WP_CLI::add_command(
    
'language plugin',
    
'Plugin_Language_Command',
    array( 
'before_invoke' => $wpcli_language_check_requirements )
);

WP_CLI::add_command(
    
'language theme',
    
'Theme_Language_Command',
    array( 
'before_invoke' => $wpcli_language_check_requirements )
);

WP_CLI::add_hook(
    
'after_add_command:site',
    function () {
        
WP_CLI::add_command'site switch-language''Site_Switch_Language_Command' );
    }
);

if ( 
class_exists'WP_CLI\Dispatcher\CommandNamespace' ) ) {
    
WP_CLI::add_command'language''Language_Namespace' );
}
<?
php

use WP_CLI\Formatter;

/**
 * Adds, removes, and lists capabilities of a user role.
 *
 * See references for [Roles and Capabilities](https://codex.wordpress.org/Roles_and_Capabilities) and [WP User class](https://codex.wordpress.org/Class_Reference/WP_User).
 *
 * ## EXAMPLES
 *
 *     # Add 'spectate' capability to 'author' role.
 *     $ wp cap add 'author' 'spectate'
 *     Success: Added 1 capability to 'author' role.
 *
 *     # Add all caps from 'editor' role to 'author' role.
 *     $ wp cap list 'editor' | xargs wp cap add 'author'
 *     Success: Added 24 capabilities to 'author' role.
 *
 *     # Remove all caps from 'editor' role that also appear in 'author' role.
 *     $ wp cap list 'author' | xargs wp cap remove 'editor'
 *     Success: Removed 34 capabilities from 'editor' role.
 */
class Capabilities_Command extends WP_CLI_Command {

    
/**
     * List of available fields.
     *
     * @var array
     */
    
private $fields = [ 'name' ];

    
/**
     * Lists capabilities for a given role.
     *
     * ## OPTIONS
     *
     * <role>
     * : Key for the role.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: list
     * options:
     *   - list
     *   - table
     *   - csv
     *   - json
     *   - count
     *   - yaml
     * ---
     *
     * [--show-grant]
     * : Display all capabilities defined for a role including grant.
     * ---
     * default: false
     * ---
     *
     * ## EXAMPLES
     *
     *     # Display alphabetical list of Contributor capabilities.
     *     $ wp cap list 'contributor' | sort
     *     delete_posts
     *     edit_posts
     *     level_0
     *     level_1
     *     read
     *
     * @subcommand list
     */
    
public function list_$args$assoc_args ) {
        
$role_obj self::get_role$args[0] );

        
$show_grant = ! empty( $assoc_args['show-grant'] );

        if ( 
$show_grant ) {
            
array_push$this->fields'grant' );
            
$capabilities $role_obj->capabilities;
        } else {
            
$capabilities array_filter$role_obj->capabilities );
        }

        
$output_caps = array();
        foreach ( 
$capabilities as $cap => $grant ) {
            
$output_cap = new stdClass();

            
$output_cap->name  $cap;
            
$output_cap->grant $grant 'true' 'false';

            
$output_caps[] = $output_cap;
        }

        if ( 
'list' === $assoc_args['format'] ) {
            foreach ( 
$output_caps as $cap ) {
                if ( 
$show_grant ) {
                    
WP_CLI::lineimplode',', array( $cap->name$cap->grant ) ) );
                } else {
                    
WP_CLI::line$cap->name );
                }
            }
        } else {
            
$formatter = new Formatter$assoc_args$this->fields );
            
$formatter->display_items$output_caps );
        }
    }

    
/**
     * Adds capabilities to a given role.
     *
     * ## OPTIONS
     *
     * <role>
     * : Key for the role.
     *
     * <cap>...
     * : One or more capabilities to add.
     *
     * [--grant]
     * : Adds the capability as an explicit boolean value, instead of implicitly defaulting to `true`.
     * ---
     * default: true
     * options:
     *   - true
     *   - false
     * ---
     *
     * ## EXAMPLES
     *
     *     # Add 'spectate' capability to 'author' role.
     *     $ wp cap add author spectate
     *     Success: Added 1 capability to 'author' role.
     */
    
public function add$args$assoc_args ) {
        
self::persistence_check();

        
$role array_shift$args );

        
$role_obj self::get_role$role );

        
$grant = ! isset( $assoc_args['grant'] ) || ! empty( $assoc_args['grant'] );

        
$count 0;

        foreach ( 
$args as $cap ) {
            if ( 
true === $grant && $role_obj->has_cap$cap ) ) {
                continue;
            }

            if ( 
false === $grant && isset( $role_obj->capabilities$cap ] ) && false === $role_obj->capabilities$cap ] ) {
                continue;
            }

            
$role_obj->add_cap$cap$grant );

            
$count++;
        }

        
$capability          WP_CLI\Utils\pluralize'capability'$count );
        
$grant_qualification $grant '' ' as false';

        
WP_CLI::success"Added {$count} {$capability} to '{$role}' role{$grant_qualification}." );
    }

    
/**
     * Removes capabilities from a given role.
     *
     * ## OPTIONS
     *
     * <role>
     * : Key for the role.
     *
     * <cap>...
     * : One or more capabilities to remove.
     *
     * ## EXAMPLES
     *
     *     # Remove 'spectate' capability from 'author' role.
     *     $ wp cap remove author spectate
     *     Success: Removed 1 capability from 'author' role.
     */
    
public function remove$args ) {
        
self::persistence_check();

        
$role array_shift$args );

        
$role_obj self::get_role$role );

        
$count 0;

        foreach ( 
$args as $cap ) {
            if ( ! isset( 
$role_obj->capabilities$cap ] ) ) {
                continue;
            }

            
$role_obj->remove_cap$cap );

            
$count++;
        }

        
$capability WP_CLI\Utils\pluralize'capability'$count );

        
WP_CLI::success"Removed {$count} {$capability} from '{$role}' role." );
    }

    
/**
     * Retrieve a specific role from the system.
     *
     * @param string $role Role to retrieve.
     * @return WP_Role Requested role.
     * @throws \WP_CLI\ExitException If the role could not be found.
     */
    
private static function get_role$role ) {
        global 
$wp_roles;

        
$role_obj $wp_roles->get_role$role );

        if ( ! 
$role_obj ) {
            
WP_CLI::error"'{$role}' role not found." );
        }

        return 
$role_obj;
    }

    
/**
     * Assert that the roles are persisted to the database.
     *
     * @throws \WP_CLI\ExitException If the roles are not persisted to the
     *                               database.
     */
    
private static function persistence_check() {
        global 
$wp_roles;

        if ( ! 
$wp_roles->use_db ) {
            
WP_CLI::error'Role definitions are not persistent.' );
        }
    }
}
<?
php

use WP_CLI\Utils;
use 
WP_CLI\Formatter;

/**
 * Manages user roles, including creating new roles and resetting to defaults.
 *
 * See references for [Roles and Capabilities](https://codex.wordpress.org/Roles_and_Capabilities) and [WP User class](https://codex.wordpress.org/Class_Reference/WP_User).
 *
 * ## EXAMPLES
 *
 *     # List roles.
 *     $ wp role list --fields=role --format=csv
 *     role
 *     administrator
 *     editor
 *     author
 *     contributor
 *     subscriber
 *
 *     # Check to see if a role exists.
 *     $ wp role exists editor
 *     Success: Role with ID 'editor' exists.
 *
 *     # Create a new role.
 *     $ wp role create approver Approver
 *     Success: Role with key 'approver' created.
 *
 *     # Delete an existing role.
 *     $ wp role delete approver
 *     Success: Role with key 'approver' deleted.
 *
 *     # Reset existing roles to their default capabilities.
 *     $ wp role reset administrator author contributor
 *     Success: Reset 3/3 roles.
 *
 * @package wp-cli
 */
class Role_Command extends WP_CLI_Command {

    
/**
     * List of available fields.
     *
     * @var array
     */
    
private $fields = [ 'name''role' ];

    
/**
     * Default roles as provided by WordPress Core.
     *
     * @var array
     */
    
private $roles = [ 'administrator''editor''author''contributor''subscriber' ];

    
/**
     * Lists all roles.
     *
     * ## OPTIONS
     *
     * [--fields=<fields>]
     * : Limit the output to specific object fields.
     *
     * [--field=<field>]
     * : Prints the value of a single field.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - count
     *   - yaml
     * ---
     *
     * ## AVAILABLE FIELDS
     *
     * These fields will be displayed by default for each role:
     *
     * * name
     * * role
     *
     * There are no optional fields.
     *
     * ## EXAMPLES
     *
     *     # List roles.
     *     $ wp role list --fields=role --format=csv
     *     role
     *     administrator
     *     editor
     *     author
     *     contributor
     *     subscriber
     *
     * @subcommand list
     */
    
public function list_$args$assoc_args ) {
        global 
$wp_roles;

        
$output_roles = array();
        foreach ( 
$wp_roles->roles as $key => $role ) {
            
$output_role = new stdClass();

            
$output_role->name $role['name'];
            
$output_role->role $key;

            
$output_roles[] = $output_role;
        }

        
$formatter = new Formatter$assoc_args$this->fields );
        
$formatter->display_items$output_roles );
    }

    
/**
     * Checks if a role exists.
     *
     * Exits with return code 0 if the role exists, 1 if it does not.
     *
     * ## OPTIONS
     *
     * <role-key>
     * : The internal name of the role.
     *
     * ## EXAMPLES
     *
     *     # Check if a role exists.
     *     $ wp role exists editor
     *     Success: Role with ID 'editor' exists.
     */
    
public function exists$args ) {
        global 
$wp_roles;

        if ( ! 
in_array$args[0], array_keys$wp_roles->roles ), true ) ) {
            
WP_CLI::error"Role with ID '{$args[0]}' does not exist." );
        }

        
WP_CLI::success"Role with ID '{$args[0]}' exists." );
    }

    
/**
     * Creates a new role.
     *
     * ## OPTIONS
     *
     * <role-key>
     * : The internal name of the role.
     *
     * <role-name>
     * : The publicly visible name of the role.
     *
     * [--clone=<role>]
     * : Clone capabilities from an existing role.
     *
     * ## EXAMPLES
     *
     *     # Create role for Approver.
     *     $ wp role create approver Approver
     *     Success: Role with key 'approver' created.
     *
     *     # Create role for Product Administrator.
     *     $ wp role create productadmin "Product Administrator"
     *     Success: Role with key 'productadmin' created.
     */
    
public function create$args$assoc_args ) {
        global 
$wp_roles;

        
self::persistence_check();

        
$role_key  array_shift$args );
        
$role_name array_shift$args );

        if ( empty( 
$role_key ) || empty( $role_name ) ) {
            
WP_CLI::error"Can't create role, insufficient information provided." );
        }

        
$capabilities false;
        if ( ! empty( 
$assoc_args['clone'] ) ) {
            
$role_obj $wp_roles->get_role$assoc_args['clone'] );
            if ( ! 
$role_obj ) {
                
WP_CLI::error"'{$assoc_args['clone']}' role not found." );
            }
            
$capabilities array_keys$role_obj->capabilities );
        }

        if ( 
add_role$role_key$role_name ) ) {
            if ( ! empty( 
$capabilities ) ) {
                
$role_obj $wp_roles->get_role$role_key );
                foreach ( 
$capabilities as $cap ) {
                    
$role_obj->add_cap$cap );
                }
                
WP_CLI::success"Role with key '{$role_key}' created. Cloned capabilities from '{$assoc_args['clone']}'." );
            } else {
                
WP_CLI::success"Role with key '{$role_key}' created." );
            }
        } else {
            
WP_CLI::error"Role couldn't be created." );
        }
    }

    
/**
     * Deletes an existing role.
     *
     * ## OPTIONS
     *
     * <role-key>
     * : The internal name of the role.
     *
     * ## EXAMPLES
     *
     *     # Delete approver role.
     *     $ wp role delete approver
     *     Success: Role with key 'approver' deleted.
     *
     *     # Delete productadmin role.
     *     wp role delete productadmin
     *     Success: Role with key 'productadmin' deleted.
     */
    
public function delete$args ) {
        global 
$wp_roles;

        
self::persistence_check();

        
$role_key array_shift$args );

        if ( empty( 
$role_key ) || ! isset( $wp_roles->roles$role_key ] ) ) {
            
WP_CLI::error'Role key not provided, or is invalid.' );
        }

        
remove_role$role_key );

        
// Note: remove_role() doesn't indicate success or otherwise, so we have to
        // check ourselves
        
if ( ! isset( $wp_roles->roles$role_key ] ) ) {
            
WP_CLI::success"Role with key '{$role_key}' deleted." );
        } else {
            
WP_CLI::error"Role with key '{$role_key}' could not be deleted." );
        }

    }

    
/**
     * Resets any default role to default capabilities.
     *
     * ## OPTIONS
     *
     * [<role-key>...]
     * : The internal name of one or more roles to reset.
     *
     * [--all]
     * : If set, all default roles will be reset.
     *
     * ## EXAMPLES
     *
     *     # Reset role.
     *     $ wp role reset administrator author contributor
     *     Success: Reset 1/3 roles.
     *
     *     # Reset all default roles.
     *     $ wp role reset --all
     *     Success: All default roles reset.
     */
    
public function reset$args$assoc_args ) {

        
self::persistence_check();

        if ( ! 
Utils\get_flag_value$assoc_args'all' ) && empty( $args ) ) {
            
WP_CLI::error'Role key not provided, or is invalid.' );
        }

        if ( ! 
function_exists'populate_roles' ) ) {
            require_once 
ABSPATH 'wp-admin/includes/schema.php';
        }

        global 
$wp_roles;
        
$all_roles     array_keys$wp_roles->roles );
        
$preserve_args $args;

        
// Get our default roles.
        
$default_roles $this->roles;
        
$preserve      $this->roles;
        
$before        = [];

        if ( 
Utils\get_flag_value$assoc_args'all' ) ) {
            foreach ( 
$default_roles as $role ) {
                
$before$role ] = get_role$role );
                
remove_role$role );
                
$args[] = $role;
            }
            
populate_roles();
            
$not_affected_roles array_diff$all_roles$default_roles );
            if ( ! empty( 
$not_affected_roles ) ) {
                foreach ( 
$not_affected_roles as $not_affected_role ) {
                    
WP_CLI::log"Custom role '{$not_affected_role}' not affected." );
                }
            }
        } else {

            foreach ( 
$args as $k => $role_key ) {
                
$key array_search$role_key$default_rolestrue );
                if ( 
false !== $key ) {
                    unset( 
$preserve$key ] );
                    
$before$role_key ] = get_role$role_key );
                    
remove_role$role_key );
                } else {
                    unset( 
$args$k ] );
                }
            }

            
$not_affected_roles array_diff$preserve_args$default_roles );
            if ( ! empty( 
$not_affected_roles ) ) {
                foreach ( 
$not_affected_roles as $not_affected_role ) {
                    
WP_CLI::log"Custom role '{$not_affected_role}' not affected." );
                }
            }

            
// No roles were unset, bail.
            
if ( count$default_roles ) === count$preserve ) ) {
                
WP_CLI::error'Must specify a default role to reset.' );
            }

            
// For the roles we're not resetting.
            
foreach ( $preserve as $k => $role ) {
                
/* save roles
                 * if get_role is null
                 * save role name for re-removal
                 */
                
$roleobj        get_role$role );
                
$preserve$k ] = is_null$roleobj ) ? $role $roleobj;

                
remove_role$role );
            }

            
// Put back all default roles and capabilities.
            
populate_roles();

            
// Restore the preserved roles.
            
foreach ( $preserve as $k => $roleobj ) {
                
// Re-remove after populating.
                
if ( is_a$roleobj'WP_Role' ) ) {
                    
remove_role$roleobj->name );
                    
add_role$roleobj->nameucwords$roleobj->name ), $roleobj->capabilities );
                } else {
                    
// When not an object, that means the role didn't exist before.
                    
remove_role$roleobj );
                }
            }
        }

        
$num_reset    0;
        
$args         array_unique$args );
        
$num_to_reset count$args );
        foreach ( 
$args as $role_key ) {
            
$after$role_key ] = get_role$role_key );

            
// phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison -- Object instances won't be same, strict check will fail here.
            
if ( $after$role_key ] != $before$role_key ] ) {
                ++
$num_reset;
                
$restored_cap       array_diff_key$after$role_key ]->capabilities$before$role_key ]->capabilities );
                
$removed_cap        array_diff_key$before$role_key ]->capabilities$after$role_key ]->capabilities );
                
$restored_cap_count count$restored_cap );
                
$removed_cap_count  count$removed_cap );
                
$restored_text      = ( === $restored_cap_count ) ? '%d capability' '%d capabilities';
                
$removed_text       = ( === $removed_cap_count ) ? '%d capability' '%d capabilities';
                
$message            "Restored {$restored_text} to and removed {$removed_text} from '%s' role.";
                
WP_CLI::logsprintf$message$restored_cap_count$removed_cap_count$role_key ) );
            } else {
                
WP_CLI::log"No changes necessary for '{$role_key}' role." );
            }
        }
        if ( 
$num_reset ) {
            if ( 
=== count$args ) ) {
                
WP_CLI::success'Role reset.' );
            } else {
                
WP_CLI::success"{$num_reset} of {$num_to_reset} roles reset." );
            }
        } else {
            if ( 
=== count$args ) ) {
                
WP_CLI::success'Role didn\'t need resetting.' );
            } else {
                
WP_CLI::success'No roles needed resetting.' );
            }
        }
    }

    
/**
     * Assert that the roles are persisted to the database.
     *
     * @throws \WP_CLI\ExitException If the roles are not persisted to the
     *                               database.
     */
    
private static function persistence_check() {
        global 
$wp_roles;

        if ( ! 
$wp_roles->use_db ) {
            
WP_CLI::error'Role definitions are not persistent.' );
        }
    }
}
<?
php

if ( ! class_exists'WP_CLI' ) ) {
    return;
}

$wpcli_role_autoloader dirname__FILE__ ) . '/vendor/autoload.php';
if ( 
file_exists$wpcli_role_autoloader ) ) {
    require_once 
$wpcli_role_autoloader;
}

WP_CLI::add_command'cap''Capabilities_Command' );
WP_CLI::add_command'role''Role_Command' );
<?
php

class Import_Command extends WP_CLI_Command {

    public 
$processed_posts = array();

    
/**
     * Imports content from a given WXR file.
     *
     * Provides a command line interface to the WordPress Importer plugin, for
     * performing data migrations.
     *
     * ## OPTIONS
     *
     * <file>...
     * : Path to one or more valid WXR files for importing. Directories are also accepted.
     *
     * --authors=<authors>
     * : How the author mapping should be handled. Options are 'create', 'mapping.csv', or 'skip'. The first will create any non-existent users from the WXR file. The second will read author mapping associations from a CSV, or create a CSV for editing if the file path doesn't exist. The CSV requires two columns, and a header row like "old_user_login,new_user_login". The last option will skip any author mapping.
     *
     * [--skip=<data-type>]
     * : Skip importing specific data. Supported options are: 'attachment' and 'image_resize' (skip time-consuming thumbnail generation).
     *
     * ## EXAMPLES
     *
     *     # Import content from a WXR file
     *     $ wp import example.wordpress.2016-06-21.xml --authors=create
     *     Starting the import process...
     *     Processing post #1 ("Hello world!") (post_type: post)
     *     -- 1 of 1
     *     -- Tue, 21 Jun 2016 05:31:12 +0000
     *     -- Imported post as post_id #1
     *     Success: Finished importing from 'example.wordpress.2016-06-21.xml' file.
     */
    
public function __invoke$args$assoc_args ) {
        
$defaults   = array(
            
'authors' => null,
            
'skip'    => array(),
        );
        
$assoc_args wp_parse_args$assoc_args$defaults );

        if ( ! 
is_array$assoc_args['skip'] ) ) {
            
$assoc_args['skip'] = explode','$assoc_args['skip'] );
        }

        
$importer $this->is_importer_available();
        if ( 
is_wp_error$importer ) ) {
            
WP_CLI::error$importer );
        }

        
$this->add_wxr_filters();

        
WP_CLI::log'Starting the import process...' );

        
$new_args = array();
        foreach ( 
$args as $arg ) {
            if ( 
is_dir$arg ) ) {
                
$dir   WP_CLI\Utils\trailingslashit$arg );
                
$files glob$dir '*.wxr' );
                if ( ! empty( 
$files ) ) {
                    
$new_args array_merge$new_args$files );
                }

                
$files glob$dir '*.xml' );
                if ( ! empty( 
$files ) ) {
                    
$new_args array_merge$new_args$files );
                }
            } else {
                if ( 
file_exists$arg ) ) {
                    
$new_args[] = $arg;
                }
            }
        }
        
$args $new_args;

        foreach ( 
$args as $file ) {
            if ( ! 
is_readable$file ) ) {
                
WP_CLI::warning"Can't read '$file' file." );
            }

            
$ret $this->import_wxr$file$assoc_args );

            if ( 
is_wp_error$ret ) ) {
                
WP_CLI::error$ret );
            } else {
                
WP_CLI::log'' ); // WXR import ends with HTML, so make sure message is on next line
                
WP_CLI::success"Finished importing from '$file' file." );
            }
        }
    }

    
/**
     * Imports a WXR file.
     */
    
private function import_wxr$file$args ) {

        
$wp_import                  = new WP_Import();
        
$wp_import->processed_posts $this->processed_posts;
        
$import_data                $wp_import->parse$file );
        if ( 
is_wp_error$import_data ) ) {
            return 
$import_data;
        }

        
// Prepare the data to be used in process_author_mapping();
        
$wp_import->get_authors_from_import$import_data );

        
// We no longer need the original data, so unset to avoid using excess
        // memory.
        
unset( $import_data );

        
$author_data = array();
        foreach ( 
$wp_import->authors as $wxr_author ) {
            
$author = new \stdClass();
            
// Always in the WXR
            
$author->user_login $wxr_author['author_login'];

            
// Should be in the WXR; no guarantees
            
if ( isset( $wxr_author['author_email'] ) ) {
                
$author->user_email $wxr_author['author_email'];
            }
            if ( isset( 
$wxr_author['author_display_name'] ) ) {
                
$author->display_name $wxr_author['author_display_name'];
            }
            if ( isset( 
$wxr_author['author_first_name'] ) ) {
                
$author->first_name $wxr_author['author_first_name'];
            }
            if ( isset( 
$wxr_author['author_last_name'] ) ) {
                
$author->last_name $wxr_author['author_last_name'];
            }

            
$author_data[] = $author;
        }

        
// Build the author mapping
        
$author_mapping $this->process_author_mapping$args['authors'], $author_data );
        if ( 
is_wp_error$author_mapping ) ) {
            return 
$author_mapping;
        }

        
$author_in  wp_list_pluck$author_mapping'old_user_login' );
        
$author_out wp_list_pluck$author_mapping'new_user_login' );
        unset( 
$author_mapping$author_data );

        
// $user_select needs to be an array of user IDs
        
$user_select         = array();
        
$invalid_user_select = array();
        foreach ( 
$author_out as $author_login ) {
            
$user get_user_by'login'$author_login );
            if ( 
$user ) {
                
$user_select[] = $user->ID;
            } else {
                
$invalid_user_select[] = $author_login;
            }
        }
        if ( ! empty( 
$invalid_user_select ) ) {
            return new 
WP_Error'invalid-author-mapping'sprintf'These user_logins are invalid: %s'implode','$invalid_user_select ) ) );
        }

        unset( 
$author_out );

        
// Drive the import
        
$wp_import->fetch_attachments = ! in_array'attachment'$args['skip'], true );

        
$_GET  = array(
            
'import' => 'wordpress',
            
'step'   => 2,
        );
        
$_POST = array(
            
'imported_authors'  => $author_in,
            
'user_map'          => $user_select,
            
'fetch_attachments' => $wp_import->fetch_attachments,
        );

        if ( 
in_array'image_resize'$args['skip'], true ) ) {
            
add_filter'intermediate_image_sizes_advanced', array( $this'filter_set_image_sizes' ) );
        }

        
$GLOBALS['wpcli_import_current_file'] = basename$file );
        
$wp_import->import$file );
        
$this->processed_posts += $wp_import->processed_posts;

        return 
true;
    }

    public function 
filter_set_image_sizes$sizes ) {
        
// Return null here to prevent the core image resizing logic from running.
        
return null;
    }

    
/**
     * Defines useful verbosity filters for the WXR importer.
     */
    
private function add_wxr_filters() {

        
add_filter(
            
'wp_import_posts',
            function( 
$posts ) {
                global 
$wpcli_import_counts;
                
$wpcli_import_counts['current_post'] = 0;
                
$wpcli_import_counts['total_posts']  = count$posts );
                return 
$posts;
            },
            
10
        
);

        
add_filter(
            
'wp_import_post_comments',
            function( 
$comments$post_id$post ) {
                global 
$wpcli_import_counts;
                
$wpcli_import_counts['current_comment'] = 0;
                
$wpcli_import_counts['total_comments']  = count$comments );
                return 
$comments;
            },
            
10,
            
3
        
);

        
add_filter(
            
'wp_import_post_data_raw',
            function( 
$post ) {
                global 
$wpcli_import_counts$wpcli_import_current_file;

                
$wpcli_import_counts['current_post']++;
                
WP_CLI::log'' );
                
WP_CLI::log'' );
                
WP_CLI::logsprintf'Processing post #%d ("%s") (post_type: %s)'$post['post_id'], $post['post_title'], $post['post_type'] ) );
                
WP_CLI::logsprintf'-- %s of %s (in file %s)'number_format$wpcli_import_counts['current_post'] ), number_format$wpcli_import_counts['total_posts'] ), $wpcli_import_current_file ) );
                
WP_CLI::log'-- ' date'r' ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date

                
return $post;
            }
        );

        
add_action(
            
'wp_import_insert_post',
            function( 
$post_id$original_post_id$post$postdata ) {
                global 
$wpcli_import_counts;
                if ( 
is_wp_error$post_id ) ) {
                    
WP_CLI::warning'-- Error importing post: ' $post_id->get_error_code() );
                } else {
                    
WP_CLI::log"-- Imported post as post_id #{$post_id});
                }

                if ( 
=== ( $wpcli_import_counts['current_post'] % 500 ) ) {
                    
WP_CLI\Utils\wp_clear_object_cache();
                    
WP_CLI::log'-- Cleared object cache.' );
                }

            },
            
10,
            
4
        
);

        
add_action(
            
'wp_import_insert_term',
            function( 
$t$import_term$post_id$post ) {
                
WP_CLI::log"-- Created term \"{$import_term['name']}\"" );
            },
            
10,
            
4
        
);

        
add_action(
            
'wp_import_set_post_terms',
            function( 
$tt_ids$term_ids$taxonomy$post_id$post ) {
                
WP_CLI::log'-- Added terms (' implode','$term_ids ) . ") for taxonomy \"{$taxonomy}\"" );
            },
            
10,
            
5
        
);

        
add_action(
            
'wp_import_insert_comment',
            function( 
$comment_id$comment$comment_post_id$post ) {
                global 
$wpcli_import_counts;
                
$wpcli_import_counts['current_comment']++;
                
WP_CLI::logsprintf'-- Added comment #%d (%s of %s)'$comment_idnumber_format$wpcli_import_counts['current_comment'] ), number_format$wpcli_import_counts['total_comments'] ) ) );
            },
            
10,
            
4
        
);

        
add_action(
            
'import_post_meta',
            function( 
$post_id$key$value ) {
                
WP_CLI::log"-- Added post_meta $key);
            },
            
10,
            
3
        
);

    }

    
/**
     * Determines whether the requested importer is available.
     */
    
private function is_importer_available() {
        require_once 
ABSPATH 'wp-admin/includes/plugin.php';

        if ( 
class_exists'WP_Import' ) ) {
            return 
true;
        }

        
$plugins            get_plugins();
        
$wordpress_importer 'wordpress-importer/wordpress-importer.php';
        if ( 
array_key_exists$wordpress_importer$plugins ) ) {
            
$error_msg "WordPress Importer needs to be activated. Try 'wp plugin activate wordpress-importer'.";
        } else {
            
$error_msg "WordPress Importer needs to be installed. Try 'wp plugin install wordpress-importer --activate'.";
        }

        return new 
WP_Error'importer-missing'$error_msg );
    }

    
/**
     * Processes how the authors should be mapped
     *
     * @param string            $authors_arg      The `--author` argument originally passed to command
     * @param array             $author_data      An array of WP_User-esque author objects
     * @return array|WP_Error   $author_mapping   Author mapping array if successful, WP_Error if something bad happened
     */
    
private function process_author_mapping$authors_arg$author_data ) {

        
// Provided an author mapping file (method checks validity)
        
if ( file_exists$authors_arg ) ) {
            return 
$this->read_author_mapping_file$authors_arg );
        }

        
// Provided a file reference, but the file doesn't yet exist
        
if ( false !== stripos$authors_arg'.csv' ) ) {
            return 
$this->create_author_mapping_file$authors_arg$author_data );
        }

        switch ( 
$authors_arg ) {
            
// Create authors if they don't yet exist; maybe match on email or user_login
            
case 'create':
                return 
$this->create_authors_for_mapping$author_data );

            
// Skip any sort of author mapping
            
case 'skip':
                return array();

            default:
                return new 
WP_Error'invalid-argument'"'authors' argument is invalid." );
        }
    }

    
/**
     * Reads an author mapping file.
     */
    
private function read_author_mapping_file$file ) {
        
$author_mapping = array();

        foreach ( new 
\WP_CLI\Iterators\CSV$file ) as $i => $author ) {
            if ( ! 
array_key_exists'old_user_login'$author ) || ! array_key_exists'new_user_login'$author ) ) {
                return new 
WP_Error'invalid-author-mapping'"Author mapping file isn't properly formatted." );
            }

            
$author_mapping[] = $author;
        }

        return 
$author_mapping;
    }

    
/**
     * Creates an author mapping file, based on provided author data.
     *
     * @return WP_Error      The file was just now created, so some action needs to be taken
     */
    
private function create_author_mapping_file$file$author_data ) {

        if ( 
touch$file ) ) {
            
$author_mapping = array();
            foreach ( 
$author_data as $author ) {
                
$author_mapping[] = array(
                    
'old_user_login' => $author->user_login,
                    
'new_user_login' => $this->suggest_user$author->user_login$author->user_email ),
                );
            }
            
$file_resource fopen$file'w' );
            
\WP_CLI\utils\write_csv$file_resource$author_mapping, array( 'old_user_login''new_user_login' ) );
            return new 
WP_Error'author-mapping-error'sprintf'Please update author mapping file before continuing: %s'$file ) );
        } else {
            return new 
WP_Error'author-mapping-error'"Couldn't create author mapping file." );
        }
    }

    
/**
     * Creates users if they don't exist, and build an author mapping file.
     */
    
private function create_authors_for_mapping$author_data ) {

        
$author_mapping = array();
        foreach ( 
$author_data as $author ) {

            if ( isset( 
$author->user_email ) ) {
                
$user get_user_by'email'$author->user_email );
                if ( 
$user instanceof WP_User ) {
                    
$author_mapping[] = array(
                        
'old_user_login' => $author->user_login,
                        
'new_user_login' => $user->user_login,
                    );
                    continue;
                }
            }

            
$user get_user_by'login'$author->user_login );
            if ( 
$user instanceof WP_User ) {
                
$author_mapping[] = array(
                    
'old_user_login' => $author->user_login,
                    
'new_user_login' => $user->user_login,
                );
                continue;
            }

            
$user = array(
                
'user_login' => '',
                
'user_email' => '',
                
'user_pass'  => wp_generate_password(),
            );
            
$user array_merge$user, (array) $author );

            
$user_id wp_insert_user$user );
            if ( 
is_wp_error$user_id ) ) {
                return 
$user_id;
            }

            
$user             get_user_by'id'$user_id );
            
$author_mapping[] = array(
                
'old_user_login' => $author->user_login,
                
'new_user_login' => $user->user_login,
            );
        }
        return 
$author_mapping;

    }

    
/**
     * Suggests a blog user based on the levenshtein distance.
     */
    
private function suggest_user$author_user_login$author_user_email '' ) {

        if ( ! isset( 
$this->blog_users ) ) {
            
$this->blog_users get_users();
        }

        
$shortest    = -1;
        
$shortestavg = array();

        
$threshold floor( ( strlen$author_user_login ) / 100 ) * 10 ); // 10 % of the strlen are valid
        
$closest   '';
        foreach ( 
$this->blog_users as $user ) {
            
// Before we resort to an algorithm, let's try for an exact match
            
if ( $author_user_email && $user->user_email === $author_user_email ) {
                return 
$user->user_login;
            }

            
$levs        = array();
            
$levs[]      = levenshtein$author_user_login$user->display_name );
            
$levs[]      = levenshtein$author_user_login$user->user_login );
            
$levs[]      = levenshtein$author_user_login$user->user_email );
            
$email_parts explode'@'$user->user_email );
            
$email_login array_shift$email_parts );
            
$levs[]      = levenshtein$author_user_login$email_login );
            
rsort$levs );
            
$lev array_pop$levs );
            if ( 
=== $lev ) {
                
$closest  $user->user_login;
                
$shortest 0;
                break;
            }

            if ( ( 
$lev <= $shortest || $shortest ) && $lev <= $threshold ) {
                
$closest  $user->user_login;
                
$shortest $lev;
            }
            
$shortestavg[] = $lev;
        }
        
// in case all usernames have a common pattern
        
if ( $shortest > ( array_sum$shortestavg ) / count$shortestavg ) ) ) {
            return 
'';
        }

        return 
$closest;
    }

}
<?
php

if ( ! class_exists'WP_CLI' ) ) {
    return;
}

$wpcli_import_autoloader dirname__FILE__ ) . '/vendor/autoload.php';
if ( 
file_exists$wpcli_import_autoloader ) ) {
    require_once 
$wpcli_import_autoloader;
}

WP_CLI::add_command'import''Import_Command' );
<?
php

use WP_CLI\Formatter;
use 
WP_CLI\Utils;

/**
 * Performs basic database operations using credentials stored in wp-config.php.
 *
 * ## EXAMPLES
 *
 *     # Create a new database.
 *     $ wp db create
 *     Success: Database created.
 *
 *     # Drop an existing database.
 *     $ wp db drop --yes
 *     Success: Database dropped.
 *
 *     # Reset the current database.
 *     $ wp db reset --yes
 *     Success: Database reset.
 *
 *     # Execute a SQL query stored in a file.
 *     $ wp db query < debug.sql
 *
 * @when after_wp_config_load
 */
class DB_Command extends WP_CLI_Command {

    
/**
     * Legacy UTF-8 encoding for MySQL.
     *
     * @var string
     */
    
const ENCODING_UTF8 'utf8';

    
/**
     * Standards-compliant UTF-8 encoding for MySQL.
     *
     * @var string
     */
    
const ENCODING_UTF8MB4 'utf8mb4';

    
/**
     * A list of incompatible SQL modes.
     *
     * Copied over from WordPress Core code.
     * @see https://github.com/WordPress/wordpress-develop/blob/5.4.0/src/wp-includes/wp-db.php#L559-L572
     *
     * @var string[] Array of SQL mode names that are incompatible with WordPress.
     */
    
protected $sql_incompatible_modes = [
        
'NO_ZERO_DATE',
        
'ONLY_FULL_GROUP_BY',
        
'STRICT_TRANS_TABLES',
        
'STRICT_ALL_TABLES',
        
'TRADITIONAL',
        
'ANSI',
    ];

    
/**
     * Creates a new database.
     *
     * Runs `CREATE_DATABASE` SQL statement using `DB_HOST`, `DB_NAME`,
     * `DB_USER` and `DB_PASSWORD` database credentials specified in
     * wp-config.php.
     *
     * ## OPTIONS
     *
     * [--dbuser=<value>]
     * : Username to pass to mysql. Defaults to DB_USER.
     *
     * [--dbpass=<value>]
     * : Password to pass to mysql. Defaults to DB_PASSWORD.
     *
     * [--defaults]
     * : Loads the environment's MySQL option files. Default behavior is to skip loading them to avoid failures due to misconfiguration.
     *
     * ## EXAMPLES
     *
     *     $ wp db create
     *     Success: Database created.
     */
    
public function create$_$assoc_args ) {

        
$this->run_queryself::get_create_query(), $assoc_args );

        
WP_CLI::success'Database created.' );
    }

    
/**
     * Deletes the existing database.
     *
     * Runs `DROP_DATABASE` SQL statement using `DB_HOST`, `DB_NAME`,
     * `DB_USER` and `DB_PASSWORD` database credentials specified in
     * wp-config.php.
     *
     * ## OPTIONS
     *
     * [--dbuser=<value>]
     * : Username to pass to mysql. Defaults to DB_USER.
     *
     * [--dbpass=<value>]
     * : Password to pass to mysql. Defaults to DB_PASSWORD.
     *
     * [--yes]
     * : Answer yes to the confirmation message.
     *
     * [--defaults]
     * : Loads the environment's MySQL option files. Default behavior is to skip loading them to avoid failures due to misconfiguration.
     *
     * ## EXAMPLES
     *
     *     $ wp db drop --yes
     *     Success: Database dropped.
     */
    
public function drop$_$assoc_args ) {
        
WP_CLI::confirm"Are you sure you want to drop the '" DB_NAME "' database?"$assoc_args );

        
$this->run_querysprintf'DROP DATABASE `%s`'DB_NAME ), $assoc_args );

        
WP_CLI::success'Database dropped.' );
    }

    
/**
     * Removes all tables from the database.
     *
     * Runs `DROP_DATABASE` and `CREATE_DATABASE` SQL statements using
     * `DB_HOST`, `DB_NAME`, `DB_USER` and `DB_PASSWORD` database credentials
     * specified in wp-config.php.
     *
     * ## OPTIONS
     *
     * [--dbuser=<value>]
     * : Username to pass to mysql. Defaults to DB_USER.
     *
     * [--dbpass=<value>]
     * : Password to pass to mysql. Defaults to DB_PASSWORD.
     *
     * [--yes]
     * : Answer yes to the confirmation message.
     *
     * [--defaults]
     * : Loads the environment's MySQL option files. Default behavior is to skip loading them to avoid failures due to misconfiguration.
     *
     * ## EXAMPLES
     *
     *     $ wp db reset --yes
     *     Success: Database reset.
     */
    
public function reset$_$assoc_args ) {
        
WP_CLI::confirm"Are you sure you want to reset the '" DB_NAME "' database?"$assoc_args );

        
$this->run_querysprintf'DROP DATABASE IF EXISTS `%s`'DB_NAME ), $assoc_args );
        
$this->run_queryself::get_create_query(), $assoc_args );

        
WP_CLI::success'Database reset.' );
    }

    
/**
     * Removes all tables with `$table_prefix` from the database.
     *
     * Runs `DROP_TABLE` for each table that has a `$table_prefix` as specified
     * in wp-config.php.
     *
     * ## OPTIONS
     *
     * [--dbuser=<value>]
     * : Username to pass to mysql. Defaults to DB_USER.
     *
     * [--dbpass=<value>]
     * : Password to pass to mysql. Defaults to DB_PASSWORD.
     *
     * [--yes]
     * : Answer yes to the confirmation message.
     *
     * [--defaults]
     * : Loads the environment's MySQL option files. Default behavior is to skip loading them to avoid failures due to misconfiguration.
     *
     * ## EXAMPLES
     *
     *     # Delete all tables that match the current site prefix.
     *     $ wp db clean --yes
     *     Success: Tables dropped.
     *
     * @when after_wp_load
     */
    
public function clean$_$assoc_args ) {
        global 
$wpdb;

        
WP_CLI::confirm(
            
sprintf(
                
"Are you sure you want to drop all the tables on '%s' that use the current site's database prefix ('%s')?",
                
DB_NAME,
                
$wpdb->get_blog_prefix()
            ),
            
$assoc_args
        
);

        
$tables Utils\wp_get_table_names(
            [],
            [ 
'all-tables-with-prefix' => true ]
        );

        foreach ( 
$tables as $table ) {
            
$this->run_query(
                
sprintf(
                    
'DROP TABLE IF EXISTS `%s`.`%s`',
                    
DB_NAME,
                    
$table
                
),
                
$assoc_args
            
);
        }

        
WP_CLI::success'Tables dropped.' );
    }

    
/**
     * Checks the current status of the database.
     *
     * Runs `mysqlcheck` utility with `--check` using `DB_HOST`,
     * `DB_NAME`, `DB_USER` and `DB_PASSWORD` database credentials
     * specified in wp-config.php.
     *
     * [See docs](http://dev.mysql.com/doc/refman/5.7/en/check-table.html)
     * for more details on the `CHECK TABLE` statement.
     *
     * ## OPTIONS
     *
     * [--dbuser=<value>]
     * : Username to pass to mysqlcheck. Defaults to DB_USER.
     *
     * [--dbpass=<value>]
     * : Password to pass to mysqlcheck. Defaults to DB_PASSWORD.
     *
     * [--<field>=<value>]
     * : Extra arguments to pass to mysqlcheck. [Refer to mysqlcheck docs](https://dev.mysql.com/doc/en/mysqlcheck.html).
     *
     * [--defaults]
     * : Loads the environment's MySQL option files. Default behavior is to skip loading them to avoid failures due to misconfiguration.
     *
     * ## EXAMPLES
     *
     *     $ wp db check
     *     Success: Database checked.
     */
    
public function check$_$assoc_args ) {

        
$command sprintf'/usr/bin/env mysqlcheck%s %s'$this->get_defaults_flag_string$assoc_args ), '%s' );
        
WP_CLI::debug"Running shell command: {$command}"'db' );

        
$assoc_args['check'] = true;
        
self::run(
            
Utils\esc_cmd$commandDB_NAME ),
            
$assoc_args
        
);

        
WP_CLI::debug'Associative arguments: ' json_encode$assoc_args ), 'db' );
        
WP_CLI::success'Database checked.' );
    }

    
/**
     * Optimizes the database.
     *
     * Runs `mysqlcheck` utility with `--optimize=true` using `DB_HOST`,
     * `DB_NAME`, `DB_USER` and `DB_PASSWORD` database credentials
     * specified in wp-config.php.
     *
     * [See docs](http://dev.mysql.com/doc/refman/5.7/en/optimize-table.html)
     * for more details on the `OPTIMIZE TABLE` statement.
     *
     * ## OPTIONS
     *
     * [--dbuser=<value>]
     * : Username to pass to mysqlcheck. Defaults to DB_USER.
     *
     * [--dbpass=<value>]
     * : Password to pass to mysqlcheck. Defaults to DB_PASSWORD.
     *
     * [--<field>=<value>]
     * : Extra arguments to pass to mysqlcheck. [Refer to mysqlcheck docs](https://dev.mysql.com/doc/en/mysqlcheck.html).
     *
     * [--defaults]
     * : Loads the environment's MySQL option files. Default behavior is to skip loading them to avoid failures due to misconfiguration.
     *
     * ## EXAMPLES
     *
     *     $ wp db optimize
     *     Success: Database optimized.
     */
    
public function optimize$_$assoc_args ) {

        
$command sprintf'/usr/bin/env mysqlcheck%s %s'$this->get_defaults_flag_string$assoc_args ), '%s' );
        
WP_CLI::debug"Running shell command: {$command}"'db' );

        
$assoc_args['optimize'] = true;
        
self::run(
            
Utils\esc_cmd$commandDB_NAME ),
            
$assoc_args
        
);

        
WP_CLI::debug'Associative arguments: ' json_encode$assoc_args ), 'db' );
        
WP_CLI::success'Database optimized.' );
    }

    
/**
     * Repairs the database.
     *
     * Runs `mysqlcheck` utility with `--repair=true` using `DB_HOST`,
     * `DB_NAME`, `DB_USER` and `DB_PASSWORD` database credentials
     * specified in wp-config.php.
     *
     * [See docs](http://dev.mysql.com/doc/refman/5.7/en/repair-table.html) for
     * more details on the `REPAIR TABLE` statement.
     *
     * ## OPTIONS
     *
     * [--dbuser=<value>]
     * : Username to pass to mysqlcheck. Defaults to DB_USER.
     *
     * [--dbpass=<value>]
     * : Password to pass to mysqlcheck. Defaults to DB_PASSWORD.
     *
     * [--<field>=<value>]
     * : Extra arguments to pass to mysqlcheck. [Refer to mysqlcheck docs](https://dev.mysql.com/doc/en/mysqlcheck.html).
     *
     * [--defaults]
     * : Loads the environment's MySQL option files. Default behavior is to skip loading them to avoid failures due to misconfiguration.
     *
     * ## EXAMPLES
     *
     *     $ wp db repair
     *     Success: Database repaired.
     */
    
public function repair$_$assoc_args ) {

        
$command sprintf'/usr/bin/env mysqlcheck%s %s'$this->get_defaults_flag_string$assoc_args ), '%s' );
        
WP_CLI::debug"Running shell command: {$command}"'db' );

        
$assoc_args['repair'] = true;
        
self::run(
            
Utils\esc_cmd$commandDB_NAME ),
            
$assoc_args
        
);

        
WP_CLI::debug'Associative arguments: ' json_encode$assoc_args ), 'db' );
        
WP_CLI::success'Database repaired.' );
    }

    
/**
     * Opens a MySQL console using credentials from wp-config.php
     *
     * ## OPTIONS
     *
     * [--database=<database>]
     * : Use a specific database. Defaults to DB_NAME.
     *
     * [--default-character-set=<character-set>]
     * : Use a specific character set. Defaults to DB_CHARSET when defined.
     *
     * [--dbuser=<value>]
     * : Username to pass to mysql. Defaults to DB_USER.
     *
     * [--dbpass=<value>]
     * : Password to pass to mysql. Defaults to DB_PASSWORD.
     *
     * [--<field>=<value>]
     * : Extra arguments to pass to mysql. [Refer to mysql docs](https://dev.mysql.com/doc/en/mysql-command-options.html).
     *
     * [--defaults]
     * : Loads the environment's MySQL option files. Default behavior is to skip loading them to avoid failures due to misconfiguration.
     *
     * ## EXAMPLES
     *
     *     # Open MySQL console
     *     $ wp db cli
     *     mysql>
     *
     * @alias connect
     */
    
public function cli$_$assoc_args ) {

        
$command sprintf'/usr/bin/env mysql%s --no-auto-rehash'$this->get_defaults_flag_string$assoc_args ) );
        
WP_CLI::debug"Running shell command: {$command}"'db' );

        if ( ! isset( 
$assoc_args['database'] ) ) {
            
$assoc_args['database'] = DB_NAME;
        }

        
WP_CLI::debug'Associative arguments: ' json_encode$assoc_args ), 'db' );
        
self::run$command$assoc_argsnulltrue );
    }

    
/**
     * Executes a SQL query against the database.
     *
     * Executes an arbitrary SQL query using `DB_HOST`, `DB_NAME`, `DB_USER`
     *  and `DB_PASSWORD` database credentials specified in wp-config.php.
     *
     * ## OPTIONS
     *
     * [<sql>]
     * : A SQL query. If not passed, will try to read from STDIN.
     *
     * [--dbuser=<value>]
     * : Username to pass to mysql. Defaults to DB_USER.
     *
     * [--dbpass=<value>]
     * : Password to pass to mysql. Defaults to DB_PASSWORD.
     *
     * [--<field>=<value>]
     * : Extra arguments to pass to mysql. [Refer to mysql docs](https://dev.mysql.com/doc/en/mysql-command-options.html).
     *
     * [--defaults]
     * : Loads the environment's MySQL option files. Default behavior is to skip loading them to avoid failures due to misconfiguration.
     *
     * ## EXAMPLES
     *
     *     # Execute a query stored in a file
     *     $ wp db query < debug.sql
     *
     *     # Check all tables in the database
     *     $ wp db query "CHECK TABLE $(wp db tables | paste -s -d, -);"
     *     +---------------------------------------+-------+----------+----------+
     *     | Table                                 | Op    | Msg_type | Msg_text |
     *     +---------------------------------------+-------+----------+----------+
     *     | wordpress_dbase.wp_users              | check | status   | OK       |
     *     | wordpress_dbase.wp_usermeta           | check | status   | OK       |
     *     | wordpress_dbase.wp_posts              | check | status   | OK       |
     *     | wordpress_dbase.wp_comments           | check | status   | OK       |
     *     | wordpress_dbase.wp_links              | check | status   | OK       |
     *     | wordpress_dbase.wp_options            | check | status   | OK       |
     *     | wordpress_dbase.wp_postmeta           | check | status   | OK       |
     *     | wordpress_dbase.wp_terms              | check | status   | OK       |
     *     | wordpress_dbase.wp_term_taxonomy      | check | status   | OK       |
     *     | wordpress_dbase.wp_term_relationships | check | status   | OK       |
     *     | wordpress_dbase.wp_termmeta           | check | status   | OK       |
     *     | wordpress_dbase.wp_commentmeta        | check | status   | OK       |
     *     +---------------------------------------+-------+----------+----------+
     *
     *     # Pass extra arguments through to MySQL
     *     $ wp db query 'SELECT * FROM wp_options WHERE option_name="home"' --skip-column-names
     *     +---+------+------------------------------+-----+
     *     | 2 | home | http://wordpress-develop.dev | yes |
     *     +---+------+------------------------------+-----+
     */
    
public function query$args$assoc_args ) {

        
$command sprintf'/usr/bin/env mysql%s --no-auto-rehash'$this->get_defaults_flag_string$assoc_args ) );
        
WP_CLI::debug"Running shell command: {$command}"'db' );

        
$assoc_args['database'] = DB_NAME;

        
// The query might come from STDIN.
        
if ( ! empty( $args ) ) {
            
$assoc_args['execute'] = $args[0];
        }

        if ( isset( 
$assoc_args['execute'] ) ) {
            
// Ensure that the SQL mode is compatible with WPDB.
            
$assoc_args['execute'] = $this->get_sql_mode_query$assoc_args ) . $assoc_args['execute'];
        }

        
WP_CLI::debug'Associative arguments: ' json_encode$assoc_args ), 'db' );
        
self::run$command$assoc_args );
    }

    
/**
     * Exports the database to a file or to STDOUT.
     *
     * Runs `mysqldump` utility using `DB_HOST`, `DB_NAME`, `DB_USER` and
     * `DB_PASSWORD` database credentials specified in wp-config.php. Accepts any valid `mysqldump` flags.
     *
     * ## OPTIONS
     *
     * [<file>]
     * : The name of the SQL file to export. If '-', then outputs to STDOUT. If
     * omitted, it will be '{dbname}-{Y-m-d}-{random-hash}.sql'.
     *
     * [--dbuser=<value>]
     * : Username to pass to mysqldump. Defaults to DB_USER.
     *
     * [--dbpass=<value>]
     * : Password to pass to mysqldump. Defaults to DB_PASSWORD.
     *
     * [--<field>=<value>]
     * : Extra arguments to pass to mysqldump. [Refer to mysqldump docs](https://dev.mysql.com/doc/en/mysqldump.html#mysqldump-option-summary).
     *
     * [--tables=<tables>]
     * : The comma separated list of specific tables to export. Excluding this parameter will export all tables in the database.
     *
     * [--exclude_tables=<tables>]
     * : The comma separated list of specific tables that should be skipped from exporting. Excluding this parameter will export all tables in the database.
     *
     * [--include-tablespaces]
     * : Skips adding the default --no-tablespaces option to mysqldump.
     *
     * [--porcelain]
     * : Output filename for the exported database.
     *
     * [--defaults]
     * : Loads the environment's MySQL option files. Default behavior is to skip loading them to avoid failures due to misconfiguration.
     *
     * ## EXAMPLES
     *
     *     # Export database with drop query included
     *     $ wp db export --add-drop-table
     *     Success: Exported to 'wordpress_dbase-db72bb5.sql'.
     *
     *     # Export certain tables
     *     $ wp db export --tables=wp_options,wp_users
     *     Success: Exported to 'wordpress_dbase-db72bb5.sql'.
     *
     *     # Export all tables matching a wildcard
     *     $ wp db export --tables=$(wp db tables 'wp_user*' --format=csv)
     *     Success: Exported to 'wordpress_dbase-db72bb5.sql'.
     *
     *     # Export all tables matching prefix
     *     $ wp db export --tables=$(wp db tables --all-tables-with-prefix --format=csv)
     *     Success: Exported to 'wordpress_dbase-db72bb5.sql'.
     *
     *     # Export certain posts without create table statements
     *     $ wp db export --no-create-info=true --tables=wp_posts --where="ID in (100,101,102)"
     *     Success: Exported to 'wordpress_dbase-db72bb5.sql'.
     *
     *     # Export relating meta for certain posts without create table statements
     *     $ wp db export --no-create-info=true --tables=wp_postmeta --where="post_id in (100,101,102)"
     *     Success: Exported to 'wordpress_dbase-db72bb5.sql'.
     *
     *     # Skip certain tables from the exported database
     *     $ wp db export --exclude_tables=wp_options,wp_users
     *     Success: Exported to 'wordpress_dbase-db72bb5.sql'.
     *
     *     # Skip all tables matching a wildcard from the exported database
     *     $ wp db export --exclude_tables=$(wp db tables 'wp_user*' --format=csv)
     *     Success: Exported to 'wordpress_dbase-db72bb5.sql'.
     *
     *     # Skip all tables matching prefix from the exported database
     *     $ wp db export --exclude_tables=$(wp db tables --all-tables-with-prefix --format=csv)
     *     Success: Exported to 'wordpress_dbase-db72bb5.sql'.
     *
     *     # Export database to STDOUT.
     *     $ wp db export -
     *     -- MySQL dump 10.13  Distrib 5.7.19, for osx10.12 (x86_64)
     *     --
     *     -- Host: localhost    Database: wpdev
     *     -- ------------------------------------------------------
     *     -- Server version    5.7.19
     *     ...
     *
     * @alias dump
     */
    
public function export$args$assoc_args ) {
        if ( ! empty( 
$args[0] ) ) {
            
$result_file $args[0];
        } else {
            
// phpcs:ignore WordPress.WP.AlternativeFunctions.rand_mt_rand -- WordPress is not loaded.
            
$hash        substrmd5mt_rand() ), 0);
            
$result_file sprintf'%s-%s-%s.sql'DB_NAMEdate'Y-m-d' ), $hash ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date

        
}
        
$stdout    = ( '-' === $result_file );
        
$porcelain Utils\get_flag_value$assoc_args'porcelain' );

        
// Bail if both porcelain and STDOUT are set.
        
if ( $stdout && $porcelain ) {
            
WP_CLI::error'Porcelain is not allowed when output mode is STDOUT.' );
        }

        if ( ! 
$stdout ) {
            
$assoc_args['result-file'] = $result_file;
        }

        
$mysqldump_binary Utils\force_env_on_nix_systems'mysqldump' );

        
$support_column_statistics exec$mysqldump_binary ' --help | grep "column-statistics"' );

        
/*
         * In case that `--default-character-set` is not given and `DB_CHARSET` is `utf8`,
         * we try to deduce what the actual character set for the posts table of the
         * current database is and use `utf8mb4` as a `default-character-set` if that
         * seems like the safer default, to ensure emojis are encoded correctly.
         */
        
if (
            ! isset( 
$assoc_args['default-character-set'] )
            &&
            ( 
defined'DB_CHARSET' ) && self::ENCODING_UTF8 === constant'DB_CHARSET' ) )
            &&
            
self::ENCODING_UTF8MB4 === $this->get_posts_table_charset$assoc_args )
        ) {
            
WP_CLI::debug'Setting missing default character set to ' self::ENCODING_UTF8MB4'db' );
            
$assoc_args['default-character-set'] = self::ENCODING_UTF8MB4;
        }

        
$initial_command sprintf"{$mysqldump_binary}%s "$this->get_defaults_flag_string$assoc_args ) );
        
WP_CLI::debug"Running initial shell command: {$initial_command}"'db' );

        
$default_arguments = [ '%s' ];

        if ( 
$support_column_statistics ) {
            
$default_arguments[] = '--skip-column-statistics';
        }

        if ( ! 
Utils\get_flag_value$assoc_args'include-tablespaces'false ) ) {
            
$default_arguments[] = '--no-tablespaces';
        }

        
$command $initial_command implode' '$default_arguments );

        
$command_esc_args = [ DB_NAME ];

        if ( isset( 
$assoc_args['tables'] ) ) {
            
$tables explode','trim$assoc_args['tables'], ',' ) );
            unset( 
$assoc_args['tables'] );
            
$command .= ' --tables';
            foreach ( 
$tables as $table ) {
                
$command           .= ' %s';
                
$command_esc_args[] = trim$table );
            }
        }

        
$exclude_tables Utils\get_flag_value$assoc_args'exclude_tables' );
        if ( isset( 
$exclude_tables ) ) {
            
$tables explode','trim$assoc_args['exclude_tables'], ',' ) );
            unset( 
$assoc_args['exclude_tables'] );
            foreach ( 
$tables as $table ) {
                
$command           .= ' --ignore-table';
                
$command           .= ' %s';
                
$command_esc_args[] = trimDB_NAME '.' $table );
            }
        }

        
$escaped_command call_user_func_array'\WP_CLI\Utils\esc_cmd'array_merge( [ $command ], $command_esc_args ) );

        
// Remove parameters not needed for SQL run.
        
unset( $assoc_args['porcelain'] );

        
WP_CLI::debug'Associative arguments: ' json_encode$assoc_args ), 'db' );
        
self::run$escaped_command$assoc_args );

        if ( 
$porcelain ) {
            
WP_CLI::line$result_file );
        } elseif ( ! 
$stdout ) {
            
WP_CLI::successsprintf"Exported to '%s'."$result_file ) );
        }
    }

    
/**
     * Get the current character set of the posts table.
     *
     * @param array Associative array of associative arguments.
     * @return string Posts table character set.
     */
    
private function get_posts_table_charset$assoc_args ) {
        
$query 'SELECT CCSA.character_set_name '
                
'FROM information_schema.`TABLES` T, '
                
'information_schema.`COLLATION_CHARACTER_SET_APPLICABILITY` CCSA '
                
'WHERE CCSA.collation_name = T.table_collation '
                
'AND T.table_schema = "' DB_NAME '" '
                
'AND T.table_name LIKE "%\_posts";';

        list( 
$stdout$stderr$exit_code ) = self::run(
            
sprintf(
                
'/usr/bin/env mysql%s --no-auto-rehash --batch --skip-column-names',
                
$this->get_defaults_flag_string$assoc_args )
            ),
            [ 
'execute' => $query ],
            
false
        
);

        if ( 
$exit_code ) {
            
WP_CLI::warning(
                
'Failed to get current character set of the posts table.'
                
. ( ! empty( $stderr ) ? " Reason: {$stderr}'' )
            );

            return 
self::ENCODING_UTF8MB4;
        }

        
$stdout trim$stdout );

        
WP_CLI::debug"Detected character set of the posts table: {$stdout}."'db' );

        return 
$stdout;
    }

    
/**
     * Imports a database from a file or from STDIN.
     *
     * Runs SQL queries using `DB_HOST`, `DB_NAME`, `DB_USER` and
     * `DB_PASSWORD` database credentials specified in wp-config.php. This
     * does not create database by itself and only performs whatever tasks are
     * defined in the SQL.
     *
     * ## OPTIONS
     *
     * [<file>]
     * : The name of the SQL file to import. If '-', then reads from STDIN. If omitted, it will look for '{dbname}.sql'.
     *
     * [--dbuser=<value>]
     * : Username to pass to mysql. Defaults to DB_USER.
     *
     * [--dbpass=<value>]
     * : Password to pass to mysql. Defaults to DB_PASSWORD.
     *
     * [--<field>=<value>]
     * : Extra arguments to pass to mysql. [Refer to mysql binary docs](https://dev.mysql.com/doc/refman/8.0/en/mysql-command-options.html).
     *
     * [--skip-optimization]
     * : When using an SQL file, do not include speed optimization such as disabling auto-commit and key checks.
     *
     * [--defaults]
     * : Loads the environment's MySQL option files. Default behavior is to skip loading them to avoid failures due to misconfiguration.
     *
     * ## EXAMPLES
     *
     *     # Import MySQL from a file.
     *     $ wp db import wordpress_dbase.sql
     *     Success: Imported from 'wordpress_dbase.sql'.
     */
    
public function import$args$assoc_args ) {
        if ( ! empty( 
$args[0] ) ) {
            
$result_file $args[0];
        } else {
            
$result_file sprintf'%s.sql'DB_NAME );
        }

        
// Process options to MySQL.
        
$mysql_args array_merge(
            [ 
'database' => DB_NAME ],
            
self::get_dbuser_dbpass_args$assoc_args ),
            
self::get_mysql_args$assoc_args )
        );

        if ( 
'-' !== $result_file ) {
            if ( ! 
is_readable$result_file ) ) {
                
WP_CLI::errorsprintf'Import file missing or not readable: %s'$result_file ) );
            }

            
$query Utils\get_flag_value$assoc_args'skip-optimization' )
                ? 
'SOURCE %s;'
                
'SET autocommit = 0; SET unique_checks = 0; SET foreign_key_checks = 0; SOURCE %s; COMMIT;';

            
$query $this->get_sql_mode_query$assoc_args ) . $query;

            
$mysql_args['execute'] = sprintf$query$result_file );
        } else {
            
$result_file 'STDIN';
        }

        
$command sprintf'/usr/bin/env mysql%s --no-auto-rehash'$this->get_defaults_flag_string$assoc_args ) );
        
WP_CLI::debug"Running shell command: {$command}"'db' );
        
WP_CLI::debug'Associative arguments: ' json_encode$assoc_args ), 'db' );

        
self::run$command$mysql_args );

        
WP_CLI::successsprintf"Imported from '%s'."$result_file ) );
    }

    
/**
     * Lists the database tables.
     *
     * Defaults to all tables registered to the $wpdb database handler.
     *
     * ## OPTIONS
     *
     * [<table>...]
     * : List tables based on wildcard search, e.g. 'wp_*_options' or 'wp_post?'.
     *
     * [--scope=<scope>]
     * : Can be all, global, ms_global, blog, or old tables. Defaults to all.
     *
     * [--network]
     * : List all the tables in a multisite install.
     *
     * [--all-tables-with-prefix]
     * : List all tables that match the table prefix even if not registered on $wpdb. Overrides --network.
     *
     * [--all-tables]
     * : List all tables in the database, regardless of the prefix, and even if not registered on $wpdb. Overrides --all-tables-with-prefix.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * default: list
     * options:
     *   - list
     *   - csv
     * ---
     *
     * ## EXAMPLES
     *
     *     # List tables for a single site, without shared tables like 'wp_users'
     *     $ wp db tables --scope=blog --url=sub.example.com
     *     wp_3_posts
     *     wp_3_comments
     *     wp_3_options
     *     wp_3_postmeta
     *     wp_3_terms
     *     wp_3_term_taxonomy
     *     wp_3_term_relationships
     *     wp_3_termmeta
     *     wp_3_commentmeta
     *
     *     # Export only tables for a single site
     *     $ wp db export --tables=$(wp db tables --url=sub.example.com --format=csv)
     *     Success: Exported to wordpress_dbase.sql
     *
     * @when after_wp_load
     */
    
public function tables$args$assoc_args ) {

        
$format Utils\get_flag_value$assoc_args'format' );
        unset( 
$assoc_args['format'] );

        if ( empty( 
$args ) && empty( $assoc_args ) ) {
            
$assoc_args['scope'] = 'all';
        }

        
$tables Utils\wp_get_table_names$args$assoc_args );

        if ( 
'csv' === $format ) {
            
WP_CLI::lineimplode','$tables ) );
        } else {
            foreach ( 
$tables as $table ) {
                
WP_CLI::line$table );
            }
        }
    }

    
/**
     * Displays the database name and size.
     *
     * Display the database name and size for `DB_NAME` specified in wp-config.php.
     * The size defaults to a human-readable number.
     *
     * Available size formats include:
     * * b (bytes)
     * * kb (kilobytes)
     * * mb (megabytes)
     * * gb (gigabytes)
     * * tb (terabytes)
     * * B   (ISO Byte setting, with no conversion)
     * * KB  (ISO Kilobyte setting, with 1 KB  = 1,000 B)
     * * KiB (ISO Kibibyte setting, with 1 KiB = 1,024 B)
     * * MB  (ISO Megabyte setting, with 1 MB  = 1,000 KB)
     * * MiB (ISO Mebibyte setting, with 1 MiB = 1,024 KiB)
     * * GB  (ISO Gigabyte setting, with 1 GB  = 1,000 MB)
     * * GiB (ISO Gibibyte setting, with 1 GiB = 1,024 MiB)
     * * TB  (ISO Terabyte setting, with 1 TB  = 1,000 GB)
     * * TiB (ISO Tebibyte setting, with 1 TiB = 1,024 GiB)
     *
     * ## OPTIONS
     *
     * [--size_format=<format>]
     * : Display the database size only, as a bare number.
     * ---
     * options:
     *   - b
     *   - kb
     *   - mb
     *   - gb
     *   - tb
     *   - B
     *   - KB
     *   - KiB
     *   - MB
     *   - MiB
     *   - GB
     *   - GiB
     *   - TB
     *   - TiB
     * ---
     *
     * [--tables]
     * : Display each table name and size instead of the database size.
     *
     * [--human-readable]
     * : Display database sizes in human readable formats.
     *
     * [--format=<format>]
     * : Render output in a particular format.
     * ---
     * options:
     *   - table
     *   - csv
     *   - json
     *   - yaml
     * ---
     *
     * [--scope=<scope>]
     * : Can be all, global, ms_global, blog, or old tables. Defaults to all.
     *
     * [--network]
     * : List all the tables in a multisite install.
     *
     * [--decimals=<decimals>]
     * : Number of digits after decimal point. Defaults to 0.
     *
     * [--all-tables-with-prefix]
     * : List all tables that match the table prefix even if not registered on $wpdb. Overrides --network.
     *
     * [--all-tables]
     * : List all tables in the database, regardless of the prefix, and even if not registered on $wpdb. Overrides --all-tables-with-prefix.
     *
     * ## EXAMPLES
     *
     *     $ wp db size
     *     +-------------------+------+
     *     | Name              | Size |
     *     +-------------------+------+
     *     | wordpress_default | 6 MB |
     *     +-------------------+------+
     *
     *     $ wp db size --tables
     *     +-----------------------+-------+
     *     | Name                  | Size  |
     *     +-----------------------+-------+
     *     | wp_users              | 64 KB |
     *     | wp_usermeta           | 48 KB |
     *     | wp_posts              | 80 KB |
     *     | wp_comments           | 96 KB |
     *     | wp_links              | 32 KB |
     *     | wp_options            | 32 KB |
     *     | wp_postmeta           | 48 KB |
     *     | wp_terms              | 48 KB |
     *     | wp_term_taxonomy      | 48 KB |
     *     | wp_term_relationships | 32 KB |
     *     | wp_termmeta           | 48 KB |
     *     | wp_commentmeta        | 48 KB |
     *     +-----------------------+-------+
     *
     *     $ wp db size --size_format=b
     *     5865472
     *
     *     $ wp db size --size_format=kb
     *     5728
     *
     *     $ wp db size --size_format=mb
     *     6
     *
     * @when after_wp_load
     */
    
public function size$args$assoc_args ) {

        global 
$wpdb;

        
$format                 Utils\get_flag_value$assoc_args'format' );
        
$size_format            Utils\get_flag_value$assoc_args'size_format' );
        
$human_readable         Utils\get_flag_value$assoc_args'human-readable'false );
        
$tables                 Utils\get_flag_value$assoc_args'tables' );
        
$tables                 = ! empty( $tables );
        
$all_tables             Utils\get_flag_value$assoc_args'all-tables' );
        
$all_tables_with_prefix Utils\get_flag_value$assoc_args'all-tables-with-prefix' );

        if ( ! 
is_null$size_format ) && $human_readable ) {
            
WP_CLI::error'Cannot use --size_format and --human-readable arguments at the same time.' );
        }

        unset( 
$assoc_args['format'] );
        unset( 
$assoc_args['size_format'] );
        unset( 
$assoc_args['human-readable'] );
        unset( 
$assoc_args['tables'] );

        if ( empty( 
$args ) && empty( $assoc_args ) ) {
            
$assoc_args['scope'] = 'all';
        }

        
// Build rows for the formatter.
        
$rows   = [];
        
$fields = [ 'Name''Size' ];

        
$default_unit = ( empty( $size_format ) && ! $human_readable ) ? ' B' '';

        if ( 
$tables || $all_tables || $all_tables_with_prefix ) {

            
// Add all of the table sizes.
            
foreach ( Utils\wp_get_table_names$args$assoc_args ) as $table_name ) {

                
// Get the table size.
                
$table_bytes $wpdb->get_var(
                    
$wpdb->prepare(
                        
'SELECT SUM(data_length + index_length) FROM information_schema.TABLES where table_schema = %s and Table_Name = %s GROUP BY Table_Name LIMIT 1',
                        
DB_NAME,
                        
$table_name
                    
)
                );

                
// Add the table size to the list.
                
$rows[] = [
                    
'Name' => $table_name,
                    
'Size' => strtoupper$table_bytes ) . $default_unit,
                ];
            }
        } else {

            
// Get the database size.
            
$db_bytes $wpdb->get_var(
                
$wpdb->prepare(
                    
'SELECT SUM(data_length + index_length) FROM information_schema.TABLES where table_schema = %s GROUP BY table_schema;',
                    
DB_NAME
                
)
            );

            
// Add the database size to the list.
            
$rows[] = [
                
'Name' => DB_NAME,
                
'Size' => strtoupper$db_bytes ) . $default_unit,
            ];
        }

        if ( ! empty( 
$size_format ) || $human_readable ) {
            foreach ( 
$rows as $index => $row ) {
                
// phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- Backfilling WP native constants.
                
if ( ! defined'KB_IN_BYTES' ) ) {
                    
define'KB_IN_BYTES'1024 );
                }
                if ( ! 
defined'MB_IN_BYTES' ) ) {
                    
define'MB_IN_BYTES'1024 KB_IN_BYTES );
                }
                if ( ! 
defined'GB_IN_BYTES' ) ) {
                    
define'GB_IN_BYTES'1024 MB_IN_BYTES );
                }
                if ( ! 
defined'TB_IN_BYTES' ) ) {
                    
define'TB_IN_BYTES'1024 GB_IN_BYTES );
                }
                
// phpcs:enable

                
if ( $human_readable ) {
                    
$size_key floorlog$row['Size'] ) / log1000 ) );
                    
$sizes    = [ 'B''KB''MB''GB''TB' ];

                    
$size_format = isset( $sizes$size_key ] ) ? $sizes$size_key ] : $sizes[0];
                }

                
// Display the database size as a number.
                
switch ( $size_format ) {
                    case 
'TB':
                        
$divisor pow1000);
                        break;

                    case 
'GB':
                        
$divisor pow1000);
                        break;

                    case 
'MB':
                        
$divisor pow1000);
                        break;

                    case 
'KB':
                        
$divisor 1000;
                        break;

                    case 
'tb':
                    case 
'TiB':
                        
$divisor TB_IN_BYTES;
                        break;

                    case 
'gb':
                    case 
'GiB':
                        
$divisor GB_IN_BYTES;
                        break;

                    case 
'mb':
                    case 
'MiB':
                        
$divisor MB_IN_BYTES;
                        break;

                    case 
'kb':
                    case 
'KiB':
                        
$divisor KB_IN_BYTES;
                        break;

                    case 
'b':
                    case 
'B':
                    default:
                        
$divisor 1;
                        break;
                }
                    
$size_format_display preg_replace'/IB$/u''iB'strtoupper$size_format ) );

                    
$decimals               Utils\get_flag_value$assoc_args'decimals');
                    
$rows$index ]['Size'] = round$row['Size'] / $divisor$decimals ) . ' ' $size_format_display;
            }
        }

        if ( ! empty( 
$size_format ) && ! $tables && ! $format && ! $human_readable && true !== $all_tables && true !== $all_tables_with_prefix ) {
            
WP_CLI::linestr_replace{$size_format_display}"''$rows[0]['Size'] ) );
        } else {
            
// Display the rows.
            
$args = [
                
'format' => $format,
            ];

            
$formatter = new Formatter$args$fields );
            
$formatter->display_items$rows );
        }
    }

    
/**
     * Displays the database table prefix.
     *
     * Display the database table prefix, as defined by the database handler's interpretation of the current site.
     *
     * ## EXAMPLES
     *
     *     $ wp db prefix
     *     wp_
     *
     * @when after_wp_load
     */
    
public function prefix() {
        global 
$wpdb;

        
WP_CLI::log$wpdb->prefix );
    }

    
/**
     * Finds a string in the database.
     *
     * Searches through all of the text columns in a selection of database tables for a given string, Outputs colorized references to the string.
     *
     * Defaults to searching through all tables registered to $wpdb. On multisite, this default is limited to the tables for the current site.
     *
     * ## OPTIONS
     *
     * <search>
     * : String to search for. The search is case-insensitive by default.
     *
     * [<tables>...]
     * : One or more tables to search through for the string.
     *
     * [--network]
     * : Search through all the tables registered to $wpdb in a multisite install.
     *
     * [--all-tables-with-prefix]
     * : Search through all tables that match the registered table prefix, even if not registered on $wpdb. On one hand, sometimes plugins use tables without registering them to $wpdb. On another hand, this could return tables you don't expect. Overrides --network.
     *
     * [--all-tables]
     * : Search through ALL tables in the database, regardless of the prefix, and even if not registered on $wpdb. Overrides --network and --all-tables-with-prefix.
     *
     * [--before_context=<num>]
     * : Number of characters to display before the match.
     * ---
     * default: 40
     * ---
     *
     * [--after_context=<num>]
     * : Number of characters to display after the match.
     * ---
     * default: 40
     * ---
     *
     * [--regex]
     * : Runs the search as a regular expression (without delimiters). The search becomes case-sensitive (i.e. no PCRE flags are added). Delimiters must be escaped if they occur in the expression. Because the search is run on individual columns, you can use the `^` and `$` tokens to mark the start and end of a match, respectively.
     *
     * [--regex-flags=<regex-flags>]
     * : Pass PCRE modifiers to the regex search (e.g. 'i' for case-insensitivity).
     *
     * [--regex-delimiter=<regex-delimiter>]
     * : The delimiter to use for the regex. It must be escaped if it appears in the search string. The default value is the result of `chr(1)`.
     *
     * [--table_column_once]
     * : Output the 'table:column' line once before all matching row lines in the table column rather than before each matching row.
     *
     * [--one_line]
     * : Place the 'table:column' output on the same line as the row id and match ('table:column:id:match'). Overrides --table_column_once.
     *
     * [--matches_only]
     * : Only output the string matches (including context). No 'table:column's or row ids are outputted.
     *
     * [--stats]
     * : Output stats on the number of matches found, time taken, tables/columns/rows searched, tables skipped.
     *
     * [--table_column_color=<color_code>]
     * : Percent color code to use for the 'table:column' output. For a list of available percent color codes, see below. Default '%G' (bright green).
     *
     * [--id_color=<color_code>]
     * : Percent color code to use for the row id output. For a list of available percent color codes, see below. Default '%Y' (bright yellow).
     *
     * [--match_color=<color_code>]
     * : Percent color code to use for the match (unless both before and after context are 0, when no color code is used). For a list of available percent color codes, see below. Default '%3%k' (black on a mustard background).
     *
     * The percent color codes available are:
     *
     * | Code | Color
     * | ---- | -----
     * |  %y  | Yellow (dark) (mustard)
     * |  %g  | Green (dark)
     * |  %b  | Blue (dark)
     * |  %r  | Red (dark)
     * |  %m  | Magenta (dark)
     * |  %c  | Cyan (dark)
     * |  %w  | White (dark) (light gray)
     * |  %k  | Black
     * |  %Y  | Yellow (bright)
     * |  %G  | Green (bright)
     * |  %B  | Blue (bright)
     * |  %R  | Red (bright)
     * |  %M  | Magenta (bright)
     * |  %C  | Cyan (bright)
     * |  %W  | White
     * |  %K  | Black (bright) (dark gray)
     * |  %3  | Yellow background (dark) (mustard)
     * |  %2  | Green background (dark)
     * |  %4  | Blue background (dark)
     * |  %1  | Red background (dark)
     * |  %5  | Magenta background (dark)
     * |  %6  | Cyan background (dark)
     * |  %7  | White background (dark) (light gray)
     * |  %0  | Black background
     * |  %8  | Reverse
     * |  %U  | Underline
     * |  %F  | Blink (unlikely to work)
     *
     * They can be concatenated. For instance, the default match color of black on a mustard (dark yellow) background `%3%k` can be made black on a bright yellow background with `%Y%0%8`.
     *
     * ## EXAMPLES
     *
     *     # Search through the database for the 'wordpress-develop' string
     *     $ wp db search wordpress-develop
     *     wp_options:option_value
     *     1:http://wordpress-develop.dev
     *     wp_options:option_value
     *     1:https://example.com/foo
     *         ...
     *
     *     # Search through a multisite database on the subsite 'foo' for the 'example.com' string
     *     $ wp db search example.com --url=example.com/foo
     *     wp_2_comments:comment_author_url
     *     1:https://example.com/
     *     wp_2_options:option_value
     *         ...
     *
     *     # Search through the database for the 'https?://' regular expression, printing stats.
     *     $ wp db search 'https?://' --regex --stats
     *     wp_comments:comment_author_url
     *     1:https://wordpress.org/
     *         ...
     *     Success: Found 99146 matches in 10.752s (10.559s searching). Searched 12 tables, 53 columns, 1358907 rows. 1 table skipped: wp_term_relationships.
     *
     *     # SQL search database table 'wp_options' where 'option_name' match 'foo'
     *     wp db query 'SELECT * FROM wp_options WHERE option_name like "%foo%"' --skip-column-names
     *     +----+--------------+--------------------------------+-----+
     *     | 98 | foo_options  | a:1:{s:12:"_multiwidget";i:1;} | yes |
     *     | 99 | foo_settings | a:0:{}                         | yes |
     *     +----+--------------+--------------------------------+-----+
     *
     *     # SQL search and delete records from database table 'wp_options' where 'option_name' match 'foo'
     *     wp db query "DELETE from wp_options where option_id in ($(wp db query "SELECT GROUP_CONCAT(option_id SEPARATOR ',') from wp_options where option_name like '%foo%';" --silent --skip-column-names))"
     *
     * @when after_wp_load
     */
    
public function search$args$assoc_args ) {
        global 
$wpdb;

        
$start_run_time microtimetrue );

        
$search array_shift$args );

        
$before_context Utils\get_flag_value$assoc_args'before_context'40 );
        
$before_context '' === $before_context $before_context : (int) $before_context;

        
$after_context Utils\get_flag_value$assoc_args'after_context'40 );
        
$after_context '' === $after_context $after_context : (int) $after_context;

        
$regex Utils\get_flag_value$assoc_args'regex'false );
        if ( 
false !== $regex ) {
            
$regex_flags             Utils\get_flag_value$assoc_args'regex-flags'false );
            
$default_regex_delimiter false;
            
$regex_delimiter         Utils\get_flag_value$assoc_args'regex-delimiter''' );
            if ( 
'' === $regex_delimiter ) {
                
$regex_delimiter         chr);
                
$default_regex_delimiter true;
            }
        }

        
$colors self::get_colors(
            
$assoc_args,
            [
                
'table_column' => '%G',
                
'id'           => '%Y',
                
'match'        => $before_context || $after_context '%3%k' '',
            ]
        );

        
$table_column_once Utils\get_flag_value$assoc_args'table_column_once'false );
        
$one_line          Utils\get_flag_value$assoc_args'one_line'false );
        
$matches_only      Utils\get_flag_value$assoc_args'matches_only'false );
        
$stats             Utils\get_flag_value$assoc_args'stats'false );

        
$column_count 0;
        
$row_count    0;
        
$match_count  0;
        
$skipped      = [];

        if ( 
$regex ) {
            
// Note the user must escape the delimiter in the search.
            
$search_regex $regex_delimiter $search $regex_delimiter;
            if ( 
$regex_flags ) {
                
$search_regex .= $regex_flags;
            }
            
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Unsure why this is needed, leaving in for now.
            
if ( false === @preg_match$search_regex'' ) ) {
                if ( 
$default_regex_delimiter ) {
                    
$flags_msg $regex_flags "flags '$regex_flags'" 'no flags';
                    
$msg       "The regex pattern '$search' with default delimiter 'chr(1)' and {$flags_msg} fails.";
                } else {
                    
$msg "The regex '$search_regex' fails.";
                }
                
WP_CLI::error$msg );
            }
        } else {
            
$search_regex    '#' preg_quote$search'#' ) . '#i';
            
$esc_like_search '%' Utils\esc_like$search ) . '%';
        }

        
$encoding null;
        if ( 
=== strpos$wpdb->charsetself::ENCODING_UTF8 ) ) {
            
$encoding 'UTF-8';
        }

        
$tables Utils\wp_get_table_names$args$assoc_args );

        
$start_search_time microtimetrue );

        foreach ( 
$tables as $table ) {
            list( 
$primary_keys$text_columns$all_columns ) = self::get_columns$table );
            if ( ! 
$all_columns ) {
                
WP_CLI::error"No such table '$table'." );
            }
            if ( ! 
$text_columns ) {
                if ( 
$stats ) {
                    
$skipped[] = $table;
                } else {
                    
// Don't bother warning for term relationships (which is just 3 int columns).
                    
if ( ! preg_match'/_term_relationships$/'$table ) ) {
                        
WP_CLI::warning$primary_keys "No text columns for table '$table' - skipped." "No primary key or text columns for table '$table' - skipped." );
                    }
                }
                continue;
            }
            
$table_sql     self::esc_sql_ident$table );
            
$column_count += count$text_columns );
            if ( ! 
$primary_keys ) {
                
WP_CLI::warning"No primary key for table '$table'. No row ids will be outputted." );
                
$primary_key     '';
                
$primary_key_sql '';
            } else {
                
$primary_key     array_shift$primary_keys );
                
$primary_key_sql self::esc_sql_ident$primary_key ) . ', ';
            }

            foreach ( 
$text_columns as $column ) {
                
$column_sql self::esc_sql_ident$column );
                if ( 
$regex ) {
                    
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Escaped through esc_sql_ident/esc_like.
                    
$results $wpdb->get_results"SELECT {$primary_key_sql}{$column_sql} FROM {$table_sql});
                } else {
                    
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Escaped through esc_sql_ident/esc_like.
                    
$results $wpdb->get_results$wpdb->prepare"SELECT {$primary_key_sql}{$column_sql} FROM {$table_sql} WHERE {$column_sql} LIKE %s;"$esc_like_search ) );
                }
                if ( 
$results ) {
                    
$row_count                  += count$results );
                    
$table_column_val            $colors['table_column'][0] . "{$table}:{$column}$colors['table_column'][1];
                    
$outputted_table_column_once false;
                    foreach ( 
$results as $result ) {
                        
$col_val $result->$column;
                        if ( 
preg_match_all$search_regex$col_val$matchesPREG_OFFSET_CAPTURE ) ) {
                            if ( ! 
$matches_only && ( ! $table_column_once || ! $outputted_table_column_once ) && ! $one_line ) {
                                
WP_CLI::log$table_column_val );
                                
$outputted_table_column_once true;
                            }
                            
$pk_val $primary_key ? ( $colors['id'][0] . $result->$primary_key $colors['id'][1] . ':' ) : '';

                            
$bits         = [];
                            
$col_encoding $encoding;
                            if ( ! 
$col_encoding && ( $before_context || $after_context ) && function_exists'mb_detect_encoding' ) ) {
                                
$col_encoding mb_detect_encoding$col_valnulltrue /*strict*/ );
                            }
                            
$append_next false;
                            
$last_offset 0;
                            
$match_cnt   count$matches[0] );
                            for ( 
$i 0$i $match_cnt$i++ ) {
                                
$match           $matches[0][ $i ][0];
                                
$offset          $matches[0][ $i ][1];
                                
$log             $colors['match'][0] . $match $colors['match'][1];
                                
$before          '';
                                
$after           '';
                                
$after_shortened false;

                                
// Offsets are in bytes, so need to use `strlen()` and `substr()` before using `safe_substr()`.
                                
if ( $before_context && $offset && ! $append_next ) {
                                    
$before \cli\safe_substrsubstr$col_val$last_offset$offset $last_offset ), -$before_contextnull /*length*/false /*is_width*/$col_encoding );
                                }
                                if ( 
$after_context ) {
                                    
$end_offset $offset strlen$match );
                                    
$after      \cli\safe_substrsubstr$col_val$end_offset ), 0$after_contextfalse /*is_width*/$col_encoding );
                                    
// To lessen context duplication in output, shorten the after context if it overlaps with the next match.
                                    
if ( $i $match_cnt && $end_offset strlen$after ) > $matches[0][ $i ][1] ) {
                                        
$after           substr$after0$matches[0][ $i ][1] - $end_offset );
                                        
$after_shortened true;
                                        
// On the next iteration, will append with no before context.
                                    
}
                                }
                                if ( 
$append_next ) {
                                    
$cnt               count$bits );
                                    
$bits$cnt ] .= $log $after;
                                } else {
                                    
$bits[] = $before $log $after;
                                }
                                
$append_next $after_shortened;
                                
$last_offset $offset;
                            }
                            
$match_count += $match_cnt;
                            
$col_val      implode' [...] '$bits );

                            
WP_CLI::log$matches_only $col_val : ( $one_line "{$table_column_val}:{$pk_val}{$col_val}"{$pk_val}{$col_val}) );
                        }
                    }
                }
            }
        }

        if ( 
$stats ) {
            
$table_count   count$tables );
            
$skipped_count count$skipped );
            
$match_str     === $match_count 'match' 'matches';
            
$table_str     === $table_count 'table' 'tables';
            
$column_str    === $column_count 'column' 'columns';
            
$row_str       === $row_count 'row' 'rows';
            
$skipped_str   === $skipped_count 'table skipped' 'tables skipped';
            if ( 
!== $skipped_count ) {
                
$skipped_str .= ': ' implode', '$skipped );
            }
            
$end_time    microtimetrue );
            
$run_time    $end_time $start_run_time;
            
$search_time $end_time $start_search_time;
            
$stats_msg   sprintf(
                
'Found %d %s in %.3fs (%.3fs searching). Searched %d %s, %d %s, %d %s. %d %s.',
                
$match_count,
                
$match_str,
                
$run_time,
                
$search_time,
                
$table_count,
                
$table_str,
                
$column_count,
                
$column_str,
                
$row_count,
                
$row_str,
                
$skipped_count,
                
$skipped_str
            
);
            
WP_CLI::success$stats_msg );
        }
    }

    
/**
     * Displays information about a given table.
     *
     * ## OPTIONS
     *
     * <table>
     * : Name of the database table.
     *
     * [--format]
     * : Render output in a particular format.
     * ---
     * default: table
     * options:
     *   - table
     *   - csv
     *   - json
     *   - yaml
     * ---
     *
     * ## EXAMPLES
     *
     *     $ wp db columns wp_posts
     *     +-----------------------+---------------------+------+-----+---------------------+----------------+
     *     |         Field         |        Type         | Null | Key |       Default       |     Extra      |
     *     +-----------------------+---------------------+------+-----+---------------------+----------------+
     *     | ID                    | bigint(20) unsigned | NO   | PRI |                     | auto_increment |
     *     | post_author           | bigint(20) unsigned | NO   | MUL | 0                   |                |
     *     | post_date             | datetime            | NO   |     | 0000-00-00 00:00:00 |                |
     *     | post_date_gmt         | datetime            | NO   |     | 0000-00-00 00:00:00 |                |
     *     | post_content          | longtext            | NO   |     |                     |                |
     *     | post_title            | text                | NO   |     |                     |                |
     *     | post_excerpt          | text                | NO   |     |                     |                |
     *     | post_status           | varchar(20)         | NO   |     | publish             |                |
     *     | comment_status        | varchar(20)         | NO   |     | open                |                |
     *     | ping_status           | varchar(20)         | NO   |     | open                |                |
     *     | post_password         | varchar(255)        | NO   |     |                     |                |
     *     | post_name             | varchar(200)        | NO   | MUL |                     |                |
     *     | to_ping               | text                | NO   |     |                     |                |
     *     | pinged                | text                | NO   |     |                     |                |
     *     | post_modified         | datetime            | NO   |     | 0000-00-00 00:00:00 |                |
     *     | post_modified_gmt     | datetime            | NO   |     | 0000-00-00 00:00:00 |                |
     *     | post_content_filtered | longtext            | NO   |     |                     |                |
     *     | post_parent           | bigint(20) unsigned | NO   | MUL | 0                   |                |
     *     | guid                  | varchar(255)        | NO   |     |                     |                |
     *     | menu_order            | int(11)             | NO   |     | 0                   |                |
     *     | post_type             | varchar(20)         | NO   | MUL | post                |                |
     *     | post_mime_type        | varchar(100)        | NO   |     |                     |                |
     *     | comment_count         | bigint(20)          | NO   |     | 0                   |                |
     *     +-----------------------+---------------------+------+-----+---------------------+----------------+
     *
     * @when after_wp_load
     */
    
public function columns$args$assoc_args ) {
        global 
$wpdb;

        
$format Utils\get_flag_value$assoc_args'format' );

        
Utils\wp_get_table_names( [ $args[0] ], [ 'all-tables' => true ] );

        
$columns $wpdb->get_results(
            
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Asserted to be a valid table name through wp_get_table_names.
            
'SHOW COLUMNS FROM ' $args[0]
        );

        
$formatter_fields = [ 'Field''Type''Null''Key''Default''Extra' ];
        
$formatter_args   = [
            
'format' => $format,
        ];

        
$formatter = new Formatter$formatter_args$formatter_fields );
        
$formatter->display_items$columns );
    }

    private static function 
get_create_query() {

        
$create_query sprintf'CREATE DATABASE %s'self::esc_sql_identDB_NAME ) );
        if ( 
defined'DB_CHARSET' ) && constant'DB_CHARSET' ) ) {
            
$create_query .= sprintf' DEFAULT CHARSET %s'self::esc_sql_identDB_CHARSET ) );
        }
        if ( 
defined'DB_COLLATE' ) && constant'DB_COLLATE' ) ) {
            
$create_query .= sprintf' DEFAULT COLLATE %s'self::esc_sql_identDB_COLLATE ) );
        }
        return 
$create_query;
    }

    
/**
     * Run a single query via the 'mysql' binary.
     *
     * This includes the necessary setup to make sure the queries behave similar
     * to what WPDB produces.
     *
     * @param string $query      Query to execute.
     * @param array  $assoc_args Optional. Associative array of arguments.
     */
    
protected function run_query$query$assoc_args = [] ) {
        
// Ensure that the SQL mode is compatible with WPDB.
        
$query $this->get_sql_mode_query$assoc_args ) . $query;

        
WP_CLI::debug"Query: {$query}"'db' );

        
$mysql_args array_merge(
            
self::get_dbuser_dbpass_args$assoc_args ),
            
self::get_mysql_args$assoc_args )
        );

        
self::run(
            
sprintf(
                
'/usr/bin/env mysql%s --no-auto-rehash',
                
$this->get_defaults_flag_string$assoc_args )
            ),
            
array_merge( [ 'execute' => $query ], $mysql_args )
        );
    }

    
/**
     * Run a MySQL command and optionally return the output.
     *
     * @param string $cmd           Command to run.
     * @param array  $assoc_args    Optional. Associative array of arguments to
     *                              use.
     * @param bool   $send_to_shell Optional. Whether to send STDOUT and STDERR
     *                              immediately to the shell. Defaults to true.
     * @param bool   $interactive   Optional. Whether MySQL is meant to be
     *                              executed as an interactive process. Defaults
     *                              to false.
     *
     * @return array {
     *     Associative array containing STDOUT and STDERR output.
     *
     *     @type string $stdout    Output that was sent to STDOUT.
     *     @type string $stderr    Output that was sent to STDERR.
     *     @type int    $exit_code Exit code of the process.
     * }
     */
    
private static function run$cmd$assoc_args = [], $send_to_shell true$interactive false ) {
        
$required = [
            
'host' => DB_HOST,
            
'user' => DB_USER,
            
'pass' => DB_PASSWORD,
        ];

        if ( ! isset( 
$assoc_args['default-character-set'] )
            && 
defined'DB_CHARSET' ) && constant'DB_CHARSET' ) ) {
            
$required['default-character-set'] = constant'DB_CHARSET' );
        }

        
// Using 'dbuser' as option name to workaround clash with WP-CLI's global WP 'user' parameter, with 'dbpass' also available for tidyness.
        
if ( isset( $assoc_args['dbuser'] ) ) {
            
$required['user'] = $assoc_args['dbuser'];
            unset( 
$assoc_args['dbuser'] );
        }
        if ( isset( 
$assoc_args['dbpass'] ) ) {
            
$required['pass'] = $assoc_args['dbpass'];
            unset( 
$assoc_args['dbpass'], $assoc_args['password'] );
        }

        
$final_args array_merge$required$assoc_args );

        
// Adapt ordering of arguments.
        
uksort(
            
$final_args,
            static function ( 
$a$b ) {
                switch ( 
$b ) {
                    case 
'force':
                        return -
1;
                    default:
                        return 
1;
                }
            }
        );

        return 
Utils\run_mysql_command$cmd$final_argsnull$send_to_shell$interactive );
    }

    
/**
     * Helper to pluck 'dbuser' and 'dbpass' from associative args array.
     *
     * @param array $assoc_args Associative args array.
     * @return array Array with `dbuser' and 'dbpass' set if in passed-in associative args array.
     */
    
private static function get_dbuser_dbpass_args$assoc_args ) {
        
$mysql_args = [];
        
$dbuser     Utils\get_flag_value$assoc_args'dbuser' );
        if ( 
null !== $dbuser ) {
            
$mysql_args['dbuser'] = $dbuser;
        }
        
$dbpass Utils\get_flag_value$assoc_args'dbpass' );
        if ( 
null !== $dbpass ) {
            
$mysql_args['dbpass'] = $dbpass;
        }
        return 
$mysql_args;
    }

    
/**
     * Gets the column names of a db table differentiated into key columns and text columns and all columns.
     *
     * @param string $table The table name.
     * @return array A 3 element array consisting of an array of primary key column names, an array of text column names, and an array containing all column names.
     */
    
private static function get_columns$table ) {
        global 
$wpdb;

        
$table_sql       self::esc_sql_ident$table );
        
$primary_keys    = [];
        
$text_columns    = [];
        
$all_columns     = [];
        
$suppress_errors $wpdb->suppress_errors();

        
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Escaped through esc_sql_ident/esc_like.
        
$results $wpdb->get_results"DESCRIBE $table_sql);
        if ( 
$results ) {
            foreach ( 
$results as $col ) {
                
// phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Property names come from database.
                
if ( 'PRI' === $col->Key ) {
                    
$primary_keys[] = $col->Field;
                }
                if ( 
self::is_text_col$col->Type ) ) {
                    
$text_columns[] = $col->Field;
                }
                
$all_columns[] = $col->Field;
                
// phpcs:enable
            
}
        }
        
$wpdb->suppress_errors$suppress_errors );
        return [ 
$primary_keys$text_columns$all_columns ];
    }

    
/**
     * Determines whether a column is considered text or not.
     *
     * @param string Column type.
     * @return bool True if text column, false otherwise.
     */
    
private static function is_text_col$type ) {
        foreach ( [ 
'text''varchar' ] as $token ) {
            if ( 
false !== strpos$type$token ) ) {
                return 
true;
            }
        }

        return 
false;
    }

    
/**
     * Escapes (backticks) MySQL identifiers (aka schema object names) - i.e. column names, table names, and database/index/alias/view etc names.
     * See https://dev.mysql.com/doc/refman/5.5/en/identifiers.html
     *
     * @param string|array $idents A single identifier or an array of identifiers.
     * @return string|array An escaped string if given a string, or an array of escaped strings if given an array of strings.
     */
    
private static function esc_sql_ident$idents ) {
        
$backtick = static function ( $v ) {
            
// Escape any backticks in the identifier by doubling.
            
return '`' str_replace'`''``'$v ) . '`';
        };
        if ( 
is_string$idents ) ) {
            return 
$backtick$idents );
        }
        return 
array_map$backtick$idents );
    }

    
/**
     * Gets the color codes from the options if any, and returns the passed in array colorized with 2 elements per entry, a color code (or '') and a reset (or '').
     *
     * @param array $assoc_args The associative argument array passed to the command.
     * @param array $colors Array of default percent color code strings keyed by the 3 color contexts 'table_column', 'id', 'match'.
     * @return array Array containing 3 2-element arrays.
     */
    
private function get_colors$assoc_args$colors ) {
        
$color_reset WP_CLI::colorize'%n' );

        
$color_codes implode(
            
'',
            
array_map(
                function ( 
$v ) {
                    return 
substr$v);
                },
                
array_keys\cli\Colors::getColors() )
            )
        );

        
$color_codes_regex '/^(?:%[' $color_codes '])*$/';

        foreach ( 
array_keys$colors ) as $color_col ) {
            
$col_color_flag Utils\get_flag_value$assoc_args$color_col '_color'false );
            if ( 
false !== $col_color_flag ) {
                if ( ! 
preg_match$color_codes_regex$col_color_flag$matches ) ) {
                    
WP_CLI::warning"Unrecognized percent color code '$col_color_flag' for '{$color_col}_color'." );
                } else {
                    
$colors$color_col ] = $matches[0];
                }
            }
            
$colors$color_col ] = $colors$color_col ] ? [ WP_CLI::colorize$colors$color_col ] ), $color_reset ] : [ '''' ];
        }

        return 
$colors;
    }

    
/**
     * Helper to pluck `mysql` options from associative args array.
     *
     * @param array $assoc_args Associative args array.
     * @return array Array with `mysql` options set if in passed-in associative args array.
     */
    
private static function get_mysql_args$assoc_args ) {

        
$allowed_mysql_options = [
            
'auto-rehash',
            
'auto-vertical-output',
            
'batch',
            
'binary-as-hex',
            
'binary-mode',
            
'bind-address',
            
'character-sets-dir',
            
'column-names',
            
'column-type-info',
            
'comments',
            
'compress',
            
'connect-expired-password',
            
'connect_timeout',
            
'database',
            
'debug',
            
'debug-check',
            
'debug-info',
            
'default-auth',
            
'default-character-set',
            
'defaults-extra-file',
            
'defaults-file',
            
'defaults-group-suffix',
            
'delimiter',
            
'enable-cleartext-plugin',
            
'execute',
            
'force',
            
'get-server-public-key',
            
'help',
            
'histignore',
            
'host',
            
'html',
            
'ignore-spaces',
            
'init-command',
            
'line-numbers',
            
'local-infile',
            
'login-path',
            
'max_allowed_packet',
            
'max_join_size',
            
'named-commands',
            
'net_buffer_length',
            
'no-beep',
            
'one-database',
            
'pager',
            
'pipe',
            
'plugin-dir',
            
'port',
            
'print-defaults',
            
'protocol',
            
'quick',
            
'raw',
            
'reconnect',
            
'i-am-a-dummy',
            
'safe-updates',
            
'secure-auth',
            
'select_limit',
            
'server-public-key-path',
            
'shared-memory-base-name',
            
'show-warnings',
            
'sigint-ignore',
            
'silent',
            
'skip-auto-rehash',
            
'skip-column-names',
            
'skip-line-numbers',
            
'skip-named-commands',
            
'skip-pager',
            
'skip-reconnect',
            
'socket',
            
'ssl-ca',
            
'ssl-capath',
            
'ssl-cert',
            
'ssl-cipher',
            
'ssl-crl',
            
'ssl-crlpath',
            
'ssl-fips-mode',
            
'ssl-key',
            
'ssl-mode',
            
'syslog',
            
'table',
            
'tee',
            
'tls-version',
            
'unbuffered',
            
'verbose',
            
'version',
            
'vertical',
            
'wait',
            
'xml',
        ];

        
$mysql_args = [];

        foreach ( 
$assoc_args as $mysql_option_key => $mysql_option_value ) {
            
// Check flags to make sure they only contain valid options.
            
if ( in_array$mysql_option_key$allowed_mysql_optionstrue ) && ! empty( $mysql_option_value ) ) {
                
$mysql_args$mysql_option_key ] = $mysql_option_value;
            }
        }

        return 
$mysql_args;
    }

    
/**
     * Writes out the `--no-defaults` flag for MySQL commands unless the --defaults flag is specified for the WP_CLI command.
     *
     * @param array $assoc_args Associative args array.
     * @return string Either the '--no-defaults' flag for use in the command or an empty string.
     */
    
protected function get_defaults_flag_string( &$assoc_args ) {

        
$flag_string ' --no-defaults';

        if ( 
array_key_exists'defaults'$assoc_args ) ) {

            if ( 
true === Utils\get_flag_value$assoc_args'defaults' ) ) {
                
$flag_string '';
            }

            unset( 
$assoc_args['defaults'] );

        }

        return 
$flag_string;
    }

    
/**
     * Get the query to change the current SQL mode, and ensure its WordPress compatibility.
     *
     * If no modes are passed, it will ensure the current MySQL server modes are
     * compatible.
     *
     * Copied and adapted from WordPress Core code.
     *
     * @see https://github.com/WordPress/wordpress-develop/blob/5.4.0/src/wp-includes/wp-db.php#L817-L880
     *
     * @param array $assoc_args The associative argument array passed to the command.
     * @param array $modes      Optional. A list of SQL modes to set.
     * @return string Query string to use for setting the SQL modes to a
     *                compatible state.
     */
    
protected function get_sql_mode_query$assoc_args$modes = [] ) {
        if ( empty( 
$modes ) ) {
            
$modes $this->get_current_sql_modes$assoc_args );
        }

        
$modes array_change_key_case$modesCASE_UPPER );

        
$is_mode_adaptation_needed false;
        foreach ( 
$modes as $i => $mode ) {
            if ( 
in_array$mode$this->sql_incompatible_modestrue ) ) {
                unset( 
$modes$i ] );
                
$is_mode_adaptation_needed true;
            }
        }

        if ( ! 
$is_mode_adaptation_needed ) {
            
WP_CLI::debug(
                
sprintf(
                    
'SQL modes look fine: %s',
                    
json_encode$modes )
                )
            );
            return 
'';
        }

        
WP_CLI::debug(
            
sprintf(
                
'SQL mode adaptation is needed: %s => %s',
                
json_encode$this->get_current_sql_modes$assoc_args ) ),
                
json_encode$modes )
            )
        );

        
$modes_str implode','$modes );

        return 
"SET SESSION sql_mode='{$modes_str}';";
    }

    
/**
     * Get the list of current SQL modes.
     *
     * @param array $assoc_args The associative argument array passed to the command.
     * @return string[] Array of SQL modes.
     */
    
protected function get_current_sql_modes$assoc_args ) {
        static 
$modes null;

        
// Make sure the provided arguments don't interfere with the expected
        // output here.
        
$args = [];
        foreach ( [] as 
$arg ) {
            if ( isset( 
$assoc_args$arg ] ) ) {
                
$args$arg ] = $assoc_args$arg ];
            }
        }

        if ( 
null === $modes ) {
            
$modes = [];

            list( 
$stdout$stderr$exit_code ) = self::run(
                
sprintf(
                    
'/usr/bin/env mysql%s --no-auto-rehash --batch --skip-column-names',
                    
$this->get_defaults_flag_string$assoc_args )
                ),
                
array_merge$args, [ 'execute' => 'SELECT @@SESSION.sql_mode' ] ),
                
false
            
);

            if ( 
$exit_code ) {
                
WP_CLI::error(
                    
'Failed to get current SQL modes.'
                    
. ( ! empty( $stderr ) ? " Reason: {$stderr}'' ),
                    
$exit_code
                
);
            }

            if ( ! empty( 
$stdout ) ) {
                
$modes array_filter(
                    
array_map(
                        
'trim',
                        
preg_split"/\r\n|\n|\r|,/"$stdout )
                    )
                );
            }

            if ( 
false === $modes ) {
                
$modes = [];
            }
        }

        return 
$modes;
    }
}
<?
php

if ( ! class_exists'WP_CLI' ) ) {
    return;
}

$wpcli_db_autoloader dirname__FILE__ ) . '/vendor/autoload.php';
if ( 
file_exists$wpcli_db_autoloader ) ) {
    require_once 
$wpcli_db_autoloader;
}

WP_CLI::add_command'db''DB_Command' );
<?
php
class Oxymel {
    private 
$xml;
    private 
$dom;
    private 
$current_element;
    private 
$last_inserted;
    private 
$go_deep_on_next_element 0;
    private 
$go_up_on_next_element 0;
    private 
$nesting_level 0;
    private 
$contains_nesting_level 0;
    private 
$indentation'  ';

    public function 
__construct() {
        
$this->xml '';
        
$this->init_new_dom();
    }

    public function 
to_string() {
        return 
$this->xml .= $this->indent$this->xml_from_dom(), $this->nesting_level );
    }

    public function 
__call$name$args ) {
        
array_unshift$args$name );
        return 
call_user_func_array( array( $this'tag' ), $args );
    }

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

    public function 
contains() {
        
$this->contains_nesting_level++;
        
$this->nesting_level++;
        if ( 
$this->go_deep_on_next_element ) {
            throw new 
OxymelException'contains cannot be used consecutively more than once' );
        }
        
$this->go_deep_on_next_element++;
        return 
$this;
    }

    public function 
end() {
        
$this->contains_nesting_level--;
        
$this->nesting_level--;
        if ( 
$this->contains_nesting_level ) {
            throw new 
OxymelException'end is used without a matching contains' );
        }
        
$this->go_up_on_next_element++;
        return 
$this;
    }

    public function 
tag$name$content_or_attributes null$attributes = array() ) {
        list( 
$content$attributes ) = $this->get_content_and_attributes_from_tag_args$content_or_attributes$attributes );
        
$is_opening =  === strpos$name'open_' );
        
$is_closing =  === strpos$name'close_' );
        
$name preg_replace("/^(open|close)_/"''$name );

        
$element $this->create_element$name$content$attributes );

        if ( !
$is_opening && !$is_closing )
            
$this->add_element_to_dom$element );
        elseif ( 
$is_opening )
            
$this->add_opening_tag_from_element$element );
        elseif ( 
$is_closing )
            
$this->add_closing_tag_from_tag_name$name );

        return 
$this;
    }

    public function 
cdata$text ) {
        
$this->add_element_to_dom$this->dom->createCDATASection$text ) );
        return 
$this;
    }

    public function 
text$text ) {
        
$this->add_element_to_dom$this->dom->createTextNode$text ) );
        return 
$this;
    }

    public function 
comment$text ) {
        
$this->add_element_to_dom$this->dom->createComment$text ) );
        return 
$this;
    }

    public function 
xml() {
        
$this->add_element_to_dom$this->dom->createProcessingInstruction'xml''version="1.0" encoding="UTF-8"' ) );
        return 
$this;
    }

    public function 
oxymelOxymel $other ) {
        foreach( 
$other->dom->childNodes as $child ) {
            
$child $this->dom->importNode$childtrue );
            
$this->add_element_to_dom$child );
        }
        return 
$this;
    }

    public function 
raw(  $raw_xml ) {
        if ( !
$raw_xml ) {
            return 
$this;
        }
        
$fragment $this->dom->createDocumentFragment();
        
$fragment->appendXML($raw_xml);
        
$this->add_element_to_dom$fragment );
        return 
$this;
    }

    private function 
add_element_to_dom$element ) {
        
$this->move_current_element_deep();
        
$this->move_current_element_up();
        
$this->last_inserted $this->current_element->appendChild($element);
    }

    private function 
move_current_element_deep() {
        if ( 
$this->go_deep_on_next_element ) {
            if ( !
$this->last_inserted ) {
                throw new 
OxymelException'contains has been used before adding any tags' );
            }
            
$this->current_element $this->last_inserted;
            
$this->go_deep_on_next_element--;
        }
    }

    private function 
move_current_element_up() {
        if ( 
$this->go_up_on_next_element ) {
            while ( 
$this->go_up_on_next_element ) {
                
$this->current_element $this->current_element->parentNode;
                
$this->go_up_on_next_element--;
            }
        }
    }

    private function 
get_content_and_attributes_from_tag_args$content_or_attributes, array $attributes ) {
        
$content null;
        if ( !
$attributes ) {
            if ( 
is_array$content_or_attributes ) )
                
$attributes $content_or_attributes;
            else
                
$content $content_or_attributes;
        } else {
            
$content $content_or_attributes;
        }
        return array( 
$content$attributes );
    }

    private function 
init_new_dom() {
        unset( 
$this->dom$this->current_element );
        
$this->dom = new DOMDocument();
        
$this->dom->formatOutput true;
        
$this->current_element $this->dom;
        
$this->last_inserted null;
    }

    private function 
xml_from_dom() {
        if ( 
!== $this->contains_nesting_level ) {
            throw new 
OxymelException'contains and end calls do not match' );
        }
        
$xml '';
        foreach( 
$this->dom->childNodes as $child ) {
            
$xml .= $this->dom->saveXML$child ) . "\n";
        }
        return 
$xml;
    }

    private function 
create_element$name$content$attributes ) {
        if ( !
is_null$content ) )
            
$element $this->dom->createElement$name$content );
        else
            
$element $this->dom->createElement$name );

        foreach( 
$attributes as $attribute_name => $attribute_value ) {
            
$element->setAttribute$attribute_name$attribute_value );
        }

        return 
$element;
    }

    private function 
add_opening_tag_from_element$element ) {
        
$this->xml .= $this->indent$this->xml_from_dom(), $this->nesting_level );
        
$tag $this->dom->saveXML($element);
        
$this->xml .= $this->indentstr_replace'/>''>'$tag ) . "\n"$this->nesting_level );
        
$this->nesting_level++;
        
$this->init_new_dom();
    }

    private function 
add_closing_tag_from_tag_name$name ) {
        
$this->xml .= $this->xml_from_dom();
        
$this->nesting_level--;
        if ( 
$this->nesting_level ) {
            
$this->xml $this->indent$this->xml, -$this->nesting_level );
            
$this->nesting_level 0;
        }
        
$this->xml .= $this->indent"</$name>\n"$this->nesting_level );
        
$this->init_new_dom();
    }

    private function 
indent$string$level ) {
        if ( !
$level ) {
            return 
$string;
        }
        
$lines explode"\n"$string );
        foreach( 
$lines as &$line ) {
            if ( !
trim$line ) )
                continue;
            
$line str_repeat$this->indentation$level ) . $line;
        }
        return 
implode"\n"$lines );
    }
}

class 
OxymelException extends Exception {
}
<?
php
require_once dirname__FILE__ ) . '/Oxymel.php';

class 
OxymelTest extends PHPUnit_Framework_TestCase {
    function 
__construct() {
        
$this->= new Oxymel;
    }

    function 
test_xml_adds_procesing_instrution_with_version_and_encoding() {
        
$this->a'<?xml version="1.0" encoding="UTF-8"?>'$this->x->xml );
    }

    function 
test_self_closing() {
        
$this->a('<baba/>'$this->x->baba);
    }
    function 
test_self_closing_method() {
        
$this->a('<baba/>'$this->x->baba());
    }
    function 
test_attribute_no_content() {
        
$this->a('<baba a="b" c="d"/>'$this->x->baba( array( 'a' => 'b''c' => 'd' ) ) );
    }

    function 
test_content_no_attribute() {
        
$this->a('<baba>content</baba>'$this->x->baba'content' ) );
    }

    function 
test_escapiing_of_content() {
        
$this->a('<baba>&lt;</baba>'$this->x->baba'<' ) );
    }

    function 
test_escaping_of_attributes() {
        
$this->a('<baba a="&lt;"/>'$this->x->baba( array( 'a' => '<' ) ) );
    }

    function 
test_content_and_attributes() {
        
$this->a('<baba a="b" c="d">content</baba>'$this->x->baba'content', array( 'a' => 'b''c' => 'd' ) ) );
    }

    function 
test_content_and_attributes_error() {
        
$this->setExpectedException'PHPUnit_Framework_Error' );
        
$this->a('<baba a="b" c="d">content</baba>'$this->x->baba( array( 'a' => 'b''c' => 'd' ), 'content' ) );
    }

    function 
test_go_down() {
        
$this->a('<baba>
  <dyado/>
</baba>'
$this->x->baba->contains->dyado->end );
    }

    function 
test_go_down_and_up() {
        
$this->a("<level0>
  <level1/>
</level0>
<level0/>"
$this->x->level0->contains->level1->end->level0 );
    }

    function 
test_cdata() {
        
$this->a('<baba><![CDATA[content]]></baba>'$this->x->baba->contains->cdata('content')->end);
    }

    function 
test_nested_cdata() {
        
$this->a'<![CDATA[x]]]]><![CDATA[>]]>'$this->x->cdata'x]]>' ) );
    }

    function 
test_raw() {
        
$this->a('<baba>
  <dyado/>
</baba>'
$this->x->baba->contains->raw('<dyado></dyado>')->end);
    }

    function 
test_raw_doesnt_do_anything_with_empty_arg() {
        
$this->a('<baba/>'$this->x->baba->raw'' ) );
    }

    function 
test_oxymel_ns() {
        
$oxymel = new Oxymel;
        
$oxymel->tag'wp:baba' );
        
$this->a'<wp:baba/>'$this->x->oxymel$oxymel ) );
    }

    function 
x_test_only_up_error() {
        
$this->a(''$this->x->end );
    }

    function 
test_open_in_the_end() {
        
$this->a("<baba/>
<newtag>"
$this->x->baba->open_newtag );
    }

    function 
test_open_with_attributes() {
        
$this->a("<baba/>
<newtag a=\"b\">"
$this->x->baba->open_newtag( array( 'a' => 'b' ) ) );
    }

    function 
test_dom_open_dom() {
        
$this->a("<baba/>
<newtag>
  <baba/>"
$this->x->baba->open_newtag->baba );
    }

    function 
test_close_in_the_beginning() {
        
$this->a("</oldtag>
<baba/>"
$this->x->close_oldtag->baba );
    }

    function 
test_dom_close_dom() {
        
$this->a("  <baba/>
</oldtag>
<baba/>"
$this->x->baba->close_oldtag->baba );
    }

    function 
test_baba() {
        
$this->a("<a>baba<x/></a>"$this->x->a('baba')->contains->x->end );
    }

    function 
test_comment() {
        
$this->a('<!--baba-->'$this->x->comment('baba'));
    }

    function 
test_two_invocations() {
        
$this->x->baba();
        
$this->x->dyado();
        
$this->a"<baba/>
<dyado/>"
$this->);
    }

    function 
test_double_end() {
        
$this->a"<out>
  <mid>
    <in/>
  </mid>
</out>
<nextout/>"
$this->x->out->contains->mid->contains->in->end->end->nextout );
    }

    function 
test_end_without_contains() {
        
$this->setExpectedException'OxymelException' );
        
$this->x->end->baba;
    }

    function 
test_leading_contains() {
        
$this->setExpectedException'OxymelException' );
        
$this->a'<baba/>'$this->x->contains->baba );
    }

    function 
test_consecutive_contains_should_error() {
        
$this->setExpectedException'OxymelException' );
        
$this->x->baba->contains->contains->dyado->end->wink;
    }

    function 
test_end_without_mathcing_contains_but_with_enough_parents() {
        
$this->setExpectedException'OxymelException' );
        
$this->x->contains->baba->end->end->baba;
    }

    function 
test_contains_after_newly_initialized_dom_should_error() {
        
$this->setExpectedException'OxymelException' );
        
$this->x->baba->open_rss->contains->baba->end;
    }

    function 
test_nested_open_tags_should_be_indented() {
        
$this->a"<baba>
  <baba>"
$this->x->open_baba->open_baba );
    }

    function 
test_normal_xml_between_two_opening_tags_should_be_indented() {
        
$this->a"<baba>
  <baba/>
  <baba>"
$this->x->open_baba->baba->open_baba );
    }

    function 
test_normal_xml_after_opening_tag_should_be_indented() {
        
$this->a"<baba>
  <baba/>"
$this->x->open_baba->baba );
    }

    function 
test_normal_xml_before_closing_tag_should_be_indented() {
        
$this->a"  <baba/>
</baba>"
$this->x->baba->close_baba );
    }

    function 
test_text_should_escape_the_content_inside() {
        
$this->a'a &amp; b'$this->x->text'a & b' ) );
    }

    function 
test_dangling_contains_should_be_caught_in_the_end() {
        
$this->setExpectedException'OxymelException' );
        
$this->x->contains->to_string();
    }

    private function 
a($value$x) {
        
$this->assertEquals$value "\n"$x->to_string());
    }
}
<?
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.
     *
     * @param LoggerInterface $logger
     *
     * @return void
     */
    
public function setLogger(LoggerInterface $logger);
}
<?
php

namespace Psr\Log;

/**
 * Basic Implementation of LoggerAwareInterface.
 */
trait LoggerAwareTrait
{
    
/**
     * The logger instance.
     *
     * @var LoggerInterface|null
     */
    
protected $logger;

    
/**
     * Sets a logger.
     *
     * @param LoggerInterface $logger
     */
    
public function setLogger(LoggerInterface $logger)
    {
        
$this->logger $logger;
    }
}
<?
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
{
    
/**
     * System is unusable.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    
public function emergency($message, array $context = array())
    {
        
$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.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    
public function alert($message, array $context = array())
    {
        
$this->log(LogLevel::ALERT$message$context);
    }

    
/**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    
public function critical($message, array $context = array())
    {
        
$this->log(LogLevel::CRITICAL$message$context);
    }

    
/**
     * Runtime errors that do not require immediate action but should typically
     * be logged and monitored.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    
public function error($message, array $context = array())
    {
        
$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.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    
public function warning($message, array $context = array())
    {
        
$this->log(LogLevel::WARNING$message$context);
    }

    
/**
     * Normal but significant events.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    
public function notice($message, array $context = array())
    {
        
$this->log(LogLevel::NOTICE$message$context);
    }

    
/**
     * Interesting events.
     *
     * Example: User logs in, SQL logs.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    
public function info($message, array $context = array())
    {
        
$this->log(LogLevel::INFO$message$context);
    }

    
/**
     * Detailed debug information.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    
public function debug($message, array $context = array())
    {
        
$this->log(LogLevel::DEBUG$message$context);
    }
}
<?
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 string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    
public function emergency($message, array $context = array());

    
/**
     * Action must be taken immediately.
     *
     * Example: Entire website down, database unavailable, etc. This should
     * trigger the SMS alerts and wake you up.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    
public function alert($message, array $context = array());

    
/**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    
public function critical($message, array $context = array());

    
/**
     * Runtime errors that do not require immediate action but should typically
     * be logged and monitored.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    
public function error($message, array $context = array());

    
/**
     * Exceptional occurrences that are not errors.
     *
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
     * that are not necessarily wrong.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    
public function warning($message, array $context = array());

    
/**
     * Normal but significant events.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    
public function notice($message, array $context = array());

    
/**
     * Interesting events.
     *
     * Example: User logs in, SQL logs.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    
public function info($message, array $context = array());

    
/**
     * Detailed debug information.
     *
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     */
    
public function debug($message, array $context = array());

    
/**
     * Logs with an arbitrary level.
     *
     * @param mixed   $level
     * @param string  $message
     * @param mixed[] $context
     *
     * @return void
     *
     * @throws \Psr\Log\InvalidArgumentException
     */
    
public function log($level$message, array $context = array());
}
<?
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  $level
     * @param string $message
     * @param array  $context
     *
     * @return void
     *
     * @throws \Psr\Log\InvalidArgumentException
     */
    
public function log($level$message, array $context = array())
    {
        
// noop
    
}
}
<?
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 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.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    
public function emergency($message, array $context = array())
    {
        
$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.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    
public function alert($message, array $context = array())
    {
        
$this->log(LogLevel::ALERT$message$context);
    }

    
/**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    
public function critical($message, array $context = array())
    {
        
$this->log(LogLevel::CRITICAL$message$context);
    }

    
/**
     * Runtime errors that do not require immediate action but should typically
     * be logged and monitored.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    
public function error($message, array $context = array())
    {
        
$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.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    
public function warning($message, array $context = array())
    {
        
$this->log(LogLevel::WARNING$message$context);
    }

    
/**
     * Normal but significant events.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    
public function notice($message, array $context = array())
    {
        
$this->log(LogLevel::NOTICE$message$context);
    }

    
/**
     * Interesting events.
     *
     * Example: User logs in, SQL logs.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    
public function info($message, array $context = array())
    {
        
$this->log(LogLevel::INFO$message$context);
    }

    
/**
     * Detailed debug information.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    
public function debug($message, array $context = array())
    {
        
$this->log(LogLevel::DEBUG$message$context);
    }

    
/**
     * Logs with an arbitrary level.
     *
     * @param mixed  $level
     * @param string $message
     * @param array  $context
     *
     * @return void
     *
     * @throws \Psr\Log\InvalidArgumentException
     */
    
abstract public function log($level$message, array $context = array());
}
<?
php

/*
 * This file is part of the JSON Lint package.
 *
 * (c) Jordi Boggiano <[email protected]>
 *
 * 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;

    private 
$lexer;

    private 
$flags;
    private 
$stack;
    private 
$vstack// semantic value stack
    
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>
     */
    
private $terminals_ = array(
        
2   => "error",
        
4   => "STRING",
        
6   => "NUMBER",
        
8   => "NULL",
        
10  => "TRUE",
        
11  => "FALSE",
        
14  => "EOF",
        
17  => "{",
        
18  => "}",
        
21  => ":",
        
22  => ",",
        
23  => "[",
        
24  => "]",
    );

    private 
$productions_ = array(
        
0,
        array(
31),
        array(
51),
        array(
71),
        array(
91),
        array(
91),
        array(
122),
        array(
131),
        array(
131),
        array(
131),
        array(
131),
        array(
131),
        array(
131),
        array(
152),
        array(
153),
        array(
203),
        array(
191),
        array(
193),
        array(
162),
        array(
163),
        array(
251),
        array(
253)
    );

    private 
$table = array(array(=> 5=> array(1,12), => 6=> array(1,13), => 3=> array(1,9), => 410 => array(1,10), 11 => array(1,11), 12 => 113 => 215 => 716 => 817 => array(1,14), 23 => array(1,15)), array( => array(3)), array( 14 => array(1,16)), array( 14 => array(2,7), 18 => array(2,7), 22 => array(2,7), 24 => array(2,7)), array( 14 => array(2,8), 18 => array(2,8), 22 => array(2,8), 24 => array(2,8)), array( 14 => array(2,9), 18 => array(2,9), 22 => array(2,9), 24 => array(2,9)), array( 14 => array(2,10), 18 => array(2,10), 22 => array(2,10), 24 => array(2,10)), array( 14 => array(2,11), 18 => array(2,11), 22 => array(2,11), 24 => array(2,11)), array( 14 => array(2,12), 18 => array(2,12), 22 => array(2,12), 24 => array(2,12)), array( 14 => array(2,3), 18 => array(2,3), 22 => array(2,3), 24 => array(2,3)), array( 14 => array(2,4), 18 => array(2,4), 22 => array(2,4), 24 => array(2,4)), array( 14 => array(2,5), 18 => array(2,5), 22 => array(2,5), 24 => array(2,5)), array( 14 => array(2,1), 18 => array(2,1), 21 => array(2,1), 22 => array(2,1), 24 => array(2,1)), array( 14 => array(2,2), 18 => array(2,2), 22 => array(2,2), 24 => array(2,2)), array( => 20=> array(1,12), 18 => array(1,17), 19 => 1820 => 19 ), array( => 5=> array(1,12), => 6=> array(1,13), => 3=> array(1,9), => 410 => array(1,10), 11 => array(1,11), 13 => 2315 => 716 => 817 => array(1,14), 23 => array(1,15), 24 => array(1,21), 25 => 22 ), array( => array(2,6)), array( 14 => array(2,13), 18 => array(2,13), 22 => array(2,13), 24 => array(2,13)), array( 18 => array(1,24), 22 => array(1,25)), array( 18 => array(2,16), 22 => array(2,16)), array( 21 => array(1,26)), array( 14 => array(2,18), 18 => array(2,18), 22 => array(2,18), 24 => array(2,18)), array( 22 => array(1,28), 24 => array(1,27)), array( 22 => array(2,20), 24 => array(2,20)), array( 14 => array(2,14), 18 => array(2,14), 22 => array(2,14), 24 => array(2,14)), array( => 20=> array(1,12), 20 => 29 ), array( => 5=> array(1,12), => 6=> array(1,13), => 3=> array(1,9), => 410 => array(1,10), 11 => array(1,11), 13 => 3015 => 716 => 817 => array(1,14), 23 => array(1,15)), array( 14 => array(2,19), 18 => array(2,19), 22 => array(2,19), 24 => array(2,19)), array( => 5=> array(1,12), => 6=> array(1,13), => 3=> array(1,9), => 410 => array(1,10), 11 => array(1,11), 13 => 3115 => 716 => 817 => array(1,14), 23 => array(1,15)), array( 18 => array(2,17), 22 => array(2,17)), array( 18 => array(2,15), 22 => array(2,15)), array( 22 => array(2,21), 24 => array(2,21)),
    );

    private 
$defaultActions = array(
        
16 => array(26)
    );

    
/**
     * @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
     */
    
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
     */
    
public function parse($input$flags 0)
    {
        
$this->failOnBOM($input);

        
$this->flags $flags;

        
$this->stack = array(0);
        
$this->vstack = array(null);
        
$this->lstack = array();

        
$yytext '';
        
$yylineno 0;
        
$yyleng 0;
        
$recovering 0;
        
$TERROR 2;
        
$EOF 1;

        
$this->lexer = new Lexer();
        
$this->lexer->setInput($input);

        
$yyloc $this->lexer->yylloc;
        
$this->lstack[] = $yyloc;

        
$symbol null;
        
$preErrorSymbol null;
        
$state null;
        
$action null;
        
$a null;
        
$r null;
        
$yyval = new stdClass;
        
$p null;
        
$len null;
        
$newState null;
        
$expected 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->lex();
                }
                
// read action for current state and first input
                
$action = isset($this->table[$state][$symbol]) ? $this->table[$state][$symbol] : false;
            }

            
// handle parse error
            
if (!$action || !$action[0]) {
                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->match01), array('"'"'"))) {
                        
$message "Invalid string";
                        if (
"'" === substr($this->lexer->match01)) {
                            
$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' => !empty($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 == $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->lex();
                }

                
// try to recover from error
                
while (true) {
                    
// check for error recovery rule in this state
                    
if (\array_key_exists($TERROR$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 $TERROR;         // insert generic error symbol as new lookahead
                
$state $this->stack[\count($this->stack)-1];
                
$action = isset($this->table[$state][$TERROR]) ? $this->table[$state][$TERROR] : false;
                
$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
                    
$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 f/ before error
                        
$symbol $preErrorSymbol;
                        
$preErrorSymbol null;
                    }
                    break;

                case 
2// reduce
                    
$len $this->productions_[$action[1]][1];

                    
// perform semantic action
                    
$yyval->token $this->vstack[\count($this->vstack) - $len]; // default to $$ = $1
                    // default location, uses first token for firsts, last for lasts
                    
$yyval->store = 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'],
                    );
                    
$r $this->performAction($yyval$yytext$yyleng$yylineno$action[1], $this->vstack);

                    if (!
$r instanceof Undefined) {
                        return 
$r;
                    }

                    if (
$len) {
                        
$this->popStack($len);
                    }

                    
$this->stack[] = $this->productions_[$action[1]][0];    // push nonterminal (reduce)
                    
$this->vstack[] = $yyval->token;
                    
$this->lstack[] = $yyval->store;
                    
$newState $this->table[$this->stack[\count($this->stack)-2]][$this->stack[\count($this->stack)-1]];
                    
$this->stack[] = $newState;
                    break;

                case 
3// accept

                    
return true;
            }
        }
    }

    protected function 
parseError($str$hash)
    {
        throw new 
ParsingException($str$hash);
    }

    
// $$ = $tokens // needs to be passed by ref?
    // $ = $token
    // _$ removed, useless?
    
private function performAction(stdClass $yyval$yytext$yyleng$yylineno$yystate, &$tokens)
    {
        
// $0 = $len
        
$len \count($tokens) - 1;
        switch (
$yystate) {
        case 
1:
            
$yytext preg_replace_callback('{(?:\\\\["bfnrt/\\\\]|\\\\u[a-fA-F0-9]{4})}', array($this'stringInterpolation'), $yytext);
            
$yyval->token $yytext;
            break;
        case 
2:
            if (
strpos($yytext'e') !== false || strpos($yytext'E') !== false) {
                
$yyval->token \floatval($yytext);
            } else {
                
$yyval->token strpos($yytext'.') === false \intval($yytext) : \floatval($yytext);
            }
            break;
        case 
3:
            
$yyval->token null;
            break;
        case 
4:
            
$yyval->token true;
            break;
        case 
5:
            
$yyval->token false;
            break;
        case 
6:
            return 
$yyval->token $tokens[$len-1];
        case 
13:
            if (
$this->flags self::PARSE_TO_ASSOC) {
                
$yyval->token = array();
            } else {
                
$yyval->token = new stdClass;
            }
            break;
        case 
14:
            
$yyval->token $tokens[$len-1];
            break;
        case 
15:
            
$yyval->token = array($tokens[$len-2], $tokens[$len]);
            break;
        case 
16:
            if (
PHP_VERSION_ID 70100) {
                
$property $tokens[$len][0] === '' '_empty_' $tokens[$len][0];
            } else {
                
$property $tokens[$len][0];
            }
            if (
$this->flags self::PARSE_TO_ASSOC) {
                
$yyval->token = array();
                
$yyval->token[$property] = $tokens[$len][1];
            } else {
                
$yyval->token = new stdClass;
                
$yyval->token->$property $tokens[$len][1];
            }
            break;
        case 
17:
            if (
$this->flags self::PARSE_TO_ASSOC) {
                
$yyval->token =& $tokens[$len-2];
                
$key $tokens[$len][0];
                if ((
$this->flags self::DETECT_KEY_CONFLICTS) && isset($tokens[$len-2][$key])) {
                    
$errStr 'Parse error on line ' . ($yylineno+1) . ":\n";
                    
$errStr .= $this->lexer->showPosition() . "\n";
                    
$errStr .= "Duplicate key: ".$tokens[$len][0];
                    throw new 
DuplicateKeyException($errStr$tokens[$len][0], array('line' => $yylineno+1));
                } elseif ((
$this->flags self::ALLOW_DUPLICATE_KEYS) && isset($tokens[$len-2][$key])) {
                    
$duplicateCount 1;
                    do {
                        
$duplicateKey $key '.' $duplicateCount++;
                    } while (isset(
$tokens[$len-2][$duplicateKey]));
                    
$key $duplicateKey;
                }
                
$tokens[$len-2][$key] = $tokens[$len][1];
            } else {
                
$yyval->token $tokens[$len-2];
                if (
PHP_VERSION_ID 70100) {
                    
$key $tokens[$len][0] === '' '_empty_' $tokens[$len][0];
                } else {
                    
$key $tokens[$len][0];
                }
                if ((
$this->flags self::DETECT_KEY_CONFLICTS) && isset($tokens[$len-2]->{$key})) {
                    
$errStr 'Parse error on line ' . ($yylineno+1) . ":\n";
                    
$errStr .= $this->lexer->showPosition() . "\n";
                    
$errStr .= "Duplicate key: ".$tokens[$len][0];
                    throw new 
DuplicateKeyException($errStr$tokens[$len][0], array('line' => $yylineno+1));
                } elseif ((
$this->flags self::ALLOW_DUPLICATE_KEYS) && isset($tokens[$len-2]->{$key})) {
                    
$duplicateCount 1;
                    do {
                        
$duplicateKey $key '.' $duplicateCount++;
                    } while (isset(
$tokens[$len-2]->$duplicateKey));
                    
$key $duplicateKey;
                }
                
$tokens[$len-2]->$key $tokens[$len][1];
            }
            break;
        case 
18:
            
$yyval->token = array();
            break;
        case 
19:
            
$yyval->token $tokens[$len-1];
            break;
        case 
20:
            
$yyval->token = array($tokens[$len]);
            break;
        case 
21:
            
$tokens[$len-2][] = $tokens[$len];
            
$yyval->token $tokens[$len-2];
            break;
        }

        return new 
Undefined();
    }

    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');
        }
    }

    private function 
popStack($n)
    {
        
$this->stack \array_slice($this->stack0, - ($n));
        
$this->vstack \array_slice($this->vstack0, - $n);
        
$this->lstack \array_slice($this->lstack0, - $n);
    }

    private function 
lex()
    {
        
$token $this->lexer->lex() ?: 1// $end = 1
        // if token isn't its numeric value, convert
        
if (!is_numeric($token)) {
            
$token = isset($this->symbols[$token]) ? $this->symbols[$token] : $token;
        }

        return 
$token;
    }

    private function 
failOnBOM($input)
    {
        
// UTF-8 ByteOrderMark sequence
        
$bom "\xEF\xBB\xBF";

        if (
substr($input03) === $bom) {
            
$this->parseError("BOM detected, make sure your input does not include a Unicode Byte-Order-Mark", array());
        }
    }
}
<?
php

/*
 * This file is part of the JSON Lint package.
 *
 * (c) Jordi Boggiano <[email protected]>
 *
 * 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
{
    private 
$EOF 1;
    
/**
     * @phpstan-var array<int, string>
     */
    
private $rules = array(
        
=> '/\G\s+/',
        
=> '/\G-?([0-9]|[1-9][0-9]+)(\.[0-9]+)?([eE][+-]?[0-9]+)?\b/',
        
=> '{\G"(?>\\\\["bfnrt/\\\\]|\\\\u[a-fA-F0-9]{4}|[^\0-\x1f\\\\"]++)*+"}',
        
=> '/\G\{/',
        
=> '/\G\}/',
        
=> '/\G\[/',
        
=> '/\G\]/',
        
=> '/\G,/',
        
=> '/\G:/',
        
=> '/\Gtrue\b/',
        
10 => '/\Gfalse\b/',
        
11 => '/\Gnull\b/',
        
12 => '/\G$/',
        
13 => '/\G./',
    );

    private 
$conditions = array(
        
"INITIAL" => array(
            
"rules" => array(0,1,2,3,4,5,6,7,8,9,10,11,12,13),
            
"inclusive" => true,
        ),
    );

    private 
$conditionStack;
    private 
$input;
    private 
$more;
    private 
$done;
    private 
$offset;

    public 
$match;
    public 
$yylineno;
    public 
$yyleng;
    public 
$yytext;
    public 
$yylloc;

    public function 
lex()
    {
        
$r $this->next();
        if (!
$r instanceof Undefined) {
            return 
$r;
        }

        return 
$this->lex();
    }

    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->conditionStack = array('INITIAL');
        
$this->yylloc = array('first_line' => 1'first_column' => 0'last_line' => 1'last_column' => 0);

        return 
$this;
    }

    public function 
showPosition()
    {
        
$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 "^";
    }

    public function 
getPastInput()
    {
        
$pastLength $this->offset \strlen($this->match);

        return (
$pastLength 20 '...' '') . substr($this->inputmax(0$pastLength 20), min(20$pastLength));
    }

    public function 
getUpcomingInput()
    {
        
$next $this->match;
        if (
\strlen($next) < 20) {
            
$next .= substr($this->input$this->offset20 \strlen($next));
        }

        return 
substr($next020) . (\strlen($next) > 20 '...' '');
    }

    public function 
getFullUpcomingInput()
    {
        
$next $this->match;
        if (
substr($next01) === '"' && substr_count($next'"') === 1) {
            
$len \strlen($this->input);
            
$strEnd min(strpos($this->input'"'$this->offset 1) ?: $lenstrpos($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->offset20 \strlen($next));
        }

        return 
$next;
    }

    protected function 
parseError($str$hash)
    {
        throw new 
\Exception($str);
    }

    private function 
next()
    {
        if (
$this->done) {
            return 
$this->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 '';
        }

        
$rules $this->getCurrentRules();
        
$rulesLen \count($rules);

        for (
$i=0$i $rulesLen$i++) {
            if (
preg_match($this->rules[$rules[$i]], $this->input$match0$this->offset)) {
                
preg_match_all('/\n.*/'$match[0], $lines);
                
$lines $lines[0];
                if (
$lines) {
                    
$this->yylineno += \count($lines);
                }

                
$this->yylloc = array(
                    
'first_line' => $this->yylloc['last_line'],
                    
'last_line' => $this->yylineno+1,
                    
'first_column' => $this->yylloc['last_column'],
                    
'last_column' => $lines \strlen($lines[\count($lines) - 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]);
                
$token $this->performAction($rules[$i], $this->conditionStack[\count($this->conditionStack)-1]);
                if (
$token) {
                    return 
$token;
                }

                return new 
Undefined();
            }
        }

        if (
$this->offset === \strlen($this->input)) {
            return 
$this->EOF;
        }

        
$this->parseError(
            
'Lexical error on line ' . ($this->yylineno+1) . ". Unrecognized text.\n" $this->showPosition(),
            array(
                
'text' => "",
                
'token' => null,
                
'line' => $this->yylineno,
            )
        );
    }

    private function 
getCurrentRules()
    {
        return 
$this->conditions[$this->conditionStack[\count($this->conditionStack)-1]]['rules'];
    }

    private function 
performAction($avoiding_name_collisions$YY_START)
    {
        switch (
$avoiding_name_collisions) {
        case 
0:/* skip whitespace */
            
break;
        case 
1:
            return 
6;
        case 
2:
            
$this->yytext substr($this->yytext1$this->yyleng-2);

            return 
4;
        case 
3:
            return 
17;
        case 
4:
            return 
18;
        case 
5:
            return 
23;
        case 
6:
            return 
24;
        case 
7:
            return 
22;
        case 
8:
            return 
21;
        case 
9:
            return 
10;
        case 
10:
            return 
11;
        case 
11:
            return 
8;
        case 
12:
            return 
14;
        case 
13:
            return 
'INVALID';
        }
    }
}
<?
php

/*
 * This file is part of the JSON Lint package.
 *
 * (c) Jordi Boggiano <[email protected]>
 *
 * 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 <[email protected]>
 *
 * 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
{
    
/**
     * @param string $message
     * @param string $key
     * @phpstan-param array{text?: string, token?: string, line?: int, loc?: array{first_line: int, first_column: int, last_line: int, last_column: int}, expected?: string[]} $details
     */
    
public function __construct($message$key, array $details = array())
    {
        
$details['key'] = $key;
        
parent::__construct($message$details);
    }

    public function 
getKey()
    {
        return 
$this->details['key'];
    }

    
/**
     * @phpstan-return array{text?: string, token?: string, line?: int, loc?: array{first_line: int, first_column: int, last_line: int, last_column: int}, expected?: string[], key: string}
     */
    
public function getDetails()
    {
        return 
$this->details;
    }
}
<?
php

/*
 * This file is part of the JSON Lint package.
 *
 * (c) Jordi Boggiano <[email protected]>
 *
 * 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
{
    protected 
$details;

    
/**
     * @param string $message
     * @phpstan-param array{text?: string, token?: string, 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, 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 PHAR Utils.
 *
 * (c) Jordi Boggiano <[email protected]>
 *
 * 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$matchPREG_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($pos4);
        
$pos += 4;

        
$numFiles $this->readUint($pos4);
        
$pos += 4;

        
// skip API version (YOLO)
        
$pos += 2;

        
// skip PHAR flags
        
$pos += 4;

        
$aliasLength $this->readUint($pos4);
        
$pos += $aliasLength;

        
$metadataLength $this->readUint($pos4);
        
$pos += $metadataLength;

        while (
$pos $stubEnd) {
            
$filenameLength $this->readUint($pos4);
            
$pos += $filenameLength;

            
// skip filesize
            
$pos += 4;

            
// update timestamp to a fixed value
            
$this->contents substr_replace($this->contentspack('L'$timestamp), $pos4);

            
// skip timestamp, compressed file size, crc32 checksum and file flags
            
$pos += 4*4;

            
$metadataLength $this->readUint($pos4);
            
$pos += $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($algosubstr($this->contents0$pos), true)
            
// sig type
            
pack('L'$signatureAlgo)
            
// ohai Greg & Marcus
            
'GBMB';

        
$this->contents substr($this->contents0$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$matchPREG_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 $this->readUint($pos4);

        
$pos += 4;
        
$numFiles $this->readUint($pos4);

        
$pos += 4;

        
// skip API version (YOLO)
        
$pos += 2;

        
// skip PHAR flags
        
$pos += 4;

        
$aliasLength $this->readUint($pos4);
        
$pos += $aliasLength;

        
$metadataLength $this->readUint($pos4);
        
$pos += $metadataLength;

        
$compressedSizes 0;
        while ((
$numFiles 0) && ($pos $manifestEnd 24)) {
            
$filenameLength $this->readUint($pos4);
            
$pos += $filenameLength;

            
// skip filesize and timestamp
            
$pos += 2*4;

            
$compressedSizes += $this->readUint($pos4);
            
// skip compressed file size, crc32 checksum and file flags
            
$pos += 3*4;

            
$metadataLength $this->readUint($pos4);
            
$pos += $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 <[email protected]>
 *
 * 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(
            
=> array('pipe''r'),
            
=> array('pipe''w'),
            
=> array('pipe''w')
        );

        
// path to phar + phar:// + trailing slash
        
$baseLen strlen(realpath($path)) + 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), $excludedPathstrue)) {
                    continue;
                }
                if (
$isWindows) {
                    
file_put_contents($tmpFilefile_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 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 <[email protected]>
 */
class JsonPointer
{
    
/** @var string */
    
private $filename;

    
/** @var string[] */
    
private $propertyPaths = array();

    
/**
     * @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('#'$value2);
        
$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 = array();
        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(
            array(
$this'encodePath'),
            
$this->getPropertyPaths()
        );
    }

    
/**
     * @param string $path
     *
     * @return string
     */
    
private function decodePath($path)
    {
        return 
strtr($path, array('~1' => '/''~0' => '~''%25' => '%'));
    }

    
/**
     * @param string $path
     *
     * @return string
     */
    
private function encodePath($path)
    {
        return 
strtr($path, array('/' => '~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 $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()
    {
        
$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

/*
 * 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

namespace JsonSchema;

class 
Rfc3339
{
    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::REGEXstrtoupper($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

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
{
    const 
INTERNAL_PROVIDED_SCHEMA_URI 'internal://provided-schema/';

    protected 
$uriRetriever;
    protected 
$uriResolver;
    protected 
$schemas = array();

    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($id$schema null)
    {
        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';
            }
        }

        
// resolve references
        
$this->expandRefs($schema$id);

        
$this->schemas[$id] = $schema;
    }

    
/**
     * Recursively resolve all references against the provided base
     *
     * @param mixed  $schema
     * @param string $base
     */
    
private function expandRefs(&$schema$base null)
    {
        if (!
is_object($schema)) {
            if (
is_array($schema)) {
                foreach (
$schema as &$member) {
                    
$this->expandRefs($member$base);
                }
            }

            return;
        }

        if (
property_exists($schema'id') && is_string($schema->id) && $base != $schema->id) {
            
$base $this->uriResolver->resolve($schema->id$base);
        }

        if (
property_exists($schema'$ref') && is_string($schema->{'$ref'})) {
            
$refPointer = new JsonPointer($this->uriResolver->resolve($schema->{'$ref'}, $base));
            
$schema->{'$ref'} = (string) $refPointer;
        }

        foreach (
$schema as &$member) {
            
$this->expandRefs($member$base);
        }
    }

    
/**
     * {@inheritdoc}
     */
    
public function getSchema($id)
    {
        if (!
array_key_exists($id$this->schemas)) {
            
$this->addSchema($id);
        }

        return 
$this->schemas[$id];
    }

    
/**
     * {@inheritdoc}
     */
    
public function resolveRef($ref)
    {
        
$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});
            } elseif (
is_array($refSchema) && array_key_exists($path$refSchema)) {
                
$refSchema $this->resolveRefSchema($refSchema[$path]);
            } 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)
    {
        if (
is_object($refSchema) && property_exists($refSchema'$ref') && is_string($refSchema->{'$ref'})) {
            
$newSchema $this->resolveRef($refSchema->{'$ref'});
            
$refSchema = (object) (get_object_vars($refSchema) + get_object_vars($newSchema));
            unset(
$refSchema->{'$ref'});
        }

        return 
$refSchema;
    }
}
<?
php

/*
 * 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

/*
 * 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

/*
 * 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

/*
 * 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

namespace JsonSchema\Exception;

interface 
ExceptionInterface
{
}
<?
php

/*
 * 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 <[email protected]>
 */
class UnresolvableJsonPointerException extends InvalidArgumentException
{
}
<?
php

/*
 * 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

/*
 * 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

/*
 * 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

/*
 * 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

/*
 * 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

/*
 * 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

/*
 * 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

/*
 * 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\Entity\JsonPointer;

/**
 * The Base Constraints, all Validators should extend this class
 *
 * @author Robert Schönthal <[email protected]>
 * @author Bruno Prieto Reis <[email protected]>
 */
abstract class Constraint extends BaseConstraint implements ConstraintInterface
{
    protected 
$inlineSchemaProperty '$schema';

    const 
CHECK_MODE_NONE =             0x00000000;
    const 
CHECK_MODE_NORMAL =           0x00000001;
    const 
CHECK_MODE_TYPE_CAST =        0x00000002;
    const 
CHECK_MODE_COERCE_TYPES =     0x00000004;
    const 
CHECK_MODE_APPLY_DEFAULTS =   0x00000008;
    const 
CHECK_MODE_EXCEPTIONS =       0x00000010;
    const 
CHECK_MODE_DISABLE_FORMAT =   0x00000020;
    const 
CHECK_MODE_ONLY_REQUIRED_DEFAULTS   0x00000080;
    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
     *
     * @return JsonPointer;
     */
    
protected function incrementPath(JsonPointer $path null$i)
    {
        
$path $path ?: new JsonPointer('');

        if (
$i === null || $i === '') {
            return 
$path;
        }

        
$path $path->withPropertyPaths(
            
array_merge(
                
$path->getPropertyPaths(),
                array(
$i)
            )
        );

        return 
$path;
    }

    
/**
     * Validates an array
     *
     * @param mixed            $value
     * @param mixed            $schema
     * @param JsonPointer|null $path
     * @param mixed            $i
     */
    
protected function checkArray(&$value$schema nullJsonPointer $path null$i null)
    {
        
$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 JsonPointer|null $path
     * @param mixed            $properties
     * @param mixed            $additionalProperties
     * @param mixed            $patternProperties
     */
    
protected function checkObject(&$value$schema nullJsonPointer $path null$properties null,
        
$additionalProperties null$patternProperties null$appliedDefaults = array())
    {
        
$validator $this->factory->createInstanceFor('object');
        
$validator->check($value$schema$path$properties$additionalProperties$patternProperties$appliedDefaults);

        
$this->addErrors($validator->getErrors());
    }

    
/**
     * Validates the type of a property
     *
     * @param mixed            $value
     * @param mixed            $schema
     * @param JsonPointer|null $path
     * @param mixed            $i
     */
    
protected function checkType(&$value$schema nullJsonPointer $path null$i null)
    {
        
$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 JsonPointer|null $path
     * @param mixed            $i
     */
    
protected function checkUndefined(&$value$schema nullJsonPointer $path null$i null$fromDefault false)
    {
        
$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 JsonPointer|null $path
     * @param mixed            $i
     */
    
protected function checkString($value$schema nullJsonPointer $path null$i null)
    {
        
$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 JsonPointer $path
     * @param mixed       $i
     */
    
protected function checkNumber($value$schema nullJsonPointer $path null$i null)
    {
        
$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 JsonPointer|null $path
     * @param mixed            $i
     */
    
protected function checkEnum($value$schema nullJsonPointer $path null$i null)
    {
        
$validator $this->factory->createInstanceFor('enum');
        
$validator->check($value$schema$path$i);

        
$this->addErrors($validator->getErrors());
    }

    
/**
     * Checks format of an element
     *
     * @param mixed            $value
     * @param mixed            $schema
     * @param JsonPointer|null $path
     * @param mixed            $i
     */
    
protected function checkFormat($value$schema nullJsonPointer $path null$i null)
    {
        
$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.
     *
     * @return TypeCheck\TypeCheckInterface
     */
    
protected function getTypeCheck()
    {
        return 
$this->factory->getTypeCheck();
    }

    
/**
     * @param JsonPointer $pointer
     *
     * @return string property path
     */
    
protected function convertJsonPointerIntoPropertyPath(JsonPointer $pointer)
    {
        
$result array_map(
            function (
$path) {
                return 
sprintf(is_numeric($path) ? '[%d]' '.%s'$path);
            },
            
$pointer->getPropertyPaths()
        );

        return 
trim(implode(''$result), '.');
    }
}
<?
php

/*
 * 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\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 <[email protected]>
 * @author Bruno Prieto Reis <[email protected]>
 */
class SchemaConstraint extends Constraint
{
    const 
DEFAULT_SCHEMA_SPEC 'http://json-schema.org/draft-04/schema#';

    
/**
     * {@inheritdoc}
     */
    
public function check(&$element$schema nullJsonPointer $path null$i null)
    {
        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($path'Schema is not valid''schema');
            }

            
// restore the initial config
            
$this->factory->setConfig($initialConfig);
            
$this->factory->setErrorContext($initialContext);
        }

        
// validate element against $validationSchema
        
$this->checkUndefined($element$validationSchema$path$i);
    }
}
<?
php

/*
 * 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\Entity\JsonPointer;

/**
 * The CollectionConstraint Constraints, validates an array against a given schema
 *
 * @author Robert Schönthal <[email protected]>
 * @author Bruno Prieto Reis <[email protected]>
 */
class CollectionConstraint extends Constraint
{
    
/**
     * {@inheritdoc}
     */
    
public function check(&$value$schema nullJsonPointer $path null$i null)
    {
        
// Verify minItems
        
if (isset($schema->minItems) && count($value) < $schema->minItems) {
            
$this->addError($path'There must be a minimum of ' $schema->minItems ' items in the array''minItems', array('minItems' => $schema->minItems));
        }

        
// Verify maxItems
        
if (isset($schema->maxItems) && count($value) > $schema->maxItems) {
            
$this->addError($path'There must be a maximum of ' $schema->maxItems ' items in the array''maxItems', array('maxItems' => $schema->maxItems));
        }

        
// Verify uniqueItems
        
if (isset($schema->uniqueItems) && $schema->uniqueItems) {
            
$unique $value;
            if (
is_array($value) && count($value)) {
                
$unique array_map(function ($e) {
                    return 
var_export($etrue);
                }, 
$value);
            }
            if (
count(array_unique($unique)) != count($value)) {
                
$this->addError($path'There are no duplicates allowed in the array''uniqueItems');
            }
        }

        
// Verify items
        
if (isset($schema->items)) {
            
$this->validateItems($value$schema$path$i);
        }
    }

    
/**
     * Validates the items
     *
     * @param array            $value
     * @param \stdClass        $schema
     * @param JsonPointer|null $path
     * @param string           $i
     */
    
protected function validateItems(&$value$schema nullJsonPointer $path null$i null)
    {
        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(
                                
$path'The item ' $i '[' $k '] is not defined and the definition does not allow additional items''additionalItems', array('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

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

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

namespace JsonSchema\Constraints\TypeCheck;

class 
LooseTypeCheck implements TypeCheckInterface
{
    public static function 
isObject($value)
    {
        return
            
is_object($value) ||
            (
is_array($value) && (count($value) == || self::isAssociativeArray($value)));
    }

    public static function 
isArray($value)
    {
        return
            
is_array($value) &&
            (
count($value) == || !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 
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(0count($arr) - 1);
    }
}
<?
php

/*
 * 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\Entity\JsonPointer;
use 
JsonSchema\Rfc3339;

/**
 * Validates against the "format" property
 *
 * @author Justin Rainbow <[email protected]>
 *
 * @see   http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.23
 */
class FormatConstraint extends Constraint
{
    
/**
     * {@inheritdoc}
     */
    
public function check(&$element$schema nullJsonPointer $path null$i null)
    {
        if (!isset(
$schema->format) || $this->factory->getConfig(self::CHECK_MODE_DISABLE_FORMAT)) {
            return;
        }

        switch (
$schema->format) {
            case 
'date':
                if (!
$date $this->validateDateTime($element'Y-m-d')) {
                    
$this->addError($pathsprintf('Invalid date %s, expected format YYYY-MM-DD'json_encode($element)), 'format', array('format' => $schema->format));
                }
                break;

            case 
'time':
                if (!
$this->validateDateTime($element'H:i:s')) {
                    
$this->addError($pathsprintf('Invalid time %s, expected format hh:mm:ss'json_encode($element)), 'format', array('format' => $schema->format));
                }
                break;

            case 
'date-time':
                if (
null === Rfc3339::createFromString($element)) {
                    
$this->addError($pathsprintf('Invalid date-time %s, expected format YYYY-MM-DDThh:mm:ssZ or YYYY-MM-DDThh:mm:ss+hh:mm'json_encode($element)), 'format', array('format' => $schema->format));
                }
                break;

            case 
'utc-millisec':
                if (!
$this->validateDateTime($element'U')) {
                    
$this->addError($pathsprintf('Invalid time %s, expected integer of milliseconds since Epoch'json_encode($element)), 'format', array('format' => $schema->format));
                }
                break;

            case 
'regex':
                if (!
$this->validateRegex($element)) {
                    
$this->addError($path'Invalid regex format ' $element'format', array('format' => $schema->format));
                }
                break;

            case 
'color':
                if (!
$this->validateColor($element)) {
                    
$this->addError($path'Invalid color''format', array('format' => $schema->format));
                }
                break;

            case 
'style':
                if (!
$this->validateStyle($element)) {
                    
$this->addError($path'Invalid style''format', array('format' => $schema->format));
                }
                break;

            case 
'phone':
                if (!
$this->validatePhone($element)) {
                    
$this->addError($path'Invalid phone number''format', array('format' => $schema->format));
                }
                break;

            case 
'uri':
                if (
null === filter_var($elementFILTER_VALIDATE_URLFILTER_NULL_ON_FAILURE)) {
                    
$this->addError($path'Invalid URL format''format', array('format' => $schema->format));
                }
                break;

            case 
'uriref':
            case 
'uri-reference':
                if (
null === filter_var($elementFILTER_VALIDATE_URLFILTER_NULL_ON_FAILURE)) {
                    
// FILTER_VALIDATE_URL does not conform to RFC-3986, and cannot handle relative URLs, but
                    // the json-schema spec uses RFC-3986, so need a bit of hackery to properly validate them.
                    // See https://tools.ietf.org/html/rfc3986#section-4.2 for additional information.
                    
if (substr($element02) === '//') { // network-path reference
                        
$validURL filter_var('scheme:' $elementFILTER_VALIDATE_URLFILTER_NULL_ON_FAILURE);
                    } elseif (
substr($element01) === '/') { // absolute-path reference
                        
$validURL filter_var('scheme://host' $elementFILTER_VALIDATE_URLFILTER_NULL_ON_FAILURE);
                    } elseif (
strlen($element)) { // relative-path reference
                        
$pathParts explode('/'$element2);
                        if (
strpos($pathParts[0], ':') !== false) {
                            
$validURL null;
                        } else {
                            
$validURL filter_var('scheme://host/' $elementFILTER_VALIDATE_URLFILTER_NULL_ON_FAILURE);
                        }
                    } else {
                        
$validURL null;
                    }
                    if (
$validURL === null) {
                        
$this->addError($path'Invalid URL format''format', array('format' => $schema->format));
                    }
                }
                break;

            case 
'email':
                
$filterFlags FILTER_NULL_ON_FAILURE;
                if (
defined('FILTER_FLAG_EMAIL_UNICODE')) {
                    
// Only available from PHP >= 7.1.0, so ignore it for coverage checks
                    
$filterFlags |= constant('FILTER_FLAG_EMAIL_UNICODE'); // @codeCoverageIgnore
                
}
                if (
null === filter_var($elementFILTER_VALIDATE_EMAIL$filterFlags)) {
                    
$this->addError($path'Invalid email''format', array('format' => $schema->format));
                }
                break;

            case 
'ip-address':
            case 
'ipv4':
                if (
null === filter_var($elementFILTER_VALIDATE_IPFILTER_NULL_ON_FAILURE FILTER_FLAG_IPV4)) {
                    
$this->addError($path'Invalid IP address''format', array('format' => $schema->format));
                }
                break;

            case 
'ipv6':
                if (
null === filter_var($elementFILTER_VALIDATE_IPFILTER_NULL_ON_FAILURE FILTER_FLAG_IPV6)) {
                    
$this->addError($path'Invalid IP address''format', array('format' => $schema->format));
                }
                break;

            case 
'host-name':
            case 
'hostname':
                if (!
$this->validateHostname($element)) {
                    
$this->addError($path'Invalid hostname''format', array('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$datetime);

        if (!
$dt) {
            return 
false;
        }

        if (
$datetime === $dt->format($format)) {
            return 
true;
        }

        
// handles the case where a non-6 digit microsecond datetime is passed
        // which will fail the above string comparison because the passed
        // $datetime may be '2000-05-01T12:12:12.123Z' but format() will return
        // '2000-05-01T12:12:12.123000Z'
        
if ((strpos('u'$format) !== -1) && (preg_match('/\.\d+Z$/'$datetime))) {
            return 
true;
        }

        return 
false;
    }

    protected function 
validateRegex($regex)
    {
        return 
false !== @preg_match('/' $regex '/u''');
    }

    protected function 
validateColor($color)
    {
        if (
in_array(strtolower($color), array('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'$propertiesPREG_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)
    {
        
$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

/*
 * 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\Entity\JsonPointer;

/**
 * The NumberConstraint Constraints, validates an number against a given schema
 *
 * @author Robert Schönthal <[email protected]>
 * @author Bruno Prieto Reis <[email protected]>
 */
class NumberConstraint extends Constraint
{
    
/**
     * {@inheritdoc}
     */
    
public function check(&$element$schema nullJsonPointer $path null$i null)
    {
        
// Verify minimum
        
if (isset($schema->exclusiveMinimum)) {
            if (isset(
$schema->minimum)) {
                if (
$schema->exclusiveMinimum && $element <= $schema->minimum) {
                    
$this->addError($path'Must have a minimum value of ' $schema->minimum'exclusiveMinimum', array('minimum' => $schema->minimum));
                } elseif (
$element $schema->minimum) {
                    
$this->addError($path'Must have a minimum value of ' $schema->minimum'minimum', array('minimum' => $schema->minimum));
                }
            } else {
                
$this->addError($path'Use of exclusiveMinimum requires presence of minimum''missingMinimum');
            }
        } elseif (isset(
$schema->minimum) && $element $schema->minimum) {
            
$this->addError($path'Must have a minimum value of ' $schema->minimum'minimum', array('minimum' => $schema->minimum));
        }

        
// Verify maximum
        
if (isset($schema->exclusiveMaximum)) {
            if (isset(
$schema->maximum)) {
                if (
$schema->exclusiveMaximum && $element >= $schema->maximum) {
                    
$this->addError($path'Must have a maximum value of ' $schema->maximum'exclusiveMaximum', array('maximum' => $schema->maximum));
                } elseif (
$element $schema->maximum) {
                    
$this->addError($path'Must have a maximum value of ' $schema->maximum'maximum', array('maximum' => $schema->maximum));
                }
            } else {
                
$this->addError($path'Use of exclusiveMaximum requires presence of maximum''missingMaximum');
            }
        } elseif (isset(
$schema->maximum) && $element $schema->maximum) {
            
$this->addError($path'Must have a maximum value of ' $schema->maximum'maximum', array('maximum' => $schema->maximum));
        }

        
// Verify divisibleBy - Draft v3
        
if (isset($schema->divisibleBy) && $this->fmod($element$schema->divisibleBy) != 0) {
            
$this->addError($path'Is not divisible by ' $schema->divisibleBy'divisibleBy', array('divisibleBy' => $schema->divisibleBy));
        }

        
// Verify multipleOf - Draft v4
        
if (isset($schema->multipleOf) && $this->fmod($element$schema->multipleOf) != 0) {
            
$this->addError($path'Must be a multiple of ' $schema->multipleOf'multipleOf', array('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

/*
 * 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\Entity\JsonPointer;

/**
 * The Constraints Interface
 *
 * @author Robert Schönthal <[email protected]>
 */
interface ConstraintInterface
{
    
/**
     * returns all collected errors
     *
     * @return array
     */
    
public function getErrors();

    
/**
     * adds errors to this validator
     *
     * @param array $errors
     */
    
public function addErrors(array $errors);

    
/**
     * adds an error
     *
     * @param JsonPointer|null $path
     * @param string           $message
     * @param string           $constraint the constraint/rule that is broken, e.g.: 'minLength'
     * @param array            $more       more array elements to add to the error
     */
    
public function addError(JsonPointer $path null$message$constraint='', array $more null);

    
/**
     * checks if the validator has not raised errors
     *
     * @return bool
     */
    
public function isValid();

    
/**
     * invokes the validation of an element
     *
     * @abstract
     *
     * @param mixed            $value
     * @param mixed            $schema
     * @param JsonPointer|null $path
     * @param mixed            $i
     *
     * @throws \JsonSchema\Exception\ExceptionInterface
     */
    
public function check(&$value$schema nullJsonPointer $path null$i null);
}
<?
php

/*
 * 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\Entity\JsonPointer;

/**
 * The StringConstraint Constraints, validates an string against a given schema
 *
 * @author Robert Schönthal <[email protected]>
 * @author Bruno Prieto Reis <[email protected]>
 */
class StringConstraint extends Constraint
{
    
/**
     * {@inheritdoc}
     */
    
public function check(&$element$schema nullJsonPointer $path null$i null)
    {
        
// Verify maxLength
        
if (isset($schema->maxLength) && $this->strlen($element) > $schema->maxLength) {
            
$this->addError($path'Must be at most ' $schema->maxLength ' characters long''maxLength', array(
                
'maxLength' => $schema->maxLength,
            ));
        }

        
//verify minLength
        
if (isset($schema->minLength) && $this->strlen($element) < $schema->minLength) {
            
$this->addError($path'Must be at least ' $schema->minLength ' characters long''minLength', array(
                
'minLength' => $schema->minLength,
            ));
        }

        
// Verify a regex pattern
        
if (isset($schema->pattern) && !preg_match('#' str_replace('#''\\#'$schema->pattern) . '#u'$element)) {
            
$this->addError($path'Does not match the regex pattern ' $schema->pattern'pattern', array(
                
'pattern' => $schema->pattern,
            ));
        }

        
$this->checkFormat($element$schema$path$i);
    }

    private function 
strlen($string)
    {
        if (
extension_loaded('mbstring')) {
            return 
mb_strlen($stringmb_detect_encoding($string));
        }

        
// mbstring is present on all test platforms, so strlen() can be ignored for coverage
        
return strlen($string); // @codeCoverageIgnore
    
}
}
<?
php

/*
 * 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 SchemaStorage
     */
    
protected $schemaStorage;

    
/**
     * @var UriRetriever
     */
    
protected $uriRetriever;

    
/**
     * @var int
     */
    
private $checkMode Constraint::CHECK_MODE_NORMAL;

    
/**
     * @var TypeCheck\TypeCheckInterface[]
     */
    
private $typeCheck = array();

    
/**
     * @var int Validation context
     */
    
protected $errorContext Validator::ERROR_DOCUMENT_VALIDATION;

    
/**
     * @var array
     */
    
protected $constraintMap = array(
        
'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',
        
'format' => 'JsonSchema\Constraints\FormatConstraint',
        
'schema' => 'JsonSchema\Constraints\SchemaConstraint',
        
'validator' => 'JsonSchema\Validator'
    
);

    
/**
     * @var array<ConstraintInterface>
     */
    
private $instanceCache = array();

    
/**
     * @param SchemaStorage         $schemaStorage
     * @param UriRetrieverInterface $uriRetriever
     * @param int                   $checkMode
     */
    
public function __construct(
        
SchemaStorageInterface $schemaStorage null,
        
UriRetrieverInterface $uriRetriever null,
        
$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
     */
    
public function setConfig($checkMode Constraint::CHECK_MODE_NORMAL)
    {
        
$this->checkMode $checkMode;
    }

    
/**
     * Enable checkMode flags
     *
     * @param int $options
     */
    
public function addConfig($options)
    {
        
$this->checkMode |= $options;
    }

    
/**
     * Disable checkMode flags
     *
     * @param int $options
     */
    
public function removeConfig($options)
    {
        
$this->checkMode &= ~$options;
    }

    
/**
     * Get checkMode option
     *
     * @param int $options Options to get, if null then return entire bitmask
     *
     * @return int
     */
    
public function getConfig($options null)
    {
        if (
$options === null) {
            return 
$this->checkMode;
        }

        return 
$this->checkMode $options;
    }

    
/**
     * @return UriRetrieverInterface
     */
    
public function getUriRetriever()
    {
        return 
$this->uriRetriever;
    }

    public function 
getSchemaStorage()
    {
        return 
$this->schemaStorage;
    }

    public function 
getTypeCheck()
    {
        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];
    }

    
/**
     * @param string $name
     * @param string $class
     *
     * @return Factory
     */
    
public function setConstraintClass($name$class)
    {
        
// 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|ObjectConstraint
     */
    
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 string
     */
    
public function getErrorContext()
    {
        return 
$this->errorContext;
    }

    
/**
     * Set the error context
     *
     * @param string $validationContext
     */
    
public function setErrorContext($errorContext)
    {
        
$this->errorContext $errorContext;
    }
}
<?
php

/*
 * 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\Entity\JsonPointer;

/**
 * The ObjectConstraint Constraints, validates an object against a given schema
 *
 * @author Robert Schönthal <[email protected]>
 * @author Bruno Prieto Reis <[email protected]>
 */
class ObjectConstraint extends Constraint
{
    
/**
     * @var array List of properties to which a default value has been applied
     */
    
protected $appliedDefaults = array();

    
/**
     * {@inheritdoc}
     */
    
public function check(&$element$schema nullJsonPointer $path null$properties null,
        
$additionalProp null$patternProperties null$appliedDefaults = array())
    {
        if (
$element instanceof UndefinedConstraint) {
            return;
        }

        
$this->appliedDefaults $appliedDefaults;

        
$matches = array();
        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($elementJsonPointer $path null$patternProperties)
    {
        
$try = array('/''#''+''~''%');
        
$matches = array();
        foreach (
$patternProperties as $pregex => $schema) {
            
$delimiter '/';
            
// Choose delimiter. Necessary for patterns like ^/ , otherwise you get error
            
foreach ($try as $delimiter) {
                if (
strpos($pregex$delimiter) === false) { // safe to use
                    
break;
                }
            }

            
// Validate the pattern before using it to test for matches
            
if (@preg_match($delimiter $pregex $delimiter 'u''') === false) {
                
$this->addError($path'The pattern "' $pregex '" is invalid''pregex', array('pregex' => $pregex));
                continue;
            }
            foreach (
$element as $i => $value) {
                if (
preg_match($delimiter $pregex $delimiter 'u'$i)) {
                    
$matches[] = $i;
                    
$this->checkUndefined($value$schema ?: new \stdClass(), $path$iin_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 nullJsonPointer $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($path'The property ' $i ' is not defined and the definition does not allow additional properties''additionalProp');
            }

            
// additional properties defined
            
if (!in_array($i$matches) && $additionalProp && !$definition) {
                if (
$additionalProp === true) {
                    
$this->checkUndefined($valuenull$path$iin_array($i$this->appliedDefaults));
                } else {
                    
$this->checkUndefined($value$additionalProp$path$iin_array($i$this->appliedDefaults));
                }
            }

            
// property requires presence of another
            
$require $this->getProperty($definition'requires');
            if (
$require && !$this->getProperty($element$require)) {
                
$this->addError($path'The presence of the property ' $i ' requires that ' $require ' also be present''requires');
            }

            
$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 nullJsonPointer $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$iin_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$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$objectDefinitionJsonPointer $path null)
    {
        
// Verify minimum number of properties
        
if (isset($objectDefinition->minProperties) && !is_object($objectDefinition->minProperties)) {
            if (
$this->getTypeCheck()->propertyCount($element) < $objectDefinition->minProperties) {
                
$this->addError($path'Must contain a minimum of ' $objectDefinition->minProperties ' properties''minProperties', array('minProperties' => $objectDefinition->minProperties));
            }
        }
        
// Verify maximum number of properties
        
if (isset($objectDefinition->maxProperties) && !is_object($objectDefinition->maxProperties)) {
            if (
$this->getTypeCheck()->propertyCount($element) > $objectDefinition->maxProperties) {
                
$this->addError($path'Must contain no more than ' $objectDefinition->maxProperties ' properties''maxProperties', array('maxProperties' => $objectDefinition->maxProperties));
            }
        }
    }
}
<?
php

/*
 * 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\Constraints\TypeCheck\LooseTypeCheck;
use 
JsonSchema\Entity\JsonPointer;
use 
JsonSchema\Exception\ValidationException;
use 
JsonSchema\Uri\UriResolver;

/**
 * The UndefinedConstraint Constraints
 *
 * @author Robert Schönthal <[email protected]>
 * @author Bruno Prieto Reis <[email protected]>
 */
class UndefinedConstraint extends Constraint
{
    
/**
     * @var array List of properties to which a default value has been applied
     */
    
protected $appliedDefaults = array();

    
/**
     * {@inheritdoc}
     */
    
public function check(&$value$schema nullJsonPointer $path null$i null$fromDefault false)
    {
        if (
is_null($schema) || !is_object($schema)) {
            return;
        }

        
$path $this->incrementPath($path ?: new JsonPointer(''), $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$schemaJsonPointer $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);
        }
    }

    
/**
     * Validates common properties
     *
     * @param mixed       $value
     * @param mixed       $schema
     * @param JsonPointer $path
     * @param string      $i
     */
    
protected function validateCommonProperties(&$value$schemaJsonPointer $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(
                            
$this->incrementPath($path ?: new JsonPointer(''), $required),
                            
'The property ' $required ' is required',
                            
'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(
                        
$path,
                        
'The property ' $propertyName ' is required',
                        
'required'
                    
);
                }
            } 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($path'Disallowed value was matched''disallow');
            } 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($path'Matched a schema which it should not''not');
            } 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)
    {
        
// only apply defaults if feature is enabled
        
if (!$this->factory->getConfig(self::CHECK_MODE_APPLY_DEFAULTS)) {
            return;
        }

        
// apply defaults if appropriate
        
$requiredOnly $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 = array();
            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$schemaJsonPointer $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($path'Failed to match all schemas''allOf');
            }
        }

        if (isset(
$schema->anyOf)) {
            
$isValid false;
            
$startErrors $this->getErrors();
            
$caughtException null;
            foreach (
$schema->anyOf as $anyOf) {
                
$initErrors $this->getErrors();
                try {
                    
$this->checkUndefined($value$anyOf$path$i);
                    if (
$isValid = (count($this->getErrors()) == count($initErrors))) {
                        break;
                    }
                } catch (
ValidationException $e) {
                    
$isValid false;
                }
            }
            if (!
$isValid) {
                
$this->addError($path'Failed to match at least one schema''anyOf');
            } else {
                
$this->errors $startErrors;
            }
        }

        if (isset(
$schema->oneOf)) {
            
$allErrors = array();
            
$matchedSchemas 0;
            
$startErrors $this->getErrors();
            foreach (
$schema->oneOf as $oneOf) {
                try {
                    
$this->errors = array();
                    
$this->checkUndefined($value$oneOf$path$i);
                    if (
count($this->getErrors()) == 0) {
                        
$matchedSchemas++;
                    }
                    
$allErrors array_merge($allErrorsarray_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 (
$matchedSchemas !== 1) {
                
$this->addErrors(array_merge($allErrors$startErrors));
                
$this->addError($path'Failed to match exactly one schema''oneOf');
            } else {
                
$this->errors $startErrors;
            }
        }
    }

    
/**
     * Validate dependencies
     *
     * @param mixed       $value
     * @param mixed       $dependencies
     * @param JsonPointer $path
     * @param string      $i
     */
    
protected function validateDependencies($value$dependenciesJsonPointer $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($path"$key depends on $dependency and $dependency is missing"'dependencies');
                    }
                } 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($path"$key depends on $d and $d is missing"'dependencies');
                        }
                    }
                } 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

/*
 * 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\Entity\JsonPointer;
use 
JsonSchema\Exception\InvalidArgumentException;
use 
UnexpectedValueException as StandardUnexpectedValueException;

/**
 * The TypeConstraint Constraints, validates an element against a given type
 *
 * @author Robert Schönthal <[email protected]>
 * @author Bruno Prieto Reis <[email protected]>
 */
class TypeConstraint extends Constraint
{
    
/**
     * @var array|string[] type wordings for validation error messages
     */
    
public static $wording = array(
        
'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 nullJsonPointer $path null$i null)
    {
        
$type = isset($schema->type) ? $schema->type null;
        
$isValid false;
        
$wording = array();

        if (
is_array($type)) {
            
$this->validateTypesArray($value$type$wording$isValid$path);
        } elseif (
is_object($type)) {
            
$this->checkUndefined($value$type$path);

            return;
        } else {
            
$isValid $this->validateType($value$type);
        }

        if (
$isValid === false) {
            if (!
is_array($type)) {
                
$this->validateTypeNameWording($type);
                
$wording[] = self::$wording[$type];
            }
            
$this->addError($pathucwords(gettype($value)) . ' value found, but ' .
                
$this->implodeWith($wording', ''or') . ' is required''type');
        }
    }

    
/**
     * 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 agains
     * @param array $validTypesWording An array of wordings of the valid types of the array $type
     * @param bool  $isValid           The current validation value
     * @param $path
     */
    
protected function validateTypesArray(&$value, array $type, &$validTypesWording, &$isValid$path)
    {
        foreach (
$type as $tp) {
            
// $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$pathnull);
                    
$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);
                }
            }
        }
    }

    
/**
     * 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($delimiterarray_slice($elements0, -1));
        
$implodedElements array_merge(array($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 (!isset(
self::$wording[$type])) {
            throw new 
StandardUnexpectedValueException(
                
sprintf(
                    
'No wording for %s available, expected wordings are: [%s]',
                    
var_export($typetrue),
                    
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)
    {
        
//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) {
            return 
$this->getTypeCheck()->isArray($value);
        }

        
$coerce $this->factory->getConfig(Constraint::CHECK_MODE_COERCE_TYPES);

        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) {
            return 
is_string($value);
        }

        if (
'email' === $type) {
            return 
is_string($value);
        }

        if (
'null' === $type) {
            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 $value The value to convert to boolean
     *
     * @return bool|mixed
     */
    
protected function toBoolean($value)
    {
        if (
$value === 'true') {
            return 
true;
        }

        if (
$value === 'false') {
            return 
false;
        }

        return 
$value;
    }

    
/**
     * Converts a numeric string to a number. For example, "4" becomes 4.
     *
     * @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
        
}

        return 
$value;
    }

    protected function 
toInteger($value)
    {
        if (
is_numeric($value) && (int) $value == $value) {
            return (int) 
$value// cast to number
        
}

        return 
$value;
    }
}
<?
php

/*
 * 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\Entity\JsonPointer;

/**
 * The EnumConstraint Constraints, validates an element against a given set of possibilities
 *
 * @author Robert Schönthal <[email protected]>
 * @author Bruno Prieto Reis <[email protected]>
 */
class EnumConstraint extends Constraint
{
    
/**
     * {@inheritdoc}
     */
    
public function check(&$element$schema nullJsonPointer $path null$i null)
    {
        
// 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 (
$this->factory->getConfig(self::CHECK_MODE_TYPE_CAST) && $type == 'array' && $enumType == 'object') {
                if ((object) 
$element == $enum) {
                    return;
                }
            }

            if (
$type === gettype($enum)) {
                if (
$type == 'object') {
                    if (
$element == $enum) {
                        return;
                    }
                } elseif (
$element === $enum) {
                    return;
                }
            }
        }

        
$this->addError($path'Does not have a value in the enumeration ' json_encode($schema->enum), 'enum', array('enum' => $schema->enum));
    }
}
<?
php

/*
 * 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\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 = array();

    
/**
     * @var int All error types which have occurred
     */
    
protected $errorMask Validator::ERROR_NONE;

    
/**
     * @var Factory
     */
    
protected $factory;

    
/**
     * @param Factory $factory
     */
    
public function __construct(Factory $factory null)
    {
        
$this->factory $factory ?: new Factory();
    }

    public function 
addError(JsonPointer $path null$message$constraint '', array $more null)
    {
        
$error = array(
            
'property' => $this->convertJsonPointerIntoPropertyPath($path ?: new JsonPointer('')),
            
'pointer' => ltrim(strval($path ?: new JsonPointer('')), '#'),
            
'message' => $message,
            
'constraint' => $constraint,
            
'context' => $this->factory->getErrorContext(),
        );

        if (
$this->factory->getConfig(Constraint::CHECK_MODE_EXCEPTIONS)) {
            throw new 
ValidationException(sprintf('Error validating %s: %s'$error['pointer'], $error['message']));
        }

        if (
is_array($more) && count($more) > 0) {
            
$error += $more;
        }

        
$this->errors[] = $error;
        
$this->errorMask |= $error['context'];
    }

    public function 
addErrors(array $errors)
    {
        if (
$errors) {
            
$this->errors array_merge($this->errors$errors);
            
$errorMask = &$this->errorMask;
            
array_walk($errors, function ($error) use (&$errorMask) {
                if (isset(
$error['context'])) {
                    
$errorMask |= $error['context'];
                }
            });
        }
    }

    public function 
getErrors($errorContext Validator::ERROR_ALL)
    {
        if (
$errorContext === Validator::ERROR_ALL) {
            return 
$this->errors;
        }

        return 
array_filter($this->errors, function ($error) use ($errorContext) {
            if (
$errorContext $error['context']) {
                return 
true;
            }
        });
    }

    public function 
numErrors($errorContext Validator::ERROR_ALL)
    {
        if (
$errorContext === Validator::ERROR_ALL) {
            return 
count($this->errors);
        }

        return 
count($this->getErrors($errorContext));
    }

    public function 
isValid()
    {
        return !
$this->getErrors();
    }

    
/**
     * Clears any reported errors.  Should be used between
     * multiple validation checks.
     */
    
public function reset()
    {
        
$this->errors = array();
        
$this->errorMask Validator::ERROR_NONE;
    }

    
/**
     * Get the error mask
     *
     * @return int
     */
    
public function getErrorMask()
    {
        return 
$this->errorMask;
    }

    
/**
     * Recursively cast an associative array to an object
     *
     * @param array $array
     *
     * @return object
     */
    
public static function arrayToObjectRecursive($array)
    {
        
$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);
    }
}
<?
php

/*
 * 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 <[email protected]>
 */
class ObjectIterator implements \Iterator\Countable
{
    
/** @var object */
    
private $object;

    
/** @var int */
    
private $position 0;

    
/** @var array */
    
private $data = array();

    
/** @var bool */
    
private $initialized false;

    
/**
     * @param object $object
     */
    
public function __construct($object)
    {
        
$this->object $object;
    }

    
/**
     * {@inheritdoc}
     */
    
public function current()
    {
        
$this->initialize();

        return 
$this->data[$this->position];
    }

    
/**
     * {@inheritdoc}
     */
    
public function next()
    {
        
$this->initialize();
        
$this->position++;
    }

    
/**
     * {@inheritdoc}
     */
    
public function key()
    {
        
$this->initialize();

        return 
$this->position;
    }

    
/**
     * {@inheritdoc}
     */
    
public function valid()
    {
        
$this->initialize();

        return isset(
$this->data[$this->position]);
    }

    
/**
     * {@inheritdoc}
     */
    
public function rewind()
    {
        
$this->initialize();
        
$this->position 0;
    }

    
/**
     * {@inheritdoc}
     */
    
public function count()
    {
        
$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 = array();

        
$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 array();
        }

        return 
is_object($item) ? get_object_vars($item) : $item;
    }
}
<?
php

/*
 * 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 <[email protected]>
 */
class UriResolver implements UriResolverInterface
{
    
/**
     * Parses a URI into five main components
     *
     * @param string $uri
     *
     * @return array
     */
    
public function parse($uri)
    {
        
preg_match('|^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?|'$uri$match);

        
$components = array();
        if (
count($match)) {
            
$components =  array(
                
'scheme'    => $match[2],
                
'authority' => $match[4],
                
'path'      => $match[5]
            );
        }
        if (
count($match)) {
            
$components['query'] = $match[7];
        }
        if (
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;
        }

        
$basePathSegments explode('/'$basePath);

        
preg_match('|^/?(\.\./(?:\./)*)*|'$relativePath$match);
        
$numLevelUp strlen($match[0]) /1;
        if (
$numLevelUp >= count($basePathSegments)) {
            throw new 
UriResolverException(sprintf("Unable to resolve URI '%s' from base '%s'"$relativePath$basePath));
        }

        
$basePathSegments array_slice($basePathSegments0, -$numLevelUp);
        
$path preg_replace('|^/?(\.\./(\./)*)*|'''$relativePath);

        return 
implode('/'$basePathSegments) . '/' $path;
    }

    
/**
     * 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

/*
 * 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 <[email protected]>
 */
class UriRetriever implements BaseUriRetrieverInterface
{
    
/**
     * @var array Map of URL translations
     */
    
protected $translationMap = array(
        
// 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 = array(
        
'http://json-schema.org/',
        
'https://json-schema.org/'
    
);

    
/**
     * @var null|UriRetrieverInterface
     */
    
protected $uriRetriever null;

    
/**
     * @var array|object[]
     *
     * @see loadSchema
     */
    
private $schemaCache = array();

    
/**
     * 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, array(Validator::SCHEMA_MEDIA_TYPE'application/json'))) {
            return;
        }

        foreach (
$this->allowedInvalidContentTypeEndpoints as $endpoint) {
            if (
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 = array();
        if (
count($match)) {
            
$components =  array(
                
'scheme'    => $match[2],
                
'authority' => $match[4],
                
'path'      => $match[5]
            );
        }

        if (
count($match)) {
            
$components['query'] = $match[7];
        }

        if (
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

/*
 * 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 <[email protected]>
 */
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

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

/*
 * 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 <[email protected]>
 */
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($uri07) == 'file://' && substr($uri, -1) == '/'
        
) {
            throw new 
ResourceNotFoundException('JSON schema not found at ' $uri);
        }

        
$this->messageBody $response;
        if (!empty(
$http_response_header)) {
            
// $http_response_header cannot be tested, because it's defined in the method's local scope
            // See http://php.net/manual/en/reserved.variables.httpresponseheader.php for more info.
            
$this->fetchContentType($http_response_header); // @codeCoverageIgnore
        
} else {                                            // @codeCoverageIgnore
            // Could be a "file://" url or something else - fake up the response
            
$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)
    {
        foreach (
$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 (
preg_match("/Content-Type:(\V*)/ims"$header$match)) {
            return 
trim($match[1]);
        }

        return 
null;
    }
}
<?
php

/*
 * 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 <[email protected]>
 */
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($chCURLOPT_URL$uri);
        
curl_setopt($chCURLOPT_HEADERtrue);
        
curl_setopt($chCURLOPT_RETURNTRANSFERtrue);
        
curl_setopt($chCURLOPT_HTTPHEADER, array('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);

        
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 (
preg_match("/Content-Type:(\V*)/ims"$response$match)) {
            
$this->contentType trim($match[1]);

            return 
true;
        }

        return 
false;
    }
}
<?
php
/**
 * JsonSchema
 *
 * @filesource
 */

namespace JsonSchema\Uri\Retrievers;

/**
 * AbstractRetriever implements the default shared behavior
 * that all descendant Retrievers should inherit
 *
 * @author Steven Garcia <[email protected]>
 */
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

/*
 * 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;

/**
 * A JsonSchema Constraint
 *
 * @author Robert Schönthal <[email protected]>
 * @author Bruno Prieto Reis <[email protected]>
 *
 * @see    README.md
 */
class Validator extends BaseConstraint
{
    const 
SCHEMA_MEDIA_TYPE 'application/schema+json';

    const 
ERROR_NONE                    0x00000000;
    const 
ERROR_ALL                     0xFFFFFFFF;
    const 
ERROR_DOCUMENT_VALIDATION     0x00000001;
    const 
ERROR_SCHEMA_VALIDATION       0x00000002;

    
/**
     * 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.
     */
    
public function validate(&$value$schema null$checkMode null)
    {
        
// make sure $schema is an object
        
if (is_array($schema)) {
            
$schema self::arrayToObjectRecursive($schema);
        }

        
// 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
        
if (is_object($schema) && property_exists($schema'id')) {
            
$schemaURI $schema->id;
        } else {
            
$schemaURI SchemaStorage::INTERNAL_PROVIDED_SCHEMA_URI;
        }
        
$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
     */
    
public function check($value$schema)
    {
        return 
$this->validate($value$schema);
    }

    
/**
     * Alias to validate(), to maintain backwards-compatibility with the previous API
     */
    
public function coerce(&$value$schema)
    {
        return 
$this->validate($value$schemaConstraint::CHECK_MODE_COERCE_TYPES);
    }
}
<?
php

namespace JsonSchema;

interface 
SchemaStorageInterface
{
    
/**
     * Adds schema with given identifier
     *
     * @param string $id
     * @param object $schema
     */
    
public function addSchema($id$schema null);

    
/**
     * Returns schema for given identifier, or null if it does not exist
     *
     * @param string $id
     *
     * @return object
     */
    
public function getSchema($id);

    
/**
     * Returns schema for given reference with all sub-references resolved
     *
     * @param string $ref
     *
     * @return object
     */
    
public function resolveRef($ref);

    
/**
     * Returns schema referenced by '$ref' property
     *
     * @param mixed $refSchema
     *
     * @return object
     */
    
public function resolveRefSchema($refSchema);
}
<?
php

namespace Gettext;

/**
 * Interface used by all translators.
 */
interface TranslatorInterface
{
    
/**
     * Register this translator as global, to use with the gettext functions __(), p__(), etc.
     * Returns the previous translator if exists.
     *
     * @return TranslatorInterface|null
     */
    
public function register();

    
/**
     * Noop, marks the string for translation but returns it unchanged.
     *
     * @param string $original
     *
     * @return string
     */
    
public function noop($original);

    
/**
     * Gets a translation using the original string.
     *
     * @param string $original
     *
     * @return string
     */
    
public function gettext($original);

    
/**
     * Gets a translation checking the plural form.
     *
     * @param string $original
     * @param string $plural
     * @param string $value
     *
     * @return string
     */
    
public function ngettext($original$plural$value);

    
/**
     * Gets a translation checking the domain and the plural form.
     *
     * @param string $domain
     * @param string $original
     * @param string $plural
     * @param string $value
     *
     * @return string
     */
    
public function dngettext($domain$original$plural$value);

    
/**
     * Gets a translation checking the context and the plural form.
     *
     * @param string $context
     * @param string $original
     * @param string $plural
     * @param string $value
     *
     * @return string
     */
    
public function npgettext($context$original$plural$value);

    
/**
     * Gets a translation checking the context.
     *
     * @param string $context
     * @param string $original
     *
     * @return string
     */
    
public function pgettext($context$original);

    
/**
     * Gets a translation checking the domain.
     *
     * @param string $domain
     * @param string $original
     *
     * @return string
     */
    
public function dgettext($domain$original);

    
/**
     * Gets a translation checking the domain and context.
     *
     * @param string $domain
     * @param string $context
     * @param string $original
     *
     * @return string
     */
    
public function dpgettext($domain$context$original);

    
/**
     * Gets a translation checking the domain, the context and the plural form.
     *
     * @param string $domain
     * @param string $context
     * @param string $original
     * @param string $plural
     * @param string $value
     */
    
public function dnpgettext($domain$context$original$plural$value);
}
<?
php

namespace Gettext;

/**
 * Class to manage a translation string.
 */
class Translation
{
    protected 
$id;
    protected 
$context;
    protected 
$original;
    protected 
$translation '';
    protected 
$plural;
    protected 
$pluralTranslation = [];
    protected 
$references = [];
    protected 
$comments = [];
    protected 
$extractedComments = [];
    protected 
$flags = [];
    protected 
$disabled false;

    
/**
     * Generates the id of a translation (context + glue + original).
     *
     * @param string $context
     * @param string $original
     *
     * @return string
     */
    
public static function generateId($context$original)
    {
        return 
"{$context}\004{$original}";
    }

    
/**
     * Create a new instance of a Translation object.
     *
     * This is a factory method that will work even when Translation is extended.
     *
     * @param string $context  The context of the translation
     * @param string $original The original string
     * @param string $plural   The original plural string
     * @return static New Translation instance
     */
    
public static function create($context$original$plural '')
    {
        return new static(
$context$original$plural);
    }

    
/**
     * Construct.
     *
     * @param string $context  The context of the translation
     * @param string $original The original string
     * @param string $plural   The original plural string
     */
    
public function __construct($context$original$plural '')
    {
        
$this->context = (string) $context;
        
$this->original = (string) $original;

        
$this->setPlural($plural);
    }

    
/**
     * Clones this translation.
     *
     * @param null|string $context  Optional new context
     * @param null|string $original Optional new original
     *
     * @return Translation
     */
    
public function getClone($context null$original null)
    {
        
$new = clone $this;

        if (
$context !== null) {
            
$new->context = (string) $context;
        }

        if (
$original !== null) {
            
$new->original = (string) $original;
        }

        return 
$new;
    }

    
/**
     * Sets the id of this translation.
     * @warning The use of this function to set a custom ID will prevent
     *  Translations::find from matching this translation.
     *
     * @param string $id
     */
    
public function setId($id)
    {
        
$this->id $id;
    }


    
/**
     * Returns the id of this translation.
     *
     * @return string
     */
    
public function getId()
    {
        if (
$this->id === null) {
            return static::
generateId($this->context$this->original);
        }
        return 
$this->id;
    }

    
/**
     * Checks whether the translation matches with the arguments.
     *
     * @param string $context
     * @param string $original
     *
     * @return bool
     */
    
public function is($context$original '')
    {
        return ((
$this->context === $context) && ($this->original === $original)) ? true false;
    }

    
/**
     * Enable or disable the translation
     *
     * @param bool $disabled
     *
     * @return self
     */
    
public function setDisabled($disabled)
    {
        
$this->disabled = (bool) $disabled;

        return 
$this;
    }

    
/**
     * Returns whether the translation is disabled
     *
     * @return bool
     */
    
public function isDisabled()
    {
        return 
$this->disabled;
    }

    
/**
     * Gets the original string.
     *
     * @return string
     */
    
public function getOriginal()
    {
        return 
$this->original;
    }

    
/**
     * Checks if the original string is empty or not.
     *
     * @return bool
     */
    
public function hasOriginal()
    {
        return (
$this->original !== '') ? true false;
    }

    
/**
     * Sets the translation string.
     *
     * @param string $translation
     *
     * @return self
     */
    
public function setTranslation($translation)
    {
        
$this->translation = (string) $translation;

        return 
$this;
    }

    
/**
     * Gets the translation string.
     *
     * @return string
     */
    
public function getTranslation()
    {
        return 
$this->translation;
    }

    
/**
     * Checks if the translation string is empty or not.
     *
     * @return bool
     */
    
public function hasTranslation()
    {
        return (
$this->translation !== '') ? true false;
    }

    
/**
     * Sets the plural translation string.
     *
     * @param string $plural
     *
     * @return self
     */
    
public function setPlural($plural)
    {
        
$this->plural = (string) $plural;

        return 
$this;
    }

    
/**
     * Gets the plural translation string.
     *
     * @return string
     */
    
public function getPlural()
    {
        return 
$this->plural;
    }

    
/**
     * Checks if the plural translation string is empty or not.
     *
     * @return bool
     */
    
public function hasPlural()
    {
        return (
$this->plural !== '') ? true false;
    }

    
/**
     * Set a new plural translation.
     *
     * @param array $plural
     *
     * @return self
     */
    
public function setPluralTranslations(array $plural)
    {
        
$this->pluralTranslation $plural;

        return 
$this;
    }

    
/**
     * Gets all plural translations.
     *
     * @param int $size
     *
     * @return array
     */
    
public function getPluralTranslations($size null)
    {
        if (
$size === null) {
            return 
$this->pluralTranslation;
        }

        
$current count($this->pluralTranslation);

        if (
$size $current) {
            return 
$this->pluralTranslation array_fill(0$size'');
        }

        if (
$size $current) {
            return 
array_slice($this->pluralTranslation0$size);
        }

        return 
$this->pluralTranslation;
    }

    
/**
     * Checks if there are any plural translation.
     *
     * @param bool $checkContent
     *
     * @return bool
     */
    
public function hasPluralTranslations($checkContent false)
    {
        if (
$checkContent) {
            return 
implode(''$this->pluralTranslation) !== '';
        }

        return !empty(
$this->pluralTranslation);
    }

    
/**
     * Removes all plural translations.
     *
     * @return self
     */
    
public function deletePluralTranslation()
    {
        
$this->pluralTranslation = [];

        return 
$this;
    }

    
/**
     * Gets the context of this translation.
     *
     * @return string
     */
    
public function getContext()
    {
        return 
$this->context;
    }

    
/**
     * Checks if the context is empty or not.
     *
     * @return bool
     */
    
public function hasContext()
    {
        return (isset(
$this->context) && ($this->context !== '')) ? true false;
    }

    
/**
     * Adds a new reference for this translation.
     *
     * @param string   $filename The file path where the translation has been found
     * @param null|int $line     The line number where the translation has been found
     *
     * @return self
     */
    
public function addReference($filename$line null)
    {
        
$key "{$filename}:{$line}";
        
$this->references[$key] = [$filename$line];

        return 
$this;
    }

    
/**
     * Checks if the translation has any reference.
     *
     * @return bool
     */
    
public function hasReferences()
    {
        return !empty(
$this->references);
    }

    
/**
     * Return all references for this translation.
     *
     * @return array
     */
    
public function getReferences()
    {
        return 
array_values($this->references);
    }

    
/**
     * Removes all references.
     *
     * @return self
     */
    
public function deleteReferences()
    {
        
$this->references = [];

        return 
$this;
    }

    
/**
     * Adds a new comment for this translation.
     *
     * @param string $comment
     *
     * @return self
     */
    
public function addComment($comment)
    {
        if (!
in_array($comment$this->commentstrue)) {
            
$this->comments[] = $comment;
        }

        return 
$this;
    }

    
/**
     * Checks if the translation has any comment.
     *
     * @return bool
     */
    
public function hasComments()
    {
        return isset(
$this->comments[0]);
    }

    
/**
     * Returns all comments for this translation.
     *
     * @return array
     */
    
public function getComments()
    {
        return 
$this->comments;
    }

    
/**
     * Removes all comments.
     *
     * @return self
     */
    
public function deleteComments()
    {
        
$this->comments = [];

        return 
$this;
    }

    
/**
     * Adds a new extracted comment for this translation.
     *
     * @param string $comment
     *
     * @return self
     */
    
public function addExtractedComment($comment)
    {
        if (!
in_array($comment$this->extractedCommentstrue)) {
            
$this->extractedComments[] = $comment;
        }

        return 
$this;
    }

    
/**
     * Checks if the translation has any extracted comment.
     *
     * @return bool
     */
    
public function hasExtractedComments()
    {
        return isset(
$this->extractedComments[0]);
    }

    
/**
     * Returns all extracted comments for this translation.
     *
     * @return array
     */
    
public function getExtractedComments()
    {
        return 
$this->extractedComments;
    }

    
/**
     * Removes all extracted comments.
     *
     * @return self
     */
    
public function deleteExtractedComments()
    {
        
$this->extractedComments = [];

        return 
$this;
    }

    
/**
     * Adds a new flag for this translation.
     *
     * @param string $flag
     *
     * @return self
     */
    
public function addFlag($flag)
    {
        if (!
in_array($flag$this->flagstrue)) {
            
$this->flags[] = $flag;
        }

        return 
$this;
    }

    
/**
     * Checks if the translation has any flag.
     *
     * @return bool
     */
    
public function hasFlags()
    {
        return isset(
$this->flags[0]);
    }

    
/**
     * Returns all extracted flags for this translation.
     *
     * @return array
     */
    
public function getFlags()
    {
        return 
$this->flags;
    }

    
/**
     * Removes all flags.
     *
     * @return self
     */
    
public function deleteFlags()
    {
        
$this->flags = [];

        return 
$this;
    }

    
/**
     * Merges this translation with other translation.
     *
     * @param Translation $translation The translation to merge with
     * @param int         $options
     *
     * @return self
     */
    
public function mergeWith(Translation $translation$options Merge::DEFAULTS)
    {
        
Merge::mergeTranslation($translation$this$options);
        
Merge::mergeReferences($translation$this$options);
        
Merge::mergeComments($translation$this$options);
        
Merge::mergeExtractedComments($translation$this$options);
        
Merge::mergeFlags($translation$this$options);

        return 
$this;
    }
}
<?
php

namespace Gettext\Utils;

/**
 * Function parsed by PhpFunctionsScanner.
 */
class ParsedFunction
{
    
/**
     * The function name.
     *
     * @var string
     */
    
protected $name;

    
/**
     * The line where the function starts.
     *
     * @var int
     */
    
protected $line;

    
/**
     * The strings extracted from the function arguments.
     *
     * @var string[]
     */
    
protected $arguments;

    
/**
     * The current index of the function (-1 if no arguments).
     *
     * @var int|null
     */
    
protected $argumentIndex;

    
/**
     * Shall we stop adding string chunks to the current argument?
     *
     * @var bool
     */
    
protected $argumentStopped;

    
/**
     * Extracted comments.
     *
     * @var ParsedComment[]|null
     */
    
protected $comments;

    
/**
     * Initializes the instance.
     *
     * @param string $name The function name.
     * @param int    $line The line where the function starts.
     */
    
public function __construct($name$line)
    {
        
$this->name $name;
        
$this->line $line;
        
$this->arguments = [];
        
$this->argumentIndex = -1;
        
$this->argumentStopped false;
        
$this->comments null;
    }

    
/**
     * Stop extracting strings from the current argument (because we found something that's not a string).
     */
    
public function stopArgument()
    {
        if (
$this->argumentIndex === -1) {
            
$this->argumentIndex 0;
        }
        
$this->argumentStopped true;
    }

    
/**
     * Go to the next argument because we a comma was found.
     */
    
public function nextArgument()
    {
        if (
$this->argumentIndex === -1) {
            
// This should neve occur, but let's stay safe - During test/development an Exception should be thrown.
            
$this->argumentIndex 1;
        } else {
            ++
$this->argumentIndex;
        }
        
$this->argumentStopped false;
    }

    
/**
     * Add a string to the current argument.
     *
     * @param string|null $chunk
     */
    
public function addArgumentChunk($chunk)
    {
        if (
$this->argumentStopped === false) {
            if (
$this->argumentIndex === -1) {
                
$this->argumentIndex 0;
            }
            if (isset(
$this->arguments[$this->argumentIndex])) {
                
$this->arguments[$this->argumentIndex] .= $chunk;
            } else {
                
$this->arguments[$this->argumentIndex] = $chunk;
            }
        }
    }

    
/**
     * Add a comment associated to this function.
     *
     * @param ParsedComment $comment
     */
    
public function addComment($comment)
    {
        if (
$this->comments === null) {
            
$this->comments = [];
        }
        
$this->comments[] = $comment;
    }

    
/**
     * Return the line the function starts.
     *
     * @return int Line number.
     */
    
public function getLine()
    {
        return 
$this->line;
    }

    
/**
     * A closing parenthesis was found: return the final data.
     * The array returned has the following values:
     *  0 => string The function name.
     *  1 => int The line where the function starts.
     *  2 => string[] the strings extracted from the function arguments.
     *  3 => string[] the comments associated to the function.
     *
     * @return array
     */
    
public function close()
    {
        
$arguments = [];
        for (
$i 0$i <= $this->argumentIndex; ++$i) {
            
$arguments[$i] = isset($this->arguments[$i]) ? $this->arguments[$i] : null;
        }

        return [
            
$this->name,
            
$this->line,
            
$arguments,
            
$this->comments,
        ];
    }
}
<?
php

namespace Gettext\Utils;

use 
Exception;
use 
Gettext\Translations;

abstract class 
FunctionsScanner
{
    
/**
     * Scan and returns the functions and the arguments.
     *
     * @param array $constants Constants used in the code to replace
     *
     * @return array
     */
    
abstract public function getFunctions(array $constants = []);

    
/**
     * Search for specific functions and create translations.
     *
     * You can pass multiple translation with different domains and value found will be sorted respectively.
     *
     * @param Translations|Translations[] $translations Multiple domain translations instances where to save the values
     * @param array $options The extractor options
     * @throws Exception
     */
    
public function saveGettextFunctions($translations, array $options)
    {
        
$translations is_array($translations) ? $translations : [$translations];

        
/** @var Translations[] $translationByDomain [domain => translations, ..] */
        
$translationByDomain array_reduce($translations, function ($carryTranslations $translations) {
            
$carry[$translations->getDomain()] = $translations;
            return 
$carry;
        }, []);

        
$functions $options['functions'];
        
$file $options['file'];

        
/**
         * List of source code comments already associated with a function.
         *
         * Prevents associating the same comment to multiple functions.
         *
         * @var ParsedComment[] $commentsCache
         */
        
$commentsCache = [];

        foreach (
$this->getFunctions($options['constants']) as $function) {
            list(
$name$line$args) = $function;

            if (isset(
$options['lineOffset'])) {
                
$line += $options['lineOffset'];
            }

            if (!isset(
$functions[$name])) {
                continue;
            }

            
$deconstructed $this->deconstructArgs($functions[$name], $args);

            if (!
$deconstructed) {
                continue;
            }

            list(
$domain$context$original$plural) = $deconstructed;

            if ((string)
$original === '') {
                continue;
            }

            
$isDefaultDomain $domain === null;

            
$domainTranslations = isset($translationByDomain[$domain]) ? $translationByDomain[$domain] : false;

            if (!empty(
$options['domainOnly']) && $isDefaultDomain) {
                
// If we want to find translations for a specific domain, skip default domain messages
                
continue;
            }

            if (!
$domainTranslations) {
                continue;
            }

            
$translation $domainTranslations->insert($context$original$plural);
            
$translation->addReference($file$line);

            if (isset(
$function[3])) {
                
/* @var ParsedComment $extractedComment */
                
foreach ($function[3] as $extractedComment) {
                    if (
in_array($extractedComment$commentsCachetrue)) {
                        continue;
                    }
                    
$translation->addExtractedComment($extractedComment->getComment());
                    
$commentsCache[] = $extractedComment;
                }
            }
        }
    }

    
/**
     * Deconstruct arguments to translation values
     *
     * @param $function
     * @param $args
     * @return array|null
     * @throws Exception
     */
    
protected function deconstructArgs($function$args)
    {
        
$domain null;
        
$context null;
        
$original null;
        
$plural null;

        switch (
$function) {
            case 
'noop':
            case 
'gettext':
                if (!isset(
$args[0])) {
                    return 
null;
                }

                
$original $args[0];
                break;
            case 
'ngettext':
                if (!isset(
$args[1])) {
                    return 
null;
                }

                list(
$original$plural) = $args;
                break;
            case 
'pgettext':
                if (!isset(
$args[1])) {
                    return 
null;
                }

                list(
$context$original) = $args;
                break;
            case 
'dgettext':
                if (!isset(
$args[1])) {
                    return 
null;
                }

                list(
$domain$original) = $args;
                break;
            case 
'dpgettext':
                if (!isset(
$args[2])) {
                    return 
null;
                }

                list(
$domain$context$original) = $args;
                break;
            case 
'npgettext':
                if (!isset(
$args[2])) {
                    return 
null;
                }

                list(
$context$original$plural) = $args;
                break;
            case 
'dnpgettext':
                if (!isset(
$args[3])) {
                    return 
null;
                }

                list(
$domain$context$original$plural) = $args;
                break;
            case 
'dngettext':
                if (!isset(
$args[2])) {
                    return 
null;
                }

                list(
$domain$original$plural) = $args;
                break;
            default:
                throw new 
Exception(sprintf('Not valid function %s'$function));
        }

        return [
$domain$context$original$plural];
    }
}
<?
php

namespace Gettext\Utils;

class 
JsFunctionsScanner extends FunctionsScanner
{
    protected 
$code;
    protected 
$status = [];

    
/**
     * Constructor.
     *
     * @param string $code The php code to scan
     */
    
public function __construct($code)
    {
        
// Normalize newline characters
        
$this->code str_replace(["\r\n""\n\r""\r"], "\n"$code);
    }

    
/**
     * {@inheritdoc}
     */
    
public function getFunctions(array $constants = [])
    {
        
$length strlen($this->code);
        
$line 1;
        
$buffer '';
        
$functions = [];
        
$bufferFunctions = [];
        
$char null;

        for (
$pos 0$pos $length; ++$pos) {
            
$prev $char;
            
$char $this->code[$pos];
            
$next = isset($this->code[$pos 1]) ? $this->code[$pos 1] : null;

            switch (
$char) {
                case 
'\\':
                    switch (
$this->status()) {
                        case 
'simple-quote':
                            if (
$next !== "'") {
                                break 
2;
                            }
                            break;

                        case 
'double-quote':
                            if (
$next !== '"') {
                                break 
2;
                            }
                            break;

                        case 
'back-tick':
                            if (
$next !== '`') {
                                break 
2;
                            }
                            break;
                    }

                    
$prev $char;
                    
$char $next;
                    
$pos++;
                    
$next = isset($this->code[$pos]) ? $this->code[$pos] : null;
                    break;

                case 
"\n":
                    ++
$line;

                    if (
$this->status('line-comment')) {
                        
$this->upStatus();
                    }
                    break;

                case 
'/':
                    switch (
$this->status()) {
                        case 
'simple-quote':
                        case 
'double-quote':
                        case 
'back-tick':
                        case 
'line-comment':
                            break;

                        case 
'block-comment':
                            if (
$prev === '*') {
                                
$this->upStatus();
                            }
                            break;

                        default:
                            if (
$next === '/') {
                                
$this->downStatus('line-comment');
                            } elseif (
$next === '*') {
                                
$this->downStatus('block-comment');
                            }
                            break;
                    }
                    break;

                case 
"'":
                    switch (
$this->status()) {
                        case 
'simple-quote':
                            
$this->upStatus();
                            break;

                        case 
'line-comment':
                        case 
'block-comment':
                        case 
'double-quote':
                        case 
'back-tick':
                            break;

                        default:
                            
$this->downStatus('simple-quote');
                            break;
                    }
                    break;

                case 
'"':
                    switch (
$this->status()) {
                        case 
'double-quote':
                            
$this->upStatus();
                            break;

                        case 
'line-comment':
                        case 
'block-comment':
                        case 
'simple-quote':
                        case 
'back-tick':
                            break;

                        default:
                            
$this->downStatus('double-quote');
                            break;
                    }
                    break;

                case 
'`':
                    switch (
$this->status()) {
                        case 
'back-tick':
                            
$this->upStatus();
                            break;

                        case 
'line-comment':
                        case 
'block-comment':
                        case 
'simple-quote':
                        case 
'double-quote':
                            break;

                        default:
                            
$this->downStatus('back-tick');
                            break;
                    }
                    break;

                case 
'(':
                    switch (
$this->status()) {
                        case 
'simple-quote':
                        case 
'double-quote':
                        case 
'back-tick':
                        case 
'line-comment':
                        case 
'block-comment':
                            break;

                        default:
                            if (
$buffer && preg_match('/(\w+)$/'$buffer$matches)) {
                                
$this->downStatus('function');
                                
array_unshift($bufferFunctions, [$matches[1], $line, []]);
                                
$buffer '';
                                continue 
3;
                            }
                            break;
                    }
                    break;

                case 
')':
                    switch (
$this->status()) {
                        case 
'function':
                            if ((
$argument = static::prepareArgument($buffer))) {
                                
$bufferFunctions[0][2][] = $argument;
                            }

                            if (!empty(
$bufferFunctions)) {
                                
$functions[] = array_shift($bufferFunctions);
                            }

                            
$this->upStatus();
                            
$buffer '';
                            continue 
3;
                    }
                    break;

                case 
',':
                    switch (
$this->status()) {
                        case 
'function':
                            if ((
$argument = static::prepareArgument($buffer))) {
                                
$bufferFunctions[0][2][] = $argument;
                            }

                            
$buffer '';
                            continue 
3;
                    }
                    break;

                case 
' ':
                case 
'\t':
                    switch (
$this->status()) {
                        case 
'double-quote':
                        case 
'simple-quote':
                        case 
'back-tick':
                            break;

                        default:
                            
$buffer '';
                            continue 
3;
                    }
                    break;
            }

            switch (
$this->status()) {
                case 
'line-comment':
                case 
'block-comment':
                    break;

                default:
                    
$buffer .= $char;
                    break;
            }
        }

        return 
$functions;
    }

    
/**
     * Get the current context of the scan.
     *
     * @param null|string $match To check whether the current status is this value
     *
     * @return string|bool
     */
    
protected function status($match null)
    {
        
$status = isset($this->status[0]) ? $this->status[0] : null;

        if (
$match !== null) {
            return 
$status === $match;
        }

        return 
$status;
    }

    
/**
     * Add a new status to the stack.
     *
     * @param string $status
     */
    
protected function downStatus($status)
    {
        
array_unshift($this->status$status);
    }

    
/**
     * Removes and return the current status.
     *
     * @return string|null
     */
    
protected function upStatus()
    {
        return 
array_shift($this->status);
    }

    
/**
     * Prepares the arguments found in functions.
     *
     * @param string $argument
     *
     * @return string
     */
    
protected static function prepareArgument($argument)
    {
        if (
$argument && in_array($argument[0], ['"'"'"'`'], true)) {
            return static::
convertString(substr($argument1, -1));
        }
    }

    
/**
     * Decodes a string with an argument.
     *
     * @param string $value
     *
     * @return string
     */
    
protected static function convertString($value)
    {
        if (
strpos($value'\\') === false) {
            return 
$value;
        }

        return 
preg_replace_callback(
            
'/\\\(n|r|t|v|e|f|"|\\\)/',
            function (
$match) {
                switch (
$match[1][0]) {
                    case 
'n':
                        return 
"\n";
                    case 
'r':
                        return 
"\r";
                    case 
't':
                        return 
"\t";
                    case 
'v':
                        return 
"\v";
                    case 
'e':
                        return 
"\e";
                    case 
'f':
                        return 
"\f";
                    case 
'"':
                        return 
'"';
                    case 
'\\':
                        return 
'\\';
                }
            },
            
$value
        
);
    }
}
<?
php

namespace Gettext\Utils;

/**
 * Comment parsed by PhpFunctionsScanner.
 */
class ParsedComment
{
    
/**
     * The comment itself.
     *
     * @var string
     */
    
protected $comment;

    
/**
     * The line where the comment starts.
     *
     * @var int
     */
    
protected $firstLine;

    
/**
     * The line where the comment ends.
     *
     * @var int
     */
    
protected $lastLine;

    
/**
     * Initializes the instance.
     *
     * @param string $comment The comment itself.
     * @param int    $firstLine The line where the comment starts.
     * @param int    $lastLine The line where the comment ends.
     */
    
public function __construct($comment$firstLine$lastLine)
    {
        
$this->comment $comment;
        
$this->firstLine $firstLine;
        
$this->lastLine $lastLine;
    }

    
/**
     * Create new object from raw comment data.
     *
     * @param string $value The PHP comment string.
     * @param int $line The line where the comment starts.
     *
     * @return static The parsed comment.
     */
    
public static function create($value$line)
    {
        
$lastLine $line substr_count($value"\n");

        
$lines array_map(function ($line) {
            if (
'' === trim($line)) {
                return 
null;
            }

            
$line ltrim($line"#*/ \t");
            
$line rtrim($line"#*/ \t");

            return 
trim($line);
        }, 
explode("\n"$value));

        
// Remove empty lines.
        
$lines array_filter($lines);
        
$value implode(' '$lines);

        return new static(
$value$line$lastLine);
    }

    
/**
     * Return the line where the comment starts.
     *
     * @return int Line number.
     */
    
public function getFirstLine()
    {
        return 
$this->firstLine;
    }

    
/**
     * Return the line where the comment ends.
     *
     * @return int Line number.
     */
    
public function getLastLine()
    {
        return 
$this->lastLine;
    }

    
/**
     * Return the actual comment string.
     *
     * @return string The comment.
     */
    
public function getComment()
    {
        return 
$this->comment;
    }

    
/**
     * Whether this comment is related with a given function.
     *
     * @param ParsedFunction $function The function to check.
     * @return bool Whether the comment is related or not.
     */
    
public function isRelatedWith(ParsedFunction $function)
    {
        return 
$this->getLastLine() === $function->getLine() || $this->getLastLine() === $function->getLine() - 1;
    }

    
/**
     * Whether the comment matches the required prefixes.
     *
     * @param array $prefixes An array of prefixes to check.
     * @return bool Whether the comment matches the prefixes or not.
     */
    
public function checkPrefixes(array $prefixes)
    {
        if (
'' === $this->comment) {
            return 
false;
        }

        if (empty(
$prefixes)) {
            return 
true;
        }

        foreach (
$prefixes as $prefix) {
            if (
strpos($this->comment$prefix) === 0) {
                return 
true;
            }
        }

        return 
false;
    }
}
<?
php

namespace Gettext\Utils;

use 
Gettext\Translations;

/**
 * Trait used by all generators that exports the translations to plain dictionary (original => singular-translation).
 */
trait DictionaryTrait
{
    use 
HeadersGeneratorTrait;
    use 
HeadersExtractorTrait;

    
/**
     * Returns a plain dictionary with the format [original => translation].
     *
     * @param Translations $translations
     * @param bool         $includeHeaders
     *
     * @return array
     */
    
protected static function toArray(Translations $translations$includeHeaders)
    {
        
$messages = [];

        if (
$includeHeaders) {
            
$messages[''] = static::generateHeaders($translations);
        }

        foreach (
$translations as $translation) {
            if (
$translation->isDisabled()) {
                continue;
            }

            
$messages[$translation->getOriginal()] = $translation->getTranslation();
        }

        return 
$messages;
    }

    
/**
     * Extract the entries from a dictionary.
     *
     * @param array        $messages
     * @param Translations $translations
     */
    
protected static function fromArray(array $messagesTranslations $translations)
    {
        foreach (
$messages as $original => $translation) {
            if (
$original === '') {
                static::
extractHeaders($translation$translations);
                continue;
            }

            
$translations->insert(null$original)->setTranslation($translation);
        }
    }
}
<?
php

namespace Gettext\Utils;

use 
Gettext\Extractors\PhpCode;

class 
PhpFunctionsScanner extends FunctionsScanner
{
    
/**
     * PHP tokens of the code to be parsed.
     *
     * @var array
     */
    
protected $tokens;

    
/**
     * If not false, comments will be extracted.
     *
     * @var string|false|array
     */
    
protected $extractComments false;

    
/**
     * Enable extracting comments that start with a tag (if $tag is empty all the comments will be extracted).
     *
     * @param mixed $tag
     */
    
public function enableCommentsExtraction($tag '')
    {
        
$this->extractComments $tag;
    }

    
/**
     * Disable comments extraction.
     */
    
public function disableCommentsExtraction()
    {
        
$this->extractComments false;
    }

    
/**
     * Constructor.
     *
     * @param string $code The php code to scan
     */
    
public function __construct($code)
    {
        
$this->tokens array_values(
            
array_filter(
                
token_get_all($code),
                function (
$token) {
                    return !
is_array($token) || $token[0] !== T_WHITESPACE;
                }
            )
        );
    }

    
/**
     * {@inheritdoc}
     */
    
public function getFunctions(array $constants = [])
    {
        
$count count($this->tokens);
        
/* @var ParsedFunction[] $bufferFunctions */
        
$bufferFunctions = [];
        
/* @var ParsedComment[] $bufferComments */
        
$bufferComments = [];
        
/* @var array $functions */
        
$functions = [];

        for (
$k 0$k $count; ++$k) {
            
$value $this->tokens[$k];

            if (
is_string($value)) {
                if (isset(
$bufferFunctions[0])) {
                    switch (
$value) {
                        case 
',':
                            
$bufferFunctions[0]->nextArgument();
                            break;
                        case 
')':
                            
$functions[] = array_shift($bufferFunctions)->close();
                            break;
                        case 
'.':
                            break;
                        default:
                            
$bufferFunctions[0]->stopArgument();
                            break;
                    }
                }
                continue;
            }

            switch (
$value[0]) {
                case 
T_CONSTANT_ENCAPSED_STRING:
                    
//add an argument to the current function
                    
if (isset($bufferFunctions[0])) {
                        
$bufferFunctions[0]->addArgumentChunk(PhpCode::convertString($value[1]));
                    }
                    break;

                case 
T_STRING:
                    if (isset(
$bufferFunctions[0])) {
                        if (isset(
$constants[$value[1]])) {
                            
$bufferFunctions[0]->addArgumentChunk($constants[$value[1]]);
                            break;
                        }

                        if (
strtolower($value[1]) === 'null') {
                            
$bufferFunctions[0]->addArgumentChunk(null);
                            break;
                        }

                        
$bufferFunctions[0]->stopArgument();
                    }

                    
//new function found
                    
for ($j $k 1$j $count; ++$j) {
                        
$nextToken $this->tokens[$j];

                        if (
is_array($nextToken) && $nextToken[0] === T_COMMENT) {
                            continue;
                        }

                        if (
$nextToken === '(') {
                            
$newFunction = new ParsedFunction($value[1], $value[2]);

                            
// add comment that was on the line before.
                            
if (isset($bufferComments[0])) {
                                
$comment $bufferComments[0];

                                if (
$comment->isRelatedWith($newFunction)) {
                                    
$newFunction->addComment($comment);
                                }
                            }

                            
array_unshift($bufferFunctions$newFunction);
                            
$k $j;
                        }
                        break;
                    }
                    break;

                case 
T_COMMENT:
                    
$comment $this->parsePhpComment($value[1], $value[2]);

                    if (
$comment) {
                        
array_unshift($bufferComments$comment);

                        
// The comment is inside the function call.
                        
if (isset($bufferFunctions[0])) {
                            
$bufferFunctions[0]->addComment($comment);
                        }
                    }
                    break;

                default:
                    if (isset(
$bufferFunctions[0])) {
                        
$bufferFunctions[0]->stopArgument();
                    }
                    break;
            }
        }

        return 
$functions;
    }

    
/**
     * Extract the actual text from a PHP comment.
     *
     * If set, only returns comments that match the prefix(es).
     *
     * @param string $value The PHP comment.
     * @param int $line Line number.
     *
     * @return null|ParsedComment Comment or null if comment extraction is disabled or if there is a prefix mismatch.
     */
    
protected function parsePhpComment($value$line)
    {
        if (
$this->extractComments === false) {
            return 
null;
        }

        
//this returns a comment or null
        
$comment ParsedComment::create($value$line);

        
$prefixes array_filter((array) $this->extractComments);

        if (
$comment && $comment->checkPrefixes($prefixes)) {
            return 
$comment;
        }

        return 
null;
    }
}
<?
php

namespace Gettext\Utils;

class 
StringReader
{
    public 
$pos;
    public 
$str;
    public 
$strlen;

    
/**
     * Constructor.
     *
     * @param string $str The string to read
     */
    
public function __construct($str)
    {
        
$this->str $str;
        
$this->strlen strlen($this->str);
    }

    
/**
     * Read and returns a part of the string.
     *
     * @param int $bytes The number of bytes to read
     *
     * @return string
     */
    
public function read($bytes)
    {
        
$data substr($this->str$this->pos$bytes);

        
$this->seekto($this->pos $bytes);

        return 
$data;
    }

    
/**
     * Move the cursor to a specific position.
     *
     * @param int $pos The amount of bytes to move
     *
     * @return int The new position
     */
    
public function seekto($pos)
    {
        
$this->pos = ($this->strlen $pos) ? $this->strlen $pos;

        return 
$this->pos;
    }
}
<?
php

namespace Gettext\Utils;

use 
Gettext\Translations;

/**
 * Trait to provide the functionality of extracting headers.
 */
trait HeadersExtractorTrait
{
    
/**
     * Add the headers found to the translations instance.
     *
     * @param string       $headers
     * @param Translations $translations
     *
     * @return array
     */
    
protected static function extractHeaders($headersTranslations $translations)
    {
        
$headers explode("\n"$headers);
        
$currentHeader null;

        foreach (
$headers as $line) {
            
$line = static::convertString($line);

            if (
$line === '') {
                continue;
            }

            if (static::
isHeaderDefinition($line)) {
                
$header array_map('trim'explode(':'$line2));
                
$currentHeader $header[0];
                
$translations->setHeader($currentHeader$header[1]);
            } else {
                
$entry $translations->getHeader($currentHeader);
                
$translations->setHeader($currentHeader$entry.$line);
            }
        }
    }

    
/**
     * Checks if it is a header definition line. Useful for distguishing between header definitions
     * and possible continuations of a header entry.
     *
     * @param string $line Line to parse
     *
     * @return bool
     */
    
protected static function isHeaderDefinition($line)
    {
        return (bool) 
preg_match('/^[\w-]+:/'$line);
    }

    
/**
     * Normalize a string.
     *
     * @param string $value
     *
     * @return string
     */
    
public static function convertString($value)
    {
        return 
$value;
    }
}
<?
php

namespace Gettext\Utils;

use 
Gettext\Translations;

/**
 * Trait used by all generators that exports the translations to multidimensional arrays
 * (context => [original => [translation, plural1, pluraln...]]).
 */
trait MultidimensionalArrayTrait
{
    use 
HeadersGeneratorTrait;
    use 
HeadersExtractorTrait;

    
/**
     * Returns a multidimensional array.
     *
     * @param Translations $translations
     * @param bool         $includeHeaders
     * @param bool         $forceArray
     *
     * @return array
     */
    
protected static function toArray(Translations $translations$includeHeaders$forceArray false)
    {
        
$pluralForm $translations->getPluralForms();
        
$pluralSize is_array($pluralForm) ? ($pluralForm[0] - 1) : null;
        
$messages = [];

        if (
$includeHeaders) {
            
$messages[''] = [
                
'' => [static::generateHeaders($translations)],
            ];
        }

        foreach (
$translations as $translation) {
            if (
$translation->isDisabled()) {
                continue;
            }

            
$context $translation->getContext();
            
$original $translation->getOriginal();

            if (!isset(
$messages[$context])) {
                
$messages[$context] = [];
            }

            if (
$translation->hasPluralTranslations(true)) {
                
$messages[$context][$original] = $translation->getPluralTranslations($pluralSize);
                
array_unshift($messages[$context][$original], $translation->getTranslation());
            } elseif (
$forceArray) {
                
$messages[$context][$original] = [$translation->getTranslation()];
            } else {
                
$messages[$context][$original] = $translation->getTranslation();
            }
        }

        return [
            
'domain' => $translations->getDomain(),
            
'plural-forms' => $translations->getHeader('Plural-Forms'),
            
'messages' => $messages,
        ];
    }

    
/**
     * Extract the entries from a multidimensional array.
     *
     * @param array        $messages
     * @param Translations $translations
     */
    
protected static function fromArray(array $messagesTranslations $translations)
    {
        if (!empty(
$messages['domain'])) {
            
$translations->setDomain($messages['domain']);
        }

        if (!empty(
$messages['plural-forms'])) {
            
$translations->setHeader(Translations::HEADER_PLURAL$messages['plural-forms']);
        }

        foreach (
$messages['messages'] as $context => $contextTranslations) {
            foreach (
$contextTranslations as $original => $value) {
                if (
$context === '' && $original === '') {
                    static::
extractHeaders(is_array($value) ? array_shift($value) : $value$translations);
                    continue;
                }

                
$translation $translations->insert($context$original);

                if (
is_array($value)) {
                    
$translation->setTranslation(array_shift($value));
                    
$translation->setPluralTranslations($value);
                } else {
                    
$translation->setTranslation($value);
                }
            }
        }
    }
}
<?
php

namespace Gettext\Utils;

/*
 * Trait to provide the functionality of read/write csv.
 */
trait CsvTrait
{
    protected static 
$csvEscapeChar;

    
/**
     * Check whether support the escape_char argument to fgetcsv/fputcsv or not
     *
     * @return bool
     */
    
protected static function supportsCsvEscapeChar()
    {
        if (static::
$csvEscapeChar === null) {
            static::
$csvEscapeChar version_compare(PHP_VERSION'5.5.4') >= 0;
        }

        return static::
$csvEscapeChar;
    }

    
/**
     * @param resource $handle
     * @param array $options
     *
     * @return array
     */
    
protected static function fgetcsv($handle$options)
    {
        if (static::
supportsCsvEscapeChar()) {
            return 
fgetcsv($handle0$options['delimiter'], $options['enclosure'], $options['escape_char']);
        }

        return 
fgetcsv($handle0$options['delimiter'], $options['enclosure']);
    }

    
/**
     * @param resource $handle
     * @param array $fields
     * @param array $options
     *
     * @return bool|int
     */
    
protected static function fputcsv($handle$fields$options)
    {
        if (static::
supportsCsvEscapeChar()) {
            return 
fputcsv($handle$fields$options['delimiter'], $options['enclosure'], $options['escape_char']);
        }

        return 
fputcsv($handle$fields$options['delimiter'], $options['enclosure']);
    }
}
<?
php

namespace Gettext\Utils;

use 
Gettext\Translations;

/**
 * Trait to provide the functionality of extracting headers.
 */
trait HeadersGeneratorTrait
{
    
/**
     * Returns the headers as a string.
     *
     * @param Translations $translations
     *
     * @return string
     */
    
protected static function generateHeaders(Translations $translations)
    {
        
$headers '';

        foreach (
$translations->getHeaders() as $name => $value) {
            
$headers .= sprintf("%s: %s\n"$name$value);
        }

        return 
$headers;
    }
}
<?
php

namespace Gettext\Generators;

use 
Gettext\Translations;
use 
Gettext\Utils\MultidimensionalArrayTrait;

class 
PhpArray extends Generator implements GeneratorInterface
{
    use 
MultidimensionalArrayTrait;

    public static 
$options = [
        
'includeHeaders' => true,
    ];

    
/**
     * {@inheritdoc}
     */
    
public static function toString(Translations $translations, array $options = [])
    {
        
$array = static::generate($translations$options);

        return 
'<?php return '.var_export($arraytrue).';';
    }

    
/**
     * Generates an array with the translations.
     *
     * @param Translations $translations
     * @param array        $options
     *
     * @return array
     */
    
public static function generate(Translations $translations, array $options = [])
    {
        
$options += static::$options;

        return static::
toArray($translations$options['includeHeaders'], true);
    }
}
<?
php

namespace Gettext\Generators;

use 
Gettext\Translations;
use 
Gettext\Utils\DictionaryTrait;
use 
Gettext\Utils\CsvTrait;

class 
CsvDictionary extends Generator implements GeneratorInterface
{
    use 
DictionaryTrait;
    use 
CsvTrait;

    public static 
$options = [
        
'includeHeaders' => false,
        
'delimiter' => ",",
        
'enclosure' => '"',
        
'escape_char' => "\\"
    
];

    
/**
     * {@parentDoc}.
     */
    
public static function toString(Translations $translations, array $options = [])
    {
        
$options += static::$options;
        
$handle fopen('php://memory''w');

        foreach (static::
toArray($translations$options['includeHeaders']) as $original => $translation) {
            static::
fputcsv($handle, [$original$translation], $options);
        }

        
rewind($handle);
        
$csv stream_get_contents($handle);
        
fclose($handle);

        return 
$csv;
    }
}
<?
php

namespace Gettext\Generators;

use 
Gettext\Translations;
use 
Gettext\Utils\DictionaryTrait;

class 
JsonDictionary extends Generator implements GeneratorInterface
{
    use 
DictionaryTrait;

    public static 
$options = [
        
'json' => 0,
        
'includeHeaders' => false,
    ];

    
/**
     * {@parentDoc}.
     */
    
public static function toString(Translations $translations, array $options = [])
    {
        
$options += static::$options;

        return 
json_encode(static::toArray($translations$options['includeHeaders']), $options['json']);
    }
}
<?
php

namespace Gettext\Generators;

use 
Gettext\Translations;

class 
Jed extends Generator implements GeneratorInterface
{
    public static 
$options = [
        
'json' => 0,
    ];

    
/**
     * {@parentDoc}.
     */
    
public static function toString(Translations $translations, array $options = [])
    {
        
$domain $translations->getDomain() ?: 'messages';
        
$options += static::$options;

        return 
json_encode([
            
$domain => [
                
'' => [
                    
'domain' => $domain,
                    
'lang' => $translations->getLanguage() ?: 'en',
                    
'plural-forms' => $translations->getHeader('Plural-Forms') ?: 'nplurals=2; plural=(n != 1);',
                ],
            ] + static::
buildMessages($translations),
        ], 
$options['json']);
    }

    
/**
     * Generates an array with all translations.
     *
     * @param Translations $translations
     *
     * @return array
     */
    
protected static function buildMessages(Translations $translations)
    {
        
$pluralForm $translations->getPluralForms();
        
$pluralSize is_array($pluralForm) ? ($pluralForm[0] - 1) : null;
        
$messages = [];
        
$context_glue '\u0004';

        foreach (
$translations as $translation) {
            if (
$translation->isDisabled()) {
                continue;
            }

            
$key = ($translation->hasContext() ? $translation->getContext().$context_glue '')
                .
$translation->getOriginal();

            if (
$translation->hasPluralTranslations(true)) {
                
$message $translation->getPluralTranslations($pluralSize);
                
array_unshift($message$translation->getTranslation());
            } else {
                
$message = [$translation->getTranslation()];
            }

            
$messages[$key] = $message;
        }

        return 
$messages;
    }
}
<?
php

namespace Gettext\Generators;

use 
Gettext\Translation;
use 
Gettext\Translations;
use 
DOMDocument;

class 
Xliff extends Generator implements GeneratorInterface
{
    const 
UNIT_ID_REGEXP '/^XLIFF_UNIT_ID: (.*)$/';

    
/**
     * {@inheritdoc}
     */
    
public static function toString(Translations $translations, array $options = [])
    {
        
$dom = new DOMDocument('1.0''utf-8');
        
$dom->formatOutput true;
        
$xliff $dom->appendChild($dom->createElement('xliff'));
        
$xliff->setAttribute('xmlns''urn:oasis:names:tc:xliff:document:2.0');
        
$xliff->setAttribute('version''2.0');
        
$xliff->setAttribute('srcLang'$translations->getLanguage());
        
$xliff->setAttribute('trgLang'$translations->getLanguage());
        
$file $xliff->appendChild($dom->createElement('file'));
        
$file->setAttribute('id'$translations->getDomain().'.'.$translations->getLanguage());

        
//Save headers as notes
        
$notes $dom->createElement('notes');

        foreach (
$translations->getHeaders() as $name => $value) {
            
$notes->appendChild(static::createTextNode($dom'note'$value))->setAttribute('id'$name);
        }

        if (
$notes->hasChildNodes()) {
            
$file->appendChild($notes);
        }

        foreach (
$translations as $translation) {
            
//Find an XLIFF unit ID, if one is available; otherwise generate
            
$unitId = static::getUnitID($translation)?:md5($translation->getContext().$translation->getOriginal());

            
$unit $dom->createElement('unit');
            
$unit->setAttribute('id'$unitId);

            
//Save comments as notes
            
$notes $dom->createElement('notes');

            
$notes->appendChild(static::createTextNode($dom'note'$translation->getContext()))
                ->
setAttribute('category''context');

            foreach (
$translation->getComments() as $comment) {
                
//Skip XLIFF unit ID comments.
                
if (preg_match(static::UNIT_ID_REGEXP$comment)) {
                    continue;
                }

                
$notes->appendChild(static::createTextNode($dom'note'$comment))
                    ->
setAttribute('category''comment');
            }

            foreach (
$translation->getExtractedComments() as $comment) {
                
$notes->appendChild(static::createTextNode($dom'note'$comment))
                    ->
setAttribute('category''extracted-comment');
            }

            foreach (
$translation->getFlags() as $flag) {
                
$notes->appendChild(static::createTextNode($dom'note'$flag))
                    ->
setAttribute('category''flag');
            }

            foreach (
$translation->getReferences() as $reference) {
                
$notes->appendChild(static::createTextNode($dom'note'$reference[0].':'.$reference[1]))
                    ->
setAttribute('category''reference');
            }

            
$unit->appendChild($notes);

            
$segment $unit->appendChild($dom->createElement('segment'));
            
$segment->appendChild(static::createTextNode($dom'source'$translation->getOriginal()));
            
$segment->appendChild(static::createTextNode($dom'target'$translation->getTranslation()));

            foreach (
$translation->getPluralTranslations() as $plural) {
                if (
$plural !== '') {
                    
$segment->appendChild(static::createTextNode($dom'target'$plural));
                }
            }

            
$file->appendChild($unit);
        }

        return 
$dom->saveXML();
    }

    protected static function 
createTextNode(DOMDocument $dom$name$string)
    {
        
$node $dom->createElement($name);
        
$text = (preg_match('/[&<>]/'$string) === 1)
             ? 
$dom->createCDATASection($string)
             : 
$dom->createTextNode($string);
        
$node->appendChild($text);

        return 
$node;
    }

    
/**
     * Gets the translation's unit ID, if one is available.
     *
     * @param Translation $translation
     *
     * @return string|null
     */
    
public static function getUnitID(Translation $translation)
    {
        foreach (
$translation->getComments() as $comment) {
            if (
preg_match(static::UNIT_ID_REGEXP$comment$matches)) {
                return 
$matches[1];
            }
        }
        return 
null;
    }
}
<?
php

namespace Gettext\Generators;

use 
Gettext\Translations;

interface 
GeneratorInterface
{
    
/**
     * Saves the translations in a file.
     *
     * @param Translations $translations
     * @param string       $file
     * @param array        $options
     *
     * @return bool
     */
    
public static function toFile(Translations $translations$file, array $options = []);

    
/**
     * Generates a string with the translations ready to save in a file.
     *
     * @param Translations $translations
     * @param array        $options
     *
     * @return string
     */
    
public static function toString(Translations $translations, array $options = []);
}
<?
php

namespace Gettext\Generators;

use 
Gettext\Translations;
use 
Gettext\Utils\HeadersGeneratorTrait;

class 
Mo extends Generator implements GeneratorInterface
{
    use 
HeadersGeneratorTrait;

    public static 
$options = [
        
'includeHeaders' => true,
    ];

    
/**
     * {@parentDoc}.
     */
    
public static function toString(Translations $translations, array $options = [])
    {
        
$options += static::$options;
        
$messages = [];

        if (
$options['includeHeaders']) {
            
$messages[''] = static::generateHeaders($translations);
        }

        foreach (
$translations as $translation) {
            if (!
$translation->hasTranslation() || $translation->isDisabled()) {
                continue;
            }

            if (
$translation->hasContext()) {
                
$originalString $translation->getContext()."\x04".$translation->getOriginal();
            } else {
                
$originalString $translation->getOriginal();
            }

            
$messages[$originalString] = $translation;
        }

        
ksort($messages);
        
$numEntries count($messages);
        
$originalsTable '';
        
$translationsTable '';
        
$originalsIndex = [];
        
$translationsIndex = [];
        
$pluralForm $translations->getPluralForms();
        
$pluralSize is_array($pluralForm) ? ($pluralForm[0] - 1) : null;

        foreach (
$messages as $originalString => $translation) {
            if (
is_string($translation)) {
                
// Headers
                
$translationString $translation;
            } else {
                
/* @var $translation \Gettext\Translation */
                
if ($translation->hasPlural() && $translation->hasPluralTranslations(true)) {
                    
$originalString .= "\x00".$translation->getPlural();
                    
$translationString $translation->getTranslation();
                    
$translationString .= "\x00".implode("\x00"$translation->getPluralTranslations($pluralSize));
                } else {
                    
$translationString $translation->getTranslation();
                }
            }

            
$originalsIndex[] = [
                
'relativeOffset' => strlen($originalsTable),
                
'length' => strlen($originalString)
            ];
            
$originalsTable .= $originalString."\x00";
            
$translationsIndex[] = [
                
'relativeOffset' => strlen($translationsTable),
                
'length' => strlen($translationString)
            ];
            
$translationsTable .= $translationString."\x00";
        }

        
// Offset of table with the original strings index: right after the header (which is 7 words)
        
$originalsIndexOffset 4;

        
// Size of table with the original strings index
        
$originalsIndexSize $numEntries * (4);

        
// Offset of table with the translation strings index: right after the original strings index table
        
$translationsIndexOffset $originalsIndexOffset $originalsIndexSize;

        
// Size of table with the translation strings index
        
$translationsIndexSize $numEntries * (4);

        
// Hashing table starts after the header and after the index table
        
$originalsStringsOffset $translationsIndexOffset $translationsIndexSize;

        
// Translations start after the keys
        
$translationsStringsOffset $originalsStringsOffset strlen($originalsTable);

        
// Let's generate the .mo file binary data
        
$mo '';

        
// Magic number
        
$mo .= pack('L'0x950412de);

        
// File format revision
        
$mo .= pack('L'0);

        
// Number of strings
        
$mo .= pack('L'$numEntries);

        
// Offset of table with original strings
        
$mo .= pack('L'$originalsIndexOffset);

        
// Offset of table with translation strings
        
$mo .= pack('L'$translationsIndexOffset);

        
// Size of hashing table: we don't use it.
        
$mo .= pack('L'0);

        
// Offset of hashing table: it would start right after the translations index table
        
$mo .= pack('L'$translationsIndexOffset $translationsIndexSize);

        
// Write the lengths & offsets of the original strings
        
foreach ($originalsIndex as $info) {
            
$mo .= pack('L'$info['length']);
            
$mo .= pack('L'$originalsStringsOffset $info['relativeOffset']);
        }

        
// Write the lengths & offsets of the translated strings
        
foreach ($translationsIndex as $info) {
            
$mo .= pack('L'$info['length']);
            
$mo .= pack('L'$translationsStringsOffset $info['relativeOffset']);
        }

        
// Write original strings
        
$mo .= $originalsTable;

        
// Write translation strings
        
$mo .= $translationsTable;

        return 
$mo;
    }
}
<?
php

namespace Gettext\Generators;

use 
Gettext\Translations;
use 
Gettext\Utils\HeadersGeneratorTrait;
use 
Gettext\Utils\CsvTrait;

/**
 * Class to export translations to csv.
 */
class Csv extends Generator implements GeneratorInterface
{
    use 
HeadersGeneratorTrait;
    use 
CsvTrait;

    public static 
$options = [
        
'includeHeaders' => false,
        
'delimiter' => ",",
        
'enclosure' => '"',
        
'escape_char' => "\\"
    
];

    
/**
     * {@parentDoc}.
     */
    
public static function toString(Translations $translations, array $options = [])
    {
        
$options += static::$options;
        
$handle fopen('php://memory''w');

        if (
$options['includeHeaders']) {
            static::
fputcsv($handle, ['''', static::generateHeaders($translations)], $options);
        }

        foreach (
$translations as $translation) {
            if (
$translation->isDisabled()) {
                continue;
            }

            
$line = [$translation->getContext(), $translation->getOriginal(), $translation->getTranslation()];

            if (
$translation->hasPluralTranslations(true)) {
                
$line array_merge($line$translation->getPluralTranslations());
            }

            static::
fputcsv($handle$line$options);
        }

        
rewind($handle);
        
$csv stream_get_contents($handle);
        
fclose($handle);

        return 
$csv;
    }
}
<?
php

namespace Gettext\Generators;

use 
Gettext\Translations;
use 
Gettext\Utils\MultidimensionalArrayTrait;

class 
Json extends Generator implements GeneratorInterface
{
    use 
MultidimensionalArrayTrait;

    public static 
$options = [
        
'json' => 0,
        
'includeHeaders' => false,
    ];

    
/**
     * {@inheritdoc}
     */
    
public static function toString(Translations $translations, array $options = [])
    {
        
$options += static::$options;

        return 
json_encode(static::toArray($translations$options['includeHeaders'], true), $options['json']);
    }
}
<?
php

namespace Gettext\Generators;

use 
Gettext\Translations;

abstract class 
Generator implements GeneratorInterface
{
    
/**
     * {@inheritdoc}
     */
    
public static function toFile(Translations $translations$file, array $options = [])
    {
        
$content = static::toString($translations$options);

        if (
file_put_contents($file$content) === false) {
            return 
false;
        }

        return 
true;
    }
}
<?
php

namespace Gettext\Generators;

use 
Gettext\Translations;
use 
Gettext\Utils\DictionaryTrait;
use 
Symfony\Component\Yaml\Yaml as YamlDumper;

class 
YamlDictionary extends Generator implements GeneratorInterface
{
    use 
DictionaryTrait;

    public static 
$options = [
        
'includeHeaders' => false,
        
'indent' => 2,
        
'inline' => 3,
    ];

    
/**
     * {@inheritdoc}
     */
    
public static function toString(Translations $translations, array $options = [])
    {
        
$options += static::$options;

        return 
YamlDumper::dump(
            static::
toArray($translations$options['includeHeaders']),
            
$options['inline'],
            
$options['indent']
        );
    }
}
<?
php

namespace Gettext\Generators;

use 
Gettext\Translations;
use 
Gettext\Utils\MultidimensionalArrayTrait;
use 
Symfony\Component\Yaml\Yaml as YamlDumper;

class 
Yaml extends Generator implements GeneratorInterface
{
    use 
MultidimensionalArrayTrait;

    public static 
$options = [
        
'includeHeaders' => false,
        
'indent' => 2,
        
'inline' => 4,
    ];

    
/**
     * {@inheritdoc}
     */
    
public static function toString(Translations $translations, array $options = [])
    {
        
$options += static::$options;

        return 
YamlDumper::dump(
            static::
toArray($translations$options['includeHeaders']),
            
$options['inline'],
            
$options['indent']
        );
    }
}
<?
php

namespace Gettext\Generators;

use 
Gettext\Translations;

class 
Po extends Generator implements GeneratorInterface
{
    public static 
$options = [
        
'noLocation' => false,
    ];

    
/**
     * {@parentDoc}.
     */
    
public static function toString(Translations $translations, array $options = [])
    {
        
$options += static::$options;

        
$pluralForm $translations->getPluralForms();
        
$pluralSize is_array($pluralForm) ? ($pluralForm[0] - 1) : null;
        
$lines = ['msgid ""''msgstr ""'];

        foreach (
$translations->getHeaders() as $name => $value) {
            
$lines[] = sprintf('"%s: %s\\n"'$name$value);
        }

        
$lines[] = '';

        
//Translations
        
foreach ($translations as $translation) {
            if (
$translation->hasComments()) {
                foreach (
$translation->getComments() as $comment) {
                    
$lines[] = '# '.$comment;
                }
            }

            if (
$translation->hasExtractedComments()) {
                foreach (
$translation->getExtractedComments() as $comment) {
                    
$lines[] = '#. '.$comment;
                }
            }

            if (!
$options['noLocation'] && $translation->hasReferences()) {
                foreach (
$translation->getReferences() as $reference) {
                    
$lines[] = '#: '.$reference[0].(!is_null($reference[1]) ? ':'.$reference[1] : null);
                }
            }

            if (
$translation->hasFlags()) {
                
$lines[] = '#, '.implode(','$translation->getFlags());
            }

            
$prefix $translation->isDisabled() ? '#~ ' '';

            if (
$translation->hasContext()) {
                
$lines[] = $prefix.'msgctxt '.static::convertString($translation->getContext());
            }

            static::
addLines($lines$prefix.'msgid'$translation->getOriginal());

            if (
$translation->hasPlural()) {
                static::
addLines($lines$prefix.'msgid_plural'$translation->getPlural());
                static::
addLines($lines$prefix.'msgstr[0]'$translation->getTranslation());

                foreach (
$translation->getPluralTranslations($pluralSize) as $k => $v) {
                    static::
addLines($lines$prefix.'msgstr['.($k 1).']'$v);
                }
            } else {
                static::
addLines($lines$prefix.'msgstr'$translation->getTranslation());
            }

            
$lines[] = '';
        }

        return 
implode("\n"$lines);
    }

    
/**
     * Escapes and adds double quotes to a string.
     *
     * @param string $string
     *
     * @return string
     */
    
protected static function multilineQuote($string)
    {
        
$lines explode("\n"$string);
        
$last count($lines) - 1;

        foreach (
$lines as $k => $line) {
            if (
$k === $last) {
                
$lines[$k] = static::convertString($line);
            } else {
                
$lines[$k] = static::convertString($line."\n");
            }
        }

        return 
$lines;
    }

    
/**
     * Add one or more lines depending whether the string is multiline or not.
     *
     * @param array  &$lines
     * @param string $name
     * @param string $value
     */
    
protected static function addLines(array &$lines$name$value)
    {
        
$newLines = static::multilineQuote($value);

        if (
count($newLines) === 1) {
            
$lines[] = $name.' '.$newLines[0];
        } else {
            
$lines[] = $name.' ""';

            foreach (
$newLines as $line) {
                
$lines[] = $line;
            }
        }
    }

    
/**
     * Convert a string to its PO representation.
     *
     * @param string $value
     *
     * @return string
     */
    
public static function convertString($value)
    {
        return 
'"'.strtr(
            
$value,
            [
                
"\x00" => '',
                
'\\' => '\\\\',
                
"\t" => '\t',
                
"\r" => '\r',
                
"\n" => '\n',
                
'"' => '\\"',
            ]
        ).
'"';
    }
}
<?
php

namespace Gettext;

use 
Gettext\Languages\Language;
use 
BadMethodCallException;
use 
InvalidArgumentException;
use 
ArrayObject;

/**
 * Class to manage a collection of translations.
 *
 * @method static $this fromBladeFile(string $filename, array $options = [])
 * @method static $this fromBladeString(string $string, array $options = [])
 * @method $this addFromBladeFile(string $filename, array $options = [])
 * @method $this addFromBladeString(string $string, array $options = [])
 * @method static $this fromCsvFile(string $filename, array $options = [])
 * @method static $this fromCsvString(string $string, array $options = [])
 * @method $this addFromCsvFile(string $filename, array $options = [])
 * @method $this addFromCsvString(string $string, array $options = [])
 * @method bool toCsvFile(string $filename, array $options = [])
 * @method string toCsvString(array $options = [])
 * @method static $this fromCsvDictionaryFile(string $filename, array $options = [])
 * @method static $this fromCsvDictionaryString(string $string, array $options = [])
 * @method $this addFromCsvDictionaryFile(string $filename, array $options = [])
 * @method $this addFromCsvDictionaryString(string $string, array $options = [])
 * @method bool toCsvDictionaryFile(string $filename, array $options = [])
 * @method string toCsvDictionaryString(array $options = [])
 * @method static $this fromJedFile(string $filename, array $options = [])
 * @method static $this fromJedString(string $string, array $options = [])
 * @method $this addFromJedFile(string $filename, array $options = [])
 * @method $this addFromJedString(string $string, array $options = [])
 * @method bool toJedFile(string $filename, array $options = [])
 * @method string toJedString(array $options = [])
 * @method static $this fromJsCodeFile(string $filename, array $options = [])
 * @method static $this fromJsCodeString(string $string, array $options = [])
 * @method $this addFromJsCodeFile(string $filename, array $options = [])
 * @method $this addFromJsCodeString(string $string, array $options = [])
 * @method static $this fromJsonFile(string $filename, array $options = [])
 * @method static $this fromJsonString(string $string, array $options = [])
 * @method $this addFromJsonFile(string $filename, array $options = [])
 * @method $this addFromJsonString(string $string, array $options = [])
 * @method bool toJsonFile(string $filename, array $options = [])
 * @method string toJsonString(array $options = [])
 * @method static $this fromJsonDictionaryFile(string $filename, array $options = [])
 * @method static $this fromJsonDictionaryString(string $string, array $options = [])
 * @method $this addFromJsonDictionaryFile(string $filename, array $options = [])
 * @method $this addFromJsonDictionaryString(string $string, array $options = [])
 * @method bool toJsonDictionaryFile(string $filename, array $options = [])
 * @method string toJsonDictionaryString(array $options = [])
 * @method static $this fromMoFile(string $filename, array $options = [])
 * @method static $this fromMoString(string $string, array $options = [])
 * @method $this addFromMoFile(string $filename, array $options = [])
 * @method $this addFromMoString(string $string, array $options = [])
 * @method bool toMoFile(string $filename, array $options = [])
 * @method string toMoString(array $options = [])
 * @method static $this fromPhpArrayFile(string $filename, array $options = [])
 * @method static $this fromPhpArrayString(string $string, array $options = [])
 * @method $this addFromPhpArrayFile(string $filename, array $options = [])
 * @method $this addFromPhpArrayString(string $string, array $options = [])
 * @method bool toPhpArrayFile(string $filename, array $options = [])
 * @method string toPhpArrayString(array $options = [])
 * @method static $this fromPhpCodeFile(string $filename, array $options = [])
 * @method static $this fromPhpCodeString(string $string, array $options = [])
 * @method $this addFromPhpCodeFile(string $filename, array $options = [])
 * @method $this addFromPhpCodeString(string $string, array $options = [])
 * @method static $this fromPoFile(string $filename, array $options = [])
 * @method static $this fromPoString(string $string, array $options = [])
 * @method $this addFromPoFile(string $filename, array $options = [])
 * @method $this addFromPoString(string $string, array $options = [])
 * @method bool toPoFile(string $filename, array $options = [])
 * @method string toPoString(array $options = [])
 * @method static $this fromTwigFile(string $filename, array $options = [])
 * @method static $this fromTwigString(string $string, array $options = [])
 * @method $this addFromTwigFile(string $filename, array $options = [])
 * @method $this addFromTwigString(string $string, array $options = [])
 * @method static $this fromVueJsFile(string $filename, array $options = [])
 * @method static $this fromVueJsString(string $filename, array $options = [])
 * @method $this addFromVueJsFile(string $filename, array $options = [])
 * @method $this addFromVueJsString(string $filename, array $options = [])
 * @method static $this fromXliffFile(string $filename, array $options = [])
 * @method static $this fromXliffString(string $string, array $options = [])
 * @method $this addFromXliffFile(string $filename, array $options = [])
 * @method $this addFromXliffString(string $string, array $options = [])
 * @method bool toXliffFile(string $filename, array $options = [])
 * @method string toXliffString(array $options = [])
 * @method static $this fromYamlFile(string $filename, array $options = [])
 * @method static $this fromYamlString(string $string, array $options = [])
 * @method $this addFromYamlFile(string $filename, array $options = [])
 * @method $this addFromYamlString(string $string, array $options = [])
 * @method bool toYamlFile(string $filename, array $options = [])
 * @method string toYamlString(array $options = [])
 * @method static $this fromYamlDictionaryFile(string $filename, array $options = [])
 * @method static $this fromYamlDictionaryString(string $string, array $options = [])
 * @method $this addFromYamlDictionaryFile(string $filename, array $options = [])
 * @method $this addFromYamlDictionaryString(string $string, array $options = [])
 * @method bool toYamlDictionaryFile(string $filename, array $options = [])
 * @method string toYamlDictionaryString(array $options = [])
 */
class Translations extends ArrayObject
{
    const 
HEADER_LANGUAGE 'Language';
    const 
HEADER_PLURAL 'Plural-Forms';
    const 
HEADER_DOMAIN 'X-Domain';

    public static 
$options = [
        
'defaultHeaders' => [
            
'Project-Id-Version' => '',
            
'Report-Msgid-Bugs-To' => '',
            
'Last-Translator' => '',
            
'Language-Team' => '',
            
'MIME-Version' => '1.0',
            
'Content-Type' => 'text/plain; charset=UTF-8',
            
'Content-Transfer-Encoding' => '8bit',
        ],
        
'headersSorting' => false,
        
'defaultDateHeaders' => [
            
'POT-Creation-Date',
            
'PO-Revision-Date',
        ],
    ];

    protected 
$headers;

    protected 
$translationClass;

    
/**
     * @see ArrayObject::__construct()
     */
    
public function __construct(
        
$input = [],
        
$flags 0,
        
$iterator_class 'ArrayIterator',
        
$translationClass 'Gettext\Translation'
    
) {
        
$this->headers = static::$options['defaultHeaders'];

        foreach (static::
$options['defaultDateHeaders'] as $header) {
            
$this->headers[$header] = date('c');
        }

        
$this->headers[self::HEADER_LANGUAGE] = '';

        
$this->translationClass $translationClass;

        
parent::__construct($input$flags$iterator_class);
    }

    
/**
     * Magic method to create new instances using extractors
     * For example: Translations::fromMoFile($filename, $options);.
     *
     * @return Translations
     */
    
public static function __callStatic($name$arguments)
    {
        if (!
preg_match('/^from(\w+)(File|String)$/i'$name$matches)) {
            throw new 
BadMethodCallException("The method $name does not exists");
        }

        return 
call_user_func_array([new static(), 'add'.ucfirst($name)], $arguments);
    }

    
/**
     * Magic method to import/export the translations to a specific format
     * For example: $translations->toMoFile($filename, $options);
     * For example: $translations->addFromMoFile($filename, $options);.
     *
     * @return self|bool
     */
    
public function __call($name$arguments)
    {
        if (!
preg_match('/^(addFrom|to)(\w+)(File|String)$/i'$name$matches)) {
            throw new 
BadMethodCallException("The method $name does not exists");
        }

        if (
$matches[1] === 'addFrom') {
            
$extractor 'Gettext\\Extractors\\'.$matches[2].'::from'.$matches[3];
            
$source array_shift($arguments);
            
$options array_shift($arguments) ?: [];

            
call_user_func($extractor$source$this$options);

            return 
$this;
        }

        
$generator 'Gettext\\Generators\\'.$matches[2].'::to'.$matches[3];

        
array_unshift($arguments$this);

        return 
call_user_func_array($generator$arguments);
    }

    
/**
     * Magic method to clone each translation on clone the translations object.
     */
    
public function __clone()
    {
        
$array = [];

        foreach (
$this as $key => $translation) {
            
$array[$key] = clone $translation;
        }

        
$this->exchangeArray($array);
    }

    
/**
     * Control the new translations added.
     *
     * @param mixed       $index
     * @param Translation $value
     *
     * @throws InvalidArgumentException If the value is not an instance of Gettext\Translation
     *
     * @return Translation
     */
    
public function offsetSet($index$value)
    {
        if (!(
$value instanceof Translation)) {
            throw new 
InvalidArgumentException(
                
'Only instances of Gettext\\Translation must be added to a Gettext\\Translations'
            
);
        }

        
$id $value->getId();

        if (
$this->offsetExists($id)) {
            
$this[$id]->mergeWith($value);

            return 
$this[$id];
        }

        
parent::offsetSet($id$value);

        return 
$value;
    }

    
/**
     * Set the plural definition.
     *
     * @param int    $count
     * @param string $rule
     *
     * @return self
     */
    
public function setPluralForms($count$rule)
    {
        if (
preg_match('/[a-z]/i'str_replace('n'''$rule))) {
            throw new 
\InvalidArgumentException('Invalid Plural form: ' $rule);
        }
        
$this->setHeader(self::HEADER_PLURAL"nplurals={$count}; plural={$rule};");

        return 
$this;
    }

    
/**
     * Returns the parsed plural definition.
     *
     * @param null|array [count, rule]
     */
    
public function getPluralForms()
    {
        
$header $this->getHeader(self::HEADER_PLURAL);

        if (!empty(
$header)
            && 
preg_match('/^nplurals\s*=\s*(\d+)\s*;\s*plural\s*=\s*([^;]+)\s*;$/'$header$matches)
        ) {
            return [
intval($matches[1]), $matches[2]];
        }
    }

    
/**
     * Set a new header.
     *
     * @param string $name
     * @param string $value
     *
     * @return self
     */
    
public function setHeader($name$value)
    {
        
$name trim($name);
        
$this->headers[$name] = trim($value);

        return 
$this;
    }

    
/**
     * Returns a header value.
     *
     * @param string $name
     *
     * @return null|string
     */
    
public function getHeader($name)
    {
        return isset(
$this->headers[$name]) ? $this->headers[$name] : null;
    }

    
/**
     * Returns all header for this translations (in alphabetic order).
     *
     * @return array
     */
    
public function getHeaders()
    {
        if (static::
$options['headersSorting']) {
            
ksort($this->headers);
        }

        return 
$this->headers;
    }

    
/**
     * Removes all headers.
     *
     * @return self
     */
    
public function deleteHeaders()
    {
        
$this->headers = [];

        return 
$this;
    }

    
/**
     * Removes one header.
     *
     * @param string $name
     *
     * @return self
     */
    
public function deleteHeader($name)
    {
        unset(
$this->headers[$name]);

        return 
$this;
    }

    
/**
     * Returns the language value.
     *
     * @return string $language
     */
    
public function getLanguage()
    {
        return 
$this->getHeader(self::HEADER_LANGUAGE);
    }

    
/**
     * Sets the language and the plural forms.
     *
     * @param string $language
     *
     * @throws InvalidArgumentException if the language hasn't been recognized
     *
     * @return self
     */
    
public function setLanguage($language)
    {
        
$this->setHeader(self::HEADER_LANGUAGEtrim($language));

        if ((
$info Language::getById($language))) {
            return 
$this->setPluralForms(count($info->categories), $info->formula);
        }

        throw new 
InvalidArgumentException(sprintf('The language "%s" is not valid'$language));
    }

    
/**
     * Checks whether the language is empty or not.
     *
     * @return bool
     */
    
public function hasLanguage()
    {
        
$language $this->getLanguage();

        return (
is_string($language) && ($language !== '')) ? true false;
    }

    
/**
     * Set a new domain for this translations.
     *
     * @param string $domain
     *
     * @return self
     */
    
public function setDomain($domain)
    {
        
$this->setHeader(self::HEADER_DOMAINtrim($domain));

        return 
$this;
    }

    
/**
     * Returns the domain.
     *
     * @return string
     */
    
public function getDomain()
    {
        return 
$this->getHeader(self::HEADER_DOMAIN);
    }

    
/**
     * Checks whether the domain is empty or not.
     *
     * @return bool
     */
    
public function hasDomain()
    {
        
$domain $this->getDomain();

        return (
is_string($domain) && ($domain !== '')) ? true false;
    }

    
/**
     * Search for a specific translation.
     *
     * @param string|Translation $context  The context of the translation or a translation instance
     * @param string             $original The original string
     * @warning Translations with custom identifiers (e.g. XLIFF unit IDs) cannot be found using this function.
     *
     * @return Translation|false
     */
    
public function find($context$original '')
    {
        if (
$context instanceof Translation) {
            
$id $context->getId();
        } else {
            
$id Translation::generateId($context$original);
        }

        return 
$this->offsetExists($id) ? $this[$id] : false;
    }

    
/**
     * Count all elements translated
     *
     * @return integer
     */
    
public function countTranslated()
    {
        
$c 0;
        foreach (
$this as $v) {
            if (
$v->hasTranslation()) {
                
$c++;
            }
        }
        return 
$c;
    }

    
/**
     * Creates and insert/merges a new translation.
     *
     * @param string $context  The translation context
     * @param string $original The translation original string
     * @param string $plural   The translation original plural string
     *
     * @return Translation The translation created
     */
    
public function insert($context$original$plural '')
    {
        return 
$this->offsetSet(null$this->createNewTranslation($context$original$plural));
    }

    
/**
     * Merges this translations with other translations.
     *
     * @param Translations $translations The translations instance to merge with
     * @param int          $options
     *
     * @return self
     */
    
public function mergeWith(Translations $translations$options Merge::DEFAULTS)
    {
        
Merge::mergeHeaders($translations$this$options);
        
Merge::mergeTranslations($translations$this$options);

        return 
$this;
    }

    
/**
     * Create a new instance of a Translation object.
     *
     * @param string $context  The context of the translation
     * @param string $original The original string
     * @param string $plural   The original plural string
     * @return Translation New Translation instance
     */
    
public function createNewTranslation($context$original$plural '')
    {
        
$class $this->translationClass;
        return 
$class::create($context$original$plural);
    }
}
<?
php

namespace Gettext;

use 
Gettext\Generators\PhpArray;

class 
Translator extends BaseTranslator implements TranslatorInterface
{
    protected 
$domain;
    protected 
$dictionary = [];
    protected 
$plurals = [];

    
/**
     * Loads translation from a Translations instance, a file on an array.
     *
     * @param Translations|string|array $translations
     *
     * @return static
     */
    
public function loadTranslations($translations)
    {
        if (
is_object($translations) && $translations instanceof Translations) {
            
$translations PhpArray::generate($translations, ['includeHeaders' => false]);
        } elseif (
is_string($translations) && is_file($translations)) {
            
$translations = include $translations;
        } elseif (!
is_array($translations)) {
            throw new 
\InvalidArgumentException(
                
'Invalid Translator: only arrays, files or instance of Translations are allowed'
            
);
        }

        
$this->addTranslations($translations);

        return 
$this;
    }

    
/**
     * Set the default domain.
     *
     * @param string $domain
     *
     * @return static
     */
    
public function defaultDomain($domain)
    {
        
$this->domain $domain;

        return 
$this;
    }

    
/**
     * @see TranslatorInterface
     *
     * {@inheritdoc}
     */
    
public function gettext($original)
    {
        return 
$this->dpgettext($this->domainnull$original);
    }

    
/**
     * @see TranslatorInterface
     *
     * {@inheritdoc}
     */
    
public function ngettext($original$plural$value)
    {
        return 
$this->dnpgettext($this->domainnull$original$plural$value);
    }

    
/**
     * @see TranslatorInterface
     *
     * {@inheritdoc}
     */
    
public function dngettext($domain$original$plural$value)
    {
        return 
$this->dnpgettext($domainnull$original$plural$value);
    }

    
/**
     * @see TranslatorInterface
     *
     * {@inheritdoc}
     */
    
public function npgettext($context$original$plural$value)
    {
        return 
$this->dnpgettext($this->domain$context$original$plural$value);
    }

    
/**
     * @see TranslatorInterface
     *
     * {@inheritdoc}
     */
    
public function pgettext($context$original)
    {
        return 
$this->dpgettext($this->domain$context$original);
    }

    
/**
     * @see TranslatorInterface
     *
     * {@inheritdoc}
     */
    
public function dgettext($domain$original)
    {
        return 
$this->dpgettext($domainnull$original);
    }

    
/**
     * @see TranslatorInterface
     *
     * {@inheritdoc}
     */
    
public function dpgettext($domain$context$original)
    {
        
$translation $this->getTranslation($domain$context$original);

        if (isset(
$translation[0]) && $translation[0] !== '') {
            return 
$translation[0];
        }

        return 
$original;
    }

    
/**
     * @see TranslatorInterface
     *
     * {@inheritdoc}
     */
    
public function dnpgettext($domain$context$original$plural$value)
    {
        
$translation $this->getTranslation($domain$context$original);
        
$key $this->getPluralIndex($domain$value$translation === false);

        if (isset(
$translation[$key]) && $translation[$key] !== '') {
            return 
$translation[$key];
        }

        return (
$key === 0) ? $original $plural;
    }

    
/**
     * Set new translations to the dictionary.
     *
     * @param array $translations
     */
    
protected function addTranslations(array $translations)
    {
        
$domain = isset($translations['domain']) ? $translations['domain'] : '';

        
//Set the first domain loaded as default domain
        
if ($this->domain === null) {
            
$this->domain $domain;
        }

        if (isset(
$this->dictionary[$domain])) {
            
$this->dictionary[$domain] = array_replace_recursive($this->dictionary[$domain], $translations['messages']);

            return;
        }

        if (!empty(
$translations['plural-forms'])) {
            list(
$count$code) = array_map('trim'explode(';'$translations['plural-forms'], 2));

            
// extract just the expression turn 'n' into a php variable '$n'.
            // Slap on a return keyword and semicolon at the end.
            
$this->plurals[$domain] = [
                
'count' => (int) str_replace('nplurals='''$count),
                
'code' => str_replace('plural=''return 'str_replace('n''$n'$code)).';',
            ];
        }

        
$this->dictionary[$domain] = $translations['messages'];
    }

    
/**
     * Search and returns a translation.
     *
     * @param string $domain
     * @param string $context
     * @param string $original
     *
     * @return string|false
     */
    
protected function getTranslation($domain$context$original)
    {
        return isset(
$this->dictionary[$domain][$context][$original])
             ? 
$this->dictionary[$domain][$context][$original]
             : 
false;
    }

    
/**
     * Executes the plural decision code given the number to decide which
     * plural version to take.
     *
     * @param string $domain
     * @param string $n
     * @param bool   $fallback set to true to get fallback plural index
     *
     * @return int
     */
    
protected function getPluralIndex($domain$n$fallback)
    {
        
//Not loaded domain or translation, use a fallback
        
if (!isset($this->plurals[$domain]) || $fallback === true) {
            return 
$n == 1;
        }

        if (!isset(
$this->plurals[$domain]['function'])) {
            
$code = static::fixTerseIfs($this->plurals[$domain]['code']);
            
$this->plurals[$domain]['function'] = eval("return function (\$n) { $code };");
        }

        if (
$this->plurals[$domain]['count'] <= 2) {
            return 
call_user_func($this->plurals[$domain]['function'], $n) ? 0;
        }

        return 
call_user_func($this->plurals[$domain]['function'], $n);
    }

    
/**
     * This function will recursively wrap failure states in brackets if they contain a nested terse if.
     *
     * This because PHP can not handle nested terse if's unless they are wrapped in brackets.
     *
     * This code probably only works for the gettext plural decision codes.
     *
     * return ($n==1 ? 0 : $n%10>=2 && $n%10<=4 && ($n%100<10 || $n%100>=20) ? 1 : 2);
     * becomes
     * return ($n==1 ? 0 : ($n%10>=2 && $n%10<=4 && ($n%100<10 || $n%100>=20) ? 1 : 2));
     *
     * @param string $code  the terse if string
     * @param bool   $inner If inner is true we wrap it in brackets
     *
     * @return string A formatted terse If that PHP can work with.
     */
    
private static function fixTerseIfs($code$inner false)
    {
        
/*
         * (?P<expression>[^?]+)   Capture everything up to ? as 'expression'
         * \?                      ?
         * (?P<success>[^:]+)      Capture everything up to : as 'success'
         * :                       :
         * (?P<failure>[^;]+)      Capture everything up to ; as 'failure'
         */
        
preg_match('/(?P<expression>[^?]+)\?(?P<success>[^:]+):(?P<failure>[^;]+)/'$code$matches);

        
// If no match was found then no terse if was present
        
if (!isset($matches[0])) {
            return 
$code;
        }

        
$expression $matches['expression'];
        
$success $matches['success'];
        
$failure $matches['failure'];

        
// Go look for another terse if in the failure state.
        
$failure = static::fixTerseIfs($failuretrue);
        
$code $expression.' ? '.$success.' : '.$failure;

        if (
$inner) {
            return 
"($code)";
        }

        
// note the semicolon. We need that for executing the code.
        
return "$code;";
    }
}
<?
php

namespace Gettext;

class 
GettextTranslator extends BaseTranslator implements TranslatorInterface
{
    
/**
     * Constructor. Detects the current language using the environment variables.
     *
     * @param string $language
     */
    
public function __construct($language null)
    {
        if (!
function_exists('gettext')) {
            throw new 
\RuntimeException('This class require the gettext extension for PHP');
        }

        
//detects the language environment respecting the priority order
        //http://php.net/manual/en/function.gettext.php#114062
        
if (empty($language)) {
            
$language getenv('LANGUAGE') ?: getenv('LC_ALL') ?: getenv('LC_MESSAGES') ?: getenv('LANG');
        }

        if (!empty(
$language)) {
            
$this->setLanguage($language);
        }
    }

    
/**
     * Define the current locale.
     *
     * @param string   $language
     * @param int|null $category
     *
     * @return self
     */
    
public function setLanguage($language$category null)
    {
        if (
$category === null) {
            
$category defined('LC_MESSAGES') ? LC_MESSAGES LC_ALL;
        }

        
setlocale($category$language);
        
putenv('LANGUAGE='.$language);

        return 
$this;
    }

    
/**
     * Loads a gettext domain.
     *
     * @param string $domain
     * @param string $path
     * @param bool   $default
     *
     * @return self
     */
    
public function loadDomain($domain$path null$default true)
    {
        
bindtextdomain($domain$path);
        
bind_textdomain_codeset($domain'UTF-8');

        if (
$default) {
            
textdomain($domain);
        }

        return 
$this;
    }

    
/**
     * @see TranslatorInterface
     *
     * {@inheritdoc}
     */
    
public function gettext($original)
    {
        return 
gettext($original);
    }

    
/**
     * @see TranslatorInterface
     *
     * {@inheritdoc}
     */
    
public function ngettext($original$plural$value)
    {
        return 
ngettext($original$plural$value);
    }

    
/**
     * @see TranslatorInterface
     *
     * {@inheritdoc}
     */
    
public function dngettext($domain$original$plural$value)
    {
        return 
dngettext($domain$original$plural$value);
    }

    
/**
     * @see TranslatorInterface
     *
     * {@inheritdoc}
     */
    
public function npgettext($context$original$plural$value)
    {
        
$message $context."\x04".$original;
        
$translation ngettext($message$plural$value);

        return (
$translation === $message) ? $original $translation;
    }

    
/**
     * @see TranslatorInterface
     *
     * {@inheritdoc}
     */
    
public function pgettext($context$original)
    {
        
$message $context."\x04".$original;
        
$translation gettext($message);

        return (
$translation === $message) ? $original $translation;
    }

    
/**
     * @see TranslatorInterface
     *
     * {@inheritdoc}
     */
    
public function dgettext($domain$original)
    {
        return 
dgettext($domain$original);
    }

    
/**
     * @see TranslatorInterface
     *
     * {@inheritdoc}
     */
    
public function dpgettext($domain$context$original)
    {
        
$message $context."\x04".$original;
        
$translation dgettext($domain$message);

        return (
$translation === $message) ? $original $translation;
    }

    
/**
     * @see TranslatorInterface
     *
     * {@inheritdoc}
     */
    
public function dnpgettext($domain$context$original$plural$value)
    {
        
$message $context."\x04".$original;
        
$translation dngettext($domain$message$plural$value);

        return (
$translation === $message) ? $original $translation;
    }
}
<?
php

namespace Gettext;

/**
 * Static class with merge contants.
 */
final class Merge
{
    const 
ADD 1;
    const 
REMOVE 2;

    const 
HEADERS_ADD 4;
    const 
HEADERS_REMOVE 8;
    const 
HEADERS_OVERRIDE 16;

    const 
LANGUAGE_OVERRIDE 32;
    const 
DOMAIN_OVERRIDE 64;
    const 
TRANSLATION_OVERRIDE 128;

    const 
COMMENTS_OURS 256;
    const 
COMMENTS_THEIRS 512;

    const 
EXTRACTED_COMMENTS_OURS 1024;
    const 
EXTRACTED_COMMENTS_THEIRS 2048;

    const 
FLAGS_OURS 4096;
    const 
FLAGS_THEIRS 8192;

    const 
REFERENCES_OURS 16384;
    const 
REFERENCES_THEIRS 32768;

    const 
DEFAULTS 5//1 + 4

    /**
     * Merge the flags of two translations.
     *
     * @param Translation $from
     * @param Translation $to
     * @param int         $options
     */
    
public static function mergeFlags(Translation $fromTranslation $to$options self::DEFAULTS)
    {
        if (
$options self::FLAGS_THEIRS) {
            
$to->deleteFlags();
        }

        if (!(
$options self::FLAGS_OURS)) {
            foreach (
$from->getFlags() as $flag) {
                
$to->addFlag($flag);
            }
        }
    }

    
/**
     * Merge the extracted comments of two translations.
     *
     * @param Translation $from
     * @param Translation $to
     * @param int         $options
     */
    
public static function mergeExtractedComments(Translation $fromTranslation $to$options self::DEFAULTS)
    {
        if (
$options self::EXTRACTED_COMMENTS_THEIRS) {
            
$to->deleteExtractedComments();
        }

        if (!(
$options self::EXTRACTED_COMMENTS_OURS)) {
            foreach (
$from->getExtractedComments() as $comment) {
                
$to->addExtractedComment($comment);
            }
        }
    }

    
/**
     * Merge the comments of two translations.
     *
     * @param Translation $from
     * @param Translation $to
     * @param int         $options
     */
    
public static function mergeComments(Translation $fromTranslation $to$options self::DEFAULTS)
    {
        if (
$options self::COMMENTS_THEIRS) {
            
$to->deleteComments();
        }

        if (!(
$options self::COMMENTS_OURS)) {
            foreach (
$from->getComments() as $comment) {
                
$to->addComment($comment);
            }
        }
    }

    
/**
     * Merge the references of two translations.
     *
     * @param Translation $from
     * @param Translation $to
     * @param int         $options
     */
    
public static function mergeReferences(Translation $fromTranslation $to$options self::DEFAULTS)
    {
        if (
$options self::REFERENCES_THEIRS) {
            
$to->deleteReferences();
        }

        if (!(
$options self::REFERENCES_OURS)) {
            foreach (
$from->getReferences() as $reference) {
                
$to->addReference($reference[0], $reference[1]);
            }
        }
    }

    
/**
     * Merge the translations of two translations.
     *
     * @param Translation $from
     * @param Translation $to
     * @param int         $options
     */
    
public static function mergeTranslation(Translation $fromTranslation $to$options self::DEFAULTS)
    {
        
$override = (boolean) ($options self::TRANSLATION_OVERRIDE);

        if (!
$to->hasTranslation() || ($from->hasTranslation() && $override)) {
            
$to->setTranslation($from->getTranslation());
        }

        if (!
$to->hasPlural() || ($from->hasPlural() && $override)) {
            
$to->setPlural($from->getPlural());
        }

        if (!
$to->hasPluralTranslations() || ($from->hasPluralTranslations() && $override)) {
            
$to->setPluralTranslations($from->getPluralTranslations());
        }
    }

    
/**
     * Merge the translations of two translations.
     *
     * @param Translations $from
     * @param Translations $to
     * @param int          $options
     */
    
public static function mergeTranslations(Translations $fromTranslations $to$options self::DEFAULTS)
    {
        if (
$options self::REMOVE) {
            
$filtered = [];

            foreach (
$to as $entry) {
                if (
$from->find($entry)) {
                    
$filtered[$entry->getId()] = $entry;
                }
            }

            
$to->exchangeArray($filtered);
        }

        foreach (
$from as $entry) {
            if ((
$existing $to->find($entry))) {
                
$existing->mergeWith($entry$options);
            } elseif (
$options self::ADD) {
                
$to[] = $entry->getClone();
            }
        }
    }

    
/**
     * Merge the headers of two translations.
     *
     * @param Translations $from
     * @param Translations $to
     * @param int          $options
     */
    
public static function mergeHeaders(Translations $fromTranslations $to$options self::DEFAULTS)
    {
        if (
$options self::HEADERS_REMOVE) {
            foreach (
array_keys($to->getHeaders()) as $name) {
                if (
$from->getHeader($name) === null) {
                    
$to->deleteHeader($name);
                }
            }
        }

        foreach (
$from->getHeaders() as $name => $value) {
            
$current $to->getHeader($name);

            if (empty(
$current)) {
                if (
$options self::HEADERS_ADD) {
                    
$to->setHeader($name$value);
                }
                continue;
            }

            if (empty(
$value)) {
                continue;
            }

            switch (
$name) {
                case 
Translations::HEADER_LANGUAGE:
                case 
Translations::HEADER_PLURAL:
                    if (
$options self::LANGUAGE_OVERRIDE) {
                        
$to->setHeader($name$value);
                    }
                    break;

                case 
Translations::HEADER_DOMAIN:
                    if (
$options self::DOMAIN_OVERRIDE) {
                        
$to->setHeader($name$value);
                    }
                    break;

                default:
                    if (
$options self::HEADERS_OVERRIDE) {
                        
$to->setHeader($name$value);
                    }
            }
        }
    }
}
<?
php

use Gettext\BaseTranslator;

/**
 * Returns the translation of a string.
 *
 * @param string $original
 *
 * @return string
 */
function __($original)
{
    
$text BaseTranslator::$current->gettext($original);

    if (
func_num_args() === 1) {
        return 
$text;
    }

    
$args array_slice(func_get_args(), 1);

    return 
is_array($args[0]) ? strtr($text$args[0]) : vsprintf($text$args);
}

/**
 * Noop, marks the string for translation but returns it unchanged.
 *
 * @param string $original
 *
 * @return string
 */
function noop__($original)
{
    return 
$original;
}

/**
 * Returns the singular/plural translation of a string.
 *
 * @param string $original
 * @param string $plural
 * @param string $value
 *
 * @return string
 */
function n__($original$plural$value)
{
    
$text BaseTranslator::$current->ngettext($original$plural$value);

    if (
func_num_args() === 3) {
        return 
$text;
    }

    
$args array_slice(func_get_args(), 3);

    return 
is_array($args[0]) ? strtr($text$args[0]) : vsprintf($text$args);
}

/**
 * Returns the translation of a string in a specific context.
 *
 * @param string $context
 * @param string $original
 *
 * @return string
 */
function p__($context$original)
{
    
$text BaseTranslator::$current->pgettext($context$original);

    if (
func_num_args() === 2) {
        return 
$text;
    }

    
$args array_slice(func_get_args(), 2);

    return 
is_array($args[0]) ? strtr($text$args[0]) : vsprintf($text$args);
}

/**
 * Returns the translation of a string in a specific domain.
 *
 * @param string $domain
 * @param string $original
 *
 * @return string
 */
function d__($domain$original)
{
    
$text BaseTranslator::$current->dgettext($domain$original);

    if (
func_num_args() === 2) {
        return 
$text;
    }

    
$args array_slice(func_get_args(), 2);

    return 
is_array($args[0]) ? strtr($text$args[0]) : vsprintf($text$args);
}

/**
 * Returns the translation of a string in a specific domain and context.
 *
 * @param string $domain
 * @param string $context
 * @param string $original
 *
 * @return string
 */
function dp__($domain$context$original)
{
    
$text BaseTranslator::$current->dpgettext($domain$context$original);

    if (
func_num_args() === 3) {
        return 
$text;
    }

    
$args array_slice(func_get_args(), 3);

    return 
is_array($args[0]) ? strtr($text$args[0]) : vsprintf($text$args);
}

/**
 * Returns the singular/plural translation of a string in a specific domain.
 *
 * @param string $domain
 * @param string $original
 * @param string $plural
 * @param string $value
 *
 * @return string
 */
function dn__($domain$original$plural$value)
{
    
$text BaseTranslator::$current->dngettext($domain$original$plural$value);

    if (
func_num_args() === 4) {
        return 
$text;
    }

    
$args array_slice(func_get_args(), 4);

    return 
is_array($args[0]) ? strtr($text$args[0]) : vsprintf($text$args);
}

/**
 * Returns the singular/plural translation of a string in a specific context.
 *
 * @param string $context
 * @param string $original
 * @param string $plural
 * @param string $value
 *
 * @return string
 */
function np__($context$original$plural$value)
{
    
$text BaseTranslator::$current->npgettext($context$original$plural$value);

    if (
func_num_args() === 4) {
        return 
$text;
    }

    
$args array_slice(func_get_args(), 4);

    return 
is_array($args[0]) ? strtr($text$args[0]) : vsprintf($text$args);
}

/**
 * Returns the singular/plural translation of a string in a specific domain and context.
 *
 * @param string $domain
 * @param string $context
 * @param string $original
 * @param string $plural
 * @param string $value
 *
 * @return string
 */
function dnp__($domain$context$original$plural$value)
{
    
$text BaseTranslator::$current->dnpgettext($domain$context$original$plural$value);

    if (
func_num_args() === 5) {
        return 
$text;
    }

    
$args array_slice(func_get_args(), 5);

    return 
is_array($args[0]) ? strtr($text$args[0]) : vsprintf($text$args);
}
<?
php

namespace Gettext\Extractors;

use 
BadMethodCallException;
use 
Gettext\Translations;
use 
Gettext\Utils\MultidimensionalArrayTrait;

/**
 * Class to get gettext strings from php files returning arrays.
 */
class PhpArray extends Extractor implements ExtractorInterface
{
    use 
MultidimensionalArrayTrait;

    
/**
     * {@inheritdoc}
     */
    
public static function fromFile($fileTranslations $translations, array $options = [])
    {
        foreach (static::
getFiles($file) as $file) {
            static::
fromArray(include($file), $translations);
        }
    }

    
/**
     * {@inheritdoc}
     */
    
public static function fromString($stringTranslations $translations, array $options = [])
    {
        throw new 
BadMethodCallException('PhpArray::fromString() cannot be called. Use PhpArray::fromFile()');
    }
}
<?
php

namespace Gettext\Extractors;

use 
Gettext\Translations;
use 
Gettext\Utils\HeadersExtractorTrait;
use 
Gettext\Utils\CsvTrait;

/**
 * Class to get gettext strings from csv.
 */
class CsvDictionary extends Extractor implements ExtractorInterface
{
    use 
HeadersExtractorTrait;
    use 
CsvTrait;

    public static 
$options = [
        
'delimiter' => ",",
        
'enclosure' => '"',
        
'escape_char' => "\\"
    
];

    
/**
     * {@inheritdoc}
     */
    
public static function fromString($stringTranslations $translations, array $options = [])
    {
        
$options += static::$options;
        
$handle fopen('php://memory''w');

        
fputs($handle$string);
        
rewind($handle);

        while (
$row = static::fgetcsv($handle$options)) {
            list(
$original$translation) = $row + [''''];

            if (
$original === '') {
                static::
extractHeaders($translation$translations);
                continue;
            }

            
$translations->insert(null$original)->setTranslation($translation);
        }

        
fclose($handle);
    }
}
<?
php

namespace Gettext\Extractors;

use 
Gettext\Translations;
use 
Twig_Loader_Array;
use 
Twig_Environment;
use 
Twig_Source;
use 
Twig_Extensions_Extension_I18n;

/**
 * Class to get gettext strings from twig files returning arrays.
 */
class Twig extends Extractor implements ExtractorInterface
{
    public static 
$options = [
        
'extractComments' => 'notes:',
        
'twig' => null,
    ];

    
/**
     * {@inheritdoc}
     */
    
public static function fromString($stringTranslations $translations, array $options = [])
    {
        
$options += static::$options;

        
$twig $options['twig'] ?: static::createTwig();

        
PhpCode::fromString($twig->compileSource(new Twig_Source($string'')), $translations$options);
    }

    
/**
     * Returns a Twig instance.
     *
     * @return Twig_Environment
     */
    
protected static function createTwig()
    {
        
$twig = new Twig_Environment(new Twig_Loader_Array(['' => '']));
        
$twig->addExtension(new Twig_Extensions_Extension_I18n());

        return static::
$options['twig'] = $twig;
    }
}
<?
php

namespace Gettext\Extractors;

use 
Gettext\Translations;
use 
Gettext\Utils\DictionaryTrait;

/**
 * Class to get gettext strings from plain json.
 */
class JsonDictionary extends Extractor implements ExtractorInterface
{
    use 
DictionaryTrait;

    
/**
     * {@inheritdoc}
     */
    
public static function fromString($stringTranslations $translations, array $options = [])
    {
        
$messages json_decode($stringtrue);

        if (
is_array($messages)) {
            static::
fromArray($messages$translations);
        }
    }
}
<?
php

namespace Gettext\Extractors;

use 
Gettext\Translations;

interface 
ExtractorInterface
{
    
/**
     * Extract the translations from a file.
     *
     * @param array|string $file         A path of a file or files
     * @param Translations $translations The translations instance to append the new translations.
     * @param array        $options
     */
    
public static function fromFile($fileTranslations $translations, array $options = []);

    
/**
     * Parses a string and append the translations found in the Translations instance.
     *
     * @param string       $string
     * @param Translations $translations
     * @param array        $options
     */
    
public static function fromString($stringTranslations $translations, array $options = []);
}
<?
php
/** @noinspection PhpComposerExtensionStubsInspection */

namespace Gettext\Extractors;

use 
DOMAttr;
use 
DOMDocument;
use 
DOMElement;
use 
DOMNode;
use 
Exception;
use 
Gettext\Translations;
use 
Gettext\Utils\FunctionsScanner;

/**
 * Class to get gettext strings from VueJS template files.
 */
class VueJs extends Extractor implements ExtractorInterfaceExtractorMultiInterface
{
    public static 
$options = [
        
'constants' => [],

        
'functions' => [
            
'gettext' => 'gettext',
            
'__' => 'gettext',
            
'ngettext' => 'ngettext',
            
'n__' => 'ngettext',
            
'pgettext' => 'pgettext',
            
'p__' => 'pgettext',
            
'dgettext' => 'dgettext',
            
'd__' => 'dgettext',
            
'dngettext' => 'dngettext',
            
'dn__' => 'dngettext',
            
'dpgettext' => 'dpgettext',
            
'dp__' => 'dpgettext',
            
'npgettext' => 'npgettext',
            
'np__' => 'npgettext',
            
'dnpgettext' => 'dnpgettext',
            
'dnp__' => 'dnpgettext',
            
'noop' => 'noop',
            
'noop__' => 'noop',
        ],
    ];

    protected static 
$functionsScannerClass 'Gettext\Utils\JsFunctionsScanner';

    
/**
     * @inheritDoc
     * @throws Exception
     */
    
public static function fromFileMultiple($file, array $translations, array $options = [])
    {
        foreach (static::
getFiles($file) as $file) {
            
$options['file'] = $file;
            static::
fromStringMultiple(static::readFile($file), $translations$options);
        }
    }

    
/**
     * @inheritdoc
     * @throws Exception
     */
    
public static function fromString($stringTranslations $translations, array $options = [])
    {
        static::
fromStringMultiple($string, [$translations], $options);
    }

    
/**
     * @inheritDoc
     * @throws Exception
     */
    
public static function fromStringMultiple($string, array $translations, array $options = [])
    {
        
$options += static::$options;
        
$options += [
            
// HTML attribute prefixes we parse as JS which could contain translations (are JS expressions)
            
'attributePrefixes' => [
                
':',
                
'v-bind:',
                
'v-on:',
                
'v-text',
            ],
            
// HTML Tags to parse
            
'tagNames' => [
                
'translate',
            ],
            
// HTML tags to parse when attribute exists
            
'tagAttributes' => [
                
'v-translate',
            ],
            
// Comments
            
'commentAttributes' => [
                
'translate-comment',
            ],
            
'contextAttributes' => [
                
'translate-context',
            ],
            
// Attribute with plural content
            
'pluralAttributes' => [
                
'translate-plural',
            ],
        ];

        
// Ok, this is the weirdest hack, but let me explain:
        // On Linux (Mac is fine), when converting HTML to DOM, new lines get trimmed after the first tag.
        // So if there are new lines between <template> and next element, they are lost
        // So we insert a "." which is a text node, and it will prevent that newlines are stripped between elements.
        // Same thing happens between template and script tag.
        
$string str_replace('<template>''<template>.'$string);
        
$string str_replace('</template>''</template>.'$string);

        
// Normalize newlines
        
$string str_replace(["\r\n""\n\r""\r"], "\n"$string);

        
// VueJS files are valid HTML files, we will operate with the DOM here
        
$dom = static::convertHtmlToDom($string);

        
$script = static::extractScriptTag($string);

        
// Parse the script part as a regular JS code
        
if ($script) {
            
$scriptLineNumber $dom->getElementsByTagName('script')->item(0)->getLineNo();
            static::
getScriptTranslationsFromString(
                
$script,
                
$translations,
                
$options,
                
$scriptLineNumber 1
            
);
        }

        
// Template part is parsed separately, all variables will be extracted
        // and handled as a regular JS code
        
$template $dom->getElementsByTagName('template')->item(0);
        if (
$template) {
            static::
getTemplateTranslations(
                
$template,
                
$translations,
                
$options,
                
$template->getLineNo() - 1
            
);
        }
    }

    
/**
     * Extracts script tag contents using regex instead of DOM operations.
     * If we parse using DOM, some contents may change, for example, tags within strings will be stripped
     *
     * @param $string
     * @return bool|string
     */
    
protected static function extractScriptTag($string)
    {
        if (
preg_match('#<\s*?script\b[^>]*>(.*?)</script\b[^>]*>#s'$string$matches)) {
            return 
$matches[1];
        }

        return 
'';
    }

    
/**
     * @param string $html
     * @return DOMDocument
     */
    
protected static function convertHtmlToDom($html)
    {
        
$dom = new DOMDocument;

        
libxml_use_internal_errors(true);

        
// Prepend xml encoding so DOMDocument document handles UTF8 correctly.
        // Assuming that vue template files will not have any xml encoding tags, because duplicate tags may be ignored.
        
$dom->loadHTML('<?xml encoding="utf-8"?>' $html);

        
libxml_clear_errors();

        return 
$dom;
    }

    
/**
     * Extract translations from script part
     *
     * @param string $scriptContents Only script tag contents, not the whole template
     * @param Translations|Translations[] $translations One or multiple domain Translation objects
     * @param array $options
     * @param int $lineOffset Number of lines the script is offset in the vue template file
     * @throws Exception
     */
    
protected static function getScriptTranslationsFromString(
        
$scriptContents,
        
$translations,
        array 
$options = [],
        
$lineOffset 0
    
) {
        
/** @var FunctionsScanner $functions */
        
$functions = new static::$functionsScannerClass($scriptContents);
        
$options['lineOffset'] = $lineOffset;
        
$functions->saveGettextFunctions($translations$options);
    }

    
/**
     * Parse template to extract all translations (element content and dynamic element attributes)
     *
     * @param DOMNode $dom
     * @param Translations|Translations[] $translations One or multiple domain Translation objects
     * @param array $options
     * @param int $lineOffset Line number where the template part starts in the vue file
     * @throws Exception
     */
    
protected static function getTemplateTranslations(
        
DOMNode $dom,
        
$translations,
        array 
$options,
        
$lineOffset 0
    
) {
        
// Build a JS string from all template attribute expressions
        
$fakeAttributeJs = static::getTemplateAttributeFakeJs($options$dom);

        
// 1 line offset is necessary because parent template element was ignored when converting to DOM
        
static::getScriptTranslationsFromString($fakeAttributeJs$translations$options$lineOffset);

        
// Build a JS string from template element content expressions
        
$fakeTemplateJs = static::getTemplateFakeJs($dom);
        static::
getScriptTranslationsFromString($fakeTemplateJs$translations$options$lineOffset);

        static::
getTagTranslations($options$dom$translations);
    }

    
/**
     * @param array $options
     * @param DOMNode $dom
     * @param Translations|Translations[] $translations
     */
    
protected static function getTagTranslations(array $optionsDOMNode $dom$translations)
    {
        
// Since tag scanning does not support domains, we always use the first translation given
        
$translations is_array($translations) ? reset($translations) : $translations;

        
$children $dom->childNodes;
        for (
$i 0$i $children->length$i++) {
            
$node $children->item($i);

            if (!(
$node instanceof DOMElement)) {
                continue;
            }

            
$translatable false;

            if (
in_array($node->tagName$options['tagNames'], true)) {
                
$translatable true;
            }

            
$attrList $node->attributes;
            
$context null;
            
$plural "";
            
$comment null;

            for (
$j 0$j $attrList->length$j++) {
                
/** @var DOMAttr $domAttr */
                
$domAttr $attrList->item($j);
                
// Check if this is a dynamic vue attribute
                
if (in_array($domAttr->name$options['tagAttributes'])) {
                    
$translatable true;
                }
                if (
in_array($domAttr->name$options['contextAttributes'])) {
                    
$context $domAttr->value;
                }
                if (
in_array($domAttr->name$options['pluralAttributes'])) {
                    
$plural $domAttr->value;
                }
                if (
in_array($domAttr->name$options['commentAttributes'])) {
                    
$comment $domAttr->value;
                }
            }

            if (
$translatable) {
                
$translation $translations->insert($contexttrim($node->textContent), $plural);
                
$translation->addReference($options['file'], $node->getLineNo());
                if (
$comment) {
                    
$translation->addExtractedComment($comment);
                }
            }

            if (
$node->hasChildNodes()) {
                static::
getTagTranslations($options$node$translations);
            }
        }
    }

    
/**
     * Extract JS expressions from element attribute bindings (excluding text within elements)
     * For example: <span :title="__('extract this')"> skip element content </span>
     *
     * @param array $options
     * @param DOMNode $dom
     * @return string JS code
     */
    
protected static function getTemplateAttributeFakeJs(array $optionsDOMNode $dom)
    {
        
$expressionsByLine = static::getVueAttributeExpressions($options['attributePrefixes'], $dom);

        if (empty(
$expressionsByLine)) {
            return 
'';
        }

        
$maxLines max(array_keys($expressionsByLine));
        
$fakeJs '';

        for (
$line 1$line <= $maxLines$line++) {
            if (isset(
$expressionsByLine[$line])) {
                
$fakeJs .= implode("; "$expressionsByLine[$line]);
            }
            
$fakeJs .= "\n";
        }

        return 
$fakeJs;
    }

    
/**
     * Loop DOM element recursively and parse out all dynamic vue attributes which are basically JS expressions
     *
     * @param array $attributePrefixes List of attribute prefixes we parse as JS (may contain translations)
     * @param DOMNode $dom
     * @param array $expressionByLine [lineNumber => [jsExpression, ..], ..]
     * @return array [lineNumber => [jsExpression, ..], ..]
     */
    
protected static function getVueAttributeExpressions(
        array 
$attributePrefixes,
        
DOMNode $dom,
        array &
$expressionByLine = []
    ) {
        
$children $dom->childNodes;

        for (
$i 0$i $children->length$i++) {
            
$node $children->item($i);

            if (!(
$node instanceof DOMElement)) {
                continue;
            }
            
$attrList $node->attributes;

            for (
$j 0$j $attrList->length$j++) {
                
/** @var DOMAttr $domAttr */
                
$domAttr $attrList->item($j);

                
// Check if this is a dynamic vue attribute
                
if (static::isAttributeMatching($domAttr->name$attributePrefixes)) {
                    
$line $domAttr->getLineNo();
                    
$expressionByLine += [$line => []];
                    
$expressionByLine[$line][] = $domAttr->value;
                }
            }

            if (
$node->hasChildNodes()) {
                
$expressionByLine = static::getVueAttributeExpressions($attributePrefixes$node$expressionByLine);
            }
        }

        return 
$expressionByLine;
    }

    
/**
     * Check if this attribute name should be parsed for translations
     *
     * @param string $attributeName
     * @param string[] $attributePrefixes
     * @return bool
     */
    
protected static function isAttributeMatching($attributeName$attributePrefixes)
    {
        foreach (
$attributePrefixes as $prefix) {
            if (
strpos($attributeName$prefix) === 0) {
                return 
true;
            }
        }
        return 
false;
    }

    
/**
     * Extract JS expressions from within template elements (excluding attributes)
     * For example: <span :title="skip attributes"> {{__("extract element content")}} </span>
     *
     * @param DOMNode $dom
     * @return string JS code
     */
    
protected static function getTemplateFakeJs(DOMNode $dom)
    {
        
$fakeJs '';
        
$lines explode("\n"$dom->textContent);

        
// Build a fake JS file from template by extracting JS expressions within each template line
        
foreach ($lines as $line) {
            
$expressionMatched = static::parseOneTemplateLine($line);

            
$fakeJs .= implode("; "$expressionMatched) . "\n";
        }

        return 
$fakeJs;
    }

    
/**
     * Match JS expressions in a template line
     *
     * @param string $line
     * @return string[]
     */
    
protected static function parseOneTemplateLine($line)
    {
        
$line trim($line);

        if (!
$line) {
            return [];
        }

        
$regex '#\{\{(.*?)\}\}#';

        
preg_match_all($regex$line$matches);

        
$matched array_map(function ($v) {
            return 
trim($v'\'"{}');
        }, 
$matches[1]);

        return 
$matched;
    }
}
<?
php

namespace Gettext\Extractors;

use 
Gettext\Translations;

interface 
ExtractorMultiInterface
{
    
/**
     * Parses a string and append the translations found in the Translations instance.
     * Allows scanning for multiple domains at a time (each Translation has to have a different domain)
     *
     * @param string $string
     * @param Translations[] $translations
     * @param array $options
     */
    
public static function fromStringMultiple($string, array $translations, array $options = []);

    
/**
     * Parses a string and append the translations found in the Translations instance.
     * Allows scanning for multiple domains at a time (each Translation has to have a different domain)
     *
     * @param $file
     * @param Translations[] $translations
     * @param array $options
     */
    
public static function fromFileMultiple($file, array $translations, array $options = []);
}
<?
php

namespace Gettext\Extractors;

use 
Gettext\Translations;

/**
 * Class to get gettext strings from json files.
 */
class Jed extends Extractor implements ExtractorInterface
{
    
/**
     * {@inheritdoc}
     */
    
public static function fromString($stringTranslations $translations, array $options = [])
    {
        static::
extract(json_decode($stringtrue), $translations);
    }

    
/**
     * Handle an array of translations and append to the Translations instance.
     *
     * @param array        $content
     * @param Translations $translations
     */
    
public static function extract(array $contentTranslations $translations)
    {
        
$messages current($content);
        
$headers = isset($messages['']) ? $messages[''] : null;
        unset(
$messages['']);

        if (!empty(
$headers['domain'])) {
            
$translations->setDomain($headers['domain']);
        }

        if (!empty(
$headers['lang'])) {
            
$translations->setLanguage($headers['lang']);
        }

        if (!empty(
$headers['plural-forms'])) {
            
$translations->setHeader(Translations::HEADER_PLURAL$headers['plural-forms']);
        }

        
$context_glue '\u0004';

        foreach (
$messages as $key => $translation) {
            
$key explode($context_glue$key);
            
$context = isset($key[1]) ? array_shift($key) : '';

            
$translations->insert($contextarray_shift($key))
                ->
setTranslation(array_shift($translation))
                ->
setPluralTranslations($translation);
        }
    }
}
<?
php

namespace Gettext\Extractors;

use 
Gettext\Translations;
use 
Gettext\Translation;
use 
SimpleXMLElement;

/**
 * Class to get gettext strings from xliff format.
 */
class Xliff extends Extractor implements ExtractorInterface
{

    public static 
$options = [
        
'unitid_as_id' => false
    
];

    
/**
     * {@inheritdoc}
     */
    
public static function fromString($stringTranslations $translations, array $options = [])
    {
        
$options += static::$options;

        
$xml = new SimpleXMLElement($stringnullfalse);

        foreach (
$xml->file as $file) {
            if (isset(
$file->notes)) {
                foreach (
$file->notes->note as $note) {
                    
$translations->setHeader($note['id'], (string) $note);
                }
            }

            foreach (
$file->unit as $unit) {
                foreach (
$unit->segment as $segment) {
                    
$targets = [];

                    foreach (
$segment->target as $target) {
                        
$targets[] = (string) $target;
                    }

                    
$translation $translations->createNewTranslation(null, (string) $segment->source);
                    if (isset(
$unit['id'])) {
                        
$unitId = (string) $unit['id'];
                        
$translation->addComment("XLIFF_UNIT_ID: $unitId");
                        if (
$options['unitid_as_id']) {
                            
$translation->setId($unitId);
                        }
                    }
                    
$translation->setTranslation(array_shift($targets));
                    
$translation->setPluralTranslations($targets);

                    if (isset(
$unit->notes)) {
                        foreach (
$unit->notes->note as $note) {
                            switch (
$note['category']) {
                                case 
'context':
                                    
$translation $translation->getClone((string) $note);
                                    break;

                                case 
'extracted-comment':
                                    
$translation->addExtractedComment((string) $note);
                                    break;

                                case 
'flag':
                                    
$translation->addFlag((string) $note);
                                    break;

                                case 
'reference':
                                    
$ref explode(':', (string) $note2);
                                    
$translation->addReference($ref[0], isset($ref[1]) ? $ref[1] : null);
                                    break;

                                default:
                                    
$translation->addComment((string) $note);
                                    break;
                            }
                        }
                    }

                    
$translations[] = $translation;
                }
            }
        }
    }
}
<?
php

namespace Gettext\Extractors;

use 
Exception;
use 
Gettext\Translations;
use 
Gettext\Utils\FunctionsScanner;

/**
 * Class to get gettext strings from javascript files.
 */
class JsCode extends Extractor implements ExtractorInterfaceExtractorMultiInterface
{
    public static 
$options = [
        
'constants' => [],

        
'functions' => [
            
'gettext' => 'gettext',
            
'__' => 'gettext',
            
'ngettext' => 'ngettext',
            
'n__' => 'ngettext',
            
'pgettext' => 'pgettext',
            
'p__' => 'pgettext',
            
'dgettext' => 'dgettext',
            
'd__' => 'dgettext',
            
'dngettext' => 'dngettext',
            
'dn__' => 'dngettext',
            
'dpgettext' => 'dpgettext',
            
'dp__' => 'dpgettext',
            
'npgettext' => 'npgettext',
            
'np__' => 'npgettext',
            
'dnpgettext' => 'dnpgettext',
            
'dnp__' => 'dnpgettext',
            
'noop' => 'noop',
            
'noop__' => 'noop',
        ],
    ];

    protected static 
$functionsScannerClass 'Gettext\Utils\JsFunctionsScanner';

    
/**
     * @inheritdoc
     * @throws Exception
     */
    
public static function fromString($stringTranslations $translations, array $options = [])
    {
        static::
fromStringMultiple($string, [$translations], $options);
    }

    
/**
     * @inheritDoc
     * @throws Exception
     */
    
public static function fromStringMultiple($string, array $translations, array $options = [])
    {
        
$options += static::$options;

        
/** @var FunctionsScanner $functions */
        
$functions = new static::$functionsScannerClass($string);
        
$functions->saveGettextFunctions($translations$options);
    }

    
/**
     * @inheritDoc
     * @throws Exception
     */
    
public static function fromFileMultiple($file, array $translations, array $options = [])
    {
        foreach (static::
getFiles($file) as $file) {
            
$options['file'] = $file;
            static::
fromStringMultiple(static::readFile($file), $translations$options);
        }
    }
}
<?
php

namespace Gettext\Extractors;

use 
Exception;
use 
Gettext\Translations;
use 
Gettext\Utils\StringReader;

/**
 * Class to get gettext strings from .mo files.
 */
class Mo extends Extractor implements ExtractorInterface
{
    const 
MAGIC1 = -1794895138;
    const 
MAGIC2 = -569244523;
    const 
MAGIC3 2500072158;

    protected static 
$stringReaderClass 'Gettext\Utils\StringReader';

    
/**
     * {@inheritdoc}
     */
    
public static function fromString($stringTranslations $translations, array $options = [])
    {
        
/** @var StringReader $stream */
        
$stream = new static::$stringReaderClass($string);
        
$magic = static::readInt($stream'V');

        if ((
$magic === static::MAGIC1) || ($magic === static::MAGIC3)) { //to make sure it works for 64-bit platforms
            
$byteOrder 'V'//low endian
        
} elseif ($magic === (static::MAGIC2 0xFFFFFFFF)) {
            
$byteOrder 'N'//big endian
        
} else {
            throw new 
Exception('Not MO file');
        }

        static::
readInt($stream$byteOrder);

        
$total = static::readInt($stream$byteOrder); //total string count
        
$originals = static::readInt($stream$byteOrder); //offset of original table
        
$tran = static::readInt($stream$byteOrder); //offset of translation table

        
$stream->seekto($originals);
        
$table_originals = static::readIntArray($stream$byteOrder$total 2);

        
$stream->seekto($tran);
        
$table_translations = static::readIntArray($stream$byteOrder$total 2);

        for (
$i 0$i $total; ++$i) {
            
$next $i 2;

            
$stream->seekto($table_originals[$next 2]);
            
$original $stream->read($table_originals[$next 1]);

            
$stream->seekto($table_translations[$next 2]);
            
$translated $stream->read($table_translations[$next 1]);

            if (
$original === '') {
                
// Headers
                
foreach (explode("\n"$translated) as $headerLine) {
                    if (
$headerLine === '') {
                        continue;
                    }

                    
$headerChunks preg_split('/:\s*/'$headerLine2);
                    
$translations->setHeader($headerChunks[0], isset($headerChunks[1]) ? $headerChunks[1] : '');
                }

                continue;
            }

            
$chunks explode("\x04"$original2);

            if (isset(
$chunks[1])) {
                
$context $chunks[0];
                
$original $chunks[1];
            } else {
                
$context '';
            }

            
$chunks explode("\x00"$original2);

            if (isset(
$chunks[1])) {
                
$original $chunks[0];
                
$plural $chunks[1];
            } else {
                
$plural '';
            }

            
$translation $translations->insert($context$original$plural);

            if (
$translated === '') {
                continue;
            }

            if (
$plural === '') {
                
$translation->setTranslation($translated);
                continue;
            }

            
$v explode("\x00"$translated);
            
$translation->setTranslation(array_shift($v));
            
$translation->setPluralTranslations($v);
        }
    }

    
/**
     * @param StringReader $stream
     * @param string       $byteOrder
     */
    
protected static function readInt(StringReader $stream$byteOrder)
    {
        if ((
$read $stream->read(4)) === false) {
            return 
false;
        }

        
$read unpack($byteOrder$read);

        return 
array_shift($read);
    }

    
/**
     * @param StringReader $stream
     * @param string       $byteOrder
     * @param int          $count
     */
    
protected static function readIntArray(StringReader $stream$byteOrder$count)
    {
        return 
unpack($byteOrder.$count$stream->read($count));
    }
}
<?
php

namespace Gettext\Extractors;

use 
Gettext\Translations;
use 
Gettext\Utils\HeadersExtractorTrait;
use 
Gettext\Utils\CsvTrait;

/**
 * Class to get gettext strings from csv.
 */
class Csv extends Extractor implements ExtractorInterface
{
    use 
HeadersExtractorTrait;
    use 
CsvTrait;

    public static 
$options = [
        
'delimiter' => ",",
        
'enclosure' => '"',
        
'escape_char' => "\\"
    
];

    
/**
     * {@inheritdoc}
     */
    
public static function fromString($stringTranslations $translations, array $options = [])
    {
        
$options += static::$options;
        
$handle fopen('php://memory''w');

        
fputs($handle$string);
        
rewind($handle);

        while (
$row = static::fgetcsv($handle$options)) {
            
$context array_shift($row);
            
$original array_shift($row);

            if (
$context === '' && $original === '') {
                static::
extractHeaders(array_shift($row), $translations);
                continue;
            }

            
$translation $translations->insert($context$original);

            if (!empty(
$row)) {
                
$translation->setTranslation(array_shift($row));
                
$translation->setPluralTranslations($row);
            }
        }

        
fclose($handle);
    }
}
<?
php

namespace Gettext\Extractors;

use 
Gettext\Translations;
use 
Gettext\Utils\MultidimensionalArrayTrait;

/**
 * Class to get gettext strings from json.
 */
class Json extends Extractor implements ExtractorInterface
{
    use 
MultidimensionalArrayTrait;

    
/**
     * {@inheritdoc}
     */
    
public static function fromString($stringTranslations $translations, array $options = [])
    {
        
$messages json_decode($stringtrue);

        if (
is_array($messages)) {
            static::
fromArray($messages$translations);
        }
    }
}
<?
php

namespace Gettext\Extractors;

use 
Exception;
use 
Gettext\Translations;
use 
Gettext\Utils\FunctionsScanner;

/**
 * Class to get gettext strings from php files returning arrays.
 */
class PhpCode extends Extractor implements ExtractorInterfaceExtractorMultiInterface
{
    public static 
$options = [
        
// - false: to not extract comments
        // - empty string: to extract all comments
        // - non-empty string: to extract comments that start with that string
        // - array with strings to extract comments format.
        
'extractComments' => false,

        
'constants' => [],

        
'functions' => [
            
'gettext' => 'gettext',
            
'__' => 'gettext',
            
'ngettext' => 'ngettext',
            
'n__' => 'ngettext',
            
'pgettext' => 'pgettext',
            
'p__' => 'pgettext',
            
'dgettext' => 'dgettext',
            
'd__' => 'dgettext',
            
'dngettext' => 'dngettext',
            
'dn__' => 'dngettext',
            
'dpgettext' => 'dpgettext',
            
'dp__' => 'dpgettext',
            
'npgettext' => 'npgettext',
            
'np__' => 'npgettext',
            
'dnpgettext' => 'dnpgettext',
            
'dnp__' => 'dnpgettext',
            
'noop' => 'noop',
            
'noop__' => 'noop',
        ],
    ];

    protected static 
$functionsScannerClass 'Gettext\Utils\PhpFunctionsScanner';

    
/**
     * {@inheritdoc}
     * @throws Exception
     */
    
public static function fromString($stringTranslations $translations, array $options = [])
    {
        static::
fromStringMultiple($string, [$translations], $options);
    }

    
/**
     * @inheritDoc
     * @throws Exception
     */
    
public static function fromStringMultiple($string, array $translations, array $options = [])
    {
        
$options += static::$options;

        
/** @var FunctionsScanner $functions */
        
$functions = new static::$functionsScannerClass($string);

        if (
$options['extractComments'] !== false) {
            
$functions->enableCommentsExtraction($options['extractComments']);
        }

        
$functions->saveGettextFunctions($translations$options);
    }

    
/**
     * @inheritDoc
     */
    
public static function fromFileMultiple($file, array $translations, array $options = [])
    {
        foreach (static::
getFiles($file) as $file) {
            
$options['file'] = $file;
            static::
fromStringMultiple(static::readFile($file), $translations$options);
        }
    }


    
/**
     * Decodes a T_CONSTANT_ENCAPSED_STRING string.
     *
     * @param string $value
     *
     * @return string
     */
    
public static function convertString($value)
    {
        if (
strpos($value'\\') === false) {
            return 
substr($value1, -1);
        }

        if (
$value[0] === "'") {
            return 
strtr(substr($value1, -1), ['\\\\' => '\\''\\\'' => '\'']);
        }

        
$value substr($value1, -1);

        return 
preg_replace_callback(
            
'/\\\(n|r|t|v|e|f|\$|"|\\\|x[0-9A-Fa-f]{1,2}|u{[0-9a-f]{1,6}}|[0-7]{1,3})/',
            function (
$match) {
                switch (
$match[1][0]) {
                    case 
'n':
                        return 
"\n";
                    case 
'r':
                        return 
"\r";
                    case 
't':
                        return 
"\t";
                    case 
'v':
                        return 
"\v";
                    case 
'e':
                        return 
"\e";
                    case 
'f':
                        return 
"\f";
                    case 
'$':
                        return 
'$';
                    case 
'"':
                        return 
'"';
                    case 
'\\':
                        return 
'\\';
                    case 
'x':
                        return 
chr(hexdec(substr($match[1], 1)));
                    case 
'u':
                        return static::
unicodeChar(hexdec(substr($match[1], 1)));
                    default:
                        return 
chr(octdec($match[1]));
                }
            },
            
$value
        
);
    }

    
/**
     * @param $dec
     * @return string|null
     * @see http://php.net/manual/en/function.chr.php#118804
     */
    
protected static function unicodeChar($dec)
    {
        if (
$dec 0x80) {
            return 
chr($dec);
        }

        if (
$dec 0x0800) {
            return 
chr(0xC0 + ($dec >> 6))
                . 
chr(0x80 + ($dec 0x3f));
        }

        if (
$dec 0x010000) {
            return 
chr(0xE0 + ($dec >> 12))
                . 
chr(0x80 + (($dec >> 6) & 0x3f))
                . 
chr(0x80 + ($dec 0x3f));
        }

        if (
$dec 0x200000) {
            return 
chr(0xF0 + ($dec >> 18))
                . 
chr(0x80 + (($dec >> 12) & 0x3f))
                . 
chr(0x80 + (($dec >> 6) & 0x3f))
                . 
chr(0x80 + ($dec 0x3f));
        }

        return 
null;
    }
}
<?
php

namespace Gettext\Extractors;

use 
Gettext\Translations;
use 
Illuminate\Filesystem\Filesystem;
use 
Illuminate\View\Compilers\BladeCompiler;

/**
 * Class to get gettext strings from blade.php files returning arrays.
 */
class Blade extends Extractor implements ExtractorInterface
{
    
/**
     * {@inheritdoc}
     */
    
public static function fromString($stringTranslations $translations, array $options = [])
    {
        if (empty(
$options['facade'])) {
            
$cachePath = empty($options['cachePath']) ? sys_get_temp_dir() : $options['cachePath'];
            
$bladeCompiler = new BladeCompiler(new Filesystem(), $cachePath);

            if (
method_exists($bladeCompiler'withoutComponentTags')) {
                
$bladeCompiler->withoutComponentTags();
            }

            
$string $bladeCompiler->compileString($string);
        } else {
            
$string $options['facade']::compileString($string);
        }

        
PhpCode::fromString($string$translations$options);
    }
}
<?
php

namespace Gettext\Extractors;

use 
Gettext\Translations;
use 
Gettext\Utils\DictionaryTrait;
use 
Symfony\Component\Yaml\Yaml as YamlParser;

/**
 * Class to get gettext strings from yaml.
 */
class YamlDictionary extends Extractor implements ExtractorInterface
{
    use 
DictionaryTrait;

    
/**
     * {@inheritdoc}
     */
    
public static function fromString($stringTranslations $translations, array $options = [])
    {
        
$messages YamlParser::parse($string);

        if (
is_array($messages)) {
            static::
fromArray($messages$translations);
        }
    }
}
<?
php

namespace Gettext\Extractors;

use 
Exception;
use 
InvalidArgumentException;
use 
Gettext\Translations;

abstract class 
Extractor implements ExtractorInterface
{
    
/**
     * {@inheritdoc}
     */
    
public static function fromFile($fileTranslations $translations, array $options = [])
    {
        foreach (static::
getFiles($file) as $file) {
            
$options['file'] = $file;
            static::
fromString(static::readFile($file), $translations$options);
        }
    }

    
/**
     * Checks and returns all files.
     *
     * @param string|array $file The file/s
     *
     * @return array The file paths
     */
    
protected static function getFiles($file)
    {
        if (empty(
$file)) {
            throw new 
InvalidArgumentException('There is not any file defined');
        }

        if (
is_string($file)) {
            if (!
is_file($file)) {
                throw new 
InvalidArgumentException("'$file' is not a valid file");
            }

            if (!
is_readable($file)) {
                throw new 
InvalidArgumentException("'$file' is not a readable file");
            }

            return [
$file];
        }

        if (
is_array($file)) {
            
$files = [];

            foreach (
$file as $f) {
                
$files array_merge($files, static::getFiles($f));
            }

            return 
$files;
        }

        throw new 
InvalidArgumentException('The first argument must be string or array');
    }

    
/**
     * Reads and returns the content of a file.
     *
     * @param string $file
     *
     * @return string
     */
    
protected static function readFile($file)
    {
        
$length filesize($file);

        if (!(
$fd fopen($file'rb'))) {
            throw new 
Exception("Cannot read the file '$file', probably permissions");
        }

        
$content $length fread($fd$length) : '';
        
fclose($fd);

        return 
$content;
    }
}
<?
php

namespace Gettext\Extractors;

use 
Gettext\Translations;
use 
Gettext\Utils\MultidimensionalArrayTrait;
use 
Symfony\Component\Yaml\Yaml as YamlParser;

/**
 * Class to get gettext strings from yaml.
 */
class Yaml extends Extractor implements ExtractorInterface
{
    use 
MultidimensionalArrayTrait;

    
/**
     * {@inheritdoc}
     */
    
public static function fromString($stringTranslations $translations, array $options = [])
    {
        
$messages YamlParser::parse($string);

        if (
is_array($messages)) {
            static::
fromArray($messages$translations);
        }
    }
}
<?
php

namespace Gettext\Extractors;

use 
Gettext\Translations;
use 
Gettext\Translation;
use 
Gettext\Utils\HeadersExtractorTrait;

/**
 * Class to get gettext strings from php files returning arrays.
 */
class Po extends Extractor implements ExtractorInterface
{
    use 
HeadersExtractorTrait;

    
/**
     * Parses a .po file and append the translations found in the Translations instance.
     *
     * {@inheritdoc}
     */
    
public static function fromString($stringTranslations $translations, array $options = [])
    {
        
$lines explode("\n"$string);
        
$i 0;

        
$translation $translations->createNewTranslation('''');

        for (
$n count($lines); $i $n; ++$i) {
            
$line trim($lines[$i]);
            
$line = static::fixMultiLines($line$lines$i);

            if (
$line === '') {
                if (
$translation->is('''')) {
                    static::
extractHeaders($translation->getTranslation(), $translations);
                } elseif (
$translation->hasOriginal()) {
                    
$translations[] = $translation;
                }

                
$translation $translations->createNewTranslation('''');
                continue;
            }

            
$splitLine preg_split('/\s+/'$line2);
            
$key $splitLine[0];
            
$data = isset($splitLine[1]) ? $splitLine[1] : '';

            if (
$key === '#~') {
                
$translation->setDisabled(true);

                
$splitLine preg_split('/\s+/'$data2);
                
$key $splitLine[0];
                
$data = isset($splitLine[1]) ? $splitLine[1] : '';
            }

            switch (
$key) {
                case 
'#':
                    
$translation->addComment($data);
                    
$append null;
                    break;

                case 
'#.':
                    
$translation->addExtractedComment($data);
                    
$append null;
                    break;

                case 
'#,':
                    foreach (
array_map('trim'explode(','trim($data))) as $value) {
                        
$translation->addFlag($value);
                    }
                    
$append null;
                    break;

                case 
'#:':
                    foreach (
preg_split('/\s+/'trim($data)) as $value) {
                        if (
preg_match('/^(.+)(:(\d*))?$/U'$value$matches)) {
                            
$translation->addReference($matches[1], isset($matches[3]) ? $matches[3] : null);
                        }
                    }
                    
$append null;
                    break;

                case 
'msgctxt':
                    
$translation $translation->getClone(static::convertString($data));
                    
$append 'Context';
                    break;

                case 
'msgid':
                    
$translation $translation->getClone(null, static::convertString($data));
                    
$append 'Original';
                    break;

                case 
'msgid_plural':
                    
$translation->setPlural(static::convertString($data));
                    
$append 'Plural';
                    break;

                case 
'msgstr':
                case 
'msgstr[0]':
                    
$translation->setTranslation(static::convertString($data));
                    
$append 'Translation';
                    break;

                case 
'msgstr[1]':
                    
$translation->setPluralTranslations([static::convertString($data)]);
                    
$append 'PluralTranslation';
                    break;

                default:
                    if (
strpos($key'msgstr[') === 0) {
                        
$p $translation->getPluralTranslations();
                        
$p[] = static::convertString($data);

                        
$translation->setPluralTranslations($p);
                        
$append 'PluralTranslation';
                        break;
                    }

                    if (isset(
$append)) {
                        if (
$append === 'Context') {
                            
$translation $translation->getClone($translation->getContext()
                                .
"\n"
                                
.static::convertString($data));
                            break;
                        }

                        if (
$append === 'Original') {
                            
$translation $translation->getClone(null$translation->getOriginal()
                                .
"\n"
                                
.static::convertString($data));
                            break;
                        }

                        if (
$append === 'PluralTranslation') {
                            
$p $translation->getPluralTranslations();
                            
$p[] = array_pop($p)."\n".static::convertString($data);
                            
$translation->setPluralTranslations($p);
                            break;
                        }

                        
$getMethod 'get'.$append;
                        
$setMethod 'set'.$append;
                        
$translation->$setMethod($translation->$getMethod()."\n".static::convertString($data));
                    }
                    break;
            }
        }

        if (
$translation->hasOriginal() && !in_array($translationiterator_to_array($translations))) {
            
$translations[] = $translation;
        }
    }

    
/**
     * Gets one string from multiline strings.
     *
     * @param string $line
     * @param array  $lines
     * @param int    &$i
     *
     * @return string
     */
    
protected static function fixMultiLines($line, array $lines, &$i)
    {
        for (
$j $i$t count($lines); $j $t; ++$j) {
            if (
substr($line, -11) == '"' && isset($lines[$j 1])) {
                
$nextLine trim($lines[$j 1]);
                if (
substr($nextLine01) == '"') {
                    
$line substr($line0, -1).substr($nextLine1);
                    continue;
                }
                if (
substr($nextLine04) == '#~ "') {
                    
$line substr($line0, -1).substr($nextLine4);
                    continue;
                }
            }
            
$i $j;
            break;
        }

        return 
$line;
    }

    
/**
     * Convert a string from its PO representation.
     *
     * @param string $value
     *
     * @return string
     */
    
public static function convertString($value)
    {
        if (!
$value) {
            return 
'';
        }

        if (
$value[0] === '"') {
            
$value substr($value1, -1);
        }

        return 
strtr(
            
$value,
            [
                
'\\\\' => '\\',
                
'\\a' => "\x07",
                
'\\b' => "\x08",
                
'\\t' => "\t",
                
'\\n' => "\n",
                
'\\v' => "\x0b",
                
'\\f' => "\x0c",
                
'\\r' => "\r",
                
'\\"' => '"',
            ]
        );
    }
}
<?
php

namespace Gettext;

abstract class 
BaseTranslator implements TranslatorInterface
{
    
/** @var TranslatorInterface */
    
public static $current;

    
/**
     * @see TranslatorInterface
     */
    
public function noop($original)
    {
        return 
$original;
    }

    
/**
     * @see TranslatorInterface
     */
    
public function register()
    {
        
$previous = static::$current;

        static::
$current $this;

        static::
includeFunctions();

        return 
$previous;
    }

    
/**
     * Include the gettext functions
     */
    
public static function includeFunctions()
    {
        include_once 
__DIR__.'/translator_functions.php';
    }
}
<?
php

spl_autoload_register
(function ($class) {
    if (
strpos($class'Gettext\\') !== 0) {
        return;
    }

    
$file __DIR__.str_replace('\\'DIRECTORY_SEPARATORsubstr($classstrlen('Gettext'))).'.php';

    if (
is_file($file)) {
        require_once 
$file;
    }
});
<?
php

namespace Gettext\Languages;

use 
Exception;

/**
 * Holds the CLDR data.
 */
class CldrData
{
    
/**
     * Super-special plural category: this should always be present for any language.
     *
     * @var string
     */
    
const OTHER_CATEGORY 'other';

    
/**
     * The list of the plural categories, sorted from 'zero' to 'other'.
     *
     * @var string[]
     */
    
public static $categories = array('zero''one''two''few''many'self::OTHER_CATEGORY);

    
/**
     * The loaded CLDR data.
     *
     * @var array
     */
    
private static $data;

    
/**
     * @var array
     */
    
private static $plurals;

    
/**
     * Returns a dictionary containing the language names.
     * The keys are the language identifiers.
     * The values are the language names in US English.
     *
     * @return string[]
     */
    
public static function getLanguageNames()
    {
        return 
self::getData('languages');
    }

    
/**
     * Return a dictionary containing the territory names (in US English).
     * The keys are the territory identifiers.
     * The values are the territory names in US English.
     *
     * @return string[]
     */
    
public static function getTerritoryNames()
    {
        return 
self::getData('territories');
    }

    
/**
     * Return a dictionary containing the script names (in US English).
     * The keys are the script identifiers.
     * The values are the script names in US English.
     *
     * @param bool $standAlone set to true to retrieve the stand-alone script names, false otherwise
     *
     * @return string[]
     */
    
public static function getScriptNames($standAlone)
    {
        return 
self::getData($standAlone 'standAloneScripts' 'scripts');
    }

    
/**
     * A dictionary containing the plural rules.
     * The keys are the language identifiers.
     * The values are arrays whose keys are the CLDR category names and the values are the CLDR category definition.
     *
     * @example The English key-value pair is somethink like this:
     * <code><pre>
     * "en": {
     *     "pluralRule-count-one": "i = 1 and v = 0 @integer 1",
     *     "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
     * }
     * </pre></code>
     *
     * @return array
     */
    
public static function getPlurals()
    {
        return 
self::getData('plurals');
    }

    
/**
     * Return a list of superseded language codes.
     *
     * @return array keys are the former language codes, values are the new language/locale codes
     */
    
public static function getSupersededLanguages()
    {
        return 
self::getData('supersededLanguages');
    }

    
/**
     * Retrieve the name of a language, as well as if a language code is deprecated in favor of another language code.
     *
     * @param string $id the language identifier
     *
     * @return array|null Returns an array with the keys 'id' (normalized), 'name', 'supersededBy' (optional), 'territory' (optional), 'script' (optional), 'baseLanguage' (optional), 'categories'. If $id is not valid returns null.
     */
    
public static function getLanguageInfo($id)
    {
        
$result null;
        
$matches = array();
        if (
preg_match('/^([a-z]{2,3})(?:[_\-]([a-z]{4}))?(?:[_\-]([a-z]{2}|[0-9]{3}))?(?:$|-)/i'$id$matches)) {
            
$languageId strtolower($matches[1]);
            
$scriptId = (isset($matches[2]) && ($matches[2] !== '')) ? ucfirst(strtolower($matches[2])) : null;
            
$territoryId = (isset($matches[3]) && ($matches[3] !== '')) ? strtoupper($matches[3]) : null;
            
$normalizedId $languageId;
            if (isset(
$scriptId)) {
                
$normalizedId .= '_' $scriptId;
            }
            if (isset(
$territoryId)) {
                
$normalizedId .= '_' $territoryId;
            }
            
// Structure precedence: see Likely Subtags - http://www.unicode.org/reports/tr35/tr35-31/tr35.html#Likely_Subtags
            
$variants = array();
            
$variantsWithScript = array();
            
$variantsWithTerritory = array();
            if (isset(
$scriptId) && isset($territoryId)) {
                
$variantsWithTerritory[] = $variantsWithScript[] = $variants[] = "{$languageId}_{$scriptId}_{$territoryId}";
            }
            if (isset(
$scriptId)) {
                
$variantsWithScript[] = $variants[] = "{$languageId}_{$scriptId}";
            }
            if (isset(
$territoryId)) {
                
$variantsWithTerritory[] = $variants[] = "{$languageId}_{$territoryId}";
            }
            
$variants[] = $languageId;
            
$allGood true;
            
$scriptName null;
            
$scriptStandAloneName null;
            if (isset(
$scriptId)) {
                
$scriptNames self::getScriptNames(false);
                if (isset(
$scriptNames[$scriptId])) {
                    
$scriptName $scriptNames[$scriptId];
                    
$scriptStandAloneNames self::getScriptNames(true);
                    
$scriptStandAloneName $scriptStandAloneNames[$scriptId];
                } else {
                    
$allGood false;
                }
            }
            
$territoryName null;
            if (isset(
$territoryId)) {
                
$territoryNames self::getTerritoryNames();
                if (isset(
$territoryNames[$territoryId])) {
                    if (
$territoryId !== '001') {
                        
$territoryName $territoryNames[$territoryId];
                    }
                } else {
                    
$allGood false;
                }
            }
            
$languageName null;
            
$languageNames self::getLanguageNames();
            foreach (
$variants as $variant) {
                if (isset(
$languageNames[$variant])) {
                    
$languageName $languageNames[$variant];
                    if (isset(
$scriptName) && (!in_array($variant$variantsWithScript))) {
                        
$languageName $scriptName ' ' $languageName;
                    }
                    if (isset(
$territoryName) && (!in_array($variant$variantsWithTerritory))) {
                        
$languageName .= ' (' $territoryNames[$territoryId] . ')';
                    }
                    break;
                }
            }
            if (!isset(
$languageName)) {
                
$allGood false;
            }
            
$baseLanguage null;
            if (isset(
$scriptId) || isset($territoryId)) {
                if (isset(
$languageNames[$languageId]) && ($languageNames[$languageId] !== $languageName)) {
                    
$baseLanguage $languageNames[$languageId];
                }
            }
            
$plural null;
            
$plurals self::getPlurals();
            foreach (
$variants as $variant) {
                if (isset(
$plurals[$variant])) {
                    
$plural $plurals[$variant];
                    break;
                }
            }
            if (!isset(
$plural)) {
                
$allGood false;
            }
            
$supersededBy null;
            
$supersededBys self::getSupersededLanguages();
            foreach (
$variants as $variant) {
                if (isset(
$supersededBys[$variant])) {
                    
$supersededBy $supersededBys[$variant];
                    break;
                }
            }
            if (
$allGood) {
                
$result = array();
                
$result['id'] = $normalizedId;
                
$result['name'] = $languageName;
                if (isset(
$supersededBy)) {
                    
$result['supersededBy'] = $supersededBy;
                }
                if (isset(
$scriptStandAloneName)) {
                    
$result['script'] = $scriptStandAloneName;
                }
                if (isset(
$territoryName)) {
                    
$result['territory'] = $territoryName;
                }
                if (isset(
$baseLanguage)) {
                    
$result['baseLanguage'] = $baseLanguage;
                }
                
$result['categories'] = $plural;
            }
        }

        return 
$result;
    }

    
/**
     * Returns the loaded CLDR data.
     *
     * @param string $key Can be 'languages', 'territories', 'plurals', 'supersededLanguages', 'scripts', 'standAloneScripts'
     *
     * @return array
     */
    
private static function getData($key)
    {
        if (!isset(
self::$data)) {
            
$fixKeys = function ($list, &$standAlone null) {
                
$result = array();
                
$standAlone = array();
                
$match null;
                foreach (
$list as $key => $value) {
                    
$variant '';
                    if (
preg_match('/^(.+)-alt-(short|variant|stand-alone|long|menu)$/'$key$match)) {
                        
$key $match[1];
                        
$variant $match[2];
                    }
                    
$key str_replace('-''_'$key);
                    switch (
$key) {
                        case 
'root'// Language: Root
                        
case 'und'// Language: Unknown Language
                        
case 'zxx'// Language: No linguistic content
                        
case 'ZZ'// Territory: Unknown Region
                        
case 'Zinh'// Script: Inherited
                        
case 'Zmth'// Script: Mathematical Notation
                        
case 'Zsym'// Script: Symbols
                        
case 'Zxxx'// Script: Unwritten
                        
case 'Zyyy'// Script: Common
                        
case 'Zzzz'// Script: Unknown Script
                            
break;
                        default:
                            switch (
$variant) {
                                case 
'stand-alone':
                                    
$standAlone[$key] = $value;
                                    break;
                                case 
'':
                                    
$result[$key] = $value;
                                    break;
                            }
                            break;
                    }
                }

                return 
$result;
            };
            
$data = array();
            
$json json_decode(file_get_contents(__DIR__ '/cldr-data/main/en-US/languages.json'), true);
            
$data['languages'] = $fixKeys($json['main']['en-US']['localeDisplayNames']['languages']);
            
$json json_decode(file_get_contents(__DIR__ '/cldr-data/main/en-US/territories.json'), true);
            
$data['territories'] = $fixKeys($json['main']['en-US']['localeDisplayNames']['territories']);
            
$json json_decode(file_get_contents(__DIR__ '/cldr-data/supplemental/plurals.json'), true);
            
$data['plurals'] = $fixKeys($json['supplemental']['plurals-type-cardinal']);
            
$json json_decode(file_get_contents(__DIR__ '/cldr-data/main/en-US/scripts.json'), true);
            
$data['scripts'] = $fixKeys($json['main']['en-US']['localeDisplayNames']['scripts'], $data['standAloneScripts']);
            
$data['standAloneScripts'] = array_merge($data['scripts'], $data['standAloneScripts']);
            
$data['scripts'] = array_merge($data['standAloneScripts'], $data['scripts']);
            
$data['supersededLanguages'] = array();
            
// Remove the languages for which we don't have plurals
            
$m null;
            foreach (
array_keys(array_diff_key($data['languages'], $data['plurals'])) as $missingPlural) {
                if (
preg_match('/^([a-z]{2,3})_/'$missingPlural$m)) {
                    if (!isset(
$data['plurals'][$m[1]])) {
                        unset(
$data['languages'][$missingPlural]);
                    }
                } else {
                    unset(
$data['languages'][$missingPlural]);
                }
            }
            
// Fix the languages for which we have plurals
            
$formerCodes = array(
                
'in' => 'id'// former Indonesian
                
'iw' => 'he'// former Hebrew
                
'ji' => 'yi'// former Yiddish
                
'jw' => 'jv'// former Javanese
                
'mo' => 'ro_MD'// former Moldavian
            
);
            
$knownMissingLanguages = array(
                
'guw' => 'Gun',
                
'hnj' => 'Mong Njua',
                
'nah' => 'Nahuatl',
                
'smi' => 'Sami',
            );
            foreach (
array_keys(array_diff_key($data['plurals'], $data['languages'])) as $missingLanguage) {
                if (isset(
$formerCodes[$missingLanguage]) && isset($data['languages'][$formerCodes[$missingLanguage]])) {
                    
$data['languages'][$missingLanguage] = $data['languages'][$formerCodes[$missingLanguage]];
                    
$data['supersededLanguages'][$missingLanguage] = $formerCodes[$missingLanguage];
                } else {
                    if (isset(
$knownMissingLanguages[$missingLanguage])) {
                        
$data['languages'][$missingLanguage] = $knownMissingLanguages[$missingLanguage];
                    } else {
                        throw new 
Exception("We have the plural rule for the language '{$missingLanguage}' but we don't have its language name");
                    }
                }
            }
            
ksort($data['languages'], SORT_STRING);
            
ksort($data['territories'], SORT_STRING);
            
ksort($data['plurals'], SORT_STRING);
            
ksort($data['scripts'], SORT_STRING);
            
ksort($data['standAloneScripts'], SORT_STRING);
            
ksort($data['supersededLanguages'], SORT_STRING);
            
self::$data $data;
        }
        if (!isset(
self::$data[$key])) {
            throw new 
Exception("Invalid CLDR data key: '{$key}'");
        }

        return 
self::$data[$key];
    }
}
<?
php

namespace Gettext\Languages;

use 
Exception;

/**
 * A helper class that handles a single category rules (eg 'zero', 'one', ...) and its formula and examples.
 */
class Category
{
    
/**
     * The category identifier (eg 'zero', 'one', ..., 'other').
     *
     * @var string
     */
    
public $id;

    
/**
     * The gettext formula that identifies this category (null if and only if the category is 'other').
     *
     * @var string|null
     */
    
public $formula;

    
/**
     * The CLDR representation of some exemplar numeric ranges that satisfy this category.
     *
     * @var string|null
     */
    
public $examples;

    
/**
     * Initialize the instance and parse the formula.
     *
     * @param string $cldrCategoryId the CLDR category identifier (eg 'pluralRule-count-one')
     * @param string $cldrFormulaAndExamples the CLDR formula and examples (eg 'i = 1 and v = 0 @integer 1')
     *
     * @throws \Exception
     */
    
public function __construct($cldrCategoryId$cldrFormulaAndExamples)
    {
        
$matches = array();
        if (!
preg_match('/^pluralRule-count-(.+)$/'$cldrCategoryId$matches)) {
            throw new 
Exception("Invalid CLDR category: '{$cldrCategoryId}'");
        }
        if (!
in_array($matches[1], CldrData::$categories)) {
            throw new 
Exception("Invalid CLDR category: '{$cldrCategoryId}'");
        }
        
$this->id $matches[1];
        
$cldrFormulaAndExamplesNormalized trim(preg_replace('/\s+/'' '$cldrFormulaAndExamples));
        if (!
preg_match('/^([^@]*)(?:@integer([^@]+))?(?:@decimal(?:[^@]+))?$/'$cldrFormulaAndExamplesNormalized$matches)) {
            throw new 
Exception("Invalid CLDR category rule: {$cldrFormulaAndExamples}");
        }
        
$cldrFormula trim($matches[1]);
        
$s = isset($matches[2]) ? trim($matches[2]) : '';
        
$this->examples = ($s === '') ? null $s;
        switch (
$this->id) {
            case 
CldrData::OTHER_CATEGORY:
                if (
$cldrFormula !== '') {
                    throw new 
Exception("The '" CldrData::OTHER_CATEGORY "' category should not have any formula, but it has '{$cldrFormula}'");
                }
                
$this->formula null;
                break;
            default:
                if (
$cldrFormula === '') {
                    throw new 
Exception("The '{$this->id}' category does not have a formula");
                }
                
$this->formula FormulaConverter::convertFormula($cldrFormula);
                break;
        }
    }

    
/**
     * Return a list of numbers corresponding to the $examples value.
     *
     * @throws \Exception throws an Exception if we weren't able to expand the examples
     *
     * @return int[]
     */
    
public function getExampleIntegers()
    {
        return 
self::expandExamples($this->examples);
    }

    
/**
     * Expand a list of examples as defined by CLDR.
     *
     * @param string $examples A string like '1, 2, 5...7, …'.
     *
     * @throws \Exception throws an Exception if we weren't able to expand $examples
     *
     * @return int[]
     */
    
public static function expandExamples($examples)
    {
        
$result = array();
        
$m null;
        if (
substr($examples, -strlen(', …')) === ', …') {
            
$examples substr($examples0strlen($examples) - strlen(', …'));
        }
        foreach (
explode(','str_replace(' '''$examples)) as $range) {
            if (
preg_match('/^(?<num>\d+)((c|e)(?<exp>\d+))?$/'$range$m)) {
                
$result[] = (int) (isset($m['exp']) ? ($m['num'] . str_repeat('0', (int) $m['exp'])) : $range);
            } elseif (
preg_match('/^(\d+)~(\d+)$/'$range$m)) {
                
$from = (int) $m[1];
                
$to = (int) $m[2];
                
$delta $to $from;
                
$step = (int) max(1$delta 100);
                for (
$i $from$i $to$i += $step) {
                    
$result[] = $i;
                }
                
$result[] = $to;
            } else {
                throw new 
Exception("Unhandled test range '{$range}' in '{$examples}'");
            }
        }
        if (empty(
$result)) {
            throw new 
Exception("No test numbers from '{$examples}'");
        }

        return 
$result;
    }
}
<?
php

namespace Gettext\Languages;

use 
Exception;

/**
 * A helper class to convert a CLDR formula to a gettext formula.
 */
class FormulaConverter
{
    
/**
     * Converts a formula from the CLDR representation to the gettext representation.
     *
     * @param string $cldrFormula the CLDR formula to convert
     *
     * @throws \Exception
     *
     * @return bool|string returns true if the gettext will always evaluate to true, false if gettext will always evaluate to false, return the gettext formula otherwise
     */
    
public static function convertFormula($cldrFormula)
    {
        if (
strpbrk($cldrFormula'()') !== false) {
            throw new 
Exception("Unable to convert the formula '{$cldrFormula}': parenthesis handling not implemented");
        }
        
$orSeparatedChunks = array();
        foreach (
explode(' or '$cldrFormula) as $cldrFormulaChunk) {
            
$gettextFormulaChunk null;
            
$andSeparatedChunks = array();
            foreach (
explode(' and '$cldrFormulaChunk) as $cldrAtom) {
                
$gettextAtom self::convertAtom($cldrAtom);
                if (
$gettextAtom === false) {
                    
// One atom joined by 'and' always evaluates to false => the whole 'and' group is always false
                    
$gettextFormulaChunk false;
                    break;
                }
                if (
$gettextAtom !== true) {
                    
$andSeparatedChunks[] = $gettextAtom;
                }
            }
            if (!isset(
$gettextFormulaChunk)) {
                if (empty(
$andSeparatedChunks)) {
                    
// All the atoms joined by 'and' always evaluate to true => the whole 'and' group is always true
                    
$gettextFormulaChunk true;
                } else {
                    
$gettextFormulaChunk implode(' && '$andSeparatedChunks);
                    
// Special cases simplification
                    
switch ($gettextFormulaChunk) {
                        case 
'n >= 0 && n <= 2 && n != 2':
                            
$gettextFormulaChunk 'n == 0 || n == 1';
                            break;
                    }
                }
            }
            if (
$gettextFormulaChunk === true) {
                
// One part of the formula joined with the others by 'or' always evaluates to true => the whole formula always evaluates to true
                
return true;
            }
            if (
$gettextFormulaChunk !== false) {
                
$orSeparatedChunks[] = $gettextFormulaChunk;
            }
        }
        if (empty(
$orSeparatedChunks)) {
            
// All the parts joined by 'or' always evaluate to false => the whole formula always evaluates to false
            
return false;
        }

        return 
implode(' || '$orSeparatedChunks);
    }

    
/**
     * Converts an atomic part of the CLDR formula to its gettext representation.
     *
     * @param string $cldrAtom the CLDR formula atom to convert
     *
     * @throws \Exception
     *
     * @return bool|string returns true if the gettext will always evaluate to true, false if gettext will always evaluate to false, return the gettext formula otherwise
     */
    
private static function convertAtom($cldrAtom)
    {
        
$m null;
        
$gettextAtom $cldrAtom;
        
$gettextAtom str_replace(' = '' == '$gettextAtom);
        
$gettextAtom str_replace('i''n'$gettextAtom);
        if (
preg_match('/^n( % \d+)? (!=|==) \d+$/'$gettextAtom)) {
            return 
$gettextAtom;
        }
        if (
preg_match('/^n( % \d+)? (!=|==) \d+(,\d+|\.\.\d+)+$/'$gettextAtom)) {
            return 
self::expandAtom($gettextAtom);
        }
        if (
preg_match('/^(?:v|w)(?: % 10+)? == (\d+)(?:\.\.\d+)?$/'$gettextAtom$m)) { // For gettext: v == 0, w == 0
            
return (int) $m[1] === true false;
        }
        if (
preg_match('/^(?:v|w)(?: % 10+)? != (\d+)(?:\.\.\d+)?$/'$gettextAtom$m)) { // For gettext: v == 0, w == 0
            
return (int) $m[1] === false true;
        }
        if (
preg_match('/^(?:f|t|c|e)(?: % 10+)? == (\d+)(?:\.\.\d+)?$/'$gettextAtom$m)) { // f == empty, t == empty, c == empty, e == empty
            
return (int) $m[1] === true false;
        }
        if (
preg_match('/^(?:f|t|c|e)(?: % 10+)? != (\d+)(?:\.\.\d+)?$/'$gettextAtom$m)) { // f == empty, t == empty, c == empty, e == empty
            
return (int) $m[1] === false true;
        }
        throw new 
Exception("Unable to convert the formula chunk '{$cldrAtom}' from CLDR to gettext");
    }

    
/**
     * Expands an atom containing a range (for instance: 'n == 1,3..5').
     *
     * @param string $atom
     *
     * @throws \Exception
     *
     * @return string
     */
    
private static function expandAtom($atom)
    {
        
$m null;
        if (
preg_match('/^(n(?: % \d+)?) (==|!=) (\d+(?:\.\.\d+|,\d+)+)$/'$atom$m)) {
            
$what $m[1];
            
$op $m[2];
            
$chunks = array();
            foreach (
explode(','$m[3]) as $range) {
                
$chunk null;
                if ((!isset(
$chunk)) && preg_match('/^\d+$/'$range)) {
                    
$chunk "{$what} {$op} {$range}";
                }
                if ((!isset(
$chunk)) && preg_match('/^(\d+)\.\.(\d+)$/'$range$m)) {
                    
$from = (int) $m[1];
                    
$to = (int) $m[2];
                    if ((
$to $from) === 1) {
                        switch (
$op) {
                            case 
'==':
                                
$chunk "({$what} == {$from} || {$what} == {$to})";
                                break;
                            case 
'!=':
                                
$chunk "{$what} != {$from} && {$what} == {$to}";
                                break;
                        }
                    } else {
                        switch (
$op) {
                            case 
'==':
                                
$chunk "{$what} >= {$from} && {$what} <= {$to}";
                                break;
                            case 
'!=':
                                if (
$what === 'n' && $from <= 0) {
                                    
$chunk "{$what} > {$to}";
                                } else {
                                    
$chunk "({$what} < {$from} || {$what} > {$to})";
                                }
                                break;
                        }
                    }
                }
                if (!isset(
$chunk)) {
                    throw new 
Exception("Unhandled range '{$range}' in '{$atom}'");
                }
                
$chunks[] = $chunk;
            }
            if (
count($chunks) === 1) {
                return 
$chunks[0];
            }
            switch (
$op) {
                case 
'==':
                    return 
'(' implode(' || '$chunks) . ')'; break;
                case 
'!=':
                    return 
implode(' && '$chunks);
            }
        }
        throw new 
Exception("Unable to expand '{$atom}'");
    }
}
<?
php

namespace Gettext\Languages;

use 
Exception;

/**
 * Main class to convert the plural rules of a language from CLDR to gettext.
 */
class Language
{
    
/**
     * The language ID.
     *
     * @var string
     */
    
public $id;

    
/**
     * The language name.
     *
     * @var string
     */
    
public $name;

    
/**
     * If this language is deprecated: the gettext code of the new language.
     *
     * @var string|null
     */
    
public $supersededBy;

    
/**
     * The script name.
     *
     * @var string|null
     */
    
public $script;

    
/**
     * The territory name.
     *
     * @var string|null
     */
    
public $territory;

    
/**
     * The name of the base language.
     *
     * @var string|null
     */
    
public $baseLanguage;

    
/**
     * The list of categories.
     *
     * @var \Gettext\Languages\Category[]
     */
    
public $categories;

    
/**
     * The gettext formula to decide which category should be applied.
     *
     * @var string
     */
    
public $formula;

    
/**
     * Initialize the instance and parse the language code.
     *
     * @param array $info The result of CldrData::getLanguageInfo()
     *
     * @throws \Exception throws an Exception if $fullId is not valid
     */
    
private function __construct($info)
    {
        
$this->id $info['id'];
        
$this->name $info['name'];
        
$this->supersededBy = isset($info['supersededBy']) ? $info['supersededBy'] : null;
        
$this->script = isset($info['script']) ? $info['script'] : null;
        
$this->territory = isset($info['territory']) ? $info['territory'] : null;
        
$this->baseLanguage = isset($info['baseLanguage']) ? $info['baseLanguage'] : null;
        
// Let's build the category list
        
$this->categories = array();
        foreach (
$info['categories'] as $cldrCategoryId => $cldrFormulaAndExamples) {
            
$category = new Category($cldrCategoryId$cldrFormulaAndExamples);
            foreach (
$this->categories as $c) {
                if (
$category->id === $c->id) {
                    throw new 
Exception("The category '{$category->id}' is specified more than once");
                }
            }
            
$this->categories[] = $category;
        }
        if (empty(
$this->categories)) {
            throw new 
Exception("The language '{$info['id']}' does not have any plural category");
        }
        
// Let's sort the categories from 'zero' to 'other'
        
usort($this->categories, function (Category $category1Category $category2) {
            return 
array_search($category1->idCldrData::$categories) - array_search($category2->idCldrData::$categories);
        });
        
// The 'other' category should always be there
        
if ($this->categories[count($this->categories) - 1]->id !== CldrData::OTHER_CATEGORY) {
            throw new 
Exception("The language '{$info['id']}' does not have the '" CldrData::OTHER_CATEGORY "' plural category");
        }
        
$this->checkAlwaysTrueCategories();
        
$this->checkAlwaysFalseCategories();
        
$this->checkAllCategoriesWithExamples();
        
$this->formula $this->buildFormula();
    }

    
/**
     * Return a list of all languages available.
     *
     * @throws \Exception
     *
     * @return \Gettext\Languages\Language[]
     */
    
public static function getAll()
    {
        
$result = array();
        foreach (
array_keys(CldrData::getLanguageNames()) as $cldrLanguageId) {
            
$result[] = new self(CldrData::getLanguageInfo($cldrLanguageId));
        }

        return 
$result;
    }

    
/**
     * Return a Language instance given the language id.
     *
     * @param string $id
     *
     * @return \Gettext\Languages\Language|null
     */
    
public static function getById($id)
    {
        
$result null;
        
$info CldrData::getLanguageInfo($id);
        if (isset(
$info)) {
            
$result = new self($info);
        }

        return 
$result;
    }

    
/**
     * Returns a clone of this instance with all the strings to US-ASCII.
     *
     * @return \Gettext\Languages\Language
     */
    
public function getUSAsciiClone()
    {
        
$clone = clone $this;
        
self::asciifier($clone->name);
        
self::asciifier($clone->formula);
        
$clone->categories = array();
        foreach (
$this->categories as $category) {
            
$categoryClone = clone $category;
            
self::asciifier($categoryClone->examples);
            
$clone->categories[] = $categoryClone;
        }

        return 
$clone;
    }

    
/**
     * Build the formula starting from the currently defined categories.
     *
     * @param bool $withoutParenthesis TRUE to build a formula in standard gettext format, FALSE (default) to build a PHP-compatible formula
     *
     * @return string
     */
    
public function buildFormula($withoutParenthesis false)
    {
        
$numCategories count($this->categories);
        switch (
$numCategories) {
            case 
1:
                
// Just one category
                
return '0';
            case 
2:
                return 
self::reduceFormula(self::reverseFormula($this->categories[0]->formula));
            default:
                
$formula = (string) ($numCategories 1);
                for (
$i $numCategories 2$i >= 0$i--) {
                    
$f self::reduceFormula($this->categories[$i]->formula);
                    if (!
$withoutParenthesis && !preg_match('/^\([^()]+\)$/'$f)) {
                        
$f "({$f})";
                    }
                    
$formula "{$f} ? {$i} : {$formula}";
                    if (!
$withoutParenthesis && $i 0) {
                        
$formula "({$formula})";
                    }
                }

                return 
$formula;
        }
    }

    
/**
     * Let's look for categories that will always occur.
     * This because with decimals (CLDR) we may have more cases, with integers (gettext) we have just one case.
     * If we found that (single) category we reduce the categories to that one only.
     *
     * @throws \Exception
     */
    
private function checkAlwaysTrueCategories()
    {
        
$alwaysTrueCategory null;
        foreach (
$this->categories as $category) {
            if (
$category->formula === true) {
                if (!isset(
$category->examples)) {
                    throw new 
Exception("The category '{$category->id}' should always occur, but it does not have examples (so for CLDR it will never occur for integers!)");
                }
                
$alwaysTrueCategory $category;
                break;
            }
        }
        if (isset(
$alwaysTrueCategory)) {
            foreach (
$this->categories as $category) {
                if ((
$category !== $alwaysTrueCategory) && isset($category->examples)) {
                    throw new 
Exception("The category '{$category->id}' should never occur, but it has some examples (so for CLDR it will occur!)");
                }
            }
            
$alwaysTrueCategory->id CldrData::OTHER_CATEGORY;
            
$alwaysTrueCategory->formula null;
            
$this->categories = array($alwaysTrueCategory);
        }
    }

    
/**
     * Let's look for categories that will never occur.
     * This because with decimals (CLDR) we may have more cases, with integers (gettext) we have some less cases.
     * If we found those categories we strip them out.
     *
     * @throws \Exception
     */
    
private function checkAlwaysFalseCategories()
    {
        
$filtered = array();
        foreach (
$this->categories as $category) {
            if (
$category->formula === false) {
                if (isset(
$category->examples)) {
                    throw new 
Exception("The category '{$category->id}' should never occur, but it has examples (so for CLDR it may occur!)");
                }
            } else {
                
$filtered[] = $category;
            }
        }
        
$this->categories $filtered;
    }

    
/**
     * Let's look for categories that don't have examples.
     * This because with decimals (CLDR) we may have more cases, with integers (gettext) we have some less cases.
     * If we found those categories, we check that they never occur and we strip them out.
     *
     * @throws \Exception
     */
    
private function checkAllCategoriesWithExamples()
    {
        
$allCategoriesIds = array();
        
$goodCategories = array();
        
$badCategories = array();
        
$badCategoriesIds = array();
        foreach (
$this->categories as $category) {
            
$allCategoriesIds[] = $category->id;
            if (isset(
$category->examples)) {
                
$goodCategories[] = $category;
            } else {
                
$badCategories[] = $category;
                
$badCategoriesIds[] = $category->id;
            }
        }
        if (empty(
$badCategories)) {
            return;
        }
        
$removeCategoriesWithoutExamples false;
        switch (
implode(','$badCategoriesIds) . '@' implode(','$allCategoriesIds)) {
            case 
CldrData::OTHER_CATEGORY '@one,few,many,' CldrData::OTHER_CATEGORY:
                switch (
$this->buildFormula()) {
                    case 
'(n % 10 == 1 && n % 100 != 11) ? 0 : ((n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14)) ? 1 : ((n % 10 == 0 || n % 10 >= 5 && n % 10 <= 9 || n % 100 >= 11 && n % 100 <= 14) ? 2 : 3))':
                        
// Numbers ending with 0                 => case 2 ('many')
                        // Numbers ending with 1 but not with 11 => case 0 ('one')
                        // Numbers ending with 11                => case 2 ('many')
                        // Numbers ending with 2 but not with 12 => case 1 ('few')
                        // Numbers ending with 12                => case 2 ('many')
                        // Numbers ending with 3 but not with 13 => case 1 ('few')
                        // Numbers ending with 13                => case 2 ('many')
                        // Numbers ending with 4 but not with 14 => case 1 ('few')
                        // Numbers ending with 14                => case 2 ('many')
                        // Numbers ending with 5                 => case 2 ('many')
                        // Numbers ending with 6                 => case 2 ('many')
                        // Numbers ending with 7                 => case 2 ('many')
                        // Numbers ending with 8                 => case 2 ('many')
                        // Numbers ending with 9                 => case 2 ('many')
                        // => the 'other' case never occurs: use 'other' for 'many'
                        
$removeCategoriesWithoutExamples true;
                        break;
                    case 
'(n == 1) ? 0 : ((n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14)) ? 1 : ((n != 1 && (n % 10 == 0 || n % 10 == 1) || n % 10 >= 5 && n % 10 <= 9 || n % 100 >= 12 && n % 100 <= 14) ? 2 : 3))':
                        
// Numbers ending with 0                  => case 2 ('many')
                        // Numbers ending with 1 but not number 1 => case 2 ('many')
                        // Number 1                               => case 0 ('one')
                        // Numbers ending with 2 but not with 12  => case 1 ('few')
                        // Numbers ending with 12                 => case 2 ('many')
                        // Numbers ending with 3 but not with 13  => case 1 ('few')
                        // Numbers ending with 13                 => case 2 ('many')
                        // Numbers ending with 4 but not with 14  => case 1 ('few')
                        // Numbers ending with 14                 => case 2 ('many')
                        // Numbers ending with 5                  => case 2 ('many')
                        // Numbers ending with 6                  => case 2 ('many')
                        // Numbers ending with 7                  => case 2 ('many')
                        // Numbers ending with 8                  => case 2 ('many')
                        // Numbers ending with 9                  => case 2 ('many')
                        // => the 'other' case never occurs: use 'other' for 'many'
                        
$removeCategoriesWithoutExamples true;
                        break;
                }
        }
        if (!
$removeCategoriesWithoutExamples) {
            throw new 
Exception("Unhandled case of plural categories without examples '" implode(', '$badCategoriesIds) . "' out of '" implode(', '$allCategoriesIds) . "'");
        }
        if (
$badCategories[count($badCategories) - 1]->id === CldrData::OTHER_CATEGORY) {
            
// We're removing the 'other' cagory: let's change the last good category to 'other'
            
$lastGood $goodCategories[count($goodCategories) - 1];
            
$lastGood->id CldrData::OTHER_CATEGORY;
            
$lastGood->formula null;
        }
        
$this->categories $goodCategories;
    }

    
/**
     * Reverse a formula.
     *
     * @param string $formula
     *
     * @throws \Exception
     *
     * @return string
     */
    
private static function reverseFormula($formula)
    {
        if (
preg_match('/^n( % \d+)? == \d+(\.\.\d+|,\d+)*?$/'$formula)) {
            return 
str_replace(' == '' != '$formula);
        }
        if (
preg_match('/^n( % \d+)? != \d+(\.\.\d+|,\d+)*?$/'$formula)) {
            return 
str_replace(' != '' == '$formula);
        }
        if (
preg_match('/^\(?n == \d+ \|\| n == \d+\)?$/'$formula)) {
            return 
trim(str_replace(array(' == '' || '), array(' != '' && '), $formula), '()');
        }
        
$m null;
        if (
preg_match('/^(n(?: % \d+)?) == (\d+) && (n(?: % \d+)?) != (\d+)$/'$formula$m)) {
            return 
"{$m[1]} != {$m[2]} || {$m[3]} == {$m[4]}";
        }
        switch (
$formula) {
            case 
'(n == 1 || n == 2 || n == 3) || n % 10 != 4 && n % 10 != 6 && n % 10 != 9':
                return 
'n != 1 && n != 2 && n != 3 && (n % 10 == 4 || n % 10 == 6 || n % 10 == 9)';
            case 
'(n == 0 || n == 1) || n >= 11 && n <= 99':
                return 
'n >= 2 && (n < 11 || n > 99)';
        }
        throw new 
Exception("Unable to reverse the formula '{$formula}'");
    }

    
/**
     * Reduce some excessively complex formulas.
     *
     * @param string $formula
     *
     * @return string
     */
    
private static function reduceFormula($formula)
    {
        
$map = array(
            
'n != 0 && n != 1' => 'n > 1',
            
'(n == 0 || n == 1) && n != 0' => 'n == 1',
        );

        return isset(
$map[$formula]) ? $map[$formula] : $formula;
    }

    
/**
     * Take one variable and, if it's a string, we transliterate it to US-ASCII.
     *
     * @param mixed $value the variable to work on
     *
     * @throws \Exception
     */
    
private static function asciifier(&$value)
    {
        if (
is_string($value) && $value !== '') {
            
// Avoid converting from 'Ÿ' to '"Y', let's prefer 'Y'
            
$value strtr($value, array(
                
'À' => 'A''Á' => 'A''Â' => 'A''Ã' => 'A''Ä' => 'A''Å' => 'A',
                
'È' => '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',
                
'è' => 'e''é' => 'e''ê' => 'e''ë' => 'e',
                
'ì' => 'i''í' => 'i''î' => 'i''ï' => 'i',
                
'ñ' => 'n''ò' => 'o''ó' => 'o''ô' => 'o''õ' => 'o''ö' => 'o',
                
'ù' => 'u''ú' => 'u''û' => 'u''ü' => 'u',
                
'ý' => 'y''ÿ' => 'y',
                
'…' => '...',
                
'ʼ' => "'"'’' => "'",
            ));
        }
    }
}
<?
php

spl_autoload_register
(
    function (
$class) {
        if (
strpos($class'Gettext\\Languages\\') !== 0) {
            return;
        }
        
$file __DIR__ str_replace('\\'DIRECTORY_SEPARATORsubstr($classstrlen('Gettext\\Languages'))) . '.php';
        if (
is_file($file)) {
            require_once 
$file;
        }
    }
);
<?
php

namespace Gettext\Languages\Exporter;

class 
Xml extends Exporter
{
    
/**
     * {@inheritdoc}
     *
     * @see \Gettext\Languages\Exporter\Exporter::getDescription()
     */
    
public static function getDescription()
    {
        return 
'Build an XML file - schema available at http://mlocati.github.io/cldr-to-gettext-plural-rules/GettextLanguages.xsd';
    }

    
/**
     * {@inheritdoc}
     *
     * @see \Gettext\Languages\Exporter\Exporter::toStringDo()
     */
    
protected static function toStringDo($languages)
    {
        
$xml = new \DOMDocument('1.0''UTF-8');
        
$xml->loadXML('<languages
            xmlns="https://github.com/mlocati/cldr-to-gettext-plural-rules"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="https://github.com/mlocati/cldr-to-gettext-plural-rules http://mlocati.github.io/cldr-to-gettext-plural-rules/GettextLanguages.xsd"
        />'
);
        
$xLanguages $xml->firstChild;
        foreach (
$languages as $language) {
            
$xLanguage $xml->createElement('language');
            
$xLanguage->setAttribute('id'$language->id);
            
$xLanguage->setAttribute('name'$language->name);
            if (isset(
$language->supersededBy)) {
                
$xLanguage->setAttribute('supersededBy'$language->supersededBy);
            }
            if (isset(
$language->script)) {
                
$xLanguage->setAttribute('script'$language->script);
            }
            if (isset(
$language->territory)) {
                
$xLanguage->setAttribute('territory'$language->territory);
            }
            if (isset(
$language->baseLanguage)) {
                
$xLanguage->setAttribute('baseLanguage'$language->baseLanguage);
            }
            
$xLanguage->setAttribute('formula'$language->formula);
            foreach (
$language->categories as $category) {
                
$xCategory $xml->createElement('category');
                
$xCategory->setAttribute('id'$category->id);
                
$xCategory->setAttribute('examples'$category->examples);
                
$xLanguage->appendChild($xCategory);
            }
            
$xLanguages->appendChild($xLanguage);
        }
        
$xml->formatOutput true;

        return 
$xml->saveXML();
    }
}
<?
php

namespace Gettext\Languages\Exporter;

use 
Exception;

class 
Prettyjson extends Json
{
    
/**
     * {@inheritdoc}
     *
     * @see \Gettext\Languages\Exporter\Exporter::getDescription()
     */
    
public static function getDescription()
    {
        return 
'Build an uncompressed JSON-encoded file (PHP 5.4 or later is needed)';
    }

    
/**
     * {@inheritdoc}
     *
     * @see \Gettext\Languages\Exporter\Json::getEncodeOptions()
     */
    
protected static function getEncodeOptions()
    {
        if (!(
defined('\JSON_PRETTY_PRINT') && defined('\JSON_UNESCAPED_SLASHES') && defined('\JSON_UNESCAPED_UNICODE'))) {
            throw new 
Exception('PHP 5.4 or later is required to export uncompressed JSON');
        }

        return 
\JSON_PRETTY_PRINT \JSON_UNESCAPED_SLASHES \JSON_UNESCAPED_UNICODE;
    }
}
<?
php

namespace Gettext\Languages\Exporter;

class 
Docs extends Html
{
    
/**
     * {@inheritdoc}
     *
     * @see \Gettext\Languages\Exporter\Exporter::isForPublicUse()
     */
    
public static function isForPublicUse()
    {
        return 
false;
    }

    
/**
     * {@inheritdoc}
     *
     * @see \Gettext\Languages\Exporter\Exporter::getDescription()
     */
    
public static function getDescription()
    {
        return 
'Build the page https://php-gettext.github.io/Languages/';
    }

    
/**
     * {@inheritdoc}
     *
     * @see \Gettext\Languages\Exporter\Exporter::toStringDo()
     */
    
protected static function toStringDo($languages)
    {
        
$result = <<<'EOT'
<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="author" content="Michele Locati">
        <title>gettext plural rules - built from CLDR</title>
        <meta name="description" content="List of all language rules for gettext .po files automatically generated from the Unicode CLDR data" />
        <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <a href="https://github.com/php-gettext/Languages" class="hidden-xs"><img style="position: fixed; top: 0; right: 0; border: 0; z-index: 2000" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub"></a>
        <div class="container-fluid">

EOT;
        
$result .= static::buildTable($languagestrue);
        
$result .= <<<'EOT'

        </div>
        <script src="//code.jquery.com/jquery-1.11.2.min.js"></script>
        <script src="script.js"></script>
    </body>
</html>
EOT;

        return 
$result;
    }
}
<?
php

namespace Gettext\Languages\Exporter;

class 
Php extends Exporter
{
    
/**
     * {@inheritdoc}
     *
     * @see \Gettext\Languages\Exporter\Exporter::getDescription()
     */
    
public static function getDescription()
    {
        return 
'Build a PHP array';
    }

    
/**
     * {@inheritdoc}
     *
     * @see \Gettext\Languages\Exporter\Exporter::toStringDo()
     */
    
protected static function toStringDo($languages)
    {
        
$lines = array();
        
$lines[] = '<?php';
        
$lines[] = 'return array(';
        foreach (
$languages as $lc) {
            
$lines[] = '    \'' $lc->id '\' => array(';
            
$lines[] = '        \'name\' => \'' addslashes($lc->name) . '\',';
            if (isset(
$lc->supersededBy)) {
                
$lines[] = '        \'supersededBy\' => \'' $lc->supersededBy '\',';
            }
            if (isset(
$lc->script)) {
                
$lines[] = '        \'script\' => \'' addslashes($lc->script) . '\',';
            }
            if (isset(
$lc->territory)) {
                
$lines[] = '        \'territory\' => \'' addslashes($lc->territory) . '\',';
            }
            if (isset(
$lc->baseLanguage)) {
                
$lines[] = '        \'baseLanguage\' => \'' addslashes($lc->baseLanguage) . '\',';
            }
            
$lines[] = '        \'formula\' => \'' $lc->formula '\',';
            
$lines[] = '        \'plurals\' => ' count($lc->categories) . ',';
            
$catNames = array();
            foreach (
$lc->categories as $c) {
                
$catNames[] = "'{$c->id}'";
            }
            
$lines[] = '        \'cases\' => array(' implode(', '$catNames) . '),';
            
$lines[] = '        \'examples\' => array(';
            foreach (
$lc->categories as $c) {
                
$lines[] = '            \'' $c->id '\' => \'' $c->examples '\',';
            }
            
$lines[] = '        ),';
            
$lines[] = '    ),';
        }
        
$lines[] = ');';
        
$lines[] = '';

        return 
implode("\n"$lines);
    }
}
<?
php

namespace Gettext\Languages\Exporter;

class 
Json extends Exporter
{
    
/**
     * {@inheritdoc}
     *
     * @see \Gettext\Languages\Exporter\Exporter::getDescription()
     */
    
public static function getDescription()
    {
        return 
'Build a compressed JSON-encoded file';
    }

    
/**
     * Return the options for json_encode.
     *
     * @return int
     */
    
protected static function getEncodeOptions()
    {
        
$result 0;
        if (
defined('\JSON_UNESCAPED_SLASHES')) {
            
$result |= \JSON_UNESCAPED_SLASHES;
        }
        if (
defined('\JSON_UNESCAPED_UNICODE')) {
            
$result |= \JSON_UNESCAPED_UNICODE;
        }

        return 
$result;
    }

    
/**
     * {@inheritdoc}
     *
     * @see \Gettext\Languages\Exporter\Exporter::toStringDo()
     */
    
protected static function toStringDo($languages)
    {
        
$list = array();
        foreach (
$languages as $language) {
            
$item = array();
            
$item['name'] = $language->name;
            if (isset(
$language->supersededBy)) {
                
$item['supersededBy'] = $language->supersededBy;
            }
            if (isset(
$language->script)) {
                
$item['script'] = $language->script;
            }
            if (isset(
$language->territory)) {
                
$item['territory'] = $language->territory;
            }
            if (isset(
$language->baseLanguage)) {
                
$item['baseLanguage'] = $language->baseLanguage;
            }
            
$item['formula'] = $language->formula;
            
$item['plurals'] = count($language->categories);
            
$item['cases'] = array();
            
$item['examples'] = array();
            foreach (
$language->categories as $category) {
                
$item['cases'][] = $category->id;
                
$item['examples'][$category->id] = $category->examples;
            }
            
$list[$language->id] = $item;
        }

        return 
json_encode($list, static::getEncodeOptions());
    }
}
<?
php

namespace Gettext\Languages\Exporter;

use 
Exception;

/**
 * Base class for all the exporters.
 */
abstract class Exporter
{
    
/**
     * @var array
     */
    
private static $exporters;

    
/**
     * Return the list of all the available exporters. Keys are the exporter handles, values are the exporter class names.
     *
     * @param bool $onlyForPublicUse if true, internal exporters will be omitted
     *
     * @return string[]
     */
    
final public static function getExporters($onlyForPublicUse false)
    {
        if (!isset(
self::$exporters)) {
            
$exporters = array();
            
$m null;
            foreach (
scandir(__DIR__) as $f) {
                if (
preg_match('/^(\w+)\.php$/'$f$m)) {
                    if (
$f !== basename(__FILE__)) {
                        
$exporters[strtolower($m[1])] = $m[1];
                    }
                }
            }
            
self::$exporters $exporters;
        }
        if (
$onlyForPublicUse) {
            
$result = array();
            foreach (
self::$exporters as $handle => $class) {
                if (
call_user_func(self::getExporterClassName($handle) . '::isForPublicUse') === true) {
                    
$result[$handle] = $class;
                }
            }
        } else {
            
$result self::$exporters;
        }

        return 
$result;
    }

    
/**
     * Return the description of a specific exporter.
     *
     * @param string $exporterHandle the handle of the exporter
     *
     * @throws \Exception throws an Exception if $exporterHandle is not valid
     *
     * @return string
     */
    
final public static function getExporterDescription($exporterHandle)
    {
        
$exporters self::getExporters();
        if (!isset(
$exporters[$exporterHandle])) {
            throw new 
Exception("Invalid exporter handle: '{$exporterHandle}'");
        }

        return 
call_user_func(self::getExporterClassName($exporterHandle) . '::getDescription');
    }

    
/**
     * Returns the fully qualified class name of a exporter given its handle.
     *
     * @param string $exporterHandle the exporter class handle
     *
     * @return string
     */
    
final public static function getExporterClassName($exporterHandle)
    {
        return 
__NAMESPACE__ '\\' ucfirst(strtolower($exporterHandle));
    }

    
/**
     * Convert a list of Language instances to string.
     *
     * @param \Gettext\Languages\Language[] $languages the Language instances to convert
     * @param array|null $options
     *
     * @return string
     */
    
final public static function toString($languages$options null)
    {
        if (isset(
$options) && is_array($options)) {
            if (isset(
$options['us-ascii']) && $options['us-ascii']) {
                
$asciiList = array();
                foreach (
$languages as $language) {
                    
$asciiList[] = $language->getUSAsciiClone();
                }
                
$languages $asciiList;
            }
        }

        return static::
toStringDo($languages);
    }

    
/**
     * Save the Language instances to a file.
     *
     * @param \Gettext\Languages\Language[] $languages the Language instances to convert
     * @param array|null $options
     *
     * @throws \Exception
     */
    
final public static function toFile($languages$filename$options null)
    {
        
$data self::toString($languages$options);
        if (@
file_put_contents($filename$data) === false) {
            throw new 
Exception("Error writing data to '{$filename}'");
        }
    }

    
/**
     * Is this exporter for public use?
     *
     * @return bool
     */
    
public static function isForPublicUse()
    {
        return 
true;
    }

    
/**
     * Return a short description of the exporter.
     *
     * @return string
     */
    
public static function getDescription()
    {
        throw new 
Exception(get_called_class() . ' does not implement the method ' __FUNCTION__);
    }

    
/**
     * Convert a list of Language instances to string.
     *
     * @param \Gettext\Languages\Language[] $languages the Language instances to convert
     *
     * @return string
     */
    
protected static function toStringDo($languages)
    {
        throw new 
Exception(get_called_class() . ' does not implement the method ' __FUNCTION__);
    }
}
<?
php

namespace Gettext\Languages\Exporter;

class 
Ruby extends Exporter
{
    
/**
     * {@inheritdoc}
     *
     * @see \Gettext\Languages\Exporter\Exporter::getDescription()
     */
    
public static function getDescription()
    {
        return 
'Build a Ruby hash';
    }

    
/**
     * {@inheritdoc}
     *
     * @see \Gettext\Languages\Exporter\Exporter::toStringDo()
     */
    
protected static function toStringDo($languages)
    {
        
$lines = array();
        
$lines[] = 'PLURAL_RULES = {';
        foreach (
$languages as $lc) {
            
$lines[] = '  \'' $lc->id '\' => {';
            
$lines[] = '    \'name\' => \'' addslashes($lc->name) . '\',';
            if (isset(
$lc->supersededBy)) {
                
$lines[] = '    \'supersededBy\' => \'' $lc->supersededBy '\',';
            }
            if (isset(
$lc->script)) {
                
$lines[] = '    \'script\' => \'' addslashes($lc->script) . '\',';
            }
            if (isset(
$lc->territory)) {
                
$lines[] = '    \'territory\' => \'' addslashes($lc->territory) . '\',';
            }
            if (isset(
$lc->baseLanguage)) {
                
$lines[] = '    \'baseLanguage\' => \'' addslashes($lc->baseLanguage) . '\',';
            }
            
$lines[] = '    \'formula\' => \'' $lc->formula '\',';
            
$lines[] = '    \'plurals\' => ' count($lc->categories) . ',';
            
$catNames = array();
            foreach (
$lc->categories as $c) {
                
$catNames[] = "'{$c->id}'";
            }
            
$lines[] = '    \'cases\' => [' implode(', '$catNames) . '],';
            
$lines[] = '    \'examples\' => {';
            foreach (
$lc->categories as $c) {
                
$lines[] = '      \'' $c->id '\' => \'' $c->examples '\',';
            }
            
$lines[] = '    },';
            
$lines[] = '  },';
        }
        
$lines[] = '}';
        
$lines[] = '';

        return 
implode("\n"$lines);
    }
}
<?
php

namespace Gettext\Languages\Exporter;

class 
Html extends Exporter
{
    
/**
     * {@inheritdoc}
     *
     * @see \Gettext\Languages\Exporter\Exporter::getDescription()
     */
    
public static function getDescription()
    {
        return 
'Build a HTML table';
    }

    
/**
     * {@inheritdoc}
     *
     * @see \Gettext\Languages\Exporter\Exporter::toStringDo()
     */
    
protected static function toStringDo($languages)
    {
        return 
self::buildTable($languagesfalse);
    }

    protected static function 
h($str)
    {
        return 
htmlspecialchars($strENT_COMPAT'UTF-8');
    }

    protected static function 
buildTable($languages$forDocs)
    {
        
$prefix $forDocs '            ' '';
        
$lines = array();
        
$lines[] = $prefix '<table' . ($forDocs ' class="table table-bordered table-condensed table-striped"' '') . '>';
        
$lines[] = $prefix '    <thead>';
        
$lines[] = $prefix '        <tr>';
        
$lines[] = $prefix '            <th>Language code</th>';
        
$lines[] = $prefix '            <th>Language name</th>';
        
$lines[] = $prefix '            <th># plurals</th>';
        
$lines[] = $prefix '            <th>Formula</th>';
        
$lines[] = $prefix '            <th>Plurals</th>';
        
$lines[] = $prefix '        </tr>';
        
$lines[] = $prefix '    </thead>';
        
$lines[] = $prefix '    <tbody>';
        foreach (
$languages as $lc) {
            
$lines[] = $prefix '        <tr>';
            
$lines[] = $prefix '            <td>' $lc->id '</td>';
            
$name self::h($lc->name);
            if (isset(
$lc->supersededBy)) {
                
$name .= '<br /><small><span>Superseded by</span> ' $lc->supersededBy '</small>';
            }
            
$lines[] = $prefix '            <td>' $name '</td>';
            
$lines[] = $prefix '            <td>' count($lc->categories) . '</td>';
            
$lines[] = $prefix '            <td>' self::h($lc->formula) . '</td>';
            
$cases = array();
            foreach (
$lc->categories as $c) {
                
$cases[] = '<li><span>' $c->id '</span><code>' self::h($c->examples) . '</code></li>';
            }
            
$lines[] = $prefix '            <td><ol' . ($forDocs ' class="cases"' '') . ' start="0">' implode(''$cases) . '</ol></td>';
            
$lines[] = $prefix '        </tr>';
        }
        
$lines[] = $prefix '    </tbody>';
        
$lines[] = $prefix '</table>';

        return 
implode("\n"$lines);
    }
}
<?
php

namespace Gettext\Languages\Exporter;

use 
Exception;

class 
Po extends Exporter
{
    
/**
     * {@inheritdoc}
     *
     * @see \Gettext\Languages\Exporter\Exporter::getDescription()
     */
    
public static function getDescription()
    {
        return 
'Build a string to be used for gettext .po files';
    }

    
/**
     * {@inheritdoc}
     *
     * @see \Gettext\Languages\Exporter\Exporter::toStringDo()
     */
    
protected static function toStringDo($languages)
    {
        if (
count($languages) !== 1) {
            throw new 
Exception('The ' get_called_class() . ' exporter can only export one language');
        }
        
$language $languages[0];
        
$lines = array();
        
$lines[] = '"Language: ' $language->id '\n"';
        
$lines[] = '"Plural-Forms: nplurals=' count($language->categories) . '; plural=' $language->formula '\n"';
        
$lines[] = '';

        return 
implode("\n"$lines);
    }
}
<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast;

/**
 * Nodes renderer class
 * 
 * @author Marco Marchiò <[email protected]>
 */
class Renderer
{
    
/**
     * Formatter to use for the rendering
     * 
     * @var Formatter\Base
     */
    
protected $formatter;
    
    
/**
     * Rendering options taken from the formatter
     * 
     * @var object
     */
    
protected $renderOpts;
    
    
/**
     * Node types that does not require semicolon insertion
     * 
     * @var array
     */
    
protected $noSemicolon = array(
        
"ClassDeclaration",
        
"ExportDefaultDeclaration",
        
"ForInStatement",
        
"ForOfStatement",
        
"ForStatement",
        
"FunctionDeclaration",
        
"IfStatement",
        
"LabeledStatement",
        
"StaticBlock",
        
"SwitchStatement",
        
"TryStatement",
        
"WhileStatement",
        
"WithStatement",
        
"MethodDefinition"
    
);
    
    
/**
     * Sets the formatter to use for the rendering
     * 
     * @param Formatter\Base    $formatter  Formatter
     * 
     * @return $this
     */
    
public function setFormatter(Formatter\Base $formatter)
    {
        
$this->formatter $formatter;
        
        
$this->renderOpts = (object) array(
            
"nl" => $this->formatter->getNewLine(),
            
"ind" => $this->formatter->getIndentation(),
            
"nlbc" => $this->formatter->getNewLineBeforeCurlyBracket(),
            
"sao" =>  $this->formatter->getSpacesAroundOperator() ? " " "",
            
"sirb" => $this->formatter->getSpacesInsideRoundBrackets() ? " " "",
            
"awb" => $this->formatter->getAlwaysWrapBlocks()
        );
        
        return 
$this;
    }
    
    
/**
     * Returns the formatter to use for the rendering
     * 
     * @return Formatter\Base
     */
    
public function getFormatter()
    {
        return 
$this->formatter;
    }
    
    
/**
     * Renders the given node
     * 
     * @param Syntax\Node\Node  $node   Node to render
     * 
     * @return string
     * 
     * @throws \Exception
     */
    
public function render(Syntax\Node\Node $node)
    {
        
//Throw exception if no formatter has been specified
        
if (!$this->formatter) {
            throw new 
\Exception("Formatter not set");
        }
        
        
//Reset indentation level
        
$this->renderOpts->indLevel 0;
        
        
//Start rendering
        
return $this->renderNode($node);
    }
    
    
/**
     * Renders a node
     * 
     * @param Syntax\Node\Node  $node   Node to render
     * 
     * @return string
     */
    
protected function renderNode(Syntax\Node\Node $node)
    {
        
$code "";
        
$type $node->getType();
        switch (
$type) {
            case 
"ArrayExpression":
            case 
"ArrayPattern":
                
$code .= "[" .
                         
$this->joinNodes(
                            
$node->getElements(),
                            
"," $this->renderOpts->sao
                         
) .
                         
"]";
            break;
            case 
"ArrowFunctionExpression":
                if (
$node->getAsync()) {
                    
$code .= "async" $this->renderOpts->sao;
                }
                
$code .= "(" .
                         
$this->renderOpts->sirb .
                         
$this->joinNodes(
                            
$node->getParams(),
                            
"," $this->renderOpts->sao
                         
) .
                         
$this->renderOpts->sirb .
                         
")" .
                         
$this->renderOpts->sao .
                         
"=>";
                
$body $node->getBody();
                if (
$body->getType() !== "BlockStatement") {
                    
$code .= $this->renderOpts->sao $this->renderNode($body);
                } else {
                    
$code .= $this->renderStatementBlock($bodytrue);
                }
            break;
            case 
"AwaitExpression":
                
$code .= "await " $this->renderNode($node->getArgument());
            break;
            case 
"AssignmentExpression":
            case 
"AssignmentPattern":
            case 
"BinaryExpression":
            case 
"LogicalExpression":
                
$operator $type === "AssignmentPattern" ?
                            
"=" :
                            
$node->getOperator();
                
$code .= $this->renderNode($node->getLeft());
                
$codeRight $this->renderNode($node->getRight());
                if (
preg_match("#^[a-z]+$#i"$operator)) {
                    
$code .= " " .
                             
$operator .
                             
" ";
                } else {
                    
//If there's no space around the operator, additional checks must
                    //be performed to prevent errors when rendering unary and update
                    //expressions inside binary expressions
                    
$checkSpace = !$this->renderOpts->sao && $type === "BinaryExpression";
                    
                    
//The space is mandatory if the left part ends with the same
                    //character used as operator
                    
if ($checkSpace && $code && substr($code, -1) === $operator) {
                        
$code .= " ";
                    }
                    
                    
$code .= $this->renderOpts->sao .
                             
$operator .
                             
$this->renderOpts->sao;
                    
                    
//The space is mandatory if the right part begins with the same
                    //character used as operator
                    
if ($checkSpace && $codeRight && $codeRight[0] === $operator) {
                        
$code .= " ";
                    }
                }
                
$code .= $codeRight;
            break;
            case 
"BlockStatement":
            case 
"ClassBody":
            case 
"Program":
                
$code .= $this->renderStatementBlock(
                    
$node->getBody(), falsefalsetruefalse
                
);
            break;
            case 
"BreakStatement":
            case 
"ContinueStatement":
                
$code .= $type === "BreakStatement" "break" "continue";
                if (
$label $node->getLabel()) {
                    
$code .= " " $this->renderNode($label);
                }
            break;
            case 
"CallExpression":
            case 
"NewExpression":
                if (
$type === "NewExpression") {
                    
$code .= "new ";
                    
$optional false;
                } else {
                    
$optional $node->getOptional();
                }
                
$code .= $this->renderNode($node->getCallee()) .
                         (
$optional "?." "") .
                         
"(" .
                         
$this->renderOpts->sirb .
                         
$this->joinNodes(
                            
$node->getArguments(),
                            
"," $this->renderOpts->sao
                         
) .
                         
$this->renderOpts->sirb .
                         
")";
            break;
            case 
"CatchClause":
                
$code .= "catch";
                if (
$params $node->getParam()) {
                    
$code .= $this->renderOpts->sao .
                             
"(" .
                             
$this->renderOpts->sirb .
                             
$this->renderNode($params) .
                             
$this->renderOpts->sirb .
                             
")";
                }
                
$code .= $this->renderStatementBlock($node->getBody(), true);
            break;
            case 
"ChainExpression":
            case 
"ExpressionStatement":
                
$code .= $this->renderNode($node->getExpression());
            break;
            case 
"ClassExpression":
            case 
"ClassDeclaration":
                
$code .= "class";
                if (
$id $node->getId()) {
                    
$code .= " " $this->renderNode($id);
                }
                if (
$superClass $node->getSuperClass()) {
                    
$code .= " extends " $this->renderNode($superClass);
                }
                
$code .= $this->renderStatementBlock(
                    
$node->getBody(), true
                
);
            break;
            case 
"ConditionalExpression":
                
$code .= $this->renderNode($node->getTest()) .
                         
$this->renderOpts->sao .
                         
"?" .
                         
$this->renderOpts->sao .
                         
$this->renderNode($node->getConsequent()) .
                         
$this->renderOpts->sao .
                         
":" .
                         
$this->renderOpts->sao .
                         
$this->renderNode($node->getAlternate());
            break;
            case 
"DebuggerStatement":
                
$code .= "debugger";
            break;
            case 
"DoWhileStatement":
                
$code .= "do" .
                         
$this->renderStatementBlock(
                            
$node->getBody(), nulltrue
                         
) .
                         
$this->renderOpts->sao .
                         
"while" .
                         
$this->renderOpts->sao .
                         
"(" .
                         
$this->renderOpts->sirb .
                         
$this->renderNode($node->getTest()) .
                         
$this->renderOpts->sirb .
                         
")";
            break;
            case 
"JSXEmptyExpression":
            case 
"EmptyStatement":
            break;
            case 
"ExportAllDeclaration":
                
$code .= "export *";
                
$exported $node->getExported();
                if (
$exported) {
                    
$code .= " as " $this->renderNode($exported);
                }
                
$code .= " from " $this->renderNode($node->getSource());
            break;
            case 
"ExportDefaultDeclaration":
                
$declaration $node->getDeclaration();
                
$code .= "export default " .
                         
$this->renderNode($declaration);
                if (
$this->requiresSemicolon($declaration)) {
                    
$code .= ";";
                }
            break;
            case 
"ExportNamedDeclaration":
                
$code .= "export";
                if (
$dec $node->getDeclaration()) {
                    
$code .= " " .
                             
$this->renderNode($dec);
                } else {
                    
$code .= $this->renderOpts->sao .
                             
"{" .
                             
$this->joinNodes(
                                 
$node->getSpecifiers(),
                                 
"," $this->renderOpts->sao
                             
) .
                             
"}";
                    if (
$source $node->getSource()) {
                        
$code .= $this->renderOpts->sao .
                                 
"from " .
                                 
$this->renderNode($source);
                    }
                }
            break;
            case 
"ExportSpecifier":
                
$local $this->renderNode($node->getLocal());
                
$ref $this->renderNode($node->getExported());
                
$code .= $local === $ref ?
                         
$local :
                         
$local " as " $ref;
            break;
            case 
"ForInStatement":
            case 
"ForOfStatement":
                
//Force single line mode for substatements
                
$this->renderOpts->forceSingleLine true;
                
$code .= "for" .
                         (
$type === "ForOfStatement" && $node->getAwait() ? " await" "") .
                         
$this->renderOpts->sao .
                         
"(" .
                         
$this->renderOpts->sirb .
                         
$this->renderNode($node->getLeft()) .
                         
" " . ($type === "ForInStatement" "in" "of") . " " .
                         
$this->renderNode($node->getRight()) .
                         
$this->renderOpts->sirb .
                         
")" .
                         
$this->renderStatementBlock($node->getBody());
                unset(
$this->renderOpts->forceSingleLine);
            break;
            case 
"ForStatement":
                
//Force single line mode for substatements
                
$this->renderOpts->forceSingleLine true;
                
$code .= "for" .
                         
$this->renderOpts->sao .
                         
"(" .
                         
$this->renderOpts->sirb;
                if (
$init $node->getInit()) {
                    
$code .= $this->renderNode($init);
                }
                
$code .= ";" $this->renderOpts->sao;
                if (
$test $node->getTest()) {
                    
$code .= $this->renderNode($test);
                }
                
$code .= ";" $this->renderOpts->sao;
                if (
$update $node->getUpdate()) {
                    
$code .= $this->renderNode($update);
                }
                
$code .= $this->renderOpts->sirb .
                         
")" .
                         
$this->renderStatementBlock($node->getBody());
                unset(
$this->renderOpts->forceSingleLine);
            break;
            case 
"FunctionDeclaration":
            case 
"FunctionExpression":
                
$id $node->getId();
                if (
$node->getAsync()) {
                    
$code .= "async ";
                }
                
$code .= "function";
                if (
$node->getGenerator()) {
                    
$code .= $this->renderOpts->sao .
                             
"*";
                } elseif (
$id) {
                    
$code .= " ";
                }
                if (
$id) {
                    if (
$node->getGenerator()) {
                        
$code .= $this->renderOpts->sao;
                    }
                    
$code .= $this->renderNode($id);
                }
                
$code .= $this->renderOpts->sao .
                         
"(" .
                         
$this->renderOpts->sirb .
                         
$this->joinNodes(
                            
$node->getParams(),
                            
"," $this->renderOpts->sao
                         
) .
                         
$this->renderOpts->sirb .
                         
")" .
                         
$this->renderStatementBlock($node->getBody(), true);
            break;
            case 
"ImportExpression":
                
$code .= "import(" .
                         
$this->renderOpts->sirb .
                         
$this->renderNode($node->getSource()) .
                         
$this->renderOpts->sirb .
                         
")";
            break;
            case 
"JSXIdentifier":
            case 
"Identifier":
                
$code .= $node->getRawName();
            break;
            case 
"IfStatement":
                
$code .= "if" .
                         
$this->renderOpts->sao .
                         
"(" .
                         
$this->renderOpts->sirb .
                         
$this->renderNode($node->getTest()) .
                         
$this->renderOpts->sirb .
                         
")";
                
$consequent $node->getConsequent();
                
$alternate $node->getAlternate();
                
                
$forceBracketsConsequent $forceBracketsAlternate null;
                if (!
$this->renderOpts->awb && $alternate) {
                    
$forceBracketsConsequent $this->checkIfPartsBracketsRequirement(
                        
$consequent
                    
);
                    
$forceBracketsAlternate $this->checkIfPartsBracketsRequirement(
                        
$alternate
                    
);
                }
                
                
$code .= $this->renderStatementBlock(
                    
$consequent$forceBracketsConsequent
                
);
                if (
$alternate) {
                    
$code .= $this->renderOpts->sao .
                             
"else" .
                             
$this->renderStatementBlock(
                                 
$alternate,
                                 
$forceBracketsAlternate,
                                 
true
                             
);
                }
            break;
            case 
"ImportDeclaration":
                
$code .= "import ";
                
$specifiers $node->getSpecifiers();
                if (
count($specifiers)) {
                    
$sep "," $this->renderOpts->sao;
                    
$groups $parts = array();
                    foreach (
$specifiers as $spec) {
                        
$specType $spec->getType();
                        if (!isset(
$groups[$specType])) {
                            
$groups[$specType] = array();
                        }
                        
$groups[$specType][] = $spec;
                    }
                    if (isset(
$groups["ImportDefaultSpecifier"])) {
                        foreach (
$groups["ImportDefaultSpecifier"] as $s) {
                            
$parts[] = $this->renderNode($s);
                        }
                    }
                    if (isset(
$groups["ImportNamespaceSpecifier"])) {
                        foreach (
$groups["ImportNamespaceSpecifier"] as $s) {
                            
$parts[] = $this->renderNode($s);
                        }
                    }
                    if (isset(
$groups["ImportSpecifier"])) {
                        
$impSpec = array();
                        foreach (
$groups["ImportSpecifier"] as $s) {
                            
$impSpec[] = $this->renderNode($s);
                        }
                        
$parts[] = "{" implode($sep$impSpec) . "}";
                    }
                    
$code .= implode($sep$parts) . " from ";
                }
                
$code .= $this->renderNode($node->getSource());
            break;
            case 
"ImportDefaultSpecifier":
                
$code .= $this->renderNode($node->getLocal());
            break;
            case 
"ImportNamespaceSpecifier":
                
$code .= "* as " $this->renderNode($node->getLocal());
            break;
            case 
"ImportSpecifier":
                
$local $this->renderNode($node->getLocal());
                
$ref $this->renderNode($node->getImported());
                
$code .= $local === $ref ?
                         
$local :
                         
$ref " as " $local;
            break;
            case 
"JSXAttribute":
                
$code .= $this->renderNode($node->getName());
                if (
$value $node->getValue()) {
                    
$code .= "=" $this->renderNode($value);
                }
            break;
            case 
"JSXClosingElement":
                
$code .= "</" $this->renderNode($node->getName()) . ">";
            break;
            case 
"JSXClosingFragment":
                
$code .= "</>";
            break;
            case 
"JSXElement":
                
$code .= $this->renderNode($node->getOpeningElement()) .
                         
$this->joinNodes($node->getChildren(), "");
                if (
$closing $node->getClosingElement()) {
                    
$code .= $this->renderNode($closing);
                }
            break;
            case 
"JSXExpressionContainer":
                
$code .= "{" $this->renderNode($node->getExpression()) . "}";
            break;
            case 
"JSXFragment":
                
$code .= $this->renderNode($node->getOpeningFragment()) .
                         
$this->joinNodes($node->getChildren(), "") .
                         
$this->renderNode($node->getClosingFragment());
            break;
            case 
"JSXNamespacedName":
                
$code .= $this->renderNode($node->getNamespace()) . ":" .
                         
$this->renderNode($node->getName());
            break;
            case 
"JSXOpeningElement":
                
$code .= "<" $this->renderNode($node->getName());
                
$attributes $node->getAttributes();
                if (
count($attributes)) {
                    
$code .= " " $this->joinNodes($attributes" ");
                }
                if (
$node->getSelfClosing()) {
                    
$code .= "/";
                }
                
$code .= ">";
            break;
            case 
"JSXOpeningFragment":
                
$code .= "<>";
            break;
            case 
"JSXSpreadAttribute":
                
$code .= "{..." $this->renderNode($node->getArgument()) . "}";
            break;
            case 
"JSXSpreadChild":
                
$code .= "{..." $this->renderNode($node->getExpression()) . "}";
            break;
            case 
"LabeledStatement":
                
$body $node->getBody();
                
$code .= $this->renderNode($node->getLabel()) .
                         
":";
                if (
$body->getType() === "BlockStatement") {
                    
$code .= $this->renderStatementBlock($bodytrue);
                } else {
                    
$code .= $this->renderOpts->nl .
                             
$this->getIndentation() .
                             
$this->renderNode($body);
                }
                if (
$this->requiresSemicolon($body)) {
                    
$code .= ";";
                }
            break;
            case 
"JSXText":
            case 
"Literal":
            case 
"RegExpLiteral":
                
$code .= $node->getRaw();
            break;
            case 
"JSXMemberExpression":
            case 
"MemberExpression":
                
$property $node->getProperty();
                
$compiledProperty $this->renderNode($property);
                
$code .= $this->renderNode($node->getObject());
                
$optional false;
                if (
$type === "MemberExpression") {
                    
$optional $node->getOptional();
                }
                
$propertyType $property->getType();
                if (
$type === "MemberExpression" &&
                    (
$node->getComputed() ||
                    (
$propertyType !== "Identifier" && $propertyType !== "PrivateIdentifier"))) {
                    
$code .= ($optional "?." "") . "[" $compiledProperty "]";
                } else {
                    
$code .= ($optional "?." ".") . $compiledProperty;
                }
            break;
            case 
"MetaProperty":
                
$code .= $node->getMeta() . "." $node->getProperty();
            break;
            case 
"MethodDefinition":
                if (
$node->getStatic()) {
                    
$code .= "static ";
                }
                
$value $node->getValue();
                
$key $node->getKey();
                
$kind $node->getKind();
                if (
$kind === $node::KIND_GET || $kind === $node::KIND_SET) {
                    
$code .= $kind " ";
                } else {
                    if (
$value->getAsync()) {
                        
$code .= "async ";
                    }
                    if (
$value->getGenerator()) {
                        
$code .= "*" .
                                 
$this->renderOpts->sao;
                    }
                }
                if (
$node->getComputed()) {
                    
$code .= "[" .
                             
$this->renderNode($key) .
                             
"]";
                } else {
                    
$code .= $this->renderNode($key);
                }
                
$code .= $this->renderOpts->sao .
                         
preg_replace("/^[^(]+/"""$this->renderNode($value));
            break;
            case 
"ObjectExpression":
                
$currentIndentation $this->getIndentation();
                
$this->renderOpts->indLevel++;
                
$indentation $this->getIndentation();
                
//Handle single line mode
                
if (isset($this->renderOpts->forceSingleLine)) {
                    
$start $end "";
                    
$separator "," $this->renderOpts->sao;
                } else {
                    
$end $this->renderOpts->nl $currentIndentation;
                    
$start $this->renderOpts->nl $indentation;
                    
$separator "," $this->renderOpts->nl $indentation;
                }
                
$code .= "{";
                
$properties $node->getProperties();
                if (
count($properties)) {
                    
$code .= $start .
                             
$this->joinNodes(
                                
$properties,
                                
$separator
                             
) .
                             
$end;
                }
                
$code .= "}";
                
$this->renderOpts->indLevel--;
            break;
            case 
"ObjectPattern":
                
$code .= "{" .
                         
$this->joinNodes(
                            
$node->getProperties(),
                            
"," $this->renderOpts->sao
                         
) .
                         
"}";
            break;
            case 
"ParenthesizedExpression":
                
$code .= "(" .
                         
$this->renderOpts->sirb .
                         
$this->renderNode($node->getExpression()) .
                         
$this->renderOpts->sirb .
                         
")";
            break;
            case 
"PrivateIdentifier":
                
$code .= "#" $node->getName();
            break;
            case 
"Property":
                
$value $node->getValue();
                
$key $node->getKey();
                
$compiledKey $this->renderNode($key);
                
$compiledValue $this->renderNode($value);
                
$keyType $key->getType();
                
$valueType $value->getType();
                if (
$valueType === "AssignmentPattern" &&
                    
$compiledKey === $this->renderNode($value->getLeft())) {
                    
$code .= $compiledValue;
                } else {
                    
$kind $node->getKind();
                    
$getterSetter $kind === $node::KIND_GET ||
                                    
$kind === $node::KIND_SET;
                    if (
$getterSetter) {
                        
$code .= $kind " ";
                    } elseif (
$value->getType() === "FunctionExpression" &&
                              
$value->getGenerator()) {
                        
$code .= "*" .
                                 
$this->renderOpts->sao;
                    }
                    if (
$node->getComputed()) {
                        
$code .= "[" $compiledKey "]";
                    } else {
                        
$code .= $compiledKey;
                    }
                    if (
$node->getMethod() || $getterSetter) {
                        
$code .= $this->renderOpts->sao .
                                 
preg_replace("/^[^(]+/"""$compiledValue);
                    } elseif (
$keyType !== "Identifier" ||
                              
$valueType !== "Identifier" ||
                              
$compiledKey !== $compiledValue
                    
) {
                        
$code .= ($node->getShorthand() ? "=" ":") .
                                 
$this->renderOpts->sao .
                                 
$compiledValue;
                    }
                }
            break;
            case 
"PropertyDefinition":
                if (
$node->getStatic()) {
                    
$code .= "static ";
                }
                
$compiledKey $this->renderNode($node->getKey());
                if (
$node->getComputed()) {
                    
$code .= "[" $compiledKey "]";
                } else {
                    
$code .= $compiledKey;
                }
                if (
$value $node->getValue()) {
                    
$code .= $this->renderOpts->sao .
                             
"=" .
                             
$this->renderOpts->sao .
                             
$this->renderNode($value);
                }
            break;
            case 
"RestElement":
            case 
"SpreadElement":
                
$code .= "..." $this->renderNode($node->getArgument());
            break;
            case 
"ReturnStatement":
                
$code .= "return";
                if (
$argument $node->getArgument()) {
                    
$code .= " " $this->renderNode($argument);
                }
            break;
            case 
"SequenceExpression":
                
$code .= $this->joinNodes(
                            
$node->getExpressions(),
                            
"," $this->renderOpts->sao
                         
);
            break;
            case 
"StaticBlock":
                
$code .= "static";
                
$code .= $this->renderStatementBlock($node->getBody(), true);
            break;
            case 
"Super":
                
$code .= "super";
            break;
            case 
"SwitchCase":
                if (
$test $node->getTest()) {
                    
$code .= "case " $this->renderNode($test);
                } else {
                    
$code .= "default";
                }
                
$code .= ":";
                if (
count($node->getConsequent())) {
                    
$code .= $this->renderOpts->nl .
                             
$this->renderStatementBlock(
                                 
$node->getConsequent(),
                                 
false
                             
);
                }
            break;
            case 
"SwitchStatement":
                
$code .= "switch" .
                         
$this->renderOpts->sao .
                         
"(" .
                         
$this->renderOpts->sirb .
                         
$this->renderNode($node->getDiscriminant()) .
                         
$this->renderOpts->sirb .
                         
")" .
                         
$this->renderStatementBlock(
                             
$node->getCases(), truefalsefalse
                         
);
            break;
            case 
"TaggedTemplateExpression":
                
$code .= $this->renderNode($node->getTag()) .
                         
$this->renderNode($node->getQuasi());
            break;
            case 
"TemplateElement":
                
$code .= $node->getRawValue();
            break;
            case 
"TemplateLiteral":
                
$code .= "`";
                foreach (
$node->getParts() as $part) {
                    if (
$part->getType() === "TemplateElement") {
                        
$code .= $this->renderNode($part);
                    } else {
                        
$code .= "$" "{" $this->renderNode($part) . "}";
                    }
                }
                
$code .= "`";
            break;
            case 
"ThisExpression":
                
$code .= "this";
            break;
            case 
"ThrowStatement":
                
$code .= "throw " $this->renderNode($node->getArgument());
            break;
            case 
"TryStatement":
                
$code .= "try" .
                         
$this->renderStatementBlock($node->getBlock(), true);
                if (
$handler $node->getHandler()) {
                    
$code .= $this->renderOpts->sao .
                             
$this->renderNode($handler);
                }
                if (
$finalizer $node->getFinalizer()) {
                    
$code .= $this->renderOpts->sao .
                             
"finally" .
                             
$this->renderStatementBlock($finalizertrue);
                }
            break;
            case 
"UnaryExpression":
            case 
"UpdateExpression":
                
$prefix $node->getPrefix();
                if (
$prefix) {
                    
$code .= $node->getOperator();
                    if (
preg_match("#^[a-z]+$#i"$node->getOperator())) {
                        
$code .= " ";
                    }
                }
                
$code .= $this->renderNode($node->getArgument());
                if (!
$prefix) {
                    
$code .= $node->getOperator();
                }
            break;
            case 
"VariableDeclaration":
                
$this->renderOpts->indLevel++;
                
$indentation $this->getIndentation();
                
//Handle single line mode
                
if (isset($this->renderOpts->forceSingleLine)) {
                    
$separator "," $this->renderOpts->sao;
                } else {
                    
$separator "," $this->renderOpts->nl $indentation;
                }
                
$code .= $node->getKind() .
                         
" " .
                         
$this->joinNodes(
                            
$node->getDeclarations(),
                            
$separator
                         
);
                
$this->renderOpts->indLevel--;
            break;
            case 
"VariableDeclarator":
                
$code .= $this->renderNode($node->getId());
                if (
$init $node->getInit()) {
                    
$code .= $this->renderOpts->sao .
                             
"=" .
                             
$this->renderOpts->sao .
                             
$this->renderNode($init);
                }
            break;
            case 
"WhileStatement":
                
$code .= "while" .
                         
$this->renderOpts->sao .
                         
"(" .
                         
$this->renderOpts->sirb .
                         
$this->renderNode($node->getTest()) .
                         
$this->renderOpts->sirb .
                         
")" .
                         
$this->renderStatementBlock($node->getBody());
            break;
            case 
"WithStatement":
                
$code .= "with" .
                         
$this->renderOpts->sao .
                         
"(" .
                         
$this->renderOpts->sirb .
                         
$this->renderNode($node->getObject()) .
                         
$this->renderOpts->sirb .
                         
")" .
                         
$this->renderStatementBlock($node->getBody());
            break;
            case 
"YieldExpression":
                
$code .= "yield";
                if (
$node->getDelegate()) {
                    
$code .= " *";
                }
                if (
$argument $node->getArgument()) {
                    
$code .= " " $this->renderNode($argument);
                }
            break;
        }
        return 
$code;
    }
    
    
/**
     * Renders a node as a block statement
     * 
     * @param Syntax\Node\Node|array    $node               Node or array of 
     *                                                      nodes to render
     * @param bool                      $forceBrackets      Overrides brackets
     *                                                      inserting rules
     * @param bool                      $mandatorySeparator True if a starting
     *                                                      separator is
     *                                                      mandatory
     * @param bool                      $addSemicolons      Semicolons are
     *                                                      inserted automatically
     *                                                      if this parameter is
     *                                                      not false
     * @param bool                      $incIndent          If false indentation
     *                                                      level won't be
     *                                                      incremented
     * 
     * @return string
     */
    
protected function renderStatementBlock(
        
$node$forceBrackets null$mandatorySeparator false,
        
$addSemicolons true$incIndent true
    
) {
        
$code "";
        
        
//Special handling of BlockStatement and ClassBody nodes by rendering
        //their child nodes
        
if (!is_array($node) &&
            
in_array($node->getType(), array("BlockStatement""ClassBody"))) {
            
$node $node->getBody();
        }
        
        
//If $forceBrackets is not null use its value to override curly brackets
        //insertion rules
        
if ($forceBrackets !== null) {
            
$hasBrackets $forceBrackets;
        } else {
            
//Insert curly brackets if required by the formatter or if the
            //number of nodes to render is different from one
            
$hasBrackets $this->renderOpts->awb ||
                           (
is_array($node) && count($node) !== 1);
        }
        
$currentIndentation $this->getIndentation();
        
        
//If $forceBrackets is not set to false then the node can be wrapped in
        //curly braces, so a separator defined by formatter must be inserted
        
if ($forceBrackets !== false) {
            if (
$this->renderOpts->nlbc) {
                
$code .= $this->renderOpts->nl $currentIndentation;
            } else {
                
$code .= $this->renderOpts->sao;
            }
        }
        
        
//Insert open curly bracket if required
        
if ($hasBrackets) {
            
$code .= "{" $this->renderOpts->nl;
        } elseif (
$mandatorySeparator) {
            
//If bracket is not inserted but a separator is still required
            //a space is added
            
$code .= " ";
        }
        
        
//Increase indentation level
        
if ($incIndent) {
            
$this->renderOpts->indLevel++;
        }
        
$subIndentation $this->getIndentation();
        
        
//Render the node or the array of nodes
        
$emptyBody is_array($node) && !count($node);
        if (
is_array($node)) {
            if (!
$emptyBody) {
                
$code .= $subIndentation .
                         
$this->joinNodes(
                            
$node,
                            
$this->renderOpts->nl $subIndentation,
                            
$addSemicolons
                         
);
            }
        } else {
            
$code .= $subIndentation $this->renderNode($node);
            if (
$addSemicolons && $this->requiresSemicolon($node)) {
                
$code .= ";";
            }
        }
        
        
//Reset the indentation level
        
if ($incIndent) {
            
$this->renderOpts->indLevel--;
        }
        
        
//Insert closing curly bracket if required
        
if ($hasBrackets) {
            
//Add a new line if something was rendered
            
if (!$emptyBody) {
                
$code .= $this->renderOpts->nl;
            }
            
$code .= $currentIndentation "}";
        }
        
        return 
$code;
    }
    
    
/**
     * Joins an array of nodes with the given separator
     * 
     * @param array     $nodes          Nodes
     * @param string    $separator      Separator
     * @param bool      $addSemicolons  True to add semicolons after each node
     * 
     * @return string
     */
    
protected function joinNodes($nodes$separator$addSemicolons=false)
    {
        
$parts = array();
        foreach (
$nodes as $node) {
            if (!
$node) {
                
$code "";
            } else {
                
$code $this->renderNode($node);
                if (
$addSemicolons && $this->requiresSemicolon($node)) {
                    
$code .= ";";
                }
            }
            
$parts[] = $code;
        }
        return 
implode($separator$parts);
    }
    
    
/**
     * Checks if consequent and alternate nodes of an IfStatement require the
     * mandatory insertion of brackets around them to avoid the pattern where
     * the "else" code is rendered as part of an inner IfStatement.
     * 
     * @param Syntax\Node\Node  $node   Node
     * 
     * @return null|bool
     */
    
protected function checkIfPartsBracketsRequirement($node)
    {
        if (
$node->getType() === "BlockStatement" && count($node->getBody()) > 1) {
            return 
null;
        }
        
$forceBrackets null;
        
$optBracketNodes = array(
            
"DoWhileStatement""ForInStatement""ForOfStatement",
            
"ForStatement""WhileStatement""WithStatement"
        
);
        
$checkFn = function ($n) use ($optBracketNodes, &$forceBrackets) {
            
$type $n->getType();
            if (
$type === "IfStatement") {
                if (!
$n->getAlternate()) {
                    
$forceBrackets true;
                }
                return 
Traverser::DONT_TRAVERSE_CHILD_NODES;
            } elseif (
$type === "BlockStatement") {
                if (
count($n->getBody()) !== 1) {
                    return 
Traverser::DONT_TRAVERSE_CHILD_NODES;
                }
            } elseif (
$type === "LabeledStatement") {
                if (
$n->getBody()->getType() === "BlockStatement") {
                    
$forceBrackets true;
                }
                return 
Traverser::DONT_TRAVERSE_CHILD_NODES;
            } elseif (!
in_array($type$optBracketNodes)) {
                return 
Traverser::DONT_TRAVERSE_CHILD_NODES;
            }
        };
        
$node->traverse($checkFn);
        return 
$forceBrackets;
    }
    
    
/**
     * Check if the given node requires semicolons insertion
     * 
     * @param Syntax\Node\Node  $node   Node
     * 
     * @return bool
     */
    
protected function requiresSemicolon($node)
    {
        return !
in_array($node->getType(), $this->noSemicolon);
    }
    
    
/**
     * Returns the current indentation string
     * 
     * @return string
     */
    
protected function getIndentation()
    {
        return 
str_repeat(
            
$this->renderOpts->ind,
            
$this->renderOpts->indLevel
        
);
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast;

/**
 * Main class of Peast library.
 * Every function of this class takes two arguments:
 * - The source code to parse
 * - The options array that is an associative array of parser settings.
 *   Available options are:
 *      - "sourceType": one of the source type constants declared in this class.
 *        This option tells the parser to parse the source in script or module
 *        mode. If this option is not provided the parser will work in script
 *        mode.
 *      - "sourceEncoding": the encoding of the source. If not specified the
 *        parser will assume UTF-8.
 *      - "strictEncoding": if false the parser will handle invalid UTF8
 *        characters in the source code by replacing them with the character
 *        defined in the "mbstring.substitute_character" ini setting, otherwise
 *        it will throw an exception.
 *      - "comments": if true it enables comments parsing.
 *      - "jsx": if true it enables parsing of JSX syntax.
 * 
 * @method static Syntax\Parser ES2015(string $source, array $options = array())
 * Returns a parser instance with ES2015 features for the given source. See Peast
 * class documentation to understand the function arguments.
 * 
 * @method static Syntax\Parser ES6(string $source, array $options = array())
 * Returns a parser instance with ES2015 features for the given source. See Peast
 * class documentation to understand function arguments.
 * 
 * @method static Syntax\Parser ES2016(string $source, array $options = array())
 * Returns a parser instance with ES2016 features for the given source. See Peast
 * class documentation to understand function arguments.
 * 
 * @method static Syntax\Parser ES7(string $source, array $options = array())
 * Returns a parser instance with ES2016 features for the given source. See Peast
 * class documentation to understand function arguments.
 * 
 * @method static Syntax\Parser ES2017(string $source, array $options = array())
 * Returns a parser instance with ES2017 features for the given source. See Peast
 * class documentation to understand function arguments.
 * 
 * @method static Syntax\Parser ES8(string $source, array $options = array())
 * Returns a parser instance with ES2017 features for the given source. See Peast
 * class documentation to understand function arguments.
 * 
 * @method static Syntax\Parser ES2018(string $source, array $options = array())
 * Returns a parser instance with ES2018 features for the given source. See Peast
 * class documentation to understand function arguments.
 * 
 * @method static Syntax\Parser ES9(string $source, array $options = array())
 * Returns a parser instance with ES2018 features for the given source. See Peast
 * class documentation to understand function arguments.
 * 
 * @method static Syntax\Parser ES2019(string $source, array $options = array())
 * Returns a parser instance with ES2019 features for the given source. See Peast
 * class documentation to understand function arguments.
 * 
 * @method static Syntax\Parser ES10(string $source, array $options = array())
 * Returns a parser instance with ES2019 features for the given source. See Peast
 * class documentation to understand function arguments.
 *
 * @method static Syntax\Parser ES2020(string $source, array $options = array())
 * Returns a parser instance with ES2020 features for the given source. See Peast
 * class documentation to understand function arguments.
 *
 * @method static Syntax\Parser ES11(string $source, array $options = array())
 * Returns a parser instance with ES2020 features for the given source. See Peast
 * class documentation to understand function arguments.
 *
 * @method static Syntax\Parser ES2021(string $source, array $options = array())
 * Returns a parser instance with ES2021 features for the given source. See Peast
 * class documentation to understand function arguments.
 *
 * @method static Syntax\Parser ES12(string $source, array $options = array())
 * Returns a parser instance with ES2021 features for the given source. See Peast
 * class documentation to understand function arguments.
 *
 * @method static Syntax\Parser ES2022(string $source, array $options = array())
 * Returns a parser instance with ES2022 features for the given source. See Peast
 * class documentation to understand function arguments.
 *
 * @method static Syntax\Parser ES13(string $source, array $options = array())
 * Returns a parser instance with ES2022 features for the given source. See Peast
 * class documentation to understand function arguments.
 *
 * @method static Syntax\Parser latest(string $source, array $options = array())
 * Returns an instance of the latest parser version for the given source. See
 * Peast class documentation to understand function arguments.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class Peast
{
    
//Source type constants
    /**
     * This source type indicates that the source is a script and import
     * and export keywords are not parsed.
     */
    
const SOURCE_TYPE_SCRIPT "script";
    
    
/**
     * This source type indicates that the source is a module, this enables
     * the parsing of import and export keywords.
     */
    
const SOURCE_TYPE_MODULE "module";
    
    
/**
     * Valid versions and aliases
     * 
     * @var array
     */
    
static protected $versions = array(
        
"ES6" => "ES2015",
        
"ES7" => "ES2016",
        
"ES8" => "ES2017",
        
"ES9" => "ES2018",
        
"ES10" => "ES2019",
        
"ES11" => "ES2020",
        
"ES12" => "ES2021",
        
"ES13" => "ES2022"
    
);
    
    
/**
     * Magic method that exposes all the functions to access parser with
     * specific features
     * 
     * @param string    $version   Parser version
     * @param array     $args      Parser arguments
     * 
     * @return Syntax\Parser
     * 
     * @throws \Exception
     */
    
public static function __callStatic($version$args)
    {
        
$source $args[0];
        
$options = isset($args[1]) ? $args[1] : array();
        
        if (!
in_array($versionself::$versions)) {
            if (
$version === "latest") {
                
$version end(self::$versions);
            } elseif (isset(
self::$versions[$version])) {
                
$version self::$versions[$version];
            } else {
                throw new 
\Exception("Invalid version $version");
            }
        }
        
        
$featuresClass "\\Peast\\Syntax\\$version\\Features";
        return new 
Syntax\Parser(
            
$source, new $featuresClass$options
        
);
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast;

/**
 * Nodes query class
 * 
 * @author Marco Marchiò <[email protected]>
 */
class Query implements \IteratorAggregate\Countable
{
    
/**
     * Current matches
     *
     * @var Selector\Matches
     */
    
protected $matches;

    
/**
     * Options array
     *
     * @var array
     */
    
protected $options;

    
/**
     * Class constructor. Available options are:
     * - encoding: selectors encoding. If not specified the
     *   parser will assume UTF-8.
     *
     * @param Syntax\Node\Program $root     Root node
     * @param array               $options  Options array
     */
    
public function __construct(Syntax\Node\Program $root$options = array())
    {
        
$this->matches = new Selector\Matches();
        
$this->matches->addMatch($root);
        
$this->options $options;
    }

    
/**
     * Finds nodes matching the given selector starting from the
     * current matched nodes, if any, or from the root
     *
     * @param string $selector Selector
     *
     * @return $this
     *
     * @throws Selector\Exception
     */
    
public function find($selector)
    {
        
$parser = new Selector\Parser($selector$this->options);
        
$selector $parser->parse();
        
$this->matches $selector->exec($this->matches);
        return 
$this;
    }

    
/**
     * Executes the given selector on the current nodes and filters
     * out the nodes which don't match
     *
     * @param string $selector Selector
     *
     * @return $this
     *
     * @throws Selector\Exception
     */
    
public function filter($selector)
    {
        
$parser = new Selector\Parser($selector$this->options);
        
$selector $parser->parse(true);
        
$this->matches->filter(function ($node$parent) use ($selector) {
            
$newMatch = new Selector\Matches();
            
$newMatch->addMatch($node$parent);
            return 
$selector->exec($newMatch)->getMatches();
        });
        return 
$this;
    }

    
/**
     * Returns the number of matched nodes
     *
     * @return int
     */
    
#[\ReturnTypeWillChange]
    public function 
count()
    {
        return 
$this->matches->count();
    }

    
/**
     * Returns the node at the given index
     *
     * @param int $index Index
     *
     * @return array
     *
     * @throws \Exception
     */
    
public function get($index)
    {
        return 
$this->matches->get($index)[0];
    }

    
/**
     * Returns the nodes iterator
     *
     * @return \ArrayIterator
     */
    
#[\ReturnTypeWillChange]
    public function 
getIterator()
    {
        return new 
\ArrayIterator($this->matches->getNodes());
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Selector\Node\Part;

use 
Peast\Syntax\Node\Node;

/**
 * Selector part base class
 * 
 * @author Marco Marchiò <[email protected]>
 *
 * @abstract
 */
abstract class Part
{
    
/**
     * Priority
     *
     * @var int
     */
    
protected $priority 5;

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

    
/**
     * Returns true if the selector part matches the given node,
     * false otherwise
     *
     * @param Node $node    Node
     * @param Node $parent  Parent node
     *
     * @return bool
     *
     * @abstract
     */
    
abstract public function check(Node $nodeNode $parent null);
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Selector\Node\Part;

use 
Peast\Syntax\Node\Node;
use 
Peast\Syntax\Utils;

/**
 * Selector part index pseudo class
 * 
 * @author Marco Marchiò <[email protected]>
 */
class PseudoIndex extends Pseudo
{
    
/**
     * Priority
     *
     * @var int
     */
    
protected $priority 2;

    
/**
     * Step
     *
     * @var int
     */
    
protected $step 0;

    
/**
     * Offset
     *
     * @var int
     */
    
protected $offset 0;

    
/**
     * Sets the step
     *
     * @param int $step Step
     *
     * @return $this
     */
    
public function setStep($step)
    {
        
$this->step $step;
        return 
$this;
    }

    
/**
     * Sets the offset
     *
     * @param int $offset Offset
     *
     * @return $this
     */
    
public function setOffset($offset)
    {
        
$this->offset $offset;
        return 
$this;
    }

    
/**
     * Returns true if the selector part matches the given node,
     * false otherwise
     *
     * @param Node $node    Node
     * @param Node $parent  Parent node
     *
     * @return bool
     */
    
public function check(Node $nodeNode $parent null)
    {
        
$props Utils::getExpandedNodeProperties($parent);
        
$count count($props);
        
$reverse $this->name === "nth-last-child";
        if (
$reverse) {
            
$start $count - ($this->offset 1);
            
$step $this->step * -1;
            if (
$step 0) {
                
$reverse false;
            }
        } else {
            
$start $this->offset 1;
            
$step $this->step;
            if (
$step 0) {
                
$reverse true;
            }
        }
        
//Step 0 will cause an infinite loop, so it must be set to the
        //number of props so that it will execute only one iteration
        
if (!$step) {
            
$step $reverse ? -$count $count;
        }
        for (
$i $start; ($reverse && $i >= 0)  || (!$reverse && $i $count); $i += $step) {
            if (isset(
$props[$i]) && $props[$i] === $node) {
                return 
true;
            }
        }
        return 
false;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Selector\Node\Part;

use 
Peast\Syntax\Node\Node;
use 
Peast\Syntax\Node\Pattern;
use 
Peast\Syntax\Node\Statement;
use 
Peast\Syntax\Node\Expression;
use 
Peast\Syntax\Node\Declaration;
use 
Peast\Syntax\Utils;

/**
 * Selector part simple pseudo class
 * 
 * @author Marco Marchiò <[email protected]>
 */
class PseudoSimple extends Pseudo
{
    
/**
     * Priority
     *
     * @var int
     */
    
protected $priority 3;

    
/**
     * Returns true if the selector part matches the given node,
     * false otherwise
     *
     * @param Node $node    Node
     * @param Node $parent  Parent node
     *
     * @return bool
     */
    
public function check(Node $nodeNode $parent null)
    {
        switch (
$this->name) {
            case 
"pattern":
                return 
$node instanceof Pattern;
            case 
"statement":
                return 
$node instanceof Statement;
            case 
"expression":
                return 
$node instanceof Expression;
            case 
"declaration":
                return 
$node instanceof Declaration;
            case 
"last-child":
            case 
"first-child":
                
$first $this->name === "first-child";
                
$props Utils::getExpandedNodeProperties($parent);
                return 
count($props) > && (
                    
$first $props[0] === $node array_pop($props) === $node
                
);
        }
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Selector\Node\Part;

use 
Peast\Selector\Matches;
use 
Peast\Selector\Node\Selector;
use 
Peast\Syntax\Node\Node;

/**
 * Selector part selector pseudo class
 * 
 * @author Marco Marchiò <[email protected]>
 */
class PseudoSelector extends Pseudo
{
    
/**
     * Priority
     *
     * @var int
     */
    
protected $priority 1;

    
/**
     * Selector
     *
     * @var Selector
     */
    
protected $selector;

    
/**
     * Sets the selector
     *
     * @param Selector $selector Selector
     *
     * @return $this
     */
    
public function setSelector(Selector $selector)
    {
        
$this->selector $selector;
        return 
$this;
    }

    
/**
     * Returns true if the selector part matches the given node,
     * false otherwise
     *
     * @param Node $node    Node
     * @param Node $parent  Parent node
     *
     * @return bool
     */
    
public function check(Node $nodeNode $parent null)
    {
        
$match = new Matches();
        
$match->addMatch($node$parent);
        
$res $this->selector->exec($match)->count();
        return 
$this->name === "not" $res === $res !== 0;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Selector\Node\Part;

/**
 * Selector pseudo part base class
 * 
 * @author Marco Marchiò <[email protected]>
 *
 * @abstract
 */
abstract class Pseudo extends Part
{
    
/**
     * Selector name
     *
     * @var string
     */
    
protected $name;

    
/**
     * Sets the name
     *
     * @param string $name Name
     *
     * @return $this
     */
    
public function setName($name)
    {
        
$this->name $name;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Selector\Node\Part;

use 
Peast\Syntax\Node\Node;
use 
Peast\Syntax\Utils;

/**
 * Selector part attribute class
 * 
 * @author Marco Marchiò <[email protected]>
 */
class Attribute extends Part
{
    
/**
     * Priority
     *
     * @var int
     */
    
protected $priority 4;

    
/**
     * Attribute names
     *
     * @var array
     */
    
protected $names = array();

    
/**
     * Attribute match operator
     *
     * @var array
     */
    
protected $operator null;

    
/**
     * Attribute value
     *
     * @var mixed
     */
    
protected $value null;

    
/**
     * Case insensitive flag
     *
     * @var bool
     */
    
protected $caseInsensitive false;

    
/**
     * Regex flag
     *
     * @var bool
     */
    
protected $regex false;

    
/**
     * Adds a name
     *
     * @param string $name Name
     *
     * @return $this
     */
    
public function addName($name)
    {
        
$this->names[] = $name;
        return 
$this;
    }

    
/**
     * Sets the operator
     *
     * @param string $operator Operator
     *
     * @return $this
     */
    
public function setOperator($operator)
    {
        
$this->operator $operator;
        return 
$this;
    }

    
/**
     * Sets the value
     *
     * @param mixed $value Value
     *
     * @return $this
     */
    
public function setValue($value)
    {
        
$this->value $value;
        return 
$this;
    }

    
/**
     * Sets the case insensitive flag
     *
     * @param bool $caseInsensitive Case insensitive flag
     *
     * @return $this
     */
    
public function setCaseInsensitive($caseInsensitive)
    {
        
$this->caseInsensitive $caseInsensitive;
        return 
$this;
    }

    
/**
     * Sets the regex flag
     *
     * @param bool $regex Regex flag
     *
     * @return $this
     */
    
public function setRegex($regex)
    {
        
$this->regex $regex;
        return 
$this;
    }

    
/**
     * Returns true if the selector part matches the given node,
     * false otherwise
     *
     * @param Node $node    Node
     * @param Node $parent  Parent node
     *
     * @return bool
     */
    
public function check(Node $nodeNode $parent null)
    {
        
$attr $node;
        foreach (
$this->names as $name) {
            
$attrFound false;
            if (
$attr instanceof Node) {
                
$props Utils::getNodeProperties($attr);
                foreach (
$props as $prop) {
                    if (
$prop["name"] === $name) {
                        
$attrFound true;
                        
$attr $attr->{$prop["getter"]}();
                        break;
                    }
                }
            }
            if (!
$attrFound) {
                return 
false;
            }
        }
        
$bothStrings is_string($attr) && is_string($this->value);
        switch (
$this->operator) {
            case 
"=":
                if (
$bothStrings) {
                    if (
$this->regex) {
                        return 
preg_match($this->value$attr);
                    }
                    return 
$this->compareStr(
                        
$this->value$attr$this->caseInsensitivetruetrue
                    
);
                }
                if (
is_int($attr) && is_float($this->value)) {
                    return (float) 
$attr === $this->value;
                }
                return 
$attr === $this->value;
            case 
"<":
                if (
is_float($this->value) && !is_float($attr) && !is_int($attr) && !is_string($attr)) {
                    return 
false;
                }
                return 
$attr $this->value;
            case 
">":
                if (
is_float($this->value) && !is_float($attr) && !is_int($attr) && !is_string($attr)) {
                    return 
false;
                }
                return 
$attr $this->value;
            case 
"<=":
                if (
is_float($this->value) && !is_float($attr) && !is_int($attr) && !is_string($attr)) {
                    return 
false;
                }
                return 
$attr <= $this->value;
            case 
">=":
                if (
is_float($this->value) && !is_float($attr) && !is_int($attr) && !is_string($attr)) {
                    return 
false;
                }
                return 
$attr >= $this->value;
            case 
"^=":
            case 
"$=":
            case 
"*=":
                return 
$this->compareStr(
                    
$this->value$attr$this->caseInsensitive,
                    
$this->operator === "^=",
                    
$this->operator === "$="
                
);
            default:
                return 
true;
        }
    }

    
/**
     * Compares two strings
     *
     * @param string $v1                Search value
     * @param string $v2                Compare value
     * @param bool   $caseInsensitive   True if the search must be case insensitive
     * @param bool   $matchStart        True if the search must be executed from the
     *                                  beginning of the string
     * @param bool   $matchEnd          True if the search must be executed from the
     *                                  end of the string
     *
     * @return bool
     */
    
protected function compareStr($v1$v2$caseInsensitive$matchStart$matchEnd)
    {
        
$regex "#" .
                 (
$matchStart "^" "") .
                 
preg_quote($v1) .
                 (
$matchEnd "$" "") .
                 
"#u" .
                 (
$caseInsensitive "i" "");
        return (bool) 
preg_match($regex$v2);
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Selector\Node\Part;

use 
Peast\Syntax\Node\Node;

/**
 * Selector part type class
 * 
 * @author Marco Marchiò <[email protected]>
 */
class Type extends Part
{
    
/**
     * Selector type
     *
     * @var string
     */
    
protected $type;

    
/**
     * Sets the selector type
     *
     * @param string $type Type
     *
     * @return $this
     */
    
public function setType($type)
    {
        
$this->type $type;
        return 
$this;
    }

    
/**
     * Returns true if the selector part matches the given node,
     * false otherwise
     *
     * @param Node $node    Node
     * @param Node $parent  Parent node
     *
     * @return bool
     */
    
public function check(Node $nodeNode $parent null)
    {
        return 
$node->getType() === $this->type;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Selector\Node;

use 
Peast\Selector\Matches;

/**
 * Selector group class
 * 
 * @author Marco Marchiò <[email protected]>
 */
class Group
{
    
/**
     * Selector combinators
     *
     * @var Combinator[]
     */
    
protected $combinators = array();

    
/**
     * Adds a combinator
     *
     * @param Combinator $combinators Combinator
     *
     * @return $this
     */
    
public function addCombinator(Combinator $combinators)
    {
        
$this->combinators[] = $combinators;
        return 
$this;
    }

    
/**
     * Executes the current group on the given matches
     *
     * @param Matches $matches Matches
     */
    
public function exec(Matches $matches)
    {
        foreach (
$this->combinators as $combinator) {
            
$combinator->exec($matches);
        }
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Selector\Node;

use 
Peast\Selector\Matches;

/**
 * Selector class
 * 
 * @author Marco Marchiò <[email protected]>
 */
class Selector
{
    
/**
     * Selector groups
     *
     * @var Group[]
     */
    
protected $groups = array();

    
/**
     * Adds a new group
     *
     * @param Group $group Group
     *
     * @return $this
     */
    
public function addGroup(Group $group)
    {
        
$this->groups[] = $group;
        return 
$this;
    }

    
/**
     * Executes the current selector on the given matches
     *
     * @param Matches $matches Matches
     *
     * @return Matches
     */
    
public function exec(Matches $matches)
    {
        
$retMatches = array();
        foreach (
$this->groups as $group) {
            
$clonedMatches $matches->createClone();
            
$group->exec($clonedMatches);
            
$retMatches[] = $clonedMatches;
        }
        if (
count($retMatches) > 1) {
            
$retMatches[0]->merge(array_slice($retMatches1));
        }
        return 
$retMatches[0];
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Selector\Node;

use 
Peast\Selector\Matches;
use 
Peast\Syntax\Utils;
use 
Peast\Traverser;

/**
 * Selector combinator class
 * 
 * @author Marco Marchiò <[email protected]>
 */
class Combinator
{
    
/**
     * Operator
     *
     * @var string|null
     */
    
protected $operator;

    
/**
     * Selector parts
     *
     * @var Part\Part[]
     */
    
protected $parts = array();

    
/**
     * Sets the operator
     *
     * @param string $operator Operator
     * @return $this
     */
    
public function setOperator($operator)
    {
        
$this->operator $operator;
        return 
$this;
    }

    
/**
     * Adds a new selector part
     *
     * @param Part\Part $part Part
     * @return $this
     */
    
public function addPart(Part\Part $part)
    {
        
$this->parts[] = $part;
        return 
$this;
    }

    
/**
     * Executes the current group on the given matches
     *
     * @param Matches $matches Matches
     */
    
public function exec(Matches $matches)
    {
        
$parts $this->parts;
        
//Sort the parts by priority to execute faster checks first
        
usort($parts, function ($p1$p2) {
            
$pr1 $p1->getPriority();
            
$pr2 $p2->getPriority();
            if (
$pr1 === $pr2) {
                return 
0;
            } elseif (
$pr1 $pr2) {
                return -
1;
            } else {
                return 
1;
            }
        });
        
$filter = function ($node$parent) use ($parts) {
            foreach (
$parts as $part) {
                if (!
$part->check($node$parent)) {
                    return 
false;
                }
            }
            return 
true;
        };
        switch (
$this->operator) {
            case 
" ":
            case 
">":
                
$children $this->operator === ">";
                
$matches->map(function ($curNode) use ($filter$children) {
                    
$ret = array();
                    
$curNode->traverse(
                        function (
$node$parent) use ($filter$children, &$ret) {
                            if (
$filter($node$parent)) {
                                
$ret[] = array($node$parent);
                            }
                            if (
$children) {
                                return 
Traverser::DONT_TRAVERSE_CHILD_NODES;
                            }
                        },
                        array(
                            
"skipStartingNode" => true,
                            
"passParentNode" => true
                        
)
                    );
                    return 
$ret;
                });
            break;
            case 
"~":
            case 
"+":
                
$adjacent $this->operator === "+";
                
$matches->map(function ($node$parent) use ($filter$adjacent) {
                    
$ret = array();
                    
$evaluate false;
                    
$props $parent Utils::getExpandedNodeProperties($parent) : array();
                    foreach (
$props as $propNode) {
                        if (
$evaluate) {
                            if (
$propNode && $filter($propNode$parent)) {
                                
$ret[] = array($propNode$parent);
                            }
                            if (
$adjacent) {
                                break;
                            }
                        } elseif (
$propNode === $node) {
                            
$evaluate true;
                        }
                    }
                    return 
$ret;
                });
            break;
            default:
                
$matches->filter($filter);
            break;
        }
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Selector;

/**
 * Selector parser class
 * 
 * @author Marco Marchiò <[email protected]>
 */
class Parser
{
    
/**
     * Selector string
     *
     * @var string
     */
    
protected $selector;

    
/**
     * Current parser index
     *
     * @var int
     */
    
protected $index 0;

    
/**
     * Selector length
     *
     * @var int
     */
    
protected $length;

    
/**
     * Whitespaces
     *
     * @var array
     */
    
protected $whitespaces = array(" ""\t""\n""\r""\f");

    
/**
     * Combinators
     *
     * @var array
     */
    
protected $combinators = array(">""+""~");

    
/**
     * Attribute selector operator characters
     *
     * @var array
     */
    
protected $attrOperatorChars = array("=""<"">""^""$""*");

    
/**
     * Attribute selector operators
     *
     * @var array
     */
    
protected $attrOperators = array("=""<"">""<="">=""^=""$=""*=");

    
/**
     * Valid pseudo selectors. The value indicates the argument type:
     * - 0: no arguments
     * - 1: index formula (An+B syntax)
     * - 2: selector
     * @var array
     */
    
protected $validPseudo = array(
        
"pattern" => 0"statement" => 0"expression" => 0"declaration" => 0,
        
"first-child" => 0"last-child" => 0,
        
"nth-child" => 1"nth-last-child" => 1,
        
"has" => 2"is" => 2"not" => 2
    
);

    
/**
     * Class constructor
     *
     * @param string $selector  Selector string
     * @param array  $options   Options array. See Query class
 *                              documentation for available options
     */
    
public function __construct($selector$options = array())
    {
        
$encoding = isset($options["encoding"]) ? $options["encoding"] : null;
        if (
$encoding && !preg_match("/UTF-?8/i"$encoding)) {
            
$selector mb_convert_encoding($selector"UTF-8"$encoding);
        }
        
$this->selector $selector;
        
$this->length strlen($selector);
    }

    
/**
     * Starts the parsing and returns the parsed selector
     *
     * @param bool $filter True if the selector must be used for a filter
     *
     * @return Node\Selector
     *
     * @throws Exception
     */
    
public function parse($filter false)
    {
        
$selector $this->parseSelector($filter);
        
//Throw an exception if the end has not been reached
        
if (($char $this->getChar()) !== null) {
            throw new 
Exception("Invalid syntax '$char'");
        }
        return 
$selector;
    }

    
/**
     * Parses a selector
     *
     * @param bool $filter True if the selector must be used for a filter
     *
     * @return Node\Selector
     *
     * @throws Exception
     */
    
public function parseSelector($filter false)
    {
        
$selector = new Node\Selector;
        do {
            
$first true;
            
$group = new Node\Group;
            while (
true) {
                
$combinator $this->consumeCombinator();
                if (!
$first && !$combinator) {
                    break;
                }
                
$parts $this->parseSelectorParts();
                if (!
count($parts)) {
                    throw new 
Exception("Missing selector after combinator");
                }
                
$first false;
                
$selCombinator = new Node\Combinator;
                
$selCombinator->setOperator(
                    
$combinator ?: ($filter null " ")
                );
                foreach (
$parts as $part) {
                    
$selCombinator->addPart($part);
                }
                
$group->addCombinator($selCombinator);
            }
            
$selector->addGroup($group);
            
$this->consumeWhitespaces();
        } while (
$this->consume(","));
        return 
$selector;
    }

    
/**
     * Parses a set of selector pats
     *
     * @return array
     *
     * @throws Exception
     */
    
protected function parseSelectorParts()
    {
        
$parts = array();
        while (
true) {
            if (
                (
$part $this->parseSelectorPartType()) ||
                (
$part $this->parseSelectorPartAttribute()) ||
                (
$part $this->parseSelectorPartPseudo())
            ) {
                
$parts[] = $part;
            } else {
                break;
            }
        }
        return 
$parts;
    }

    
/**
     * Parses a type selector part
     *
     * @return Node\Part\Type|null
     */
    
protected function parseSelectorPartType()
    {
        
$type $this->consumeWord();
        if (
$type) {
            
$part = new Node\Part\Type;
            
$part->setType($type);
            return 
$part;
        }
        return 
null;
    }

    
/**
     * Parses an attribute selector part
     *
     * @return Node\Part\Attribute|null
     *
     * @throws Exception
     */
    
protected function parseSelectorPartAttribute()
    {
        if (!
$this->consume("[")) {
            return 
null;
        }
        
$this->consumeWhitespaces();
        
$part = new Node\Part\Attribute;
        if (!(
$name $this->consumeWord())) {
            throw new 
Exception("Missing attribute name");
        }
        
$part->addName($name);
        while (
$this->consume(".")) {
            if (!(
$name $this->consumeWord())) {
                throw new 
Exception("Missing attribute name after dot");
            }
            
$part->addName($name);
        }
        
$this->consumeWhitespaces();
        
$operator $this->consumeAny($this->attrOperatorChars);
        if (
$operator) {
            if (!
in_array($operator$this->attrOperators)) {
                throw new 
Exception("Invalid attribute operator '$operator'");
            }
            
$part->setOperator($operator);
            
$this->consumeWhitespaces();
            if (!(
$value $this->parseLiteral())) {
                throw new 
Exception("Missing attribute value");
            }
            
$part->setValue($value[0]);
            if (
$value[1]) {
                if (
$operator != "=") {
                    throw new 
Exception(
                        
"Only '=' operator is valid for attribute regex match"
                    
);
                }
                
$part->setRegex(true);
            }
            
$this->consumeWhitespaces();
            if (
$this->consume("i")) {
                if (!
is_string($value[0]) || $value[1]) {
                    throw new 
Exception(
                        
"Case insensitive flag can be used only for string values"
                    
);
                }
                
$part->setCaseInsensitive(true);
                
$this->consumeWhitespaces();
            }
        }
        if (!
$this->consume("]")) {
            throw new 
Exception("Unterminated attribute selector");
        }
        return 
$part;
    }

    
/**
     * Parses a pseudo selector part
     *
     * @return Node\Part\Pseudo|null
     *
     * @throws Exception
     */
    
protected function parseSelectorPartPseudo()
    {
        if (!
$this->consume(":")) {
            return 
null;
        }
        
$name $this->consumeWord("-");
        if (!isset(
$this->validPseudo[$name])) {
            throw new 
Exception("Unsupported pseudo selector '$name'");
        }
        
$argsType $this->validPseudo[$name];
        
$error false;
        if (
$argsType === 1) {
            
$part = new Node\Part\PseudoIndex;
            if (!
$this->consume("(")) {
                
$error true;
            }
            if (!
$error) {
                
$this->consumeWhitespaces();
                if (
$indices $this->consumeRegex("-?\d*n(?:\+\d+)?|\d+")) {
                    
$indices explode("n"$indices);
                    if (
count($indices) === 1) {
                        
$part->setOffset((int) $indices[0]);
                    } else {
                        switch (
$indices[0]) {
                            case 
"":
                                
$part->setStep(1);
                            break;
                            case 
"-":
                                
$part->setStep(-1);
                            break;
                            default:
                                
$part->setStep((int) $indices[0]);
                            break;
                        }
                        if (
$indices[1] !== "") {
                            
$part->setOffset((int) $indices[1]);
                        }
                    }
                } elseif (
                    (
$word $this->consumeWord()) &&
                    (
$word === "even" || $word === "odd")
                ) {
                    
$part->setStep(2);
                    if (
$word === "odd") {
                        
$part->setOffset(1);
                    }
                } else {
                    
$error true;
                }
                
$this->consumeWhitespaces();
                if (!
$error && !$this->consume(")")) {
                    
$error true;
                }
            }
        } elseif (
$argsType === 2) {
            
$part = new Node\Part\PseudoSelector;
            if (
                
$this->consume("(") &&
                (
$selector $this->parseSelector($name !== "has")) &&
                
$this->consume(")")
            ) {
                
$part->setSelector($selector);
            } else {
                
$error true;
            }
        } else {
            
$part = new Node\Part\PseudoSimple;
        }
        if (
$error) {
            throw new 
Exception(
                
"Invalid argument for pseudo selector '$name'"
            
);
        }
        
$part->setName($name);
        return 
$part;
    }

    
/**
     * Parses a literal value
     *
     * @return array|null
     *
     * @throws Exception
     */
    
protected function parseLiteral()
    {
        if (
            (
$literal $this->parseLiteralBoolNull()) !== ||
            (
$literal $this->parseLiteralString()) !== null ||
            (
$literal $this->parseLiteralNumber()) !== null
        
) {
            return array(
$literalfalse);
        } elseif (
$literal $this->parseLiteralRegex()) {
            return array(
$literaltrue);
        }
        return 
null;
    }

    
/**
     * Parses a literal boolean or null value
     *
     * @return int|bool|null
     *
     * @throws Exception
     */
    
protected function parseLiteralBoolNull()
    {
        
$word $this->consumeWord();
        if (!
$word) {
            return 
0;
        } elseif (
$word === "true") {
            return 
true;
        } elseif (
$word === "false") {
            return 
false;
        } elseif (
$word === "null") {
            return 
null;
        }
        throw new 
Exception("Invalid attribute value '$word'");
    }

    
/**
     * Parses a literal string
     *
     * @return string|null
     *
     * @throws Exception
     */
    
protected function parseLiteralString()
    {
        if (!(
$quote $this->consumeAny(array("'"'"'), true))) {
            return 
null;
        }
        if ((
$str $this->consumeUntil($quote)) === null) {
            throw new 
Exception("Unterminated string in attribute value");
        }
        return 
$str;
    }

    
/**
     * Parses a literal number
     *
     * @return int|float|null
     */
    
protected function parseLiteralNumber()
    {
        if (
            
$this->getChar() === "0" &&
            (
$val $this->consumeRegex("0[xX][a-fA-F]+|0[bB][01]+|0[oO][0-7]+"))
        ) {
            
$form strtolower($val[1]);
            
$val substr($val2);
            if (
$form === "x") {
                return 
hexdec($val);
            } elseif (
$form === "o") {
                return 
octdec($val);
            }
            return 
bindec($val);
        }
        
$reg "-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?|-?\.\d+(?:[eE][+-]?\d+)?";
        if (!(
$val $this->consumeRegex($reg))) {
            return 
null;
        }
        return (float) 
$val;
    }

    
/**
     * Parses a literal regex
     *
     * @return string|null
     *
     * @throws Exception
     */
    
protected function parseLiteralRegex()
    {
        if (!(
$sep $this->consume("/"))) {
            return 
null;
        }
        if ((
$reg $this->consumeUntil($sepfalsetrue)) === null) {
            throw new 
Exception("Unterminated regex in attribute value");
        }
        
$modifiers $this->consumeWord();
        return 
$sep $reg . ($modifiers ?: "");
    }

    
/**
     * Consumes the given regex
     *
     * @param string $regex Regex to consume
     *
     * @return mixed|null
     */
    
protected function consumeRegex($regex)
    {
        if (
$this->getChar() === null) {
            return 
null;
        }
        if (!
preg_match("#^($regex)#"substr($this->selector$this->index), $matches)) {
            return 
null;
        }
        
$this->index += strlen($matches[1]);
        return 
$matches[1];
    }

    
/**
     * Consumes all the characters until the given one is reached
     *
     * @param string $stop          Stop character
     * @param bool   $removeEscapes If false escape characters won't be removed
     * @param false  $includeStop   If true stop character will be returned
     *
     * @return string|null
     */
    
protected function consumeUntil($stop$removeEscapes true$includeStop false)
    {
        
$buffer "";
        
$escaped false;
        while ((
$char $this->getChar()) !== null) {
            
$this->index += 1;
            if (!
$escaped) {
                if (
$char === "\\") {
                    
$escaped true;
                    if (!
$removeEscapes) {
                        
$buffer .= $char;
                    }
                    continue;
                } elseif (
$char === $stop) {
                    if (
$includeStop) {
                        
$buffer .= $char;
                    }
                    return 
$buffer;
                }
            }
            
$buffer .= $char;
            
$escaped false;
        }
        return 
null;
    }

    
/**
     * Consumes a word composed by characters a-z
     *
     * @param null|string $extraChar Extra character to match
     *
     * @return string
     */
    
protected function consumeWord($extraChar null)
    {
        
$buffer "";
        while (
$char $this->getChar()) {
            if (
                (
$char >= "a" && $char <= "z") ||
                (
$char >= "A" && $char <= "Z") ||
                (
$extraChar !== null && $char === $extraChar)
            ) {
                
$buffer .= $char;
                
$this->index += 1;
            } else {
                break;
            }
        }
        return 
$buffer;
    }

    
/**
     * Consumes a combinator
     *
     * @return string|null
     */
    
protected function consumeCombinator()
    {
        
//Initial ws can be trimmed if followed by another combinator
        
$ws $this->consumeWhitespaces();
        if (
$combinator $this->consumeAny($this->combinatorstrue)) {
            
$this->consumeWhitespaces();
        } elseif (
$ws) {
            
//If there's no other combinators use the space
            
$combinator " ";
        } else {
            
$combinator null;
        }
        return 
$combinator;
    }

    
/**
     * Consumes as much whitespaces as possible
     *
     * @return string
     */
    
protected function consumeWhitespaces()
    {
        return 
$this->consumeAny($this->whitespaces);
    }

    
/**
     * Consumes the given characters
     *
     * @param array $chars       Characters to consume
     * @param false $stopAtFirst If true only the first matching character
     *                           is consumed
     *
     * @return string
     */
    
protected function consumeAny($chars$stopAtFirst false)
    {
        
$buffer "";
        while ((
$char $this->getChar()) !== null) {
            if (
in_array($char$chars)) {
                
$buffer .= $char;
                
$this->index++;
                if (
$stopAtFirst) {
                    break;
                }
            } else {
                break;
            }
        }
        return 
$buffer;
    }

    
/**
     * Consumes the current character if it is equal to the
     * given one
     *
     * @param string $char Character to compare
     *
     * @return string|null
     */
    
protected function consume($char)
    {
        if (
$this->getChar() === $char) {
            
$this->index++;
            return 
$char;
        }
        return 
null;
    }

    
/**
     * Returns the current character or null if the end
     * have been reached
     *
     * @return string|null
     */
    
protected function getChar()
    {
        if (
$this->index $this->length) {
            return 
$this->selector[$this->index];
        }
        return 
null;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Selector;

/**
 * Selector exception class. Syntax errors in selectors are thrown
 * using this exception class.
 * 
 * @author Marco Marchiò <[email protected]>
 * 
 * @codeCoverageIgnore
 */
class Exception extends \Exception
{
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Selector;

use 
Peast\Syntax\Node\Node;

/**
 * Selector matches class
 * 
 * @author Marco Marchiò <[email protected]>
 */
class Matches
{
    
/**
     * Matches array
     *
     * @var array
     */
    
protected $matches;

    
/**
     * Class constructor
     *
     * @param array $matches Matches
     */
    
public function __construct($matches = array())
    {
        
$this->matches $matches;
    }

    
/**
     * Adds a new match
     *
     * @param Node $node
     * @param Node|null $parent
     */
    
public function addMatch(Node $nodeNode $parent null)
    {
        
$this->matches[] = array($node$parent);
    }

    
/**
     * Returns the matches
     *
     * @returns array
     */
    
public function getMatches()
    {
        return 
$this->matches;
    }

    
/**
     * Returns the matched nodes
     *
     * @return array
     */
    
public function getNodes() {
        return 
array_map(function ($m) {
            return 
$m[0];
        }, 
$this->matches);
    }

    
/**
     * Filters the matches using the given function, if it returns
     * a false value the match will be removed. The function will
     * receive the node and its parent as arguments.
     *
     * @param callable $fn Filter function
     * @return $this
     */
    
public function filter(callable $fn)
    {
        
$newMatches = array();
        foreach (
$this->matches as $match) {
            if (
$fn($match[0], $match[1])) {
                
$newMatches[] = $match;
            }
        }
        
$this->matches $newMatches;
        return 
$this;
    }

    
/**
     * Replaces all the matches with the result of the given function.
     * The function will receive the node and its parent as arguments
     * and must return an array of matches
     *
     * @param callable $fn Map function
     *
     * @return $this
     */
    
public function map(callable $fn)
    {
        
$newMatches = array();
        foreach (
$this->matches as $match) {
            
$res $fn($match[0], $match[1]);
            if (
$res) {
                
$newMatches array_merge($newMatches$res);
            }
        }
        
$this->matches $newMatches;
        return 
$this->unique();
    }

    
/**
     * Merges the current object with the other given Matches objects
     *
     * @param Matches[] $matchesArr Array of Matches to merge
     *
     * @return $this
     */
    
public function merge($matchesArr)
    {
        foreach (
$matchesArr as $matches) {
            foreach (
$matches->getMatches() as $match) {
                
$this->addMatch($match[0], $match[1]);
            }
        }
        return 
$this->unique();
    }

    
/**
     * Remove all duplicated matches
     *
     * @return $this
     */
    
public function unique()
    {
        
$newMatches = array();
        
$newNodes = array();
        foreach (
$this->matches as $match) {
            if (!
in_array($match[0], $newNodestrue)) {
                
$newMatches[] = $match;
                
$newNodes[] = $match[0];
            }
        }
        
$this->matches $newMatches;
        return 
$this;
    }

    
/**
     * Returns a clone of the current object
     *
     * @return Matches
     */
    
public function createClone()
    {
        return new 
self($this->matches);
    }

    
/**
     * Returns the number of matches
     *
     * @return int
     */
    
public function count()
    {
        return 
count($this->matches);
    }

    
/**
     * Returns the match at the given index
     *
     * @param int $index Index
     *
     * @return array
     *
     * @throws \Exception
     */
    
public function get($index)
    {
        
$index = (int) $index;
        if (!isset(
$this->matches[$index])) {
            throw new 
\Exception("Invalid index $index");
        }
        return 
$this->matches[$index];
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast;

/**
 * Nodes traverser class
 * 
 * @author Marco Marchiò <[email protected]>
 */
class Traverser
{
    
/**
     * If a function return this value, the current node will be removed
     */
    
const REMOVE_NODE 1;
    
    
/**
     * If a function return this value, the current node's children won't be
     * traversed
     */
    
const DONT_TRAVERSE_CHILD_NODES 2;
    
    
/**
     * If a function return this value, the traverser will stop running
     */
    
const STOP_TRAVERSING 4;
    
    
/**
     * Array of functions to call on each node
     * 
     * @var array
     */
    
protected $functions = array();

    
/**
     * Pass parent node flag
     *
     * @var bool
     */
    
protected $passParentNode false;

    
/**
     * Skip starting node flag
     *
     * @var bool
     */
    
protected $skipStartingNode false;

    
/**
     * Class constructor. Available options are:
     * - skipStartingNode: if true the starting node will be skipped
     * - passParentNode: if true the parent node of each node will be
     *   passed as second argument when the functions are called. Note
     *   that the parent node is calculated during traversing, so for
     *   the starting node it will always be null.
     *
     * @param array $options Options array
     */
    
public function __construct($options = array())
    {
        if (isset(
$options["passParentNode"])) {
            
$this->passParentNode = (bool) $options["passParentNode"];
        }
        if (isset(
$options["skipStartingNode"])) {
            
$this->skipStartingNode = (bool) $options["skipStartingNode"];
        }
    }
    
    
/**
     * Adds a function that will be called for each node in the tree. The
     * function will receive the current node as argument. The action that will
     * be executed on the node by the traverser depends on the returned value
     * of the function:
     * - a node: it will replace the node with the returned one
     * - a numeric value that is a combination of the constants defined in this
     *   class: it will execute the function related to each constant
     * - an array where the first element is a node and the second element is a
     *   numeric value that is a combination of the constants defined in this
     *   class: it will replace the node with the returned one and  it will
     *   execute the function related to each constant (REMOVE_NODE will be
     *   ignored since it does not make any sense in this case)
     * - other: nothing
     * 
     * @param callable $fn Function to add
     * 
     * @return $this
     */
    
public function addFunction(callable $fn)
    {
        
$this->functions[] = $fn;
        return 
$this;
    }
    
    
/**
     * Starts the traversing
     * 
     * @param Syntax\Node\Node  $node   Starting node
     * 
     * @return Syntax\Node\Node
     */
    
public function traverse(Syntax\Node\Node $node)
    {
        if (
$this->skipStartingNode) {
            
$this->traverseChildren($node);
        } else {
            
$this->execFunctions($node);
        }
        return 
$node;
    }
    
    
/**
     * Executes all functions on the given node and, if required, starts
     * traversing its children. The returned value is an array where the first
     * value is the node or null if it has been removed and the second value is
     * a boolean indicating if the traverser must continue the traversing or not
     * 
     * @param Syntax\Node\Node       $node     Node
     * @param Syntax\Node\Node|null  $parent   Parent node
     * 
     * @return array
     */
    
protected function execFunctions($node$parent null)
    {
        
$traverseChildren true;
        
$continueTraversing true;
        
        foreach (
$this->functions as $fn) {
            
$ret $this->passParentNode $fn($node$parent) : $fn($node);
            if (
$ret) {
                if (
is_array($ret) && $ret[0] instanceof Syntax\Node\Node) {
                    
$node $ret[0];
                    if (isset(
$ret[1]) && is_numeric($ret[1])) {
                        if (
$ret[1] & self::DONT_TRAVERSE_CHILD_NODES) {
                            
$traverseChildren false;
                        }
                        if (
$ret[1] & self::STOP_TRAVERSING) {
                            
$continueTraversing false;
                        }
                    }
                } elseif (
$ret instanceof Syntax\Node\Node) {
                    
$node $ret;
                } elseif (
is_numeric($ret)) {
                    if (
$ret self::DONT_TRAVERSE_CHILD_NODES) {
                        
$traverseChildren false;
                    }
                    if (
$ret self::STOP_TRAVERSING) {
                        
$continueTraversing false;
                    }
                    if (
$ret self::REMOVE_NODE) {
                        
$node null;
                        
$traverseChildren false;
                        break;
                    }
                }
            }
        }
        
        if (
$traverseChildren && $continueTraversing) {
            
$continueTraversing $this->traverseChildren($node);
        }
        
        return array(
$node$continueTraversing);
    }
    
    
/**
     * Traverses node children. It returns a boolean indicating if the
     * traversing must continue or not
     * 
     * @param Syntax\Node\Node  $node   Node
     * 
     * @return bool
     */
    
protected function traverseChildren(Syntax\Node\Node $node)
    {
        
$continue true;
        
        foreach (
Syntax\Utils::getNodeProperties($nodetrue) as $prop) {
            
$getter $prop["getter"];
            
$setter $prop["setter"];
            
$child $node->$getter();
            if (!
$child) {
                continue;
            } elseif (
is_array($child)) {
                
$newChildren = array();
                foreach (
$child as $c) {
                    if (!
$c || !$continue) {
                        
$newChildren[] = $c;
                    } else {
                        list(
$c$continue) = $this->execFunctions($c$node);
                        if (
$c) {
                            
$newChildren[] = $c;
                        }
                    }
                }
                
$node->$setter($newChildren);
            } else {
                list(
$child$continue) = $this->execFunctions($child$node);
                
$node->$setter($child);
            }
            
            if (!
$continue) {
                break;
            }
        }
        
        return 
$continue;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\ES2021;

/**
 * ES2021 features class
 * 
 * @author Marco Marchiò <[email protected]>
 * 
 * @codeCoverageIgnore
 */
class Features extends \Peast\Syntax\ES2020\Features
{
    
/**
     * Logical assignment operators
     *
     * @var bool
     */
    
public $logicalAssignmentOperators true;

    
/**
     * Numeric literal separator
     *
     * @var bool
     */
    
public $numericLiteralSeparator true;
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax;

/**
 * Base class for scanners.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class Scanner
{
    use 
JSX\Scanner;

    
/**
     * Scanner features
     * 
     * @var Features
     */
    
protected $features;

    
/**
     * Current column
     * 
     * @var int
     */
    
protected $column 0;
    
    
/**
     * Current line
     * 
     * @var int
     */
    
protected $line 1;
    
    
/**
     * Current index
     * 
     * @var int
     */
    
protected $index 0;
    
    
/**
     * Source length
     * 
     * @var int
     */
    
protected $length;
    
    
/**
     * Source characters
     * 
     * @var array
     */
    
protected $source;
    
    
/**
     * Consumed position
     * 
     * @var Position
     */
    
protected $position;
    
    
/**
     * Current token
     * 
     * @var Token 
     */
    
protected $currentToken;
    
    
/**
     * Next token
     * 
     * @var Token 
     */
    
protected $nextToken;
    
    
/**
     * Strict mode flag
     * 
     * @var bool 
     */
    
protected $strictMode false;
    
    
/**
     * True to register tokens in the tokens array
     * 
     * @var bool 
     */
    
protected $registerTokens false;
    
    
/**
     * Module mode
     * 
     * @var bool 
     */
    
protected $isModule false;
    
    
/**
     * Comments handling
     * 
     * @var bool 
     */
    
protected $comments false;
    
    
/**
     * Internal JSX scan flag
     * 
     * @var bool
     */
    
protected $jsx false;
    
    
/**
     * Registered tokens array
     * 
     * @var array 
     */
    
protected $tokens = array();
    
    
/**
     * Comments to tokens map
     * 
     * @var array 
     */
    
protected $commentsMap = array();
    
    
/**
     * Events emitter
     *
     * @var EventsEmitter
     */
    
protected $eventsEmitter;
    
    
/**
     * Regex to match identifiers starts
     * 
     * @var string 
     */
    
protected $idStartRegex "/[\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}\x{1885}\x{1886}\x{2118}\x{212E}\x{309B}\x{309C}]/u";
    
    
/**
     * Regex to match identifiers parts
     * 
     * @var string 
     */
    
protected $idPartRegex "/[\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}\x{1885}\x{1886}\x{2118}\x{212E}\x{309B}\x{309C}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\x{00B7}\x{0387}\x{1369}\x{136A}\x{136B}\x{136C}\x{136D}\x{136E}\x{136F}\x{1370}\x{1371}\x{19DA}\x{200C}\x{200D}]/u";
    
    
/**
     * Keywords array
     * 
     * @var array 
     */
    
protected $keywords = array(
        
"break""do""in""typeof""case""else""instanceof""var",
        
"catch""export""new""void""class""extends""return""while",
        
"const""finally""super""with""continue""for""switch",
        
"debugger""function""this""default""if""throw",
        
"delete""import""try""enum""await"
    
);
    
    
/**
     * Array of words that are keywords only in strict mode
     * 
     * @var array 
     */
    
protected $strictModeKeywords = array(
        
"implements""interface""package""private""protected""public",
        
"static""let""yield"
    
);

    
/**
     * Punctuators array
     * 
     * @var array 
     */
    
protected $punctuators = array(
        
"."";"",""<"">""<="">=""==""!=""===""!==""+",
        
"-""*""%""++""--""<<"">>"">>>""&""|""^""!""~",
        
"&&""||""?"":""=""+=""-=""*=""%=""<<="">>="">>>=",
        
"&=""|=""^=""=>""...""/""/=""**""**=""??""?.",
        
"&&=""||=""??="
    
);
    
    
/**
     * Punctuators LSM
     * 
     * @var LSM 
     */
    
protected $punctuatorsLSM;
    
    
/**
     * Strings stops LSM
     * 
     * @var LSM 
     */
    
protected $stringsStopsLSM;
    
    
/**
     * Brackets array
     * 
     * @var array 
     */
    
protected $brackets = array(
        
"(" => """[" => """{" => """)" => "(""]" => "[""}" => "{"
    
);
    
    
/**
     * Open brackets array
     * 
     * @var array 
     */
    
protected $openBrackets = array();
    
    
/**
     * Open templates array
     * 
     * @var array 
     */
    
protected $openTemplates = array();
    
    
/**
     * Whitespaces array
     * 
     * @var array 
     */
    
protected $whitespaces = array(
        
" ""\t""\n""\r""\f""\v"0x00A00xFEFF0x00A0,
        
0x16800x20000x20010x20020x20030x20040x20050x2006,
        
0x20070x20080x20090x200A0x202F0x205F0x30000x2028,
        
0x2029
    
);
    
    
/**
     * Line terminators characters array
     * 
     * @var array 
     * 
     * @static
     */
    
public static $lineTerminatorsChars = array("\n""\r"0x20280x2029);
    
    
/**
     * Line terminators sequences array
     * 
     * @var array
     * 
     * @static
     */
    
public static $lineTerminatorsSequences = array("\r\n");
    
    
/**
     * Regex to split texts using valid ES line terminators
     * 
     * @var array 
     */
    
protected $linesSplitter;
    
    
/**
     * Concatenation of line terminators characters and line terminators
     * sequences
     * 
     * @var array 
     */
    
protected $lineTerminators;
    
    
/**
     * Properties to copy when getting the scanner state
     * 
     * @var array
     */
    
protected $stateProps = array("position""index""column""line",
                                  
"currentToken""nextToken""strictMode",
                                  
"openBrackets""openTemplates",
                                  
"commentsMap");
    
    
/**
     * Decimal numbers
     * 
     * @var array
     */
    
protected $numbers = array("0""1""2""3""4""5""6""7""8",
                               
"9");
    
    
/**
     * Hexadecimal numbers
     * 
     * @var array
     */
    
protected $xnumbers = array("0""1""2""3""4""5""6""7""8",
                                
"9""a""b""c""d""e""f""A""B",
                                
"C""D""E""F");

    
/**
     * Octal numbers
     *
     * @var array
     */
    
protected $onumbers = array("0""1""2""3""4""5""6""7");

    
/**
     * Binary numbers
     *
     * @var array
     */
    
protected $bnumbers = array("0""1");

    
/**
     * Class constructor
     * 
     * @param string   $source   Source code
     * @param Features $features Scanner features
     * @param array    $options  Parsing options
     */
    
function __construct(
        
$sourceFeatures $features$options
    
) {
        
$this->features $features;

        
$encoding = isset($options["sourceEncoding"]) ?
                    
$options["sourceEncoding"] :
                    
null;
        
        
//Strip BOM characters from the source
        
$this->stripBOM($source$encoding);
        
        
//Convert to UTF8 if needed
        
if ($encoding && !preg_match("/UTF-?8/i"$encoding)) {
            
$source mb_convert_encoding($source"UTF-8"$encoding);
        }
        
        
//Instead of using mb_substr for each character, split the source
        //into an array of UTF8 characters for performance reasons
        
$this->source Utils::stringToUTF8Array(
            
$source,
            !isset(
$options["strictEncoding"]) || $options["strictEncoding"]
        );
        
$this->length count($this->source);
        
        
//Convert character codes to UTF8 characters in whitespaces and line
        //terminators
        
$this->lineTerminators array_merge(
            
self::$lineTerminatorsSequencesself::$lineTerminatorsChars
        
);
        foreach (array(
"whitespaces""lineTerminators") as $key) {
            foreach (
$this->$key as $i => $char) {
                if (
is_int($char)) {
                    
$this->{$key}[$i] = Utils::unicodeToUtf8($char);
                }
            }
        }

        
//Remove exponentiation operator if the feature
        //is not enabled
        
if (!$this->features->exponentiationOperator) {
            
Utils::removeArrayValue($this->punctuators"**");
            
Utils::removeArrayValue($this->punctuators"**=");
        }

        if (!
$this->features->optionalChaining) {
            
Utils::removeArrayValue($this->punctuators"?.");
        }

        
//Remove logical assignment operators if the feature
        //is not enabled
        
if (!$this->features->logicalAssignmentOperators) {
            
Utils::removeArrayValue($this->punctuators"&&=");
            
Utils::removeArrayValue($this->punctuators"||=");
            
Utils::removeArrayValue($this->punctuators"??=");
        }
        
        
//Create a LSM for punctuators array
        
$this->punctuatorsLSM = new LSM($this->punctuators);
        
        
//Create a LSM for strings stops
        
$this->stringsStopsLSM = new LSM($this->lineTerminatorstrue);
        
        
//Allow paragraph and line separators in strings
        
if ($this->features->paragraphLineSepInStrings) {
            
$this->stringsStopsLSM->remove(Utils::unicodeToUtf8(0x2028));
            
$this->stringsStopsLSM->remove(Utils::unicodeToUtf8(0x2029));
        }

        
//Remove await as keyword if async/await is enabled
        
if ($this->features->asyncAwait) {
            
Utils::removeArrayValue($this->keywords"await");
        }
        
        
$this->linesSplitter "/" .
                               
implode("|"$this->lineTerminators) .
                               
"/uS";
        
$this->position = new Position(000);
    }
    
    
/**
     * Strips BOM characters from the source and detects source encoding if not
     * given by the user
     * 
     * @param string $source   Source
     * @param string $encoding User specified encoding
     */
    
public function stripBOM(&$source, &$encoding)
    {
        
$boms = array(
            
"\xEF" => array(array("\xBB""\xBF"), "UTF-8"),
            
"\xFE" => array(array("\xFF"), "UTF-16BE"),
            
"\xFF" => array(array("\xFE"), "UTF-16LE"),
        );
        if (!isset(
$source[0]) || !isset($boms[$source[0]])) {
            return;
        }
        
$bom $boms[$source[0]];
        
$l count($bom[0]);
        for (
$i 0$i $l$i++) {
            if (!isset(
$source[$i 1]) || $source[$i 1] !== $bom[0][$i]) {
                return;
            }
        }
        
$source substr($source$l 1);
        if (!
$encoding) {
            
$encoding $bom[1];
        }
    }
    
    
/**
     * Enables or disables module scanning mode
     * 
     * @param bool $enable True to enable module scanning mode, false to disable it
     * 
     * @return $this
     */
    
public function enableModuleMode($enable true)
    {
        
$this->isModule $enable;
        return 
$this;
    }
    
    
/**
     * Enables or disables comments handling
     * 
     * @param bool $enable True to enable comments handling, false to disable it
     * 
     * @return $this
     */
    
public function enableComments($enable true)
    {
        
$this->comments $enable;
        return 
$this;
    }
    
    
/**
     * Enables or disables tokens registration in the token array
     * 
     * @param bool $enable True to enable token registration, false to disable it
     * 
     * @return $this
     */
    
public function enableTokenRegistration($enable true)
    {
        
$this->registerTokens $enable;
        return 
$this;
    }
    
    
/**
     * Return registered tokens
     * 
     * @return array
     */
    
public function getTokens()
    {
        return 
$this->tokens;
    }
    
    
/**
     * Returns the scanner's event emitter
     * 
     * @return EventsEmitter
     */
    
public function getEventsEmitter()
    {
        if (!
$this->eventsEmitter) {
            
//The event emitter is created here so that it won't exist if not
            //necessary
            
$this->eventsEmitter = new EventsEmitter;
        }
        return 
$this->eventsEmitter;
    }
    
    
/**
     * Enables or disables strict mode
     * 
     * @param bool $strictMode Strict mode state
     * 
     * @return $this
     */
    
public function setStrictMode($strictMode)
    {
        
$this->strictMode $strictMode;
        return 
$this;
    }
    
    
/**
     * Return strict mode state
     * 
     * @return bool
     */
    
public function getStrictMode()
    {
        return 
$this->strictMode;
    }
    
    
/**
     * Checks if the given token is a keyword in the current strict mode state
     * 
     * @param Token $token Token to checks
     * 
     * @return bool
     */
    
public function isStrictModeKeyword($token)
    {
        return 
$token->type === Token::TYPE_KEYWORD &&
               (
in_array($token->value$this->keywords) || (
                
$this->strictMode &&
                
in_array($token->value$this->strictModeKeywords)));
    }
    
    
/**
     * Returns the current scanner state
     * 
     * @return array
     */
    
public function getState()
    {
        
//Consume current and next tokens so that they wont' be parsed again
        //if the state is restored. If the current token is a slash the next
        //token isn't parsed, this prevents some edge cases where a regexp
        //that contains something that can be interpreted as a comment causes
        //the content to be parsed as a real comment too
        
$token $this->currentToken ?: $this->getToken();
        if (
$token && $token->value !== "/") {
            
$this->getNextToken();
        }
        
$state = array();
        foreach (
$this->stateProps as $prop) {
            
$state[$prop] = $this->$prop;
        }
        if (
$this->registerTokens) {
            
$state["tokensNum"] = count($this->tokens);
        }
        
//Emit the FreezeState event and pass the given state so that listeners
        //attached to this event can add data
        
$this->eventsEmitter && $this->eventsEmitter->fire(
            
"FreezeState", array(&$state)
        );
        return 
$state;
    }
    
    
/**
     * Sets the current scanner state
     * 
     * @param array $state State
     * 
     * @return $this
     */
    
public function setState($state)
    {
        if (
$this->registerTokens) {
            
//Check if tokens have been added
            
if (isset($this->tokens[$state["tokensNum"]])) {
                
//Remove all added tokens
                
for ($i count($this->tokens) - 1$i >= $state["tokensNum"]; $i--) {
                    
array_pop($this->tokens);
                }
            }
            unset(
$state["tokensNum"]);
        }
        
//Emit the ResetState event and pass the given state
        
$this->eventsEmitter && $this->eventsEmitter->fire(
            
"ResetState", array(&$state)
        );
        foreach (
$state as $key => $value) {
            
$this->$key $value;
        }
        return 
$this;
    }
    
    
/**
     * Returns current scanner state
     * 
     * @param bool $scanPosition By default this method returns the scanner
     *                           consumed position, if this parameter is true
     *                           the scanned position will be returned
     * 
     * @return Position
     */
    
public function getPosition($scanPosition false)
    {
        if (
$scanPosition) {
            return new 
Position($this->line$this->column$this->index);
        } else {
            return 
$this->position;
        }
    }
    
    
/**
     * Sets the current scan position at the given one
     * 
     * @param Position $position Position at which the scan position will be set
     * 
     * @return $this
     */
    
public function setScanPosition(Position $position null)
    {
        
$this->line $position->getLine();
        
$this->column $position->getColumn();
        
$this->index $position->getIndex();
        return 
$this;
    }
    
    
/**
     * Return the character at the given index in the source code or null if the
     * end is reached.
     * 
     * @param int $index Index, if not given it will use the current index
     * 
     * @return string|null
     */
    
public function charAt($index null)
    {
        if (
$index === null) {
            
$index $this->index;
        }
        return 
$index $this->length $this->source[$index] : null;
    }
    
    
/**
     * Throws a syntax error
     * 
     * @param string $message Error message
     * 
     * @return void
     * 
     * @throws Exception
     */
    
protected function error($message null)
    {
        if (!
$message) {
            
$message "Unexpected " $this->charAt();
        }
        throw new 
Exception($message$this->getPosition(true));
    }
    
    
/**
     * Consumes the current token
     * 
     * @return $this
     */
    
public function consumeToken()
    {
        
//Move the scanner position to the end of the current position
        
$this->position $this->currentToken->location->end;
        
        
//Before consume the token, consume comments associated with it
        
if ($this->comments) {
            
$this->consumeCommentsForCurrentToken();
        }
        
        
//Register the token if required
        
if ($this->registerTokens) {
            
$this->tokens[] = $this->currentToken;
        }
        
        
//Emit the TokenConsumed event for the consumed token
        
$this->eventsEmitter && $this->eventsEmitter->fire(
            
"TokenConsumed", array($this->currentToken)
        );
        
        
$this->currentToken $this->nextToken;
        
$this->nextToken null;
        return 
$this;
    }
    
    
/**
     * Checks if the given string is matched, if so it consumes the token
     * 
     * @param string $expected String to check
     * 
     * @return Token|null
     */
    
public function consume($expected)
    {
        
//Do not call getToken if there's already a pending token for
        //performance reasons
        
$token $this->currentToken ?: $this->getToken();
        if (
$token && $token->value === $expected) {
            
$this->consumeToken();
            return 
$token;
        }
        return 
null;
    }
    
    
/**
     * Checks if one of the given strings is matched, if so it consumes the
     * token
     * 
     * @param array $expected Strings to check
     * 
     * @return Token|null
     */
    
public function consumeOneOf($expected)
    {
        
//Do not call getToken if there's already a pending token for
        //performance reasons
        
$token $this->currentToken ?: $this->getToken();
        if (
$token && in_array($token->value$expected)) {
            
$this->consumeToken();
            return 
$token;
        }
        return 
null;
    }
    
    
/**
     * Checks that there are not line terminators following the current scan
     * position before next token
     * 
     * @param bool $nextToken By default it checks the current token position
     *                        relative to the current position, if this
     *                        parameter is true the check will be done relative
     *                        to the next token
     * 
     * @return bool
     */
    
public function noLineTerminators($nextToken false)
    {
        if (
$nextToken) {
            
$nextToken $this->getNextToken();
            
$refLine = !$nextToken null :
                        
$nextToken->location->end->getLine();
        } else {
            
$refLine $this->getPosition()->getLine();
        }
        
$token $this->currentToken ?: $this->getToken();
        return 
$token &&
               
$token->location->start->getLine() === $refLine;
    }
    
    
/**
     * Checks if one of the given strings follows the current scan position
     * 
     * @param string|array $expected  String or array of strings to check
     * @param bool         $nextToken This parameter must be true if the first
     *                                parameter is an array so that it will
     *                                check also next tokens
     * 
     * @return bool
     */
    
public function isBefore($expected$nextToken false)
    {
        
$token $this->currentToken ?: $this->getToken();
        if (!
$token) {
            return 
false;
        } elseif (
in_array($token->value$expected)) {
            return 
true;
        } elseif (!
$nextToken) {
            return 
false;
        }
        if (!
$this->getNextToken()) {
            return 
false;
        }
        foreach (
$expected as $val) {
            if (!
is_array($val) || $val[0] !== $token->value) {
                continue;
            }
            
//If the second value in the array is true check that the current
            //token is not followed by line terminators, otherwise compare its
            //value to the next token
            
if (($val[1] === true && $this->noLineTerminators(true)) ||
                (
$val[1] !== true && $val[1] === $this->nextToken->value)) {
                return 
true;
            }
        }
        return 
false;
    }
    
    
/**
     * Returns the next token
     * 
     * @return Token|null
     */
    
public function getNextToken()
    {
        if (!
$this->nextToken) {
            
$token $this->currentToken ?: $this->getToken();
            
$this->currentToken null;
            
$this->nextToken $this->getToken(true);
            
$this->currentToken $token;
        }
        return 
$this->nextToken;
    }
    
    
/**
     * Returns the current token
     *
     * @param bool $skipEOFChecks  True to skip end of file checks
     *                             even if the end is reached
     * 
     * @return Token|null
     */
    
public function getToken($skipEOFChecks false)
    {
        
//The current token is returned until consumed
        
if ($this->currentToken) {
            return 
$this->currentToken;
        }
        
        
$comments $this->skipWhitespacesAndComments();
        
        
//Emit the TokenCreated event for all the comments found
        
if ($comments) {
            foreach (
$comments as $comment) {
                
$this->eventsEmitter && $this->eventsEmitter->fire(
                    
"TokenCreated", array($comment)
                );
            }
        }

        
//When the end of the source is reached
        
if ($this->index >= $this->length) {
            
//Check if there are open brackets
            
if (!$skipEOFChecks) {
                foreach (
$this->openBrackets as $bracket => $num) {
                    if (
$num) {
                        
$this->error("Unclosed $bracket");
                    }
                }

                
//Check if there are open templates
                
if (count($this->openTemplates)) {
                    
$this->error("Unterminated template");
                }
            }
            
            
//Register comments and consume them
            
if ($this->comments && $comments) {
                
$this->commentsForCurrentToken($comments);
            }
            
            
//Emit the EndReached event when at the end of the source
            
$this->eventsEmitter && $this->eventsEmitter->fire(
                
"EndReached"
            
);
            
            return 
null;
        }
        
        
$startPosition $this->getPosition(true);
        
$origException null;
        try {
            
            
//Try to match a token
            
if (
                (
$this->jsx && ($token $this->scanJSXIdentifier())) ||
                (
$token $this->scanTemplate()) ||
                (
$token $this->scanNumber()) ||
                (
$this->jsx && ($token $this->scanJSXPunctuator())) ||
                (
$token $this->scanPunctuator()) ||
                (
$token $this->scanKeywordOrIdentifier()) ||
                (
$this->jsx && ($token $this->scanJSXString())) ||
                (
$token $this->scanString())
            ) {
                
//Set the token start and end positions
                
$token->location->start $startPosition;
                
$token->location->end $this->getPosition(true);
                
$this->currentToken $token;
                                            
                
//Register comments if required
                
if ($this->comments && $comments) {
                    
$this->commentsForCurrentToken($comments);
                }
                
                
//Emit the TokenCreated event for the token just created
                
$this->eventsEmitter && $this->eventsEmitter->fire(
                    
"TokenCreated", array($this->currentToken)
                );
                
                return 
$this->currentToken;
            }
            
        } catch (
Exception $e) {
            
$origException $e;
        }
        
        
//If last token was "/" do not throw an error if the token has not be
        //recognized since it can be the first character in a regexp and it will
        //be consumed when the current token will be reconsumed as a regexp
        
if ($this->isAfterSlash($startPosition)) {
            
$this->setScanPosition($startPosition);
            return 
null;
        }
        
        
//No valid token found. If there was a scan error, throw the same
        //exception again, otherwise throw a new error
        
if ($origException) {
            throw 
$origException;
        }
        
$this->error();
    }
    
    
/**
     * Executes the operations to handle the end of the source scanning
     * 
     * @return $this
     */
    
public function consumeEnd()
    {
        
//Consume final comments
        
if ($this->comments) {
            
$this->consumeCommentsForCurrentToken();
        }
        
        
//Emit the EndReached event when at the end of the source
        
$this->eventsEmitter && $this->eventsEmitter->fire(
            
"EndReached"
        
);
        
        return 
$this;
    }
    
    
/**
     * Gets or sets comments for the current token. If the parameter is an
     * array it associates the given comments array to the current node,
     * otherwise comments for the current token are returned
     * 
     * @param array $comments  Comments array
     * 
     * @return array
     */
    
protected function commentsForCurrentToken($comments null)
    {
        
$id $this->currentToken spl_object_hash($this->currentToken) : "";
        if (
$comments !== null) {
            
$this->commentsMap[$id] = $comments;
        } elseif (isset(
$this->commentsMap[$id])) {
            
$comments $this->commentsMap[$id];
            unset(
$this->commentsMap[$id]);
        }
        return 
$comments;
    }
    
    
/**
     * Consumes comment tokens associated with the current token
     * 
     * @return $this
     */
    
protected function consumeCommentsForCurrentToken()
    {
        
$comments $this->commentsForCurrentToken();
        if (
$comments && ($this->registerTokens || $this->eventsEmitter)) {
            foreach (
$comments as $comment) {
                
//Register the token if required
                
if ($this->registerTokens) {
                    
$this->tokens[] = $comment;
                }
                
//Emit the TokenConsumed event for the comment
                
$this->eventsEmitter && $this->eventsEmitter->fire(
                    
"TokenConsumed", array($comment)
                );
            }
        }
        return 
$this;
    }
    
    
/**
     * Checks if the consumed or the scanned position follow a slash.
     * 
     * @param Position $position  Additional position to check
     * 
     * @return bool
     */
    
protected function isAfterSlash($position null)
    {
        
$consumedIndex $this->getPosition()->getIndex();
        
$checkIndices = array($consumedIndex$consumedIndex 1);
        if (
$position) {
            
$checkIndices[] = $position->getIndex() - 1;
        }
        foreach (
$checkIndices as $i) {
            if (
$i >= && $this->charAt($i) === "/") {
                return 
true;
            }
        }
        return 
false;
    }
    
    
/**
     * Tries to reconsume the current token as a regexp if possible
     * 
     * @return Token|null
     */
    
public function reconsumeCurrentTokenAsRegexp()
    {
        
$token $this->currentToken ?: $this->getToken();
        
$value $token $token->value null;
        
        
//Check if the token starts with "/"
        
if (!$value || $value[0] !== "/") {
            return 
null;
        }
        
        
//Reset the scanner position to the token's start position
        
$startPosition $token->location->start;
        
$this->setScanPosition($startPosition);
        
        
$buffer "/";
        
$this->index++;
        
$this->column++;
        
$inClass false;
        while (
true) {
            
//In a characters class the delimiter "/" is allowed without escape,
            //so the characters class must be closed before closing the regexp
            
$stops $inClass ? array("]") : array("/""[");
            
$tempBuffer $this->consumeUntil($stops);
            if (
$tempBuffer === null) {
                if (
$inClass) {
                    
$this->error(
                        
"Unterminated character class in regexp"
                    
);
                } else {
                    
$this->error("Unterminated regexp");
                }
            }
            
$buffer .= $tempBuffer[0];
            if (
$tempBuffer[1] === "/") {
                break;
            } else {
                
$inClass $tempBuffer[1] === "[";
            }
        }
        
        
//Flags
        
while (($char $this->charAt()) !== null) {
            
$lower strtolower($char);
            if (
$lower >= "a" && $lower <= "z") {
                
$buffer .= $char;
                
$this->index++;
                
$this->column++;
            } else {
                break;
            }
        }
        
        
//If next token has already been parsed and it's a bracket exclude it
        //from the count of open brackets
        
if ($this->nextToken) {
            
$nextVal $this->nextToken->value;
            if (isset(
$this->brackets[$nextVal]) &&
                isset(
$this->openBrackets[$nextVal])
            ) {
                if (
$this->brackets[$nextVal]) {
                    
$this->openBrackets[$nextVal]++;
                } else {
                    
$this->openBrackets[$nextVal]--;
                }
            }
            
$this->nextToken null;
        }
        
        
//If comments handling is enabled, get the comments associated with the
        //current token
        
$comments $this->comments $this->commentsForCurrentToken() : null;
            
        
//Replace the current token with a regexp token
        
$token = new Token(Token::TYPE_REGULAR_EXPRESSION$buffer);
        
$token->location->start $startPosition;
        
$token->location->end $this->getPosition(true);
        
$this->currentToken $token;
                                    
        if (
$comments) {
            
//Attach the comments to the new current token
            
$this->commentsForCurrentToken($comments);
        }
        
        return 
$this->currentToken;
    }
    
    
/**
     * Skips whitespaces and comments from the current scan position. If
     * comments handling is enabled, the array of parsed comments
     * 
     * @return array
     */
    
protected function skipWhitespacesAndComments()
    {
        
$comments = [];
        
$content "";
        
$secStartIdx $this->index;
        while ((
$char $this->charAt()) !== null) {
            
//Whitespace
            
if (in_array($char$this->whitespaces)) {
                
                
$content .= $char;
                
$this->index++;
                
            } elseif (
$char === "/") {
                
                
//Comment
                
$nextChar $this->charAt($this->index 1);
                if (
$nextChar === "/" || $nextChar === "*") {
                    
                    
//If comments must be handled, empty the current content too
                    //and get the comment start position
                    
if ($this->comments) {
                        if (
$content !== "") {
                            
$this->adjustColumnAndLine($content);
                            
$content "";
                        }
                        
$start $this->getPosition(true);
                    }
                    
                    
$inline $nextChar === "/";
                    
$this->index += 2;
                    
$content .= $char $nextChar;
                    
                    while (
true) {
                        
$char $this->charAt();
                        
                        if (
$char === null) {
                            if (!
$inline) {
                                
//If the end of the source has been reached and
                                //a multiline comment is still open, it's an
                                //error
                                
$this->error("Unterminated comment");
                            }
                            
$isEnd true;
                        } else {
                            
$content .= $char;
                            
$this->index++;
                            
$isEnd $inline ?
                                     
//Inline comment
                                     
in_array($char$this->lineTerminators) :
                                     
//Multiline comment
                                     
$char === "*" && $this->charAt() === "/";
                        }
                        
                        if (
$isEnd) {
                            if (!
$inline) {
                                
$content .= "/";
                                
$this->index++;
                            }
                            if (
$this->comments) {
                                
//For inline comments the closing line
                                //terminator must be excluded from comment text
                                
if ($inline && $char !== null) {
                                    
$this->index--;
                                    
$content substr($content0, -strlen($char));
                                }
                                
$this->adjustColumnAndLine($content);
                                
$token = new Token(Token::TYPE_COMMENT$content);
                                
$token->location->start $start;
                                
$token->location->end $this->getPosition(true);
                                
$comments[] = $token;
                                
//For inline comments the new content contains
                                //the closing line terminator since the char has
                                //already been processed
                                
$content "";
                                if (
$inline && $char !== null) {
                                    
$content $char;
                                    
$this->index++;
                                }
                            }
                            break;
                        }
                    }
                    
                } else {
                    break;
                }
                
            } elseif (!
$this->isModule && $char === "<" &&
                
$this->charAt($this->index 1) === "!" &&
                
$this->charAt($this->index 2) === "-" &&
                
$this->charAt($this->index 3) === "-"
            
) {
                
                
//If comments must be handled, empty the current content too
                //and get the comment start position
                
if ($this->comments) {
                    if (
$content !== "") {
                        
$this->adjustColumnAndLine($content);
                        
$content "";
                    }
                    
$start $this->getPosition(true);
                }
                
                
//Open html comment
                
$this->index += 4;
                
$content .= "<!--";
                while (
true) {
                    
$char $this->charAt();
                    if (
$char === null) {
                        
$isEnd true;
                    } else {
                        
$content .= $char;
                        
$this->index++;
                        
$isEnd in_array($char$this->lineTerminators);
                    }
                    if (
$isEnd) {
                        if (
$this->comments) {
                            
//Remove the closing line terminator from the
                            //comment text
                            
if ($char !== null) {
                                
$this->index--;
                                
$content substr($content0, -strlen($char));
                            }
                            
$this->adjustColumnAndLine($content);
                            
$token = new Token(Token::TYPE_COMMENT$content);
                            
$token->location->start $start;
                            
$token->location->end $this->getPosition(true);
                            
$comments[] = $token;
                            
$content "";
                            if (
$char !== null) {
                                
$content $char;
                                
$this->index++;
                            }
                        }
                        break;
                    }
                }
                
            } elseif (!
$this->isModule && $char === "-" &&
                
$this->charAt($this->index 1) === "-" &&
                
$this->charAt($this->index 2) === ">"
            
) {
                
                
//Close html comment
                //Check if it is on it's own line
                
$allow false;
                if (!
$secStartIdx) {
                    
$allow true;
                } else {
                    for (
$index $this->index 1$index >= $secStartIdx$index--) {
                        if (
in_array($this->charAt($index), $this->lineTerminators)) {
                            
$allow true;
                            break;
                        }
                    }
                }
                if (
$allow) {
                    
                    
//If comments must be handled, empty the current content too
                    //and get the comment start position
                    
if ($this->comments) {
                        if (
$content !== "") {
                            
$this->adjustColumnAndLine($content);
                            
$content "";
                        }
                        
$start $this->getPosition(true);
                    }
                    
                    
$this->index += 3;
                    
$content .= "-->";
                    while (
true) {
                        
$char $this->charAt();
                        
                        if (
$char === null) {
                            
$isEnd true;
                        } else {
                            
$content .= $char;
                            
$this->index++;
                            
$isEnd in_array($char$this->lineTerminators);
                        }
                        
                        if (
$isEnd) {
                            if (
$this->comments) {
                                
//Remove the closing line terminator from the
                                //comment text
                                
if ($char !== null) {
                                    
$this->index--;
                                    
$content substr($content0, -strlen($char));
                                }
                                
$this->adjustColumnAndLine($content);
                                
$token = new Token(Token::TYPE_COMMENT$content);
                                
$token->location->start $start;
                                
$token->location->end $this->getPosition(true);
                                
$comments[] = $token;
                                
$content "";
                                if (
$char !== null) {
                                    
$content $char;
                                    
$this->index++;
                                }
                            }
                            break;
                        }
                    }
                } else {
                    break;
                }
                
            } else {
                break;
            }
        }
        
        if (
$content !== "") {
            
$this->adjustColumnAndLine($content);
        }
        
        return 
$comments;
    }
    
    
/**
     * String scanning method
     * 
     * @param bool $handleEscape True to handle escaping
     * 
     * @return Token|null
     */
    
protected function scanString($handleEscape true)
    {
        
$char $this->charAt();
        if (
$char === "'" || $char === '"') {
            
$this->index++;
            
$this->column++;
            
//Add the quote to the LSM and then remove it after consuming
            
$this->stringsStopsLSM->add($char);
            
$buffer $this->consumeUntil($this->stringsStopsLSM$handleEscape);
            
$this->stringsStopsLSM->remove($char);
            if (
$buffer === null || $buffer[1] !== $char) {
                
$this->error("Unterminated string");
            }
            return new 
Token(Token::TYPE_STRING_LITERAL$char $buffer[0]);
        }
        
        return 
null;
    }
    
    
/**
     * Template scanning method
     * 
     * @return Token|null
     */
    
protected function scanTemplate()
    {
        
$char $this->charAt();
        
        
//Get the current number of open curly brackets
        
$openCurly = isset($this->openBrackets["{"]) ?
                     
$this->openBrackets["{"] :
                     
0;
        
        
//If the character is a curly bracket check and the number of open
        //curly brackets matches the last number in the open templates stack,
        //then the bracket closes the open template expression
        
$endExpression false;
        if (
$char === "}") {
            
$len count($this->openTemplates);
            if (
$len && $this->openTemplates[$len 1] === $openCurly) {
                
$endExpression true;
                
array_pop($this->openTemplates);
            }
        }
        
        if (
$char === "`" || $endExpression) {
            
$this->index++;
            
$this->column++;
            
$buffer $char;
            while (
true) {
                
$tempBuffer $this->consumeUntil(array("`""$"));
                if (!
$tempBuffer) {
                    
$this->error("Unterminated template");
                }
                
$buffer .= $tempBuffer[0];
                if (
$tempBuffer[1] !== "$" || $this->charAt() === "{") {
                    
//If "${" is found it's a new template expression, register
                    //the current number of open curly brackets in the open
                    //templates stack
                    
if ($tempBuffer[1] === "$") {
                        
$this->index++;
                        
$this->column++;
                        
$buffer .= "{";
                        
$this->openTemplates[] = $openCurly;
                    }
                    break;
                }
            }
            return new 
Token(Token::TYPE_TEMPLATE$buffer);
        }
        
        return 
null;
    }
    
    
/**
     * Number scanning method
     * 
     * @return Token|null
     */
    
protected function scanNumber()
    {
        
//Numbers can start with a decimal number or with a dot (.5)
        
$char $this->charAt();
        if (!((
$char >= "0" && $char <= "9") || $char === ".")) {
            return 
null;
        }

        
$buffer "";
        
$allowedDecimals true;
        
        
//Parse the integer part
        
if ($char !== ".") {
            
            
//Consume all decimal numbers
            
$buffer $this->consumeNumbers();
            
$char $this->charAt();
            
            if (
$this->features->bigInt && $char === "n") {
                
$this->index++;
                
$this->column++;
                return new 
Token(Token::TYPE_BIGINT_LITERAL$buffer $char);
            }
            
            
$lower $char !== null strtolower($char) : null;
            
            
//Handle hexadecimal (0x), octal (0o) and binary (0b) forms
            
if ($buffer === "0" && $lower !== null &&
                isset(
$this->{$lower "numbers"})
            ) {
                
                
$this->index++;
                
$this->column++;
                
$tempBuffer $this->consumeNumbers($lower);
                if (
$tempBuffer === null) {
                    
$this->error("Missing numbers after 0$char");
                }
                
$buffer .= $char $tempBuffer;
                
                
//Check that there are not numbers left
                
if ($this->consumeNumbers() !== null) {
                    
$this->error();
                }

                if (
$this->features->bigInt && $this->charAt() === "n") {
                    
$this->index++;
                    
$this->column++;
                    return new 
Token(Token::TYPE_BIGINT_LITERAL$buffer $char);
                }
                
                return new 
Token(Token::TYPE_NUMERIC_LITERAL$buffer);
            }
            
            
//Consume exponent part if present
            
if ($tempBuffer $this->consumeExponentPart()) {
                
$buffer .= $tempBuffer;
                
$allowedDecimals false;
            }
        }
        
        
//Parse the decimal part
        
if ($allowedDecimals && $this->charAt() === ".") {
            
            
//Consume the dot
            
$this->index++;
            
$this->column++;
            
$buffer .= ".";
            
            
//Consume all decimal numbers
            
$tempBuffer $this->consumeNumbers();
            
$buffer .= $tempBuffer;
            
            
//If the buffer contains only the dot it should be parsed as
            //punctuator
            
if ($buffer === ".") {
                
$this->index--;
                
$this->column--;
                return 
null;
            }
            
            
//Consume exponent part if present
            
if (($tempBuffer $this->consumeExponentPart()) !== null) {
                
$buffer .= $tempBuffer;
            }
        }
        
        return new 
Token(Token::TYPE_NUMERIC_LITERAL$buffer);
    }
    
    
/**
     * Consumes the maximum number of digits
     * 
     * @param string $type Digits type (decimal, hexadecimal, etc...)
     * @param int    $max  Maximum number of digits to match
     * 
     * @return string|null
     */
    
protected function consumeNumbers($type ""$max null)
    {
        
$buffer "";
        
$char $this->charAt();
        
$count 0;
        
$extra $this->features->numericLiteralSeparator "_" "";
        while (
            
in_array($char$this->{$type "numbers"}) ||
            (
$count && $char === $extra)
        ) {
            
$buffer .= $char;
            
$this->index++;
            
$this->column++;
            
$count ++;
            if (
$count === $max) {
                break;
            }
            
$char $this->charAt();
        }
        if (
$count && substr($buffer, -1) === "_") {
            
$this->error(
                
"Numeric separators are not allowed at the end of a number"
            
);
        }
        return 
$count $buffer null;
    }
    
    
/**
     * Consumes the exponent part of a number
     * 
     * @return string|null
     */
    
protected function consumeExponentPart()
    {
        
$buffer "";
        
$char $this->charAt();
        if (
$char !== null && strtolower($char) === "e") {
            
$this->index++;
            
$this->column++;
            
$buffer .= $char;
            
$char $this->charAt();
            if (
$char === "+" || $char === "-") {
                
$this->index++;
                
$this->column++;
                
$buffer .= $char;
            }
            
$tempBuffer $this->consumeNumbers();
            if (
$tempBuffer === null) {
                
$this->error("Missing exponent");
            }
            
$buffer .= $tempBuffer;
        }
        return 
$buffer;
    }
    
    
/**
     * Punctuator scanning method
     * 
     * @return Token|null
     */
    
protected function scanPunctuator()
    {
        
$token null;
        
$char $this->charAt();
        
        
//Check if the next char is a bracket
        
if (isset($this->brackets[$char])) {
            
//Check if it is a closing bracket
            
if ($this->brackets[$char]) {
                
$openBracket $this->brackets[$char];
                
//Check if there is a corresponding open bracket
                
if (!isset($this->openBrackets[$openBracket]) ||
                    !
$this->openBrackets[$openBracket]
                ) {
                    if (!
$this->isAfterSlash($this->getPosition(true))) {
                        
$this->error();
                    }
                } else {
                    
$this->openBrackets[$openBracket]--;
                }
            } else {
                if (!isset(
$this->openBrackets[$char])) {
                    
$this->openBrackets[$char] = 0;
                }
                
$this->openBrackets[$char]++;
            }
            
$this->index++;
            
$this->column++;
            
$token = new Token(Token::TYPE_PUNCTUATOR$char);
        } elseif (
            
//Try to match the longest punctuator
            
$match $this->punctuatorsLSM->match($this$this->index$char)
        ) {
            
//Optional chaining punctuator cannot appear before a number, in this
            //case only the question mark must be consumed
            
if ($match[1] === "?." &&
                (
$nextChar $this->charAt($this->index $match[0])) !== null &&
                
$nextChar >= "0" && $nextChar <= "9"
            
) {
                
$match = array(1"?");
            }
            
$this->index += $match[0];
            
$this->column += $match[0];
            
$token = new Token(Token::TYPE_PUNCTUATOR$match[1]);
        }
        return 
$token;
    }
    
    
/**
     * Keywords and identifiers scanning method
     * 
     * @return Token|null
     */
    
protected function scanKeywordOrIdentifier()
    {
        
//Check private identifier start character
        
if ($private $this->features->privateMethodsAndFields && $this->charAt() === "#") {
            
$this->index++;
            
$this->column++;
        }

        
//Consume the maximum number of characters that are unicode escape
        //sequences or valid identifier starts (only the first character) or
        //parts
        
$buffer "";
        
$start true;
        while ((
$char $this->charAt()) !== null) {
            if (
                (
$char >= "a" && $char <= "z") ||
                (
$char >= "A" && $char <= "Z") ||
                
$char === "_" || $char === "$" ||
                (!
$start && $char >= "0" && $char <= "9") ||
                
$this->isIdentifierChar($char$start)
            ) {
                
$buffer .= $char;
                
$this->index++;
                
$this->column++;
            } elseif (
$char === "\\" && ($seq $this->consumeUnicodeEscapeSequence())) {
                
//Verify that it's a valid character
                
if (!$this->isIdentifierChar($seq[1], $start)) {
                    break;
                }
                
$buffer .= $seq[0];
            } else {
                break;
            }
            
$start false;
        }
        
        
//Identify token type
        
if ($buffer === "") {
            
//Unconsume the hash if nothing was found after that
            
if ($private) {
                
$this->index--;
                
$this->column--;
            }
            return 
null;
        } elseif (
$private) {
            
$type Token::TYPE_PRIVATE_IDENTIFIER;
            
$buffer "#" $buffer;
        } elseif (
$buffer === "null") {
            
$type Token::TYPE_NULL_LITERAL;
        } elseif (
$buffer === "true" || $buffer === "false") {
            
$type Token::TYPE_BOOLEAN_LITERAL;
        } elseif (
in_array($buffer$this->keywords) ||
            
in_array($buffer$this->strictModeKeywords)
        ) {
            
$type Token::TYPE_KEYWORD;
        } else {
            
$type Token::TYPE_IDENTIFIER;
        }
        
        return new 
Token($type$buffer);
    }
    
    
/**
     * Consumes an unicode escape sequence
     * 
     * @return array|null
     */
    
protected function consumeUnicodeEscapeSequence()
    {
        if (
$this->charAt() !== "\\" ||
            
$this->charAt($this->index 1) !== "u"
        
) {
            return 
null;
        }
        
        
$startIndex $this->index;
        
$startColumn $this->column;
        
$this->index += 2;
        
$this->column += 2;
        
$brackets false;
        if (
$this->charAt() === "{") {
            
//\u{FFF}
            
$brackets true;
            
$this->index++;
            
$this->column++;
            
$code $this->consumeNumbers("x");
            if (
$code && $this->charAt() !== "}") {
                
$code null;
            } else {
                
$this->index++;
                
$this->column++;
            }
        } else {
            
//\uFFFF
            
$code $this->consumeNumbers("x"4);
            if (
$code && strlen($code) !== 4) {
                
$code null;
            }
        }
        
        
//Unconsume everything if the format is invalid
        
if ($code === null) {
            
$this->index $startIndex;
            
$this->column $startColumn;
            return 
null;
        }
        
        
//Return an array where the first element is the matched sequence
        //and the second one is the decoded character
        
return array(
            
$brackets "\\u{" $code "}" "\\u" $code,
            
Utils::unicodeToUtf8(hexdec($code))
        );
    }
    
    
/**
     * Checks if the given character is valid for an identifier
     * 
     * @param string $char  Character to check
     * @param bool   $start If true it will check that the character is
     *                      valid to start an identifier
     * 
     * @return bool
     */
    
protected function isIdentifierChar($char$start true)
    {
        return (
$char >= "a" && $char <= "z") ||
               (
$char >= "A" && $char <= "Z") ||
               
$char === "_" || $char === "$" ||
               (!
$start && $char >= "0" && $char <= "9") ||
               
preg_match($start $this->idStartRegex $this->idPartRegex$char);
    }
    
    
/**
     * Increases columns and lines count according to the given string
     * 
     * @param string $buffer String to analyze
     * 
     * @return void
     */
    
protected function adjustColumnAndLine($buffer)
    {
        
$lines preg_split($this->linesSplitter$buffer);
        
$linesCount count($lines) - 1;
        
$this->line += $linesCount;
        
$columns mb_strlen($lines[$linesCount], "UTF-8");
        if (
$linesCount) {
            
$this->column $columns;
        } else {
            
$this->column += $columns;
        }
    }
    
    
/**
     * Consumes characters until one of the given characters is found
     * 
     * @param array|LSM $stops          Characters to search
     * @param bool      $handleEscape   True to handle escaping
     * @param bool      $collectStop    True to include the stop character
     * 
     * @return array|null
     */
    
protected function consumeUntil(
        
$stops$handleEscape true$collectStop true
    
) {
        
$isLSM $stops instanceof LSM;
        
$buffer "";
        
$escaped false;
        while ((
$char $this->charAt()) !== null) {
            
$incrIndex 1;
            
$isStop false;
            if (
$isLSM) {
                
$m $stops->match($this$this->index$char);
                if (
$m) {
                    
$isStop true;
                    
$incrIndex $m[0];
                    
$char $m[1];
                }
            } else {
                
$isStop in_array($char$stops);
            }
            
$validStop $isStop && !$escaped;
            if (!
$validStop || $collectStop) {
                
$this->index += $incrIndex;
                
$buffer .= $char;
            }
            if (
$validStop) {
                if (!
$collectStop && $buffer === "") {
                    return 
null;
                }
                
$this->adjustColumnAndLine($buffer);
                return array(
$buffer$char);
            } elseif (!
$escaped && $char === "\\" && $handleEscape) {
                
$escaped true;
            } else {
                
$escaped false;
            }
        }
        return 
null;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\ES2022;

/**
 * ES2022 features class
 * 
 * @author Marco Marchiò <[email protected]>
 * 
 * @codeCoverageIgnore
 */
class Features extends \Peast\Syntax\ES2021\Features
{
    
/**
     * Private methods and fields
     *
     * @var bool
     */
    
public $privateMethodsAndFields true;

    
/**
     * Class fields
     *
     * @var bool
     */
    
public $classFields true;

    
/**
     * "in" operator for private fields
     *
     * @var bool
     */
    
public $classFieldsPrivateIn true;

    
/**
     * Top level await
     *
     * @var bool
     */
    
public $topLevelAwait true;

    
/**
     * Class static block
     *
     * @var bool
     */
    
public $classStaticBlock true;

    
/**
     * Arbitrary module namespace identifier names
     *
     * @var bool
     */
    
public $arbitraryModuleNSNames true;
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\ES2015;

/**
 * ES2015 features class
 * 
 * @author Marco Marchiò <[email protected]>
 * 
 * @codeCoverageIgnore
 */
class Features extends \Peast\Syntax\Features
{
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax;

/**
 * Base class for parsers.
 * 
 * @author Marco Marchiò <[email protected]>
 * 
 * @abstract
 */
abstract class ParserAbstract
{
    
/**
     * Associated scanner
     * 
     * @var Scanner 
     */
    
protected $scanner;

    
/**
     * Parser features
     * 
     * @var Features
     */
    
protected $features;
    
    
/**
     * Parser context
     * 
     * @var \stdClass
     */
    
protected $context;
    
    
/**
     * Source type
     * 
     * @var string 
     */
    
protected $sourceType;
    
    
/**
     * Comments handling
     *
     * @var bool
     */
    
protected $comments;
    
    
/**
     * JSX syntax handling
     *
     * @var bool
     */
    
protected $jsx;
    
    
/**
     * Events emitter
     *
     * @var EventsEmitter
     */
    
protected $eventsEmitter;

    
/**
     * Class constructor
     * 
     * @param string   $source   Source code
     * @param Features $features Parser features
     * @param array    $options  Parsing options
     */
    
public function __construct(
        
$sourceFeatures $features$options = array()
    ) {
        
$this->features $features;
        
        
$this->sourceType = isset($options["sourceType"]) ?
                            
$options["sourceType"] :
                            
\Peast\Peast::SOURCE_TYPE_SCRIPT;
        
        
//Create the scanner
        
$this->scanner = new Scanner($source$features$options);
        
        
//Enable module scanning if required
        
if ($this->sourceType === \Peast\Peast::SOURCE_TYPE_MODULE) {
            
$this->scanner->enableModuleMode();
        }
        
        
//Enable comments scanning
        
$this->comments = isset($options["comments"]) && $options["comments"];
        if (
$this->comments) {
            
$this->scanner->enableComments();
            
//Create the comments registry
            
new CommentsRegistry($this);
        }
        
        
// Enable jsx syntax if required
        
$this->jsx = isset($options["jsx"]) && $options["jsx"];
        
        
$this->initContext();
        
$this->postInit();
    }
    
    
/**
     * Initializes parser context
     * 
     * @return void
     */
    
abstract protected function initContext();
    
    
/**
     * Post initialize operations
     * 
     * @return void
     */
    
abstract protected function postInit();
    
    
/**
     * Parses the source
     * 
     * @return Node\Node
     * 
     * @abstract
     */
    
abstract public function parse();
    
    
/**
     * Returns parsed tokens from the source code
     * 
     * @return Token[]
     */
    
public function tokenize()
    {
        
$this->scanner->enableTokenRegistration();
        
$this->parse();
        return 
$this->scanner->getTokens();
    }
    
    
/**
     * Returns the scanner associated with the parser
     * 
     * @return Scanner
     */
    
public function getScanner()
    {
        return 
$this->scanner;
    }
    
    
/**
     * Returns the parser features class
     * 
     * @return Features
     */
    
public function getFeatures()
    {
        return 
$this->features;
    }
    
    
/**
     * Returns the parser's events emitter
     * 
     * @return EventsEmitter
     */
    
public function getEventsEmitter()
    {
        if (!
$this->eventsEmitter) {
            
//The event emitter is created here so that it won't exist if not
            //necessary
            
$this->eventsEmitter = new EventsEmitter;
        }
        return 
$this->eventsEmitter;
    }
    
    
/**
     * Calls a method with an isolated parser context, applying the given flags,
     * but restoring their values after the execution.
     * 
     * @param array|null  $flags  Key/value array of changes to apply to the
     *                            context flags. If it's null or the first
     *                            element of the array is null the context will
*                                 be reset before applying new values.
     * @param string      $fn     Method to call
     * @param array|null  $args   Method arguments
     * 
     * @return mixed
     */
    
protected function isolateContext($flags$fn$args null)
    {
        
//Store the current context
        
$oldContext = clone $this->context;
        
        
//If flag argument is null reset the context
        
if ($flags === null) {
            
$this->initContext();
        } else {
            
//Apply new values to the flags
            
foreach ($flags as $k => $v) {
                
// If null reset the context
                
if ($v === null) {
                    
$this->initContext();
                } else {
                    
$this->context->$k $v;
                }
            }
        }
        
        
//Call the method with the given arguments
        
$ret $args call_user_func_array(array($this$fn), $args) : $this->$fn();
        
        
//Restore previous context
        
$this->context $oldContext;
        
        return 
$ret;
    }
    
    
/**
     * Creates a node
     * 
     * @param string $nodeType Node's type
     * @param mixed  $position Node's start position
     * 
     * @return Node\Node
     * 
     * @codeCoverageIgnore
     */
    
protected function createNode($nodeType$position)
    {
        
//Use the right class to get an instance of the node
        
$nodeClass "\\Peast\\Syntax\\Node\\" $nodeType;
        
$node = new $nodeClass;
        
        
//Add the node start position
        
if ($position instanceof Node\Node || $position instanceof Token) {
            
$position $position->location->start;
        } elseif (
is_array($position)) {
            if (
count($position)) {
                
$position $position[0]->location->start;
            } else {
                
$position $this->scanner->getPosition();
            }
        }
        
$node->location->start $position;
        
        
//Emit the NodeCreated event for the node
        
$this->eventsEmitter && $this->eventsEmitter->fire(
            
"NodeCreated", array($node)
        );
        
        return 
$node;
    }
    
    
/**
     * Completes a node by adding the end position
     * 
     * @param Node\Node   $node     Node to complete
     * @param Position    $position Node's end position
     * 
     * @return mixed    It actually returns a Node but mixed solves
     *                  a lot of PHPDoc problems
     * 
     * @codeCoverageIgnore
     */
    
protected function completeNode(Node\Node $node$position null)
    {
        
//Add the node end position
        
$node->location->end $position ?: $this->scanner->getPosition();
        
        
//Emit the NodeCompleted event for the node
        
$this->eventsEmitter && $this->eventsEmitter->fire(
            
"NodeCompleted", array($node)
        );
        
        return 
$node;
    }
    
    
/**
     * Throws a syntax error
     * 
     * @param string   $message  Error message
     * @param Position $position Error position
     * 
     * @return void
     * 
     * @throws Exception
     */
    
protected function error($message ""$position null)
    {
        if (!
$message) {
            
$token $this->scanner->getToken();
            if (
$token === null) {
                
$message "Unexpected end of input";
            } else {
                
$position $token->location->start;
                
$message "Unexpected: " $token->value;
            }
        }
        if (!
$position) {
            
$position $this->scanner->getPosition();
        }
        throw new 
Exception($message$position);
    }
    
    
/**
     * Asserts that a valid end of statement follows the current position
     * 
     * @return boolean
     * 
     * @throws Exception
     */
    
protected function assertEndOfStatement()
    {
        
//The end of statement is reached when it is followed by line
        //terminators, end of source, "}" or ";". In the last case the token
        //must be consumed
        
if (!$this->scanner->noLineTerminators()) {
            return 
true;
        } else {
            if (
$this->scanner->consume(";")) {
                return 
true;
            }
            
$token $this->scanner->getToken();
            if (!
$token || $token->value === "}") {
                return 
true;
            }
        }
        
$this->error();
    }
    
    
/**
     * Parses a character separated list of instructions or null if the
     * sequence is not valid
     * 
     * @param callable $fn   Parsing instruction function
     * @param string   $char Separator
     * 
     * @return array
     * 
     * @throws Exception
     */
    
protected function charSeparatedListOf($fn$char ",")
    {
        
$list = array();
        
$valid true;
        while (
$param $this->$fn()) {
            
$list[] = $param;
            
$valid true;
            if (!
$this->scanner->consume($char)) {
                break;
            } else {
                
$valid false;
            }
        }
        if (!
$valid) {
            
$this->error();
        }
        return 
$list;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\JSX;

use 
Peast\Syntax\Token;

/**
 * JSX scanner trait
 * 
 * @author Marco Marchiò <[email protected]>
 */
trait Scanner
{
    
/**
     * Tries to reconsume the current token as a jsx text if possible
     * 
     * @return Token|null
     */
    
public function reconsumeCurrentTokenAsJSXText()
    {
        
//Current and next tokens must be reset and the open brackets count must be correct
        //like they were never scanned
        
foreach (array($this->currentToken$this->nextToken) as $token) {
            if (
$token && isset($this->brackets[$token->value])) {
                if (
$refBracket $this->brackets[$token->value]) {
                    
$this->openBrackets[$refBracket]++;
                } else {
                    
$this->openBrackets[$token->value]--;
                }
            }
        }
        
$this->nextToken null;
        
$this->currentToken null;

        
$startPosition $this->getPosition();
        
$this->setScanPosition($startPosition);
        
$result $this->consumeUntil(array("{""<"), falsefalse);
        if (
$result) {
            
$this->currentToken = new Token(Token::TYPE_JSX_TEXT$result[0]);
            
$this->currentToken->location->start $startPosition;
            
$this->currentToken->location->end $this->getPosition(true);
        }
        return 
$this->currentToken;
    }
    
    
    
/**
     * Reconsumes the current token in jsx mode
     * 
     * @return Token|null
     */
    
public function reconsumeCurrentTokenInJSXMode()
    {
        
$this->jsx true;
        
$this->nextToken null;
        
$this->currentToken null;
        
$startPosition $this->getPosition();
        
$this->setScanPosition($startPosition);
        
$token $this->getToken();
        
$this->jsx false;
        return 
$token;
    }
    
    
/**
     * String scanning method in jsx mode
     * 
     * @return Token|null
     */
    
public function scanJSXString()
    {
        return 
$this->scanString(false);
    }
    
    
/**
     * String punctuator method in jsx mode
     * 
     * @return Token|null
     */
    
public function scanJSXPunctuator()
    {
        
//The ">" character in jsx mode must be emitted in its own token
        //without matching longer sequences like ">>"
        
$char $this->charAt();
        if (
$char === ">") {
            
$this->index++;
            
$this->column++;
            return new 
Token(Token::TYPE_PUNCTUATOR$char);
        }
        return 
$this->scanPunctuator();
    }
    
    
/**
     * Identifier scanning method in jsx mode
     * 
     * @return Token|null
     */
    
public function scanJSXIdentifier()
    {
        
$buffer "";
        
$char $this->charAt();
        if (
$char !== null && $this->isIdentifierChar($char)) {
            
            do {
                
$buffer .= $char;
                
$this->index++;
                
$this->column++;
                
$char $this->charAt();
            } while (
                
$char !== null &&
                (
$this->isIdentifierChar($charfalse) || $char === "-")
            );
        }
        
        return 
$buffer === "" null : new Token(Token::TYPE_JSX_IDENTIFIER$buffer);
    }
}
<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\JSX;

use 
Peast\Syntax\Token;

/**
 * JSX parser trait
 * 
 * @author Marco Marchiò <[email protected]>
 */
trait Parser
{
    
/**
     * Creates a JSX node
     * 
     * @param string $nodeType Node's type
     * @param mixed  $position Node's start position
     * 
     * @return \Peast\Syntax\Node\Node
     */
    
protected function createJSXNode($nodeType$position)
    {
        return 
$this->createNode("JSX\\$nodeType"$position);
    }
    
    
/**
     * Parses a jsx fragment
     * 
     * @return \Peast\Syntax\Node\JSX\JSXFragment|null
     */
    
protected function parseJSXFragment()
    {
        
$startOpeningToken $this->scanner->getToken();
        if (!
$startOpeningToken || $startOpeningToken->value !== "<") {
            return 
null;
        }
        
        
$endOpeningToken $this->scanner->getNextToken();
        if (!
$endOpeningToken || $endOpeningToken->value !== ">") {
            return 
null;
        }
        
        
$this->scanner->consumeToken();
        
$this->scanner->consumeToken();
        
        
$children $this->parseJSXChildren();
        
        if (!(
$startClosingToken $this->scanner->consume("<")) ||
            !
$this->scanner->consume("/") ||
            !
$this->scanner->reconsumeCurrentTokenInJSXMode() ||
            
$endOpeningToken->value !== ">") {
            
$this->error();
        }
        
$this->scanner->consumeToken();
        
        
//Opening tag
        
$openingNode $this->createJSXNode(
            
"JSXOpeningFragment",
            
$startOpeningToken
        
);
        
$this->completeNode(
            
$openingNode,
            
$endOpeningToken->location->end
        
);
        
        
//Closing tag
        
$closingNode $this->createJSXNode(
            
"JSXClosingFragment",
            
$startClosingToken
        
);
        
$this->completeNode($closingNode);
        
        
//Fragment
        
$node $this->createJSXNode("JSXFragment"$startOpeningToken);
        
$node->setOpeningFragment($openingNode);
        
$node->setClosingFragment($closingNode);
        if (
$children) {
            
$node->setChildren($children);
        }
        return 
$this->completeNode($node);
    }
    
    
/**
     * Parses a group of jsx children
     * 
     * @return \Peast\Syntax\Node\Node[]|null
     */
    
protected function parseJSXChildren()
    {
        
$children = array();
        while (
$child $this->parseJSXChild()) {
            
$children[] = $child;
        }
        return 
count($children) ? $children null;
    }
    
    
/**
     * Parses a jsx child
     * 
     * @return \Peast\Syntax\Node\Node|null
     */
    
protected function parseJSXChild()
    {
        if (
$node $this->parseJSXText()) {
            return 
$node;
        } elseif (
$node $this->parseJSXFragment()) {
            return 
$node;
        } elseif(
$node $this->parseJSXElement()) {
            return 
$node;
        } elseif (
$startToken $this->scanner->consume("{")) {
            
$spread $this->scanner->consume("...");
            
$exp $this->parseAssignmentExpression();
            
$midPos $this->scanner->getPosition();
            if ((
$spread && !$exp) || !$this->scanner->consume("}")) {
                
$this->error();
            }
            
$node $this->createJSXNode(
                
$spread "JSXSpreadChild" "JSXExpressionContainer",
                
$startToken
            
);
            if (!
$exp) {
                
$exp $this->createJSXNode("JSXEmptyExpression"$midPos);
                
$this->completeNode($exp$midPos);
            }
            
$node->setExpression($exp);
            return 
$this->completeNode($node);
        }
        return 
null;
    }
    
    
/**
     * Parses a jsx text
     * 
     * @return \Peast\Syntax\Node\JSX\JSXText|null
     */
    
protected function parseJSXText()
    {
        if (!(
$token $this->scanner->reconsumeCurrentTokenAsJSXText())) {
            return 
null;
        }
        
$this->scanner->consumeToken();
        
$node $this->createJSXNode("JSXText"$token);
        
$node->setRaw($token->value);
        return 
$this->completeNode($node$token->location->end);
    }
    
    
/**
     * Parses a jsx element
     * 
     * @return \Peast\Syntax\Node\JSX\JSXElement|null
     */
    
protected function parseJSXElement()
    {
        
$startOpeningToken $this->scanner->getToken();
        if (!
$startOpeningToken || $startOpeningToken->value !== "<") {
            return 
null;
        }
        
        
$nextToken $this->scanner->getNextToken();
        if (
$nextToken && $nextToken->value === "/") {
            return 
null;
        }
        
        
$this->scanner->consumeToken();
        
        if (!(
$name $this->parseJSXIdentifierOrMemberExpression())) {
            
$this->error();
        }
        
        
$attributes $this->parseJSXAttributes();
        
        
$selfClosing $this->scanner->consume("/");
        
        
$endOpeningToken $this->scanner->reconsumeCurrentTokenInJSXMode();
        if (!
$endOpeningToken || $endOpeningToken->value !== ">") {
            
$this->error();
        }
        
$this->scanner->consumeToken();
        
        if (!
$selfClosing) {
            
            
$children $this->parseJSXChildren();
            
            if (
                (
$startClosingToken $this->scanner->consume("<")) &&
                
$this->scanner->consume("/") &&
                (
$closingName $this->parseJSXIdentifierOrMemberExpression()) &&
                (
$endClosingToken $this->scanner->reconsumeCurrentTokenInJSXMode()) &&
                (
$endClosingToken->value === ">")
            ) {
                
$this->scanner->consumeToken();
                if (!
$this->isSameJSXElementName($name$closingName)) {
                    
$this->error("Closing tag does not match opening tag");
                }
            } else {
                
$this->error();
            }
            
        }
        
        
//Opening tag
        
$openingNode $this->createJSXNode(
            
"JSXOpeningElement",
            
$startOpeningToken
        
);
        
$openingNode->setName($name);
        
$openingNode->setSelfClosing($selfClosing);
        if (
$attributes) {
            
$openingNode->setAttributes($attributes);
        }
        
$this->completeNode(
            
$openingNode,
            
$endOpeningToken->location->end
        
);
        
        
//Closing tag
        
$closingNode null;
        if (!
$selfClosing) {
            
$closingNode $this->createJSXNode(
                
"JSXClosingElement",
                
$startClosingToken
            
);
            
$closingNode->setName($closingName);
            
$this->completeNode($closingNode);
        }
        
        
//Element
        
$node $this->createJSXNode("JSXElement"$startOpeningToken);
        
$node->setOpeningElement($openingNode);
        if (
$closingNode) {
            
$node->setClosingElement($closingNode);
            if (
$children) {
                
$node->setChildren($children);
            }
        }
        return 
$this->completeNode($node);
    }
    
    
/**
     * Parses a jsx identifier, namespaced identifier or member expression
     * 
     * @param bool $allowMember True to allow member expressions
     * 
     * @return \Peast\Syntax\Node\Node|null
     */
    
protected function parseJSXIdentifierOrMemberExpression($allowMember true)
    {
        
$idToken $this->scanner->reconsumeCurrentTokenInJSXMode();
        if (!
$idToken || $idToken->type !== Token::TYPE_JSX_IDENTIFIER) {
            return 
null;
        }
        
$this->scanner->consumeToken();
        
        
$idNode $this->createJSXNode("JSXIdentifier"$idToken);
        
$idNode->setName($idToken->value);
        
$idNode $this->completeNode($idNode);
        
        
//Namespaced identifier
        
if ($this->scanner->consume(":")) {
            
            
$idToken2 $this->scanner->reconsumeCurrentTokenInJSXMode();
            if (!
$idToken2 || $idToken2->type !== Token::TYPE_JSX_IDENTIFIER) {
                
$this->error();
            }
            
$this->scanner->consumeToken();
            
            
$idNode2 $this->createJSXNode("JSXIdentifier"$idToken2);
            
$idNode2->setName($idToken2->value);
            
$idNode2 $this->completeNode($idNode2);
            
            
$node $this->createJSXNode("JSXNamespacedName"$idToken);
            
$node->setNamespace($idNode);
            
$node->setName($idNode2);
            return 
$this->completeNode($node);
            
        }
        
        
//Get following identifiers
        
$nextIds = array();
        if (
$allowMember) {
            while (
$this->scanner->consume(".")) {
                
$nextId $this->scanner->reconsumeCurrentTokenInJSXMode();
                if (!
$nextId || $nextId->type !== Token::TYPE_JSX_IDENTIFIER) {
                    
$this->error();
                }
                
$this->scanner->consumeToken();
                
$nextIds[] = $nextId;
            }
        }
        
        
//Create the member expression if required
        
$objectNode $idNode;
        foreach (
$nextIds as $nid) {
            
$propEnd $nid->location->end;
            
$propNode $this->createJSXNode("JSXIdentifier"$nid);
            
$propNode->setName($nid->value);
            
$propNode $this->completeNode($propNode$propEnd);
            
            
$node $this->createJSXNode("JSXMemberExpression"$objectNode);
            
$node->setObject($objectNode);
            
$node->setProperty($propNode);
            
$objectNode $this->completeNode($node$propEnd);
        }
        
        return 
$objectNode;
    }
    
    
/**
     * Parses a jsx attributes list
     * 
     * @return \Peast\Syntax\Node\Node[]|null
     */
    
protected function parseJSXAttributes()
    {
        
$attributes = array();
        while (
            (
$attr $this->parseJSXSpreadAttribute()) ||
            (
$attr $this->parseJSXAttribute())
        ) {
            
$attributes[] = $attr;
        }
        return 
count($attributes) ? $attributes null;
    }
    
    
/**
     * Parses a jsx spread attribute
     * 
     * @return \Peast\Syntax\Node\JSX\JSXSpreadAttribute|null
     */
    
protected function parseJSXSpreadAttribute()
    {
        if (!(
$openToken $this->scanner->consume("{"))) {
            return 
null;
        }
        
        if (
            
$this->scanner->consume("...") &&
            (
$exp $this->parseAssignmentExpression()) &&
            
$this->scanner->consume("}")
        ) {
            
$node $this->createJSXNode("JSXSpreadAttribute"$openToken);
            
$node->setArgument($exp);
            return 
$this->completeNode($node);
        }
        
        
$this->error();
    }
    
    
/**
     * Parses a jsx spread attribute
     * 
     * @return \Peast\Syntax\Node\JSX\JSXSpreadAttribute|null
     */
    
protected function parseJSXAttribute()
    {
        if (!(
$name $this->parseJSXIdentifierOrMemberExpression(false))) {
            return 
null;
        }
        
        
$value null;
        if (
$this->scanner->consume("=")) {
            
$strToken $this->scanner->reconsumeCurrentTokenInJSXMode();
            if (
$strToken && $strToken->type === Token::TYPE_STRING_LITERAL) {
                
$this->scanner->consumeToken();
                
$value $this->createNode("StringLiteral"$strToken);
                
$value->setRaw($strToken->value);
                
$value $this->completeNode($value);
            } elseif (
$startExp $this->scanner->consume("{")) {
                
                if (
                    (
$exp $this->parseAssignmentExpression()) &&
                    
$this->scanner->consume("}")
                ) {
                    
                    
$value $this->createJSXNode(
                        
"JSXExpressionContainer",
                        
$startExp
                    
);
                    
$value->setExpression($exp);
                    
$value $this->completeNode($value);
                    
                } else {
                    
$this->error();
                }
                
            } elseif (
                !(
$value $this->parseJSXFragment()) &&
                !(
$value $this->parseJSXElement())
            ) {
                
$this->error();
            }
        }
        
        
$node $this->createJSXNode("JSXAttribute"$name);
        
$node->setName($name);
        if (
$value) {
            
$node->setValue($value);
        }
        return 
$this->completeNode($node);
    }
    
    
/**
     * Checks that 2 tag names are equal
     * 
     * @param \Peast\Syntax\Node\Node   $n1 First name
     * @param \Peast\Syntax\Node\Node   $n2 Second name
     * 
     * @return bool
     */
    
protected function isSameJSXElementName($n1$n2)
    {
        
$type $n1->getType();
        if (
$type !== $n2->getType()) {
            return 
false;
        } elseif (
$type === "JSXNamespacedName") {
            return 
$this->isSameJSXElementName(
                
$n1->getNamespace(), $n2->getNamespace()
            ) && 
$this->isSameJSXElementName(
                
$n1->getName(), $n2->getName()
            );
        } elseif (
$type === "JSXMemberExpression") {
            return 
$this->isSameJSXElementName(
                
$n1->getObject(), $n2->getObject()
            ) && 
$this->isSameJSXElementName(
                
$n1->getProperty(), $n2->getProperty()
            );
        }
        return 
$type === "JSXIdentifier" && $n1->getName() === $n2->getName();
    }
}
<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax;

/**
 * Comments registry class. Internal class used to manage comments
 * 
 * @author Marco Marchiò <[email protected]>
 */
class CommentsRegistry
{
    
/**
     * Map of the indices where nodes start
     * 
     * @var int 
     */
    
protected $nodesStartMap = array();
    
    
/**
     * Map of the indices where nodes end
     * 
     * @var int 
     */
    
protected $nodesEndMap = array();
    
    
/**
     * Comments buffer
     * 
     * @var array
     */
    
protected $buffer null;
    
    
/**
     * Last token index
     * 
     * @var int
     */
    
protected $lastTokenIndex null;
    
    
/**
     * Comments registry
     * 
     * @var array
     */
    
protected $registry = array();
    
    
/**
     * Class constructor
     * 
     * @param Parser    $parser     Parser
     */
    
public function __construct(Parser $parser)
    {
        
$parser->getEventsEmitter()
               ->
addListener("NodeCompleted", array($this"onNodeCompleted"))
               ->
addListener("EndParsing", array($this"onEndParsing"));
        
        
$parser->getScanner()->getEventsEmitter()
               ->
addListener("TokenConsumed", array($this"onTokenConsumed"))
               ->
addListener("EndReached", array($this"onTokenConsumed"))
               ->
addListener("FreezeState", array($this"onScannerFreezeState"))
               ->
addListener("ResetState", array($this"onScannerResetState"));
    }
    
    
/**
     * Listener called every time the scanner compose the array that represents
     * its current state
     * 
     * @param array   $state   State
     * 
     * @return void
     */
    
public function onScannerFreezeState(&$state)
    {
        
//Register the current last token index
        
$state["commentsLastTokenIndex"] = $this->lastTokenIndex;
    }
    
    
/**
     * Listener called every time the scanner reset its state using the given
     * array
     * 
     * @param array   $state   State
     * 
     * @return void
     */
    
public function onScannerResetState(&$state)
    {
        
//Reset the last token index and delete it from the state array
        
$this->lastTokenIndex $state["commentsLastTokenIndex"];
        unset(
$state["commentsLastTokenIndex"]);
    }
    
    
/**
     * Listener called every time a token is consumed and when the scanner
     * reaches the end of the source
     * 
     * @param Token|null   $token   Consumed token or null if the end has
     *                              been reached
     * 
     * @return void
     */
    
public function onTokenConsumed(Token $token null)
    {
        
//Check if it's a comment
        
if ($token && $token->type === Token::TYPE_COMMENT) {
            
//If there is not an open comments buffer, create it
            
if (!$this->buffer) {
                
$this->buffer = array(
                    
"prev" => $this->lastTokenIndex,
                    
"next" => null,
                    
"comments" => array()
                );
            }
            
//Add the comment token to the buffer
            
$this->buffer["comments"][] = $token;
        } else {
            
            if (
$token) {
                
$loc $token->location;
                
//Store the token end position
                
$this->lastTokenIndex $loc->end->getIndex();
                if (
$this->buffer) {
                    
//Fill the "next" key on the comments buffer with the token
                    //start position
                    
$this->buffer["next"] = $loc->start->getIndex();
                }
            }
            
            
//If there is an open comment buffer, close it and move it to the
            //registry
            
if ($buffer $this->buffer) {
                
//Use the location as key to add the group of comments to the
                //registry, in this way if comments are reprocessed they won't
                //be duplicated
                
$key implode("-", array(
                    
$buffer["prev"] !== null $buffer["prev"] : "",
                    
$buffer["next"] !== null $buffer["next"] : ""
                
));
                
$this->registry[$key] = $this->buffer;
                
$this->buffer null;
            }
            
        }
    }
    
    
/**
     * Listener called every time a node is completed by the parser
     * 
     * @param Node\Node   $node     Completed node
     * 
     * @return void
     */
    
public function onNodeCompleted(Node\Node $node)
    {
        
//Every time a node is completed, register its start and end indices
        //in the relative properties
        
$loc $node->location;
        foreach (array(
"Start""End") as $pos) {
            
$val $loc->{"get$pos"}()->getIndex();
            
$map = &$this->{"nodes{$pos}Map"};
            if (!isset(
$map[$val])) {
                
$map[$val] = array();
            }
            
$map[$val][] = $node;
        }
    }
    
    
/**
     * Listener called when parsing process ends
     * 
     * @return void
     */
    
public function onEndParsing()
    {
        
//Return if there are no comments to process
        
if ($this->registry) {
            
            
//Make sure nodes start indices map is sorted
            
ksort($this->nodesStartMap);
            
            
//Loop all comment groups in the registry
            
foreach ($this->registry as $group) {
                
$this->findNodeForCommentsGroup($group);
            }
        }
    }
    
    
/**
     * Finds the node to attach the given comments group
     * 
     * @param array    $group   Comments group
     * 
     * @return void
     */
    
public function findNodeForCommentsGroup($group)
    {
        
$next $group["next"];
        
$prev $group["prev"];
        
$comments $group["comments"];
        
$leading true;
        
        
//If the group of comments has a next token index that appears
        //in the map of start node indices, add the group to the
        //corresponding node's leading comments. This associates
        //comments that appear immediately before a node.
        //For example: /*comment*/ for (;;){}
        
if (isset($this->nodesStartMap[$next])) {
            
$nodes $this->nodesStartMap[$next];
        }
        
//If the group of comments has a previous token index that appears
        //in the map of end node indices, add the group to the
        //corresponding node's trailing comments. This associates
        //comments that appear immediately after a node.
        //For example: for (;;){} /*comment*/ 
        
elseif (isset($this->nodesEndMap[$prev])) {
            
$nodes $this->nodesEndMap[$prev];
            
$leading false;
        }
        
//Otherwise, find a node that wraps the comments position.
        //This associates inner comments:
        //For example: for /*comment*/ (;;){}
        
else {
            
//Calculate comments group boundaries
            
$start $comments[0]->location->start->getIndex();
            
$end $comments[count($comments) -1]->location->end->getIndex();
            
$nodes = array();
            
            
//Loop all the entries in the start index map
            
foreach ($this->nodesStartMap as $idx => $ns) {
                
//If the index is higher than the start index of the comments
                //group, stop
                
if ($idx $start) {
                    break;
                }
                foreach (
$ns as $node) {
                    
//Check if the comments group is inside node indices range
                    
if ($node->location->end->getIndex() >= $end) {
                        
$nodes[] = $node;
                    }
                }
            }
            
            
//If comments can't be associated with any node, associate it as
            //leading comments of the program, this happens when the source is
            //empty
            
if (!$nodes) {
                
$firstNode array_values($this->nodesStartMap);
                
$nodes = array($firstNode[0][0]);
            }
        }
        
        
//If there are multiple possible nodes to associate the comments to,
        //find the shortest one
        
if (count($nodes) > 1) {
            
usort($nodes, array($this"compareNodesLength"));
        }
        
$this->associateComments($nodes[0], $comments$leading);
    }
    
    
/**
     * Compares node length 
     * 
     * @param Node\Node  $node1     First node
     * @param Node\Node  $node2     Second node
     * 
     * @return int
     * 
     * @codeCoverageIgnore
     */
    
public function compareNodesLength($node1$node2)
    {
        
$loc1 $node1->location;
        
$length1 $loc1->end->getIndex() - $loc1->start->getIndex();
        
$loc2 $node2->location;
        
$length2 $loc2->end->getIndex() - $loc2->start->getIndex();
        
//If the nodes have the same length make sure to choose nodes
        //different from Program nodes
        
if ($length1 === $length2) {
            if (
$node1 instanceof Node\Program) {
                
$length1 += 1000;
            } elseif (
$node2 instanceof Node\Program) {
                
$length2 += 1000;
            }
        }
        return 
$length1 $length2 ? -1;
    }
    
    
/**
     * Adds comments to the given node
     * 
     * @param Node\Node     $node       Node
     * @param array         $comments   Array of comments to add
     * @param bool          $leading    True to add comments as leading comments
     *                                  or false to add them as trailing comments
     * 
     * @return void
     */
    
public function associateComments($node$comments$leading)
    {
        
$fn = ($leading "Leading" "Trailing") . "Comments";
        
$currentComments $node->{"get$fn"}();
        foreach (
$comments as $comment) {
            
$loc $comment->location;
            
$commentNode = new Node\Comment;
            
$commentNode->location->start $loc->start;
            
$commentNode->location->end $loc->end;
            
$commentNode->setRawText($comment->value);
            
$currentComments[] = $commentNode;
        }
        
$node->{"set$fn"}($currentComments);
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a switch statement.
 * For example: switch (test) {}
 * 
 * @author Marco Marchiò <[email protected]>
 */
class SwitchStatement extends Node implements Statement
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"discriminant" => true,
        
"cases" => true
    
);
    
    
/**
     * Discriminant expression
     * 
     * @var Expression 
     */
    
protected $discriminant;
    
    
/**
     * Cases array
     * 
     * @var SwitchCase[] 
     */
    
protected $cases = array();
    
    
/**
     * Returns the discriminant expression
     * 
     * @return Expression
     */
    
public function getDiscriminant()
    {
        return 
$this->discriminant;
    }
    
    
/**
     * Sets the discriminant expression
     * 
     * @param Expression $discriminant Discriminant expression
     * 
     * @return $this
     */
    
public function setDiscriminant(Expression $discriminant)
    {
        
$this->discriminant $discriminant;
        return 
$this;
    }
    
    
/**
     * Returns the cases array
     * 
     * @return SwitchCase[]
     */
    
public function getCases()
    {
        return 
$this->cases;
    }
    
    
/**
     * Sets the cases array
     * 
     * @param SwitchCase[] $cases Cases array
     * 
     * @return $this
     */
    
public function setCases($cases)
    {
        
$this->assertArrayOf($cases"SwitchCase");
        
$this->cases $cases;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a "new" expression.
 * For example: new test()
 * 
 * @author Marco Marchiò <[email protected]>
 */
class NewExpression extends CallExpression
{
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

use 
Peast\Query;
use 
Peast\Selector;

/**
 * Root node for scripts and modules.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class Program extends Node
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"body" => true,
        
"sourceType" => false
    
);
    
    
/**
     * Source type that is one of the source type constants in the Peast class
     * 
     * @var string 
     */
    
protected $sourceType \Peast\Peast::SOURCE_TYPE_SCRIPT;
    
    
/**
     * Program's body
     * 
     * @var Statement[]|ModuleDeclaration[]
     */
    
protected $body = array();
    
    
/**
     * Returns the source type that is one of the source type constants in the
     * Peast class
     * 
     * @return string
     */
    
public function getSourceType()
    {
        return 
$this->sourceType;
    }
    
    
/**
     * Sets the source type that is one of the source type constants in the
     * Peast class
     * 
     * @param string $sourceType Source type
     * 
     * @return $this
     */
    
public function setSourceType($sourceType)
    {
        
$this->sourceType $sourceType;
        return 
$this;
    }
    
    
/**
     * Returns the program's body
     * 
     * @return Statement[]|ModuleDeclaration[]
     */
    
public function getBody()
    {
        return 
$this->body;
    }
    
    
/**
     * Sets the program's body
     * 
     * @param Statement[]|ModuleDeclaration[] $body Program's body
     * 
     * @return $this
     */
    
public function setBody($body)
    {
        
$this->assertArrayOf($body, array("Statement""ModuleDeclaration"));
        
$this->body $body;
        return 
$this;
    }

    
/**
     * Finds nodes matching the given selector.
     *
     * @param string    $selector   Selector
     * @param array     $options    Options array. See Query class
     *                              documentation for available options
     *
     * @return Query
     *
     * @throws Selector\Exception
     */
    
public function query($selector$options = array())
    {
        
$query = new Query($this$options);
        return 
$query->find($selector);
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a specifier in an import declaration.
 * For example "{a}" in: import {a} from "test"
 * 
 * @author Marco Marchiò <[email protected]>
 */
class ImportSpecifier extends ModuleSpecifier
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"imported" => true
    
);
    
    
/**
     * Imported identifier
     * 
     * @var Identifier|StringLiteral
     */
    
protected $imported;
    
    
/**
     * Returns the imported identifier
     * 
     * @return Identifier|StringLiteral
     */
    
public function getImported()
    {
        return 
$this->imported;
    }
    
    
/**
     * Sets the imported identifier
     * 
     * @param Identifier|StringLiteral $imported Imported identifier
     * 
     * @return $this
     */
    
public function setImported($imported)
    {
        
$this->assertType($imported, array("Identifier""StringLiteral"));
        
$this->imported $imported;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents the throw statement.
 * For example: throw err
 * 
 * @author Marco Marchiò <[email protected]>
 */
class ThrowStatement extends Node implements Statement
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"argument" => true
    
);
    
    
/**
     * The thrown expression
     * 
     * @var Expression 
     */
    
protected $argument;
    
    
/**
     * Returns the thrown expression
     * 
     * @return Expression
     */
    
public function getArgument()
    {
        return 
$this->argument;
    }
    
    
/**
     * Sets the thrown expression
     * 
     * @param Expression $argument The node to set
     * 
     * @return $this
     */
    
public function setArgument(Expression $argument)
    {
        
$this->argument $argument;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a for-in statement.
 * For example: for (var a in b) {}
 * 
 * @author Marco Marchiò <[email protected]>
 */
class ForInStatement extends Node implements Statement
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"left" => true,
        
"right" => true,
        
"body" => true
    
);
    
    
/**
     * Iteration variable
     * 
     * @var VariableDeclaration|Expression|Pattern
     */
    
protected $left;
    
    
/**
     * Iterated object
     * 
     * @var Expression
     */
    
protected $right;
    
    
/**
     * Loop body
     * 
     * @var Statement 
     */
    
protected $body;
    
    
/**
     * Returns the iteration variable
     * 
     * @return VariableDeclaration|Expression|Pattern
     */
    
public function getLeft()
    {
        return 
$this->left;
    }
    
    
/**
     * Sets the iteration variable
     * 
     * @param VariableDeclaration|Expression|Pattern $left Iteration variable
     * 
     * @return $this
     */
    
public function setLeft($left)
    {
        
$this->assertType(
            
$left, array("VariableDeclaration""Expression""Pattern")
        );
        
$this->left $left;
        return 
$this;
    }
    
    
/**
     * Returns the iterated object
     * 
     * @return Expression
     */
    
public function getRight()
    {
        return 
$this->right;
    }
    
    
/**
     * Sets the iterated object
     * 
     * @param Expression $right Iterated object
     * 
     * @return $this
     */
    
public function setRight(Expression $right)
    {
        
$this->right $right;
        return 
$this;
    }
    
    
/**
     * Returns the loop body
     * 
     * @return Statement
     */
    
public function getBody()
    {
        return 
$this->body;
    }
    
    
/**
     * Sets the loop body
     * 
     * @param Statement $body Loop body
     * 
     * @return $this
     */
    
public function setBody(Statement $body)
    {
        
$this->body $body;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a property definition in class body.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class PropertyDefinition extends Node
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"key" => true,
        
"value" => true,
        
"computed" => false,
        
"static" => false
    
);
    
    
/**
     * Property key
     * 
     * @var Expression|PrivateIdentifier
     */
    
protected $key;
    
    
/**
     * Optional property value
     * 
     * @var Expression|null
     */
    
protected $value;
    
    
/**
     * Property computed flag that is true when the property is declared using
     * the square brackets syntax
     * 
     * @var bool 
     */
    
protected $computed false;
    
    
/**
     * Property static flag that is true when the property is declared static
     * 
     * @var bool 
     */
    
protected $static false;
    
    
/**
     * Returns the property key
     * 
     * @return Expression|PrivateIdentifier
     */
    
public function getKey()
    {
        return 
$this->key;
    }
    
    
/**
     * Sets the property key
     * 
     * @param Expression|PrivateIdentifier $key Property key
     * 
     * @return $this
     */
    
public function setKey($key)
    {
        
$this->assertType($key, array("Expression""PrivateIdentifier"));
        
$this->key $key;
        return 
$this;
    }
    
    
/**
     * Returns the property value
     * 
     * @return Expression|null
     */
    
public function getValue()
    {
        return 
$this->value;
    }
    
    
/**
     * Sets the property value
     * 
     * @param Expression|null $value Property value
     * 
     * @return $this
     */
    
public function setValue($value)
    {
        
$this->assertType($value"Expression"true);
        
$this->value $value;
        return 
$this;
    }
    
    
/**
     * Returns the property computed flag that is true when the property is
     * declared using the square brackets syntax
     * 
     * @return bool
     */
    
public function getComputed()
    {
        return 
$this->computed;
    }
    
    
/**
     * Sets the property computed flag that is true when the property is
     * declared using the square brackets syntax
     * 
     * @param bool $computed Property computed flag
     * 
     * @return $this
     */
    
public function setComputed($computed)
    {
        
$this->computed = (bool) $computed;
        return 
$this;
    }
    
    
/**
     * Returns the property static flag that is true when the property is
     * declared static
     * 
     * @return bool
     */
    
public function getStatic()
    {
        return 
$this->static;
    }
    
    
/**
     * Sets the property static flag that is true when the property is
     * declared static
     * 
     * @param bool $static Property static flag
     * 
     * @return $this
     */
    
public function setStatic($static)
    {
        
$this->static = (bool) $static;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a namespace import specifier.
 * For example "* as test" in: import * as test from "test.js".
 * 
 * @author Marco Marchiò <[email protected]>
 */
class ImportDefaultSpecifier extends ModuleSpecifier
{
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a with statement.
 * For example: with (test) {}
 * 
 * @author Marco Marchiò <[email protected]>
 */
class WithStatement extends Node implements Statement
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"object" => true,
        
"body" => true
    
);
    
    
/**
     * The statement subject
     * 
     * @var Expression 
     */
    
protected $object;
    
    
/**
     * The statement body
     * 
     * @var Expression
     */
    
protected $body;
    
    
/**
     * Returns the statement subject
     * 
     * @return Expression
     */
    
public function getObject()
    {
        return 
$this->object;
    }
    
    
/**
     * Sets the statement subject
     * 
     * @param Expression $object Statement subject
     * 
     * @return $this
     */
    
public function setObject(Expression $object)
    {
        
$this->object $object;
        return 
$this;
    }
    
    
/**
     * Returns the statement body
     * 
     * @return Expression
     */
    
public function getBody()
    {
        return 
$this->body;
    }
    
    
/**
     * Sets the statement body
     * 
     * @param Statement $body Statement body
     * 
     * @return $this
     */
    
public function setBody(Statement $body)
    {
        
$this->body $body;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * Abstract class for functions.
 * 
 * @author Marco Marchiò <[email protected]>
 */
abstract class Function_ extends Node
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"id" => true,
        
"params" => true,
        
"body" => true,
        
"generator" => false,
        
"async" => false
    
);
    
    
/**
     * Function name
     * 
     * @var Identifier 
     */
    
protected $id;
    
    
/**
     * Function parameters array
     * 
     * @var Pattern[] 
     */
    
protected $params = array();
    
    
/**
     * Function body
     * 
     * @var BlockStatement 
     */
    
protected $body;
    
    
/**
     * Generator flag that is true when the function is a generator
     * 
     * @var bool 
     */
    
protected $generator false;
    
    
/**
     * Async flag that is true when it is an async function
     * 
     * @var bool 
     */
    
protected $async false;
    
    
/**
     * Returns function name
     * 
     * @return Identifier
     */
    
public function getId()
    {
        return 
$this->id;
    }
    
    
/**
     * Sets function name
     * 
     * @param Identifier $id Function name
     * 
     * @return $this
     */
    
public function setId($id)
    {
        
$this->assertType($id"Identifier"true);
        
$this->id $id;
        return 
$this;
    }
    
    
/**
     * Returns function parameters array
     * 
     * @return Pattern[]
     */
    
public function getParams()
    {
        return 
$this->params;
    }
    
    
/**
     * Sets function parameters array
     * 
     * @param Pattern[] $params Function parameters array
     * 
     * @return $this
     */
    
public function setParams($params)
    {
        
$this->assertArrayOf($params"Pattern");
        
$this->params $params;
        return 
$this;
    }
    
    
/**
     * Returns function body
     * 
     * @return BlockStatement
     */
    
public function getBody()
    {
        return 
$this->body;
    }
    
    
/**
     * Sets function body
     * 
     * @param BlockStatement $body Function body
     * 
     * @return $this
     */
    
public function setBody($body)
    {
        
$this->assertType($body"BlockStatement");
        
$this->body $body;
        return 
$this;
    }
    
    
/**
     * Returns the generator flag that is true when the function is a generator
     * 
     * @return bool
     */
    
public function getGenerator()
    {
        return 
$this->generator;
    }
    
    
/**
     * Sets the generator flag that is true when the function is a generator
     * 
     * @param bool $generator Generator flag
     * 
     * @return $this
     */
    
public function setGenerator($generator)
    {
        
$this->generator = (bool) $generator;
        return 
$this;
    }
    
    
/**
     * Returns the async flag that is true when it is an async function
     * 
     * @return bool
     */
    
public function getAsync()
    {
        return 
$this->async;
    }
    
    
/**
     * Sets the async flag that is true when it is an async function
     * 
     * @param bool $async Async flag
     * 
     * @return $this
     */
    
public function setAsync($async)
    {
        
$this->async = (bool) $async;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a binary expression.
 * For example: a + b
 * 
 * @author Marco Marchiò <[email protected]>
 */
class BinaryExpression extends Node implements Expression
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"left" => true,
        
"operator" => false,
        
"right" => true
    
);
    
    
/**
     * Operator
     * 
     * @var string
     */
    
protected $operator;
    
    
/**
     * Left expression
     * 
     * @var Expression|PrivateIdentifier
     */
    
protected $left;
    
    
/**
     * Right expression
     * 
     * @var Expression
     */
    
protected $right;
    
    
/**
     * Returns the operator
     * 
     * @return string
     */
    
public function getOperator()
    {
        return 
$this->operator;
    }
    
    
/**
     * Sets the operator
     * 
     * @param string $operator Operator
     * 
     * @return $this
     */
    
public function setOperator($operator)
    {
        
$this->operator $operator;
        return 
$this;
    }
    
    
/**
     * Returns the left expression
     * 
     * @return Expression|PrivateIdentifier
     */
    
public function getLeft()
    {
        return 
$this->left;
    }
    
    
/**
     * Sets the left expression
     * 
     * @param Expression|PrivateIdentifier $left Left expression
     * 
     * @return $this
     */
    
public function setLeft($left)
    {
        
$this->assertType($left, array("Expression""PrivateIdentifier"));
        
$this->left $left;
        return 
$this;
    }
    
    
/**
     * Returns the right expression
     * 
     * @return Expression
     */
    
public function getRight()
    {
        return 
$this->right;
    }
    
    
/**
     * Sets the right expression
     * 
     * @param Expression $right Right expression
     * 
     * @return $this
     */
    
public function setRight(Expression $right)
    {
        
$this->right $right;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a for statement.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class ForStatement extends Node implements Statement
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"init" => true,
        
"test" => true,
        
"update" => true,
        
"body" => true
    
);
    
    
/**
     * Initializer
     * 
     * @var VariableDeclaration|Expression
     */
    
protected $init;
    
    
/**
     * Test expression
     * 
     * @var Expression 
     */
    
protected $test;
    
    
/**
     * Update expression
     * 
     * @var Expression 
     */
    
protected $update;
    
    
/**
     * Loop body
     * 
     * @var Statement 
     */
    
protected $body;
    
    
/**
     * Returns the initializer
     * 
     * @return VariableDeclaration|Expression
     */
    
public function getInit()
    {
        return 
$this->init;
    }
    
    
/**
     * Sets the initializer
     * 
     * @param VariableDeclaration|Expression $init Initializer
     * 
     * @return $this
     */
    
public function setInit($init)
    {
        
$this->assertType(
            
$init,
            array(
"VariableDeclaration""Expression"),
            
true
        
);
        
$this->init $init;
        return 
$this;
    }
    
    
/**
     * Returns the test expression
     * 
     * @return Expression
     */
    
public function getTest()
    {
        return 
$this->test;
    }
    
    
/**
     * Sets the test expression
     * 
     * @param Expression $test Test expression
     * 
     * @return $this
     */
    
public function setTest($test)
    {
        
$this->assertType($test"Expression"true);
        
$this->test $test;
        return 
$this;
    }
    
    
/**
     * Returns the update expression
     * 
     * @return Expression
     */
    
public function getUpdate()
    {
        return 
$this->update;
    }
    
    
/**
     * Sets the update expression
     * 
     * @param Expression $update Update expression
     * 
     * @return $this
     */
    
public function setUpdate($update)
    {
        
$this->assertType($update"Expression"true);
        
$this->update $update;
        return 
$this;
    }
    
    
/**
     * Returns the loop body
     * 
     * @return Statement
     */
    
public function getBody()
    {
        return 
$this->body;
    }
    
    
/**
     * Sets the loop body
     * 
     * @param Statement $body Loop body
     * 
     * @return $this
     */
    
public function setBody(Statement $body)
    {
        
$this->body $body;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents the rest element in array binding patterns or function
 * parameters.
 * For example "...rest" in: [a, ...rest] = b
 * 
 * @author Marco Marchiò <[email protected]>
 */
class RestElement extends Node implements Pattern
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"argument" => true
    
);
    
    
/**
     * The node's argument
     * 
     * @var Pattern
     */
    
protected $argument;
    
    
/**
     * Returns the node's argument
     * 
     * @return Pattern
     */
    
public function getArgument()
    {
        return 
$this->argument;
    }
    
    
/**
     * Sets the node's argument
     * 
     * @param Pattern $argument Node's argument
     * 
     * @return $this
     */
    
public function setArgument(Pattern $argument)
    {
        
$this->argument $argument;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a numeric literal.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class NumericLiteral extends Literal
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"format" => false
    
);
    
    
//Format constants
    /**
     * Decimal number format
     */
    
const DECIMAL "decimal";
    
    
/**
     * Hexadecimal number format
     */
    
const HEXADECIMAL "hexadecimal";
    
    
/**
     * Octal number format
     */
    
const OCTAL "octal";
    
    
/**
     * Binary number format
     */
    
const BINARY "binary";
    
    
/**
     * Node's numeric format
     * 
     * @var string
     */
    
protected $format self::DECIMAL;
    
    
/**
     * Numeric forms conversion rules
     * 
     * @var array
     */
    
protected $forms = array(
        
"b" => array(
            
"check" => "/^0b[01]+[01_]*$/i",
            
"conv" => "bindec",
            
"format" => self::BINARY
        
),
        
"o" => array(
            
"check" => "/^0o[0-7]+[0-7_]*$/i",
            
"conv" => "octdec",
            
"format" => self::OCTAL
        
),
        
"x" => array(
            
"check" => "/^0x[0-9a-f]+[0-9a-f_]*$/i",
            
"conv" => "hexdec",
            
"format" => self::HEXADECIMAL
        
),
    );
    
    
/**
     * Sets node's value
     * 
     * @param float $value Value
     * 
     * @return $this
     */
    
public function setValue($value)
    {
        
$value = (float) $value;
        
$intValue = (int) $value;
        if (
$value == $intValue) {
            
$value $intValue;
        }
        
$this->value $value;
        
//Force recalculation of the raw value
        
return $this->setFormat($this->format);
    }
    
    
/**
     * Sets node's raw value
     * 
     * @param mixed $raw Raw value
     * 
     * @return $this
     * 
     * @throws \Exception
     */
    
public function setRaw($raw)
    {
        
$value $raw;
        
$format self::DECIMAL;
        if (
is_string($value) && $value !== "") {
            
//Hexadecimal, binary or octal
            
$startZero $value[0] === "0";
            
$form $startZero && isset($value[1]) ? strtolower($value[1]) : null;
            
//Numeric separator cannot appear at the beginning or at the end of the number
            
if (preg_match("/^_|_$/"$value)) {
                throw new 
\Exception("Invalid numeric value");
            } elseif (isset(
$this->forms[$form])) {
                
$formDef $this->forms[$form];
                if (!
preg_match($formDef["check"], $value)) {
                    throw new 
\Exception("Invalid " $formDef["format"]);
                }
                
$value str_replace("_"""$value);
                
$value $formDef["conv"]($value);
                
$format $formDef["format"];
            } elseif (
$startZero && preg_match("/^0[0-7_]+$/"$value)) {
                
//Legacy octal form
                
$value str_replace("_"""$value);
                
$value octdec($value);
                
$format self::OCTAL;
            } elseif (
                
preg_match("/^([\d_]*\.?[\d_]*)(?:e[+\-]?[\d_]+)?$/i"$value$match) &&
                
$match[1] !== "" &&
                
$match[1] !== "." &&
                !
preg_match("/_e|e[+-]?_|_$/"$value)
            ) {
                
$value str_replace("_"""$value);
            } else {
                throw new 
\Exception("Invalid numeric value");
            }
        } elseif (!
is_int($value) && !is_float($value)) {
            throw new 
\Exception("Invalid numeric value");
        }
        
$value = (float) $value;
        
$intValue = (int) $value;
        if (
$value == $intValue) {
            
$value $intValue;
        }
        
$this->format $format;
        
$this->value $value;
        
$this->raw $raw;
        return 
$this;
    }
    
    
/**
     * Returns node's numeric format
     * 
     * @return string
     */
    
public function getFormat()
    {
        return 
$this->format;
    }
    
    
/**
     * Sets node's numeric format
     * 
     * @param string $format Format, one of the format constants
     * 
     * @return $this
     */
    
public function setFormat($format)
    {
        
$this->format $format;
        switch (
$format) {
            case 
self::BINARY:
                
$this->raw "0b" decbin($this->value);
            break;
            case 
self::OCTAL:
                
$this->raw "0o" decoct($this->value);
            break;
            case 
self::HEXADECIMAL:
                
$this->raw "0x" dechex($this->value);
            break;
            default:
                
$this->raw = (string) $this->value;
            break;
        }
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a unary expression.
 * For example: !a
 * 
 * @author Marco Marchiò <[email protected]>
 */
class UnaryExpression extends Node implements Expression
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"argument" => true,
        
"operator" => false,
        
"prefix" => false
    
);
    
    
/**
     * Expression's operator
     * 
     * @var string
     */
    
protected $operator;
    
    
/**
     * Prefix flag that is always true since the operator precedes the argument
     * 
     * @var bool
     */
    
protected $prefix true;
    
    
/**
     * Expression's argument
     * 
     * @var Expression 
     */
    
protected $argument;
    
    
/**
     * Returns the expression's operator
     * 
     * @return string
     */
    
public function getOperator()
    {
        return 
$this->operator;
    }
    
    
/**
     * Sets the expression's operator
     * 
     * @param string $operator Operator
     * 
     * @return $this
     */
    
public function setOperator($operator)
    {
        
$this->operator $operator;
        return 
$this;
    }
    
    
/**
     * Returns the prefix flag that is always true since the operator precedes
     * the argument
     * 
     * @return bool
     */
    
public function getPrefix()
    {
        return 
$this->prefix;
    }
    
    
/**
     * This method does nothing since the prefix flag is always true for
     * unary expressions
     * 
     * @param bool $prefix Prefix flag
     * 
     * @return $this
     * 
     * @codeCoverageIgnore
     */
    
public function setPrefix($prefix)
    {
        return 
$this;
    }
    
    
/**
     * Returns the expression's argument
     * 
     * @return Expression
     */
    
public function getArgument()
    {
        return 
$this->argument;
    }
    
    
/**
     * Sets the expression's argument
     * 
     * @param Expression $argument Argument
     * 
     * @return $this
     */
    
public function setArgument(Expression $argument)
    {
        
$this->argument $argument;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a try-catch statement.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class TryStatement extends Node implements Statement
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"block" => true,
        
"handler" => true,
        
"finalizer" => true
    
);
    
    
/**
     * Wrapped block
     * 
     * @var BlockStatement
     */
    
protected $block;
    
    
/**
     * Catch clause
     * 
     * @var CatchClause 
     */
    
protected $handler;
    
    
/**
     * "finally" block
     * 
     * @var BlockStatement 
     */
    
protected $finalizer;
    
    
/**
     * Returns the wrapped block
     * 
     * @return BlockStatement
     */
    
public function getBlock()
    {
        return 
$this->block;
    }
    
    
/**
     * Sets the wrapped block
     * 
     * @param BlockStatement $block Wrapped block
     * 
     * @return $this
     */
    
public function setBlock(BlockStatement $block)
    {
        
$this->block $block;
        return 
$this;
    }
    
    
/**
     * Returns the catch clause
     * 
     * @return CatchClause
     */
    
public function getHandler()
    {
        return 
$this->handler;
    }
    
    
/**
     * Sets the catch clause
     * 
     * @param CatchClause $handler Catch clause
     * 
     * @return $this
     */
    
public function setHandler($handler)
    {
        
$this->assertType($handler"CatchClause"true);
        
$this->handler $handler;
        return 
$this;
    }
    
    
/**
     * Returns the "finally" block
     * 
     * @return BlockStatement
     */
    
public function getFinalizer()
    {
        return 
$this->finalizer;
    }
    
    
/**
     * Sets the "finally" block
     * 
     * @param BlockStatement $finalizer The "finally" block
     * 
     * @return $this
     */
    
public function setFinalizer($finalizer)
    {
        
$this->assertType($finalizer"BlockStatement"true);
        
$this->finalizer $finalizer;
        return 
$this;
    }
}
<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a default import specifier.
 * For example "test" in: import test from "test.js".
 * 
 * @author Marco Marchiò <[email protected]>
 */
class ImportNamespaceSpecifier extends ModuleSpecifier
{
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents an await expression.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class AwaitExpression extends Node implements Expression
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"argument" => true
    
);
    
    
/**
     * Expression's argument
     * 
     * @var Expression 
     */
    
protected $argument;
    
    
/**
     * Returns the expression's argument
     * 
     * @return Expression
     */
    
public function getArgument()
    {
        return 
$this->argument;
    }
    
    
/**
     * Sets the expression's argument
     * 
     * @param Expression $argument Argument
     * 
     * @return $this
     */
    
public function setArgument(Expression $argument)
    {
        
$this->argument $argument;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents an export named declaration.
 * For example: export {foo} from "bar"
 * 
 * @author Marco Marchiò <[email protected]>
 */
class ExportNamedDeclaration extends Node implements ModuleDeclaration
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"declaration" => true,
        
"specifiers" => true,
        
"source" => true
    
);
    
    
/**
     * Exported declaration
     * 
     * @var Declaration 
     */
    
protected $declaration;
    
    
/**
     * Exported specifiers
     * 
     * @var ExportSpecifier[] 
     */
    
protected $specifiers = array();
    
    
/**
     * The export source
     * 
     * @var Literal 
     */
    
protected $source;
    
    
/**
     * Returns the exported declaration
     * 
     * @return Declaration
     */
    
public function getDeclaration()
    {
        return 
$this->declaration;
    }
    
    
/**
     * Sets the exported declaration
     * 
     * @param Declaration $declaration Exported declaration
     * 
     * @return $this
     */
    
public function setDeclaration($declaration)
    {
        
$this->assertType($declaration"Declaration"true);
        
$this->declaration $declaration;
        return 
$this;
    }
    
    
/**
     * Return the exported specifiers
     * 
     * @return ExportSpecifier[]
     */
    
public function getSpecifiers()
    {
        return 
$this->specifiers;
    }
    
    
/**
     * Sets the exported specifiers
     * 
     * @param ExportSpecifier[] $specifiers Exported specifiers
     * 
     * @return $this
     */
    
public function setSpecifiers($specifiers)
    {
        
$this->assertArrayOf($specifiers"ExportSpecifier");
        
$this->specifiers $specifiers;
        return 
$this;
    }
    
    
/**
     * Returns the export source
     * 
     * @return Literal
     */
    
public function getSource()
    {
        return 
$this->source;
    }
    
    
/**
     * Sets the export source
     * 
     * @param Literal $source Export source
     * 
     * @return $this
     */
    
public function setSource($source)
    {
        
$this->assertType($source"Literal"true);
        
$this->source $source;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents an array literal.
 * For example: [a, b, c]
 * 
 * @author Marco Marchiò <[email protected]>
 */
class ArrayExpression extends Node implements Expression
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"elements" => true
    
);
    
    
/**
     * Array elements
     * 
     * @var Expression[]|SpreadElement[]
     */
    
protected $elements = array();
    
    
/**
     * Returns array elements
     * 
     * @return Expression[]|SpreadElement[]
     */
    
public function getElements()
    {
        return 
$this->elements;
    }
    
    
/**
     * Sets array elements
     * 
     * @param Expression[]|SpreadElement[] $elements Array elements to set
     * 
     * @return $this
     */
    
public function setElements($elements)
    {
        
$this->assertArrayOf(
            
$elements,
            array(
"Expression""SpreadElement"),
            
true
        
);
        
$this->elements $elements;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a function declaration
 * For example: function test () {}
 * 
 * @author Marco Marchiò <[email protected]>
 */
class FunctionDeclaration extends Function_ implements Declaration
{
    
/**
     * Sets the function identifier
     * 
     * @param Identifier $id Function identifier
     * 
     * @return $this
     */
    
public function setId($id)
    {
        
$this->assertType($id"Identifier");
        
$this->id $id;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a meta property.
 * For example: new.target
 * 
 * @author Marco Marchiò <[email protected]>
 */
class MetaProperty extends Node implements Expression
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"meta" => false,
        
"property" => false
    
);
    
    
/**
     * Subject
     * 
     * @var string 
     */
    
protected $meta;
    
    
/**
     * Property
     * 
     * @var string 
     */
    
protected $property;
    
    
/**
     * Returns the subject
     * 
     * @return string
     */
    
public function getMeta()
    {
        return 
$this->meta;
    }
    
    
/**
     * Sets the subject
     * 
     * @param string $meta Subject
     * 
     * @return $this
     */
    
public function setMeta($meta)
    {
        
$this->meta $meta;
        return 
$this;
    }
    
    
/**
     * Returns the property
     * 
     * @return string
     */
    
public function getProperty()
    {
        return 
$this->property;
    }
    
    
/**
     * Sets the property
     * 
     * @param string $property Property
     * 
     * @return $this
     */
    
public function setProperty($property)
    {
        
$this->property $property;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents an if statement.
 * For example: if (test) {} else {}
 * 
 * @author Marco Marchiò <[email protected]>
 */
class IfStatement extends Node implements Statement
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"test" => true,
        
"consequent" => true,
        
"alternate" => true
    
);
    
    
/**
     * The test expression
     * 
     * @var Expression 
     */
    
protected $test;
    
    
/**
     * The statement that is activated if the test expression is true
     * 
     * @var Statement|FunctionDeclaration
     */
    
protected $consequent;
    
    
/**
     * The "else" statement
     * 
     * @var Statement 
     */
    
protected $alternate;
    
    
/**
     * Returns the test expression
     * 
     * @return Expression
     */
    
public function getTest()
    {
        return 
$this->test;
    }
    
    
/**
     * Sets the test expression
     * 
     * @param Expression $test Test expression
     * 
     * @return $this
     */
    
public function setTest(Expression $test)
    {
        
$this->test $test;
        return 
$this;
    }
    
    
/**
     * Returns the statement that is activated if the test expression is true
     * 
     * @return Statement|FunctionDeclaration
     */
    
public function getConsequent()
    {
        return 
$this->consequent;
    }
    
    
/**
     * Sets the statement that is activated if the test expression is true
     * 
     * @param Statement|FunctionDeclaration $consequent The consequent expression
     * 
     * @return $this
     */
    
public function setConsequent($consequent)
    {
        
$this->assertType(
            
$consequent,
            array(
"Statement""FunctionDeclaration"),
            
true
        
);
        
$this->consequent $consequent;
        return 
$this;
    }
    
    
/**
     * Returns the "else" statement
     * 
     * @return Statement
     */
    
public function getAlternate()
    {
        return 
$this->alternate;
    }
    
    
/**
     * Sets the "else" statement
     * 
     * @param Statement|FunctionDeclaration $alternate The "else" statement
     * 
     * @return $this
     */
    
public function setAlternate($alternate)
    {
        
$this->assertType(
            
$alternate,
            array(
"Statement""FunctionDeclaration"),
            
true
        
);
        
$this->alternate $alternate;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a class declaration.
 * For example: class test {}
 * 
 * @author Marco Marchiò <[email protected]>
 */
class ClassDeclaration extends Class_ implements Declaration
{
    
/**
     * Sets the class identifier
     * 
     * @param Identifier $id Class identifier
     * 
     * @return $this
     */
    
public function setId($id)
    {
        
$this->assertType($id"Identifier");
        
$this->id $id;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a declaration in a VariableDeclaration node.
 * For example "a=1" in: var a = 1
 * 
 * @author Marco Marchiò <[email protected]>
 */
class VariableDeclarator extends Node
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"id" => true,
        
"init" => true
    
);
    
    
/**
     * Declaration identifier or pattern
     * 
     * @var Pattern 
     */
    
protected $id;
    
    
/**
     * Optional initializer
     * 
     * @var Expression 
     */
    
protected $init;
    
    
/**
     * Returns the declaration identifier or pattern
     * 
     * @return Pattern
     */
    
public function getId()
    {
        return 
$this->id;
    }
    
    
/**
     * Sets the declaration identifier or pattern
     * 
     * @param Pattern $id Declaration identifier or pattern
     * 
     * @return $this
     */
    
public function setId(Pattern $id)
    {
        
$this->id $id;
        return 
$this;
    }
    
    
/**
     * Returns the initializer
     * 
     * @return Expression
     */
    
public function getInit()
    {
        return 
$this->init;
    }
    
    
/**
     * Sets the initializer
     * 
     * @param Expression $init Initializer
     * 
     * @return $this
     */
    
public function setInit($init)
    {
        
$this->assertType($init"Expression"true);
        
$this->init $init;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a private identifier.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class PrivateIdentifier extends Node
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"name" => false
    
);
    
    
/**
     * The identifier's name
     * 
     * @var string
     */
    
protected $name;
    
    
/**
     * Returns the identifier's name
     * 
     * @return string
     */
    
public function getName()
    {
        return 
$this->name;
    }
    
    
/**
     * Sets the identifier's name
     * 
     * @param string $name The name to set
     * 
     * @return $this
     */
    
public function setName($name)
    {
        
$this->name $name;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node\JSX;

use 
Peast\Syntax\Node\Identifier;

/**
 * A node that represents a JSX identifier.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class JSXIdentifier extends Identifier
{
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node\JSX;

use 
Peast\Syntax\Node\Node;

/**
 * A node that represents a JSX attribute.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class JSXAttribute extends Node
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"name" => true,
        
"value" => true
    
);
    
    
/**
     * Attribute name
     * 
     * @var JSXIdentifier|JSXNamespacedName
     */
    
protected $name;
    
    
/**
     * Attribute value
     * 
     * @var Node|null
     */
    
protected $value;
    
    
/**
     * Returns the attribute name
     * 
     * @return Node
     */
    
public function getName()
    {
        return 
$this->name;
    }
    
    
/**
     * Sets the attribute name
     * 
     * @param JSXIdentifier|JSXNamespacedName $name Attribute name
     * 
     * @return $this
     */
    
public function setName($name)
    {
        
$this->assertType($name, array("JSX\\JSXIdentifier""JSX\\JSXNamespacedName"));
        
$this->name $name;
        return 
$this;
    }
    
    
/**
     * Returns the attribute value
     * 
     * @return Node|null
     */
    
public function getValue()
    {
        return 
$this->value;
    }
    
    
/**
     * Sets the attribute value
     * 
     * @param Node|null $value Attribute value
     * 
     * @return $this
     */
    
public function setValue($value)
    {
        
$this->assertType(
            
$value,
            array(
                
"Literal""JSX\\JSXExpressionContainer",
                
"JSX\\JSXElement""JSX\\JSXFragment"
            
),
            
true
        
);
        
$this->value $value;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node\JSX;

use 
Peast\Syntax\Node\SpreadElement;

/**
 * A node that represents a JSX spread attribute.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class JSXSpreadAttribute extends SpreadElement
{
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node\JSX;

/**
 * A node that represents a JSX closing element tag.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class JSXClosingElement extends JSXBoundaryElement
{
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node\JSX;

use 
Peast\Syntax\Node\Node;

/**
 * A node that represents an expression container in JSX.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class JSXExpressionContainer extends Node
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"expression" => true
    
);
    
    
/**
     * The wrapped expression
     * 
     * @var \Peast\Syntax\Node\Expression|JSXEmptyExpression
     */
    
protected $expression;
    
    
/**
     * Returns the wrapped expression
     * 
     * @return \Peast\Syntax\Node\Expression|JSXEmptyExpression
     */
    
public function getExpression()
    {
        return 
$this->expression;
    }
    
    
/**
     * Sets the wrapped expression
     * 
     * @param \Peast\Syntax\Node\Expression|JSXEmptyExpression $expression Wrapped
     *                                                                     expression
     * 
     * @return $this
     */
    
public function setExpression($expression)
    {
        
$this->assertType(
            
$expression,
            array(
"Expression""JSX\\JSXEmptyExpression")
        );
        
$this->expression $expression;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node\JSX;

use 
Peast\Syntax\Node\Node;
use 
Peast\Syntax\Node\Expression;

/**
 * A node that represents a spread child expression in JSX.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class JSXSpreadChild extends Node
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"expression" => true
    
);
    
    
/**
     * The wrapped expression
     * 
     * @var Expression
     */
    
protected $expression;
    
    
/**
     * Returns the wrapped expression
     * 
     * @return Expression
     */
    
public function getExpression()
    {
        return 
$this->expression;
    }
    
    
/**
     * Sets the wrapped expression
     * 
     * @param Expression $expression Wrapped expression
     * 
     * @return $this
     */
    
public function setExpression(Expression $expression)
    {
        
$this->expression $expression;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node\JSX;

use 
Peast\Syntax\Node\Node;

/**
 * A node that represents a JSX closing fragment tag.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class JSXText extends Node
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"value" => false,
        
"raw" => false
    
);
    
    
/**
     * Node's value
     * 
     * @var mixed
     */
    
protected $value;
    
    
/**
     * Node's raw value
     * 
     * @var string
     */
    
protected $raw;
    
    
/**
     * Returns node's value
     * 
     * @return mixed
     */
    
public function getValue()
    {
        return 
$this->value;
    }
    
    
/**
     * Sets node's value
     * 
     * @param mixed $value Value
     * 
     * @return $this
     */
    
public function setValue($value)
    {
        
$this->value $value;
        
$this->raw $value;
        return 
$this;
    }
    
    
/**
     * Return node's raw value
     * 
     * @return string
     */
    
public function getRaw()
    {
        return 
$this->raw;
    }
    
    
/**
     * Sets node's raw value
     * 
     * @param mixed $raw Raw value
     * 
     * @return $this
     */
    
public function setRaw($raw)
    {
        return 
$this->setValue($raw);
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node\JSX;

use 
Peast\Syntax\Node\Node;

/**
 * A node that represents a JSX opening fragment tag.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class JSXOpeningFragment extends Node
{
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node\JSX;

use 
Peast\Syntax\Node\Node;
use 
Peast\Syntax\Node\Expression;

/**
 * A node that represents a JSX fragment.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class JSXFragment extends Node implements Expression
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"openingFragment" => true,
        
"children" => true,
        
"closingFragment" => true
    
);
    
    
/**
     * Opening fragment node
     * 
     * @var JSXOpeningFragment
     */
    
protected $openingFragment;
    
    
/**
     * Children nodes array
     * 
     * @var Node[]
     */
    
protected $children = array();
    
    
/**
     * Closing fragment node
     * 
     * @var JSXClosingFragment
     */
    
protected $closingFragment;
    
    
/**
     * Returns the opening fragment node
     * 
     * @return JSXOpeningFragment
     */
    
public function getOpeningFragment()
    {
        return 
$this->openingFragment;
    }
    
    
/**
     * Sets the opening fragment node
     * 
     * @param JSXOpeningFragment $openingFragment Opening fragment node
     * 
     * @return $this
     */
    
public function setOpeningFragment(JSXOpeningFragment $openingFragment)
    {
        
$this->openingFragment $openingFragment;
        return 
$this;
    }
    
    
/**
     * Returns the children nodes array
     * 
     * @return Node[]
     */
    
public function getChildren()
    {
        return 
$this->children;
    }
    
    
/**
     * Sets the children nodes array
     * 
     * @param Node[] $children Children nodes array
     * 
     * @return $this
     */
    
public function setChildren($children)
    {
        
$this->assertArrayOf($children, array(
            
"JSX\\JSXText""JSX\\JSXExpressionContainer""JSX\\JSXSpreadChild",
            
"JSX\\JSXElement""JSX\\JSXFragment"
        
));
        
$this->children $children;
        return 
$this;
    }
    
    
/**
     * Returns the closing fragment node
     * 
     * @return JSXClosingFragment
     */
    
public function getClosingFragment()
    {
        return 
$this->closingFragment;
    }
    
    
/**
     * Sets the closing fragment node
     * 
     * @param JSXClosingFragment $closingFragment Closing fragment node
     * 
     * @return $this
     */
    
public function setClosingFragment(JSXClosingFragment $closingFragment)
    {
        
$this->closingFragment $closingFragment;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node\JSX;

use 
Peast\Syntax\Node\Node;

/**
 * A node that represents a JSX closing fragment tag.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class JSXClosingFragment extends Node
{
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node\JSX;

use 
Peast\Syntax\Node\Node;
use 
Peast\Syntax\Node\Expression;

/**
 * A node that represents a JSX element.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class JSXElement extends Node implements Expression
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"openingElement" => true,
        
"children" => true,
        
"closingElement" => true
    
);
    
    
/**
     * Opening element node
     * 
     * @var JSXOpeningElement
     */
    
protected $openingElement;
    
    
/**
     * Children nodes array
     * 
     * @var Node[]
     */
    
protected $children = array();
    
    
/**
     * Closing element node
     * 
     * @var JSXClosingElement|null
     */
    
protected $closingElement;
    
    
/**
     * Returns the opening element node
     * 
     * @return JSXOpeningElement
     */
    
public function getOpeningElement()
    {
        return 
$this->openingElement;
    }
    
    
/**
     * Sets the opening element node
     * 
     * @param JSXOpeningElement $openingElement Opening element node
     * 
     * @return $this
     */
    
public function setOpeningElement(JSXOpeningElement $openingElement)
    {
        
$this->openingElement $openingElement;
        return 
$this;
    }
    
    
/**
     * Returns the children nodes array
     * 
     * @return Node[]
     */
    
public function getChildren()
    {
        return 
$this->children;
    }
    
    
/**
     * Sets the children nodes array
     * 
     * @param Node[] $children Children nodes array
     * 
     * @return $this
     */
    
public function setChildren($children)
    {
        
$this->assertArrayOf($children, array(
            
"JSX\\JSXText""JSX\\JSXExpressionContainer""JSX\\JSXSpreadChild",
            
"JSX\\JSXElement""JSX\\JSXFragment"
        
));
        
$this->children $children;
        return 
$this;
    }
    
    
/**
     * Returns the closing element node
     * 
     * @return JSXClosingElement|null
     */
    
public function getClosingElement()
    {
        return 
$this->closingElement;
    }
    
    
/**
     * Sets the closing element node
     * 
     * @param JSXClosingElement|null $closingElement Closing element node
     * 
     * @return $this
     */
    
public function setClosingElement($closingElement)
    {
        
$this->assertType($closingElement"JSX\\JSXClosingElement"true);
        
$this->closingElement $closingElement;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node\JSX;

use 
Peast\Syntax\Node\Node;

/**
 * A node that represents a JSX empty expression.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class JSXEmptyExpression extends Node
{
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node\JSX;

use 
Peast\Syntax\Node\Node;
use 
Peast\Syntax\Node\Expression;

/**
 * A node that represents a JSX namespaced name.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class JSXNamespacedName extends Node implements Expression
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"namespace" => false,
        
"name" => false
    
);
    
    
/**
     * Node's namespace
     * 
     * @var JSXIdentifier
     */
    
protected $namespace;
    
    
/**
     * Node's name
     * 
     * @var JSXIdentifier
     */
    
protected $name;
    
    
/**
     * Returns node's namespace
     * 
     * @return JSXIdentifier
     */
    
public function getNamespace()
    {
        return 
$this->namespace;
    }
    
    
/**
     * Sets node's namespace
     * 
     * @param JSXIdentifier $namespace Namespace
     * 
     * @return $this
     */
    
public function setNamespace($namespace)
    {
        
$this->namespace $namespace;
        return 
$this;
    }
    
    
/**
     * Return node's name
     * 
     * @return JSXIdentifier
     */
    
public function getName()
    {
        return 
$this->name;
    }
    
    
/**
     * Sets node's name
     * 
     * @param JSXIdentifier $name Name
     * 
     * @return $this
     */
    
public function setName($name)
    {
        
$this->name $name;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node\JSX;

/**
 * A node that represents a JSX opening element tag.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class JSXOpeningElement extends JSXBoundaryElement
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"attributes" => true,
        
"selfClosing" => false
    
);
    
    
/**
     * Children nodes array
     * 
     * @var JSXAttribute[]|JSXSpreadAttribute[]
     */
    
protected $attributes = array();
    
    
/**
     * Self closing tag mode
     * 
     * @var bool
     */
    
protected $selfClosing false;
    
    
/**
     * Returns the children attributes array
     * 
     * @return JSXAttribute[]|JSXSpreadAttribute[]
     */
    
public function getAttributes()
    {
        return 
$this->attributes;
    }
    
    
/**
     * Sets the attributes nodes array
     * 
     * @param JSXAttribute[]|JSXSpreadAttribute[] $attributes Attributes nodes
     *                                                         array
     * 
     * @return $this
     */
    
public function setAttributes($attributes)
    {
        
$this->assertArrayOf($attributes, array(
            
"JSX\\JSXAttribute""JSX\\JSXSpreadAttribute"
        
));
        
$this->attributes $attributes;
        return 
$this;
    }
    
    
/**
     * Returns the self closing tag mode
     * 
     * @return bool
     */
    
public function getSelfClosing()
    {
        return 
$this->selfClosing;
    }
    
    
/**
     * Sets the self closing tag mode
     * 
     * @param bool $selfClosing Self closing tag mode
     * 
     * @return $this
     */
    
public function setSelfClosing($selfClosing)
    {
        
$this->selfClosing = (bool) $selfClosing;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node\JSX;

use 
Peast\Syntax\Node\Node;

/**
 * A base class for boundary elements.
 * 
 * @author Marco Marchiò <[email protected]>
 * 
 * @abstract
 */
abstract class JSXBoundaryElement extends Node
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"name" => true
    
);
    
    
/**
     * Element name
     * 
     * @var JSXIdentifier|JSXMemberExpression|JSXNamespacedName
     */
    
protected $name;
    
    
/**
     * Returns the element name
     * 
     * @return JSXIdentifier|JSXMemberExpression|JSXNamespacedName
     */
    
public function getName()
    {
        return 
$this->name;
    }
    
    
/**
     * Sets the element name
     * 
     * @param JSXIdentifier|JSXMemberExpression|JSXNamespacedName $name Element
     *                                                                  name
     * 
     * @return $this
     */
    
public function setName($name)
    {
        
$this->assertType(
            
$name,
            array(
"JSX\\JSXIdentifier""JSX\\JSXMemberExpression""JSX\\JSXNamespacedName")
        );
        
$this->name $name;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node\JSX;

use 
Peast\Syntax\Node\Node;
use 
Peast\Syntax\Node\Expression;

/**
 * A node that represents a JSX member expression.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class JSXMemberExpression extends Node implements Expression
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"object" => true,
        
"property" => true
    
);
    
    
/**
     * Expression's object
     * 
     * @var JSXMemberExpression|JSXIdentifier 
     */
    
protected $object;
    
    
/**
     * Expression's property
     * 
     * @var JSXIdentifier
     */
    
protected $property;
    
    
/**
     * Returns the expression's object
     * 
     * @return JSXMemberExpression|JSXIdentifier
     */
    
public function getObject()
    {
        return 
$this->object;
    }
    
    
/**
     * Sets the expression's object
     * 
     * @param JSXMemberExpression|JSXIdentifier $object Object
     * 
     * @return $this
     */
    
public function setObject($object)
    {
        
$this->assertType($object, array("JSX\\JSXMemberExpression""JSX\\JSXIdentifier"));
        
$this->object $object;
        return 
$this;
    }
    
    
/**
     * Returns the expression's property
     * 
     * @return JSXIdentifier
     */
    
public function getProperty()
    {
        return 
$this->property;
    }
    
    
/**
     * Sets the expression's property
     * 
     * @param JSXIdentifier $property Property
     * 
     * @return $this
     */
    
public function setProperty(JSXIdentifier $property)
    {
        
$this->property $property;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a method declaration in classes and object literals.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class MethodDefinition extends Node
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"key" => true,
        
"value" => true,
        
"kind" => false,
        
"computed" => false,
        
"static" => false
    
);
    
    
//Kind constants
    /**
     * Constructor method
     */
    
const KIND_CONSTRUCTOR "constructor";
    
    
/**
     * Standard method
     */
    
const KIND_METHOD "method";
    
    
/**
     * Getter method
     */
    
const KIND_GET "get";
    
    
/**
     * Setter method
     */
    
const KIND_SET "set";
    
    
/**
     * Method's key
     * 
     * @var Expression|PrivateIdentifier
     */
    
protected $key;
    
    
/**
     * Method's value
     * 
     * @var FunctionExpression 
     */
    
protected $value;
    
    
/**
     * Method's kind that is one of the kind constants
     * 
     * @var string 
     */
    
protected $kind self::KIND_METHOD;
    
    
/**
     * Computed flag that is true if method's key is declared using square
     * brackets syntax
     * 
     * @var bool 
     */
    
protected $computed false;
    
    
/**
     * Static flag that is true if the method is static
     * 
     * @var bool 
     */
    
protected $static false;
    
    
/**
     * Returns the method's key
     * 
     * @return Expression|PrivateIdentifier
     */
    
public function getKey()
    {
        return 
$this->key;
    }
    
    
/**
     * Sets the method's key
     * 
     * @param Expression|PrivateIdentifier $key Method's key
     * 
     * @return $this
     */
    
public function setKey($key)
    {
        
$this->assertType($key, array("Expression""PrivateIdentifier"));
        
$this->key $key;
        return 
$this;
    }
    
    
/**
     * Returns the method's value
     * 
     * @return FunctionExpression
     */
    
public function getValue()
    {
        return 
$this->value;
    }
    
    
/**
     * Sets the method's value
     * 
     * @param FunctionExpression $value Method's value
     * 
     * @return $this
     */
    
public function setValue(FunctionExpression $value)
    {
        
$this->value $value;
        return 
$this;
    }
    
    
/**
     * Returns the method's kind that is one of the kind constants
     * 
     * @return string
     */
    
public function getKind()
    {
        return 
$this->kind;
    }
    
    
/**
     * Sets the method's kind that is one of the kind constants
     * 
     * @param string $kind Method's kind
     * 
     * @return $this
     */
    
public function setKind($kind)
    {
        
$this->kind $kind;
        return 
$this;
    }
    
    
/**
     * Returns the computed flag that is true if method's key is declared using
     * square brackets syntax
     * 
     * @return bool
     */
    
public function getComputed()
    {
        return 
$this->computed;
    }
    
    
/**
     * Sets the computed flag that is true if method's key is declared using
     * square brackets syntax
     * 
     * @param bool $computed Computed flag
     * 
     * @return $this
     */
    
public function setComputed($computed)
    {
        
$this->computed = (bool) $computed;
        return 
$this;
    }
    
    
/**
     * Returns the static flag that is true if the method is static
     * 
     * @return bool
     */
    
public function getStatic()
    {
        return 
$this->{"static"};
    }
    
    
/**
     * Sets the static flag that is true if the method is static
     * 
     * @param bool $static Static flag
     * 
     * @return $this
     */
    
public function setStatic($static)
    {
        
$this->{"static"} = (bool) $static;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents an expression statement and wraps another expression.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class ExpressionStatement extends Node implements Statement
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"expression" => true
    
);
    
    
/**
     * Wrapped expression
     * 
     * @var Expression 
     */
    
protected $expression;
    
    
/**
     * Returns the wrapped expression
     * 
     * @return Expression
     */
    
public function getExpression()
    {
        return 
$this->expression;
    }
    
    
/**
     * Sets the wrapped expression
     * 
     * @param Expression $expression Wrapped expression
     * 
     * @return $this
     */
    
public function setExpression(Expression $expression)
    {
        
$this->expression $expression;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a class body.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class ClassBody extends Node
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"body" => true
    
);
    
    
/**
     * Class methods and properties
     * 
     * @var MethodDefinition[]|PropertyDefinition[]|StaticBlock[]
     */
    
protected $body = array();
    
    
/**
     * Returns class methods and properties
     * 
     * @return MethodDefinition[]|PropertyDefinition[]|StaticBlock[]
     */
    
public function getBody()
    {
        return 
$this->body;
    }
    
    
/**
     * Sets class methods and properties
     * 
     * @param MethodDefinition[]|PropertyDefinition[]|StaticBlock[] $body Class methods array
     * 
     * @return $this
     */
    
public function setBody($body)
    {
        
$this->assertArrayOf(
            
$body,
            array(
"MethodDefinition""PropertyDefinition""StaticBlock")
        );
        
$this->body $body;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a null literal.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class NullLiteral extends Literal
{
    
/**
     * Node's value
     * 
     * @var mixed
     */
    
protected $value null;
    
    
/**
     * Node's raw value
     * 
     * @var string
     */
    
protected $raw "null";
    
    
/**
     * Sets node's value
     * 
     * @param mixed $value Value
     * 
     * @return $this
     */
    
public function setValue($value)
    {
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a do-while loop.
 * For example: do {} while (test)
 * 
 * @author Marco Marchiò <[email protected]>
 */
class DoWhileStatement extends Node implements Statement
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"body" => true,
        
"test" => true
    
);
    
    
/**
     * The loop body
     * 
     * @var Statement 
     */
    
protected $body;
    
    
/**
     * The loop condition
     * 
     * @var Expression 
     */
    
protected $test;
    
    
/**
     * Returns the loop body
     * 
     * @return Statement
     */
    
public function getBody()
    {
        return 
$this->body;
    }
    
    
/**
     * Sets the loop body
     * 
     * @param Statement $body Loop body
     * 
     * @return $this
     */
    
public function setBody(Statement $body)
    {
        
$this->body $body;
        return 
$this;
    }
    
    
/**
     * Returns the loop condition
     * 
     * @return Expression
     */
    
public function getTest()
    {
        return 
$this->test;
    }
    
    
/**
     * Sets the loop condition
     * 
     * @param Expression $test Loop
     * 
     * @return $this
     */
    
public function setTest(Expression $test)
    {
        
$this->test $test;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents an object binding pattern.
 * For example: var {a, b, c} = d
 * 
 * @author Marco Marchiò <[email protected]>
 */
class ObjectPattern extends Node implements Pattern
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"properties" => true
    
);
    
    
/**
     * Object properties
     * 
     * @var Property[] 
     */
    
protected $properties = array();
    
    
/**
     * Returns object properties
     * 
     * @return Property[] 
     */
    
public function getProperties()
    {
        return 
$this->properties;
    }
    
    
/**
     * Sets object properties
     * 
     * @param Property[] $properties Object properties
     * 
     * @return $this
     */
    
public function setProperties($properties)
    {
        
$this->assertArrayOf($properties, array("AssignmentProperty""RestElement"));
        
$this->properties $properties;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents the "for...of" statement.
 * For example: for (var a of b) {}
 * 
 * @author Marco Marchiò <[email protected]>
 */
class ForOfStatement extends ForInStatement
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"left" => true,
        
"right" => true,
        
"body" => true,
        
"await" => false
    
);
    
    
/**
     * Async iteration flag
     * 
     * @var bool
     */
    
protected $await false;
    
    
/**
     * Returns the async iteration flag
     * 
     * @return bool
     */
    
public function getAwait()
    {
        return 
$this->await;
    }
    
    
/**
     * Sets the async iteration flag
     * 
     * @param bool $await Async iteration flag
     * 
     * @return $this
     */
    
public function setAwait($await)
    {
        
$this->await = (bool) $await;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * Interface that every pattern node must implement.
 * 
 * @author Marco Marchiò <[email protected]>
 */
interface Pattern
{
}<?
php /** @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */

/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

use 
Peast\Syntax\SourceLocation;
use 
Peast\Syntax\Position;

/**
 * Base class for all the nodes generated by Peast.
 * 
 * @author Marco Marchiò <[email protected]>
 * 
 * @abstract
 */
abstract class Node implements \JSONSerializable
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"type" => false,
        
"location" => false,
        
"leadingComments" => false,
        
"trailingComments" => false
    
);
    
    
/**
     * Node location in the source code
     * 
     * @var SourceLocation
     */
    
public $location;
    
    
/**
     * Leading comments array
     *
     * @var Comment[]
     */
    
protected $leadingComments = array();

    
/**
     * Trailing comments array
     *
     * @var Comment[]
     */
    
protected $trailingComments = array();

    
/**
     * Class constructor
     */
    
public function __construct()
    {
        
$this->location = new SourceLocation;
    }
    
    
/**
     * Returns node type
     * 
     * @return string
     */
    
public function getType()
    {
        
$class explode("\\"get_class($this));
        return 
array_pop($class);
    }
    
    
/**
     * Sets leading comments array
     *
     * @param Comment[] $comments Comments array
     *
     * @return $this
     */
    
public function setLeadingComments($comments)
    {
        
$this->assertArrayOf($comments"Comment");
        
$this->leadingComments $comments;
        return 
$this;
    }

    
/**
     * Returns leading comments array
     *
     * @return Comment[]
     */
    
public function getLeadingComments()
    {
        return 
$this->leadingComments;
    }

    
/**
     * Sets trailing comments array
     *
     * @param Comment[] $comments Comments array
     *
     * @return $this
     */
    
public function setTrailingComments($comments)
    {
        
$this->assertArrayOf($comments"Comment");
        
$this->trailingComments $comments;
        return 
$this;
    }

    
/**
     * Returns trailing comments array
     *
     * @return Comment[]
     */
    
public function getTrailingComments()
    {
        return 
$this->trailingComments;
    }

    
/**
     * Returns node location in the source code
     * 
     * @return SourceLocation
     */
    
public function getLocation()
    {
        return 
$this->location;
    }
    
    
/**
     * Sets the start position of the node in the source code
     * 
     * @param Position $position Start position
     * 
     * @return $this
     */
    
public function setStartPosition(Position $position)
    {
        
$this->location->start $position;
        return 
$this;
    }
    
    
/**
     * Sets the end position of the node in the source code
     * 
     * @param Position $position Start position
     * 
     * @return $this
     */
    
public function setEndPosition(Position $position)
    {
        
$this->location->end $position;
        return 
$this;
    }
    
    
/**
     * Traverses the current node and all its child nodes using the given
     * function
     * 
     * @param callable $fn      Function that will be called on each node
     * @param array    $options Options array. See Traverser class
     *                          documentation for available options
     * 
     * @return $this
     */
    
public function traverse(callable $fn$options = array())
    {
        
$traverser = new \Peast\Traverser($options);
        
$traverser->addFunction($fn)->traverse($this);
        return 
$this;
    }
    
    
/**
     * Returns a serializable version of the node
     * 
     * @return array
     */
    
#[\ReturnTypeWillChange]
    public function 
jsonSerialize()
    {
        
$ret = array();
        
$props \Peast\Syntax\Utils::getNodeProperties($this);
        foreach (
$props as $prop) {
            
$ret[$prop["name"]] = $this->{$prop["getter"]}();
        }
        return 
$ret;
    }
    
    
/**
     * Renders the current node
     * 
     * @param \Peast\Formatter\Base $formatter Formatter to use for the
     *                                         rendering
     * 
     * @return string
     */
    
public function render(\Peast\Formatter\Base $formatter)
    {
        
$renderer = new \Peast\Renderer();
        return 
$renderer->setFormatter($formatter)->render($this);
    }
    
    
/**
     * Asserts that the given value is an array of defined type
     * 
     * @param mixed        $params    Value to check
     * @param string|array $classes   Class or array of classes to check against
     * @param bool         $allowNull If true, null values are allowed
     * 
     * @return void
     * 
     * @codeCoverageIgnore
     */
    
protected function assertArrayOf($params$classes$allowNull false)
    {
        if (!
is_array($classes)) {
            
$classes = array($classes);
        }
        if (!
is_array($params)) {
            
$this->typeError($params$classes$allowNulltrue);
        } else {
            foreach (
$params as $param) {
                foreach (
$classes as $class) {
                    if (
$param === null && $allowNull) {
                        continue 
2;
                    } else {
                        
$c "Peast\\Syntax\\Node\\$class";
                        if (
$param instanceof $c) {
                            continue 
2;
                        }
                    }
                }
                
$this->typeError($param$classes$allowNulltruetrue);
            }
        }
    }
    
    
/**
     * Asserts that the given value respects the defined type
     * 
     * @param mixed        $param     Value to check
     * @param string|array $classes   Class or array of classes to check against
     * @param bool         $allowNull If true, null values are allowed
     * 
     * @return void
     * 
     * @codeCoverageIgnore
     */
    
protected function assertType($param$classes$allowNull false)
    {
        if (!
is_array($classes)) {
            
$classes = array($classes);
        }
        if (
$param === null) {
            if (!
$allowNull) {
                
$this->typeError($param$classes$allowNull);
            }
        } else {
            foreach (
$classes as $class) {
                
$c "Peast\\Syntax\\Node\\$class";
                if (
$param instanceof $c) {
                    return;
                }
            }
            
$this->typeError($param$classes$allowNull);
        }
    }
    
    
/**
     * Throws an error if the defined type is not supported b
     * 
     * @param mixed $param        The value to check
     * @param mixed $allowedTypes Class or array of classes to check against
     * @param bool  $allowNull    If true, null values are allowed
     * @param bool  $array        If true, the value must be an array
     * @param bool  $inArray      If true, the value is an array but the content
     *                            does not respects the type
     * 
     * @return void
     * 
     * @throws \TypeError
     * 
     * @codeCoverageIgnore
     */
    
protected function typeError(
        
$param$allowedTypes$allowNull false$array false,
        
$inArray false
    
) {
        
$backtrace debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
        
$method $backtrace[2]["class"] . "::" $backtrace[2]["function"];
        
$msg "Argument 0 passed to $method must be ";
        if (
$array) {
            
$msg .= "an array of ";
        }
        
$msg .= implode(" or "$allowedTypes);
        if (
$allowNull) {
            
$msg .= " or null";
        }
        if (
is_object($param)) {
            
$parts explode("\\"get_class($param));
            
$type array_pop($parts);
        } else {
            
$type gettype($param);
        }
        if (
$inArray) {
            
$type "array of $type";
        }
        
$msg .= ", $type given";
        if (
version_compare(phpversion(), '7''>=')) {
            throw new 
\TypeError($msg);
        } else {
            
trigger_error($msgE_USER_ERROR);
        }
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a tagged template expression.
 * For example: fn`template`
 * 
 * @author Marco Marchiò <[email protected]>
 */
class TaggedTemplateExpression extends Node implements Expression
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"tag" => true,
        
"quasi" => true
    
);
    
    
/**
     * Tag expression
     * 
     * @var Expression 
     */
    
protected $tag;
    
    
/**
     * Template
     * 
     * @var TemplateLiteral 
     */
    
protected $quasi;
    
    
/**
     * Returns the tag expression
     * 
     * @return Expression
     */
    
public function getTag()
    {
        return 
$this->tag;
    }
    
    
/**
     * Sets the tag expression
     * 
     * @param Expression $tag Tag expression
     * 
     * @return $this
     */
    
public function setTag(Expression $tag)
    {
        
$this->tag $tag;
        return 
$this;
    }
    
    
/**
     * Returns the template
     * 
     * @return TemplateLiteral
     */
    
public function getQuasi()
    {
        return 
$this->quasi;
    }
    
    
/**
     * Sets the the template
     * 
     * @param TemplateLiteral $quasi Template
     * 
     * @return $this
     */
    
public function setQuasi(TemplateLiteral $quasi)
    {
        
$this->quasi $quasi;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

use 
Peast\Syntax\Utils;

/**
 * A node that represents a template element.
 * For example `foo` and `bar` in: `foo${exp}bar`
 * 
 * @author Marco Marchiò <[email protected]>
 */
class TemplateElement extends Node
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"value" => false,
        
"tail" => false,
        
"rawValue" => false
    
);
    
    
/**
     * Node's value
     * 
     * @var string 
     */
    
protected $value;
    
    
/**
     * Tail flag that is true when the element is the tail element in a template
     * 
     * @var bool 
     */
    
protected $tail false;
    
    
/**
     * Node's raw value
     * 
     * @var string
     */
    
protected $rawValue;
    
    
/**
     * Return node's value
     * 
     * @return string
     */
    
public function getValue()
    {
        return 
$this->value;
    }
    
    
/**
     * Sets node's value
     * 
     * @param mixed $value Value
     * 
     * @return $this
     */
    
public function setValue($value)
    {
        
$this->value $value;
        
$this->rawValue Utils::quoteLiteralString($value"`");
        return 
$this;
    }
    
    
/**
     * Returns the tail flag that is true when the element is the tail element
     * in a template
     * 
     * @return bool
     */
    
public function getTail()
    {
        return 
$this->tail;
    }
    
    
/**
     * Sets the tail flag that is true when the element is the tail element
     * in a template
     * 
     * @param bool $tail Tail flag
     * 
     * @return $this
     */
    
public function setTail($tail)
    {
        
$this->tail = (bool) $tail;
        return 
$this;
    }
    
    
/**
     * Returns node's raw value
     * 
     * @return string
     */
    
public function getRawValue()
    {
        return 
$this->rawValue;
    }
    
    
/**
     * Sets node's raw value that must be wrapped in templates quotes.
     * 
     * @param string $rawValue Raw value
     * 
     * @return $this
     */
    
public function setRawValue($rawValue)
    {
        
$rawValue preg_replace("#^[`}]|(?:`|\\\$\{)$#"""$rawValue);
        
$this->setValue(Utils::unquoteLiteralString("`$rawValue`"));
        
$this->rawValue $rawValue;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a template literal.
 * For example: `this is a ${test()} template`
 * 
 * @author Marco Marchiò <[email protected]>
 */
class TemplateLiteral extends Node implements Expression
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"parts" => true,
        
"quasis" => false,
        
"expressions" => false
    
);
    
    
/**
     * Array of quasis that are the literal parts of the template
     * 
     * @var TemplateElement[] 
     */
    
protected $quasis = array();
    
    
/**
     * Array of expressions inside the template
     * 
     * @var Expression[] 
     */
    
protected $expressions = array();
    
    
/**
     * Returns the array of quasis that are the literal parts of the template
     * 
     * @return TemplateElement[] 
     */
    
public function getQuasis()
    {
        return 
$this->quasis;
    }
    
    
/**
     * Sets the array of quasis that are the literal parts of the template
     * 
     * @param TemplateElement[] $quasis Quasis
     * 
     * @return $this
     */
    
public function setQuasis($quasis)
    {
        
$this->assertArrayOf($quasis"TemplateElement");
        
$this->quasis $quasis;
        return 
$this;
    }
    
    
/**
     * Returns the array of expressions inside the template
     * 
     * @return Expression[]
     */
    
public function getExpressions()
    {
        return 
$this->expressions;
    }
    
    
/**
     * Sets the array of expressions inside the template
     * 
     * @param Expression[] $expressions Expressions
     * 
     * @return $this
     */
    
public function setExpressions($expressions)
    {
        
$this->assertArrayOf($expressions"Expression");
        
$this->expressions $expressions;
        return 
$this;
    }
    
    
/**
     * Returns an array of the template parts (quasis and expressions)
     * 
     * @return array
     */
    
public function getParts()
    {
        
// It must be a list of quasis and expressions alternated
        
$parts = array();
        foreach (
$this->quasis as $k => $val) {
            
$parts[] = $val;
            if (isset(
$this->expressions[$k])) {
                
$parts[] = $this->expressions[$k];
            }
        }
        return 
$parts;
    }
    
    
/**
     * Sets the array of the template parts (quasis and expressions)
     * 
     * @param array Template parts
     * 
     * @return $this
     */
    
public function setParts($parts)
    {
        
$this->assertArrayOf($parts, array("Expression""TemplateElement"));
        
$quasis $expressions = array();
        foreach (
$parts as $part) {
            if (
$part instanceof TemplateElement) {
                
$quasis[] = $part;
            } else {
                
$expressions[] = $part;
            }
        }
        return 
$this->setQuasis($quasis)->setExpressions($expressions);
    }
    
    
/**
     * Returns a serializable version of the node
     * 
     * @return array
     */
    
#[\ReturnTypeWillChange]
    public function 
jsonSerialize()
    {
        
$ret parent::jsonSerialize();
        unset(
$ret["parts"]);
        return 
$ret;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a while loop.
 * For example: while (test) {}
 * 
 * @author Marco Marchiò <[email protected]>
 */
class WhileStatement extends Node implements Statement
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"test" => true,
        
"body" => true
    
);
    
    
/**
     * The loop condition
     * 
     * @var Expression 
     */
    
protected $test;
    
    
/**
     * The loop body
     * 
     * @var Statement 
     */
    
protected $body;
    
    
/**
     * Returns the loop condition
     * 
     * @return Expression
     */
    
public function getTest()
    {
        return 
$this->test;
    }
    
    
/**
     * Sets the loop condition
     * 
     * @param Expression $test Loop
     * 
     * @return $this
     */
    
public function setTest(Expression $test)
    {
        
$this->test $test;
        return 
$this;
    }
    
    
/**
     * Returns the loop body
     * 
     * @return Statement
     */
    
public function getBody()
    {
        return 
$this->body;
    }
    
    
/**
     * Sets the loop body
     * 
     * @param Statement $body Loop body
     * 
     * @return $this
     */
    
public function setBody(Statement $body)
    {
        
$this->body $body;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a class static block
 * 
 * @author Marco Marchiò <[email protected]>
 */
class StaticBlock extends BlockStatement
{
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a logical expression.
 * For example: a && b
 * 
 * @author Marco Marchiò <[email protected]>
 */
class LogicalExpression extends Node implements Expression
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"left" => true,
        
"operator" => false,
        
"right" => true
    
);
    
    
/**
     * Operator
     * 
     * @var string
     */
    
protected $operator;
    
    
/**
     * Left expression
     * 
     * @var Expression
     */
    
protected $left;
    
    
/**
     * Right expression
     * 
     * @var Expression
     */
    
protected $right;
    
    
/**
     * Returns the operator
     * 
     * @return string
     */
    
public function getOperator()
    {
        return 
$this->operator;
    }
    
    
/**
     * Sets the operator
     * 
     * @param string $operator Operator
     * 
     * @return $this
     */
    
public function setOperator($operator)
    {
        
$this->operator $operator;
        return 
$this;
    }
    
    
/**
     * Returns the left expression
     * 
     * @return Expression
     */
    
public function getLeft()
    {
        return 
$this->left;
    }
    
    
/**
     * Sets the left expression
     * 
     * @param Expression $left Left expression
     * 
     * @return $this
     */
    
public function setLeft(Expression $left)
    {
        
$this->left $left;
        return 
$this;
    }
    
    
/**
     * Returns the right expression
     * 
     * @return Expression
     */
    
public function getRight()
    {
        return 
$this->right;
    }
    
    
/**
     * Sets the right expression
     * 
     * @param Expression $right Right expression
     * 
     * @return $this
     */
    
public function setRight(Expression $right)
    {
        
$this->right $right;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a BigInt literal.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class BigIntLiteral extends Literal
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"bigint" => false
    
);
    
    
/**
     * Node's value
     * 
     * @var mixed
     */
    
protected $bigint;
    
    
/**
     * Sets node's value
     * 
     * @param float $value Value
     * 
     * @return $this
     */
    
public function setValue($value)
    {
        
//Value, Raw and Bigint are always the same value
        
$this->value $this->raw $this->bigint $value;
        return 
$this;
    }
    
    
/**
     * Returns node's value
     * 
     * @return string
     */
    
public function getBigint()
    {
        return 
$this->bigint;
    }
    
    
/**
     * Sets node's value
     * 
     * @param string $bigint Value
     * 
     * @return $this
     */
    
public function setBigint($bigint)
    {
        return 
$this->setValue($bigint);
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a chain expression.
 * For example: test?.test?.()
 * 
 * @author Marco Marchiò <[email protected]>
 */
class ChainExpression extends Node implements Expression
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"expression" => true
    
);
    
    
/**
     * The wrapped expression
     * 
     * @var Expression
     */
    
protected $expression;
    
    
/**
     * Returns the wrapped expression
     * 
     * @return Expression
     */
    
public function getExpression()
    {
        return 
$this->expression;
    }
    
    
/**
     * Sets the wrapped expression
     * 
     * @param Expression $expression Wrapped expression
     * 
     * @return $this
     */
    
public function setExpression(Expression $expression)
    {
        
$this->expression $expression;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a case in a switch statement.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class SwitchCase extends Node
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"test" => true,
        
"consequent" => true
    
);
    
    
/**
     * Test expression that is null in the "default" case
     * 
     * @var Expression 
     */
    
protected $test;
    
    
/**
     * Consequent statements array
     * 
     * @var Statement[] 
     */
    
protected $consequent = array();
    
    
/**
     * Returns the test expression that is null in the "default" case
     * 
     * @return Expression
     */
    
public function getTest()
    {
        return 
$this->test;
    }
    
    
/**
     * Sets the test expression that is null in the "default" case
     * 
     * @param Expression $test Test expression
     * 
     * @return $this
     */
    
public function setTest($test)
    {
        
$this->assertType($test"Expression"true);
        
$this->test $test;
        return 
$this;
    }
    
    
/**
     * Returns the consequent statements array
     * 
     * @return Statement[]
     */
    
public function getConsequent()
    {
        return 
$this->consequent;
    }
    
    
/**
     * Sets the consequent statements array
     * 
     * @param Expression[] $consequent Consequent statements array
     * 
     * @return $this
     */
    
public function setConsequent($consequent)
    {
        
$this->assertArrayOf($consequent"Statement");
        
$this->consequent $consequent;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a regular expression literal.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class RegExpLiteral extends Literal
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"flags" => false,
        
"pattern" => false
    
);
    
    
/**
     * Regex flags
     * 
     * @var string 
     */
    
protected $flags "";
    
    
/**
     * Regex pattern
     * 
     * @var string 
     */
    
protected $pattern "";
    
    
/**
     * Returns node's type
     * 
     * @return string
     */
    
public function getType()
    {
        return 
"RegExpLiteral";
    }
    
    
/**
     * Returns regex pattern
     * 
     * @return string
     */
    
public function getPattern()
    {
        return 
$this->pattern;
    }
    
    
/**
     * Sets regex pattern
     * 
     * @param string $pattern Regex pattern
     * 
     * @return $this
     */
    
public function setPattern($pattern)
    {
        
$this->pattern $pattern;
        return 
$this;
    }
    
    
/**
     * Returns regex flags
     * 
     * @return string
     */
    
public function getFlags()
    {
        return 
$this->flags;
    }
    
    
/**
     * Sets regex flags
     * 
     * @param string $flags Regex flags
     * 
     * @return $this
     */
    
public function setFlags($flags)
    {
        
$this->flags $flags;
        return 
$this;
    }
    
    
/**
     * Returns node's raw value
     * 
     * @return string
     */
    
public function getRaw()
    {
        return 
"/" $this->getPattern() . "/" $this->getFlags();
    }
    
    
/**
     * Sets node's raw value that must include delimiters
     * 
     * @param string $raw Raw value
     * 
     * @return $this
     */
    
public function setRaw($raw)
    {
        
        
$parts explode("/"substr($raw1));
        
$flags array_pop($parts);
        
$this->setPattern(implode("/"$parts));
        
$this->setFlags($flags);
        return 
$this;
    }
    
    
/**
     * Returns node's value
     * 
     * @return string
     */
    
public function getValue()
    {
        return 
$this->getRaw();
    }
    
    
/**
     * Sets node's value
     * 
     * @param mixed $value Value
     * 
     * @return $this
     */
    
public function setValue($value)
    {
        return 
$this->setRaw($value);
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents an arrow function.
 * For example: var fn = (a, b) => console.log(a, b)
 * 
 * @author Marco Marchiò <[email protected]>
 */
class ArrowFunctionExpression extends Function_ implements Expression
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"expression" => false
    
);
    
    
/**
     * This flag is true when function body is wrapped in curly braces
     * 
     * @var bool
     */
    
protected $expression false;
    
    
/**
     * Sets the function body
     * 
     * @param BlockStatement|Expression $body Function body
     * 
     * @return $this
     */
    
public function setBody($body)
    {
        
$this->assertType($body, array("BlockStatement""Expression"));
        
$this->body $body;
        return 
$this;
    }
    
    
/**
     * Returns the expression flag
     * 
     * @return bool
     */
    
public function getExpression()
    {
        return 
$this->expression;
    }
    
    
/**
     * Sets the expression flag
     * 
     * @param bool $expression Expression flag
     * 
     * @return $this
     */
    
public function setExpression($expression)
    {
        
$this->expression = (bool) $expression;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents the "continue" statement inside loops.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class ContinueStatement extends Node implements Statement
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"label" => true
    
);
    
    
/**
     * The optional label of the continue statement
     * 
     * @var Identifier 
     */
    
protected $label;
    
    
/**
     * Returns the node's label
     * 
     * @return Identifier
     */
    
public function getLabel()
    {
        return 
$this->label;
    }
    
    
/**
     * Sets the node's label
     * 
     * @param Identifier $label Node's label
     * 
     * @return $this
     */
    
public function setLabel($label)
    {
        
$this->assertType($label"Identifier"true);
        
$this->label $label;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * Abstract class for literals.
 * 
 * @author Marco Marchiò <[email protected]>
 */
abstract class Literal extends Node implements Expression
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"value" => false,
        
"raw" => false
    
);
    
    
/**
     * Node's value
     * 
     * @var mixed
     */
    
protected $value;
    
    
/**
     * Node's raw value
     * 
     * @var string
     */
    
protected $raw;
    
    
/**
     * Returns node's type
     * 
     * @return string
     */
    
public function getType()
    {
        return 
"Literal";
    }
    
    
/**
     * Returns node's value
     * 
     * @return mixed
     */
    
public function getValue()
    {
        return 
$this->value;
    }
    
    
/**
     * Sets node's value
     * 
     * @param mixed $value Value
     * 
     * @return $this
     */
    
abstract public function setValue($value);
    
    
/**
     * Return node's raw value
     * 
     * @return string
     */
    
public function getRaw()
    {
        return 
$this->raw;
    }
    
    
/**
     * Sets node's raw value
     * 
     * @param mixed $raw Raw value
     * 
     * @return $this
     */
    
public function setRaw($raw)
    {
        return 
$this->setValue($raw);
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a call expression.
 * For example: test()
 * 
 * @author Marco Marchiò <[email protected]>
 */
class CallExpression extends ChainElement
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"callee" => true,
        
"arguments" => true
    
);
    
    
/**
     * The callee expression
     * 
     * @var Expression|Super 
     */
    
protected $callee;
    
    
/**
     * The arguments array
     * 
     * @var Expression[]|SpreadElement[]
     */
    
protected $arguments = array();
    
    
/**
     * Returns the callee expression
     * 
     * @return Expression|Super
     */
    
public function getCallee()
    {
        return 
$this->callee;
    }
    
    
/**
     * Sets the callee expression
     * 
     * @param Expression|Super $callee Callee expression
     * 
     * @return $this
     */
    
public function setCallee($callee)
    {
        
$this->assertType($callee, array("Expression""Super"));
        
$this->callee $callee;
        return 
$this;
    }
    
    
/**
     * Returns the arguments array
     * 
     * @return Expression[]|SpreadElement[]
     */
    
public function getArguments()
    {
        return 
$this->arguments;
    }
    
    
/**
     * Sets the arguments array
     * 
     * @param Expression[]|SpreadElement[] $arguments Arguments array
     * 
     * @return $this
     */
    
public function setArguments($arguments)
    {
        
$this->assertArrayOf($arguments, array("Expression""SpreadElement"));
        
$this->arguments $arguments;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

use 
Peast\Syntax\Utils;

/**
 * A node that represents an identifier.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class Identifier extends Node implements ExpressionPattern
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"name" => false,
        
"rawName" => false,
    );
    
    
/**
     * The identifier's name
     * 
     * @var string
     */
    
protected $name;
    
    
/**
     * The identifier's raw name
     * 
     * @var string
     */
    
protected $rawName;
    
    
/**
     * Returns the identifier's name
     * 
     * @return string
     */
    
public function getName()
    {
        return 
$this->name;
    }
    
    
/**
     * Sets the identifier's name
     * 
     * @param string $name The name to set
     * 
     * @return $this
     */
    
public function setName($name)
    {
        
$this->name $this->rawName $name;
        return 
$this;
    }
    
    
/**
     * Returns the identifier's raw name
     * 
     * @return string
     */
    
public function getRawName()
    {
        return 
$this->rawName;
    }
    
    
/**
     * Sets the identifier's raw name
     * 
     * @param string $name The raw name to set
     * 
     * @return $this
     */
    
public function setRawName($name)
    {
        
$this->rawName $name;
        if (
strpos($name"\\") !== false) {
            
$this->name preg_replace_callback(
                
"#\\\\u(?:\{([a-fA-F0-9]+)\}|([a-fA-F0-9]{4}))#",
                function (
$match) {
                    return 
Utils::unicodeToUtf8(hexdec($match[1] ? : $match[2]));
                },
                
$name
            
);
        } else {
            
$this->name $name;
        }
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * Interface that every expression node must implement.
 * 
 * @author Marco Marchiò <[email protected]>
 */
interface Expression
{
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents in import declaration.
 * For example: import a from "mod"
 * 
 * @author Marco Marchiò <[email protected]>
 */
class ImportDeclaration extends Node implements ModuleDeclaration
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"specifiers" => true,
        
"source" => true
    
);
    
    
/**
     * Import specifiers array
     * 
     * @var array
     */
    
protected $specifiers = array();
    
    
/**
     * Import source
     * 
     * @var Literal 
     */
    
protected $source;
    
    
/**
     * Returns the import specifiers array
     * 
     * @return array
     */
    
public function getSpecifiers()
    {
        return 
$this->specifiers;
    }
    
    
/**
     * Sets the import specifiers array
     * 
     * @param array $specifiers Import specifiers array
     * 
     * @return $this
     */
    
public function setSpecifiers($specifiers)
    {
        
$this->assertArrayOf(
            
$specifiers,
            array(
                
"ImportSpecifier",
                
"ImportDefaultSpecifier",
                
"ImportNamespaceSpecifier"
            
)
        );
        
$this->specifiers $specifiers;
        return 
$this;
    }
    
    
/**
     * Returns the import source
     * 
     * @return Literal
     */
    
public function getSource()
    {
        return 
$this->source;
    }
    
    
/**
     * Sets the import source
     * 
     * @param Literal $source Import source
     * 
     * @return $this
     */
    
public function setSource(Literal $source)
    {
        
$this->source $source;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents an array binding pattern.
 * For example: [a, b, c] = d
 * 
 * @author Marco Marchiò <[email protected]>
 */
class ArrayPattern extends Node implements Pattern
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"elements" => true
    
);
    
    
/**
     * Array elements
     * 
     * @var Pattern[]
     */
    
protected $elements = array();
    
    
/**
     * Returns array elements
     * 
     * @return Pattern[]
     */
    
public function getElements()
    {
        return 
$this->elements;
    }
    
    
/**
     * Sets array elements
     * 
     * @param Pattern[] $elements Array elements to set
     * 
     * @return $this
     */
    
public function setElements($elements)
    {
        
$this->assertArrayOf($elements"Pattern"true);
        
$this->elements $elements;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

use 
Peast\Syntax\Utils;

/**
 * A node that represents a string literal.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class StringLiteral extends Literal
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"format" => false
    
);
    
    
//Format constants
    /**
     * Double quoted string
     */
    
const DOUBLE_QUOTED "double";
    
    
/**
     * Single quoted string
     */
    
const SINGLE_QUOTED "single";
    
    
/**
     * String format
     * 
     * @var string
     */
    
protected $format self::DOUBLE_QUOTED;
    
    
/**
     * Sets node's value
     * 
     * @param string $value Value
     * 
     * @return $this
     */
    
public function setValue($value)
    {
        
$this->value = (string) $value;
        
//Force recalculation of the raw value
        
return $this->setFormat($this->format);
    }
    
    
/**
     * Sets node's raw value
     * 
     * @param mixed $raw Raw value
     * 
     * @return $this
     * 
     * @throws \Exception
     */
    
public function setRaw($raw)
    {
        if (!
is_string($raw) || strlen($raw) < 2) {
            throw new 
\Exception("Invalid string");
        }
        
$startQuote $raw[0];
        
$endQuote substr($raw, -1);
        if ((
$startQuote !== "'" && $startQuote !== '"') ||
            
$startQuote !== $endQuote
        
) {
            throw new 
\Exception("Invalid string");
        }
        
$this->value Utils::unquoteLiteralString($raw);
        
$this->setFormat($raw[0] === "'" ?
            
self::SINGLE_QUOTED :
            
self::DOUBLE_QUOTED
        
);
        
$this->raw $raw;
        return 
$this;
    }
    
    
/**
     * Returns string format
     * 
     * @return string
     */
    
public function getFormat()
    {
        return 
$this->format;
    }
    
    
/**
     * Sets string format
     * 
     * @param string $format Format, one of the format constants
     * 
     * @return $this
     */
    
public function setFormat($format)
    {
        
$this->format $format;
        
$quote $format === self::SINGLE_QUOTED "'" '"';
        
$this->raw Utils::quoteLiteralString($this->value$quote);
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents the export default declaration.
 * For example: export default a
 * 
 * @author Marco Marchiò <[email protected]>
 */
class ExportDefaultDeclaration extends Node implements ModuleDeclaration
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"declaration" => true
    
);
    
    
/**
     * The exported declaration
     * 
     * @var Declaration|Expression
     */
    
protected $declaration;
    
    
/**
     * Returns the exported declaration
     * 
     * @return Declaration|Expression
     */
    
public function getDeclaration()
    {
        return 
$this->declaration;
    }
    
    
/**
     * Sets the exported declaration
     * 
     * @param Declaration|Expression $declaration The exported declaration
     * 
     * @return $this
     */
    
public function setDeclaration($declaration)
    {
        
$this->assertType($declaration, array("Declaration""Expression"));
        
$this->declaration $declaration;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a labeled statement.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class LabeledStatement extends Node implements Statement
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"label" => true,
        
"body" => true
    
);
    
    
/**
     * Label
     * 
     * @var Identifier 
     */
    
protected $label;
    
    
/**
     * Body
     * 
     * @var Statement 
     */
    
protected $body;
    
    
/**
     * Returns the label
     * 
     * @return Identifier
     */
    
public function getLabel()
    {
        return 
$this->label;
    }
    
    
/**
     * Sets the label
     * 
     * @param Identifier $label Label
     * 
     * @return $this
     */
    
public function setLabel(Identifier $label)
    {
        
$this->label $label;
        return 
$this;
    }
    
    
/**
     * Returns the body
     * 
     * @return Statement
     */
    
public function getBody()
    {
        return 
$this->body;
    }
    
    
/**
     * Sets the body
     * 
     * @param Statement $body Body
     * 
     * @return $this
     */
    
public function setBody(Statement $body)
    {
        
$this->body $body;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * Interface that every statement node must implement.
 * 
 * @author Marco Marchiò <[email protected]>
 */
interface Statement
{
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * Common interface for function import and export nodes.
 * 
 * @author Marco Marchiò <[email protected]>
 */
interface ModuleDeclaration
{
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents the catch clause in a try-catch statement.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class CatchClause extends Node
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"param" => true,
        
"body" => true
    
);
    
    
/**
     * The catch clause parameter
     * 
     * @var Pattern 
     */
    
protected $param;
    
    
/**
     * The body of the catch clause
     * 
     * @var BlockStatement 
     */
    
protected $body;
    
    
/**
     * Returns the catch clause parameter
     * 
     * @return Pattern
     */
    
public function getParam()
    {
        return 
$this->param;
    }
    
    
/**
     * Sets the catch clause parameter
     * 
     * @param Pattern $param Catch clause parameter
     * 
     * @return $this
     */
    
public function setParam($param)
    {
        
$this->assertType($param"Pattern"true);
        
$this->param $param;
        return 
$this;
    }
    
    
/**
     * Returns the body of the catch clause
     * 
     * @return BlockStatement
     */
    
public function getBody()
    {
        return 
$this->body;
    }
    
    
/**
     * Sets the body of the catch clause
     * 
     * @param BlockStatement $body The block of code inside the catch clause
     * 
     * @return $this
     */
    
public function setBody(BlockStatement $body)
    {
        
$this->body $body;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a "var", "const" or "let" declaration.
 * For example: var a = 1
 * 
 * @author Marco Marchiò <[email protected]>
 */
class VariableDeclaration extends Node implements Declaration
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"declarations" => true,
        
"kind" => false
    
);
    
    
//Kind constants
    /**
     * "var" kind
     */
    
const KIND_VAR "var";
    
    
/**
     * "let" kind
     */
    
const KIND_LET "let";
    
    
/**
     * "const" kind
     */
    
const KIND_CONST "const";
    
    
/**
     * Declarations array
     * 
     * @var VariableDeclarator[] 
     */
    
protected $declarations = array();
    
    
/**
     * Declaration kind that is one of the kind constants
     * 
     * @var string 
     */
    
protected $kind self::KIND_VAR;
    
    
/**
     * Returns the declarations array
     * 
     * @return VariableDeclarator[]
     */
    
public function getDeclarations()
    {
        return 
$this->declarations;
    }
    
    
/**
     * Sets the declarations array
     * 
     * @param VariableDeclarator[] $declarations Declarations array
     * 
     * @return $this
     */
    
public function setDeclarations($declarations)
    {
        
$this->assertArrayOf($declarations"VariableDeclarator");
        
$this->declarations $declarations;
        return 
$this;
    }
    
    
/**
     * Returns the declaration kind that is one of the kind constants
     * 
     * @return string
     */
    
public function getKind()
    {
        return 
$this->kind;
    }
    
    
/**
     * Sets the declaration kind that is one of the kind constants
     * 
     * @param string $kind Declaration kind
     * 
     * @return $this
     */
    
public function setKind($kind)
    {
        
$this->kind $kind;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a boolean literal.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class BooleanLiteral extends Literal
{
    
/**
     * Sets node's value
     * 
     * @param mixed $value Value
     * 
     * @return $this
     */
    
public function setValue($value)
    {
        if (
$value === "true") {
            
$this->value true;
        } elseif (
$value === "false") {
            
$this->value false;
        } else {
            
$this->value = (bool) $value;
        }
        
$this->raw $this->value "true" "false";
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents an expression wrapped in round brackets.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class ParenthesizedExpression extends Node implements Expression
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"expression" => true
    
);
    
    
/**
     * The wrapped expression
     * 
     * @var Expression 
     */
    
protected $expression;
    
    
/**
     * Returns the wrapped expression
     * 
     * @return Expression
     */
    
public function getExpression()
    {
        return 
$this->expression;
    }
    
    
/**
     * Sets the wrapped expression
     * 
     * @param Expression $expression Wrapped expression
     * 
     * @return $this
     */
    
public function setExpression(Expression $expression)
    {
        
$this->expression $expression;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents the "break" statement inside loops.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class BreakStatement extends Node implements Statement
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"label" => true
    
);
    
    
/**
     * The optional label of the break statement
     * 
     * @var Identifier 
     */
    
protected $label;
    
    
/**
     * Returns the node's label
     * 
     * @return Identifier
     */
    
public function getLabel()
    {
        return 
$this->label;
    }
    
    
/**
     * Sets the node's label
     * 
     * @param Identifier $label Node's label
     * 
     * @return $this
     */
    
public function setLabel($label)
    {
        
$this->assertType($label"Identifier"true);
        
$this->label $label;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a yield statement.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class YieldExpression extends Node implements Expression
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"argument" => true,
        
"delegate" => false
    
);
    
    
/**
     * Yield argument
     * 
     * @var Expression 
     */
    
protected $argument;
    
    
/**
     * Delegate flag that is true when the yield keyword is preceded by *
     * 
     * @var bool 
     */
    
protected $delegate false;
    
    
/**
     * Returns yield argument
     * 
     * @return Expression
     */
    
public function getArgument()
    {
        return 
$this->argument;
    }
    
    
/**
     * Sets yield argument
     * 
     * @param Expression $argument Argument
     * 
     * @return $this
     */
    
public function setArgument($argument)
    {
        
$this->assertType($argument"Expression"true);
        
$this->argument $argument;
        return 
$this;
    }
    
    
/**
     * Returns the delegate flag that is true when the yield keyword is
     * preceded by *
     * 
     * @return bool
     */
    
public function getDelegate()
    {
        return 
$this->delegate;
    }
    
    
/**
     * Sets the delegate flag that is true when the yield keyword is
     * preceded by *
     * 
     * @param bool $delegate Delegate flag
     * 
     * @return $this
     */
    
public function setDelegate($delegate)
    {
        
$this->delegate = (bool) $delegate;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents an "export all" declaration.
 * For example: export * from "test"
 * 
 * @author Marco Marchiò <[email protected]>
 */
class ExportAllDeclaration extends Node implements ModuleDeclaration
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"source" => true,
        
"exported" => true
    
);

    
/**
     * The export source
     *
     * @var Literal
     */
    
protected $source;

    
/**
     * The exported name
     *
     * @var Identifier|StringLiteral
     */
    
protected $exported;

    
/**
     * Returns the export source
     *
     * @return Literal
     */
    
public function getSource()
    {
        return 
$this->source;
    }

    
/**
     * Sets the export source
     *
     * @param Literal $source Export source
     *
     * @return $this
     */
    
public function setSource(Literal $source)
    {
        
$this->source $source;
        return 
$this;
    }

    
/**
     * Returns the exported name
     *
     * @return Identifier|StringLiteral
     */
    
public function getExported()
    {
        return 
$this->exported;
    }

    
/**
     * Sets the exported name
     *W
     * @param Identifier|StringLiteral $exported Exported name
     *
     * @return $this
     */
    
public function setExported($exported)
    {
        
$this->assertType($exported, array("Identifier""StringLiteral"), true);
        
$this->exported $exported;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents the "this" keyword.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class ThisExpression extends Node implements Expression
{
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents the spread element in array literals or function
 * calls.
 * For example "...params" in: test(...params)
 * 
 * @author Marco Marchiò <[email protected]>
 */
class SpreadElement extends Node
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"argument" => true
    
);
    
    
/**
     * The node's argument
     * 
     * @var Expression
     */
    
protected $argument;
    
    
/**
     * Returns the node's argument
     * 
     * @return Expression
     */
    
public function getArgument()
    {
        return 
$this->argument;
    }
    
    
/**
     * Sets the node's argument
     * 
     * @param Expression $argument Node's argument
     * 
     * @return $this
     */
    
public function setArgument(Expression $argument)
    {
        
$this->argument $argument;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a chain element.
 * 
 * @author Marco Marchiò <[email protected]>
 */
abstract class ChainElement extends Node implements Expression
{
    protected 
$propertiesMap = array(
        
"optional" => false
    
);

    
/**
     * Optional flag that is true if the node is in the optional
     * part of a chain expression
     *
     * @var bool
     */
    
protected $optional false;

    
/**
     * Returns the optional flag that is true if the node is in
     * the optional part of a chain expression
     *
     * @return bool
     */
    
public function getOptional()
    {
        return 
$this->optional;
    }

    
/**
     * Sets the optional flag that is true if the node is in
     * the optional part of a chain expression
     *
     * @param bool $optional Optional flag
     *
     * @return $this
     */
    
public function setOptional($optional)
    {
        
$this->optional = (bool) $optional;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a specifier in an export declaration.
 * For example "{a}" in: export {a}
 * 
 * @author Marco Marchiò <[email protected]>
 */
class ExportSpecifier extends ModuleSpecifier
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"exported" => true
    
);
    
    
/**
     * Exported identifier
     * 
     * @var Identifier|StringLiteral
     */
    
protected $exported;
    
    
/**
     * Returns the exported identifier
     * 
     * @return Identifier|StringLiteral
     */
    
public function getExported()
    {
        return 
$this->exported;
    }
    
    
/**
     * Sets the exported identifier
     * 
     * @param Identifier|StringLiteral $exported Exported identifier
     * 
     * @return $this
     */
    
public function setExported($exported)
    {
        
$this->assertType($exported, array("Identifier""StringLiteral"));
        
$this->exported $exported;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents the "super" keyword.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class Super extends Node
{
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents an empty statement (;).
 * 
 * @author Marco Marchiò <[email protected]>
 */
class EmptyStatement extends Node implements Statement
{
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a debugger statement.
 * For example: debugger;
 * 
 * @author Marco Marchiò <[email protected]>
 */
class DebuggerStatement extends Node implements Statement
{
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a comment.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class Comment extends Node
{
    
//Comment kind constants
    /**
     * Inline comment
     */
    
const KIND_INLINE "inline";
    
    
/**
     * Multiline comment
     */
    
const KIND_MULTILINE "multiline";
    
    
/**
     * Html open comment
     */
    
const KIND_HTML_OPEN "html-open";
    
    
/**
     * Html close comment
     */
    
const KIND_HTML_CLOSE "html-close";
    
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"kind" => false,
        
"text" => false
    
);
    
    
/**
     * The comment kind
     * 
     * @var string 
     */
    
protected $kind;
    
    
/**
     * The comment text
     * 
     * @var string 
     */
    
protected $text;
    
    
/**
     * Returns the comment kind
     * 
     * @return string
     */
    
public function getKind()
    {
        return 
$this->kind;
    }
    
    
/**
     * Sets the comment kind
     * 
     * @param string $kind Comment kind
     * 
     * @return $this
     */
    
public function setKind($kind)
    {
        
$this->kind $kind;
        return 
$this;
    }
    
    
/**
     * Returns the comment text
     * 
     * @return string
     */
    
public function getText()
    {
        return 
$this->text;
    }
    
    
/**
     * Sets the comment text
     * 
     * @param string $text Comment text
     * 
     * @return $this
     */
    
public function setText($text)
    {
        
$this->text $text;
        return 
$this;
    }
    
    
/**
     * Returns the comment raw text
     * 
     * @return string
     */
    
public function getRawText()
    {
        
$text $this->getText();
        
$kind $this->getKind();
        
        if (
$kind === self::KIND_INLINE) {
            return 
"//" $text;
        } elseif (
$kind === self::KIND_HTML_OPEN) {
            return 
"<!--" $text;
        } elseif (
$kind === self::KIND_HTML_CLOSE) {
            return 
"-->" $text;
        } else {
            return 
"/*" $text "*/";
        }
    }
    
    
/**
     * Sets the comment raw text
     * 
     * @param string $rawText Comment raw text
     * 
     * @return $this
     */
    
public function setRawText($rawText)
    {
        
$start substr($rawText02);
        if (
$start === "//") {
            
$kind self::KIND_INLINE;
            
$text substr($rawText2);
        } elseif (
$start === "/*" && substr($rawText, -2) === "*/") {
            
$kind self::KIND_MULTILINE;
            
$text substr($rawText2, -2);
        } elseif (
$start === "<!" && substr($rawText22) === "--") {
            
$kind self::KIND_HTML_OPEN;
            
$text substr($rawText4);
        } elseif (
$start === "--" && substr($rawText21) === ">") {
            
$kind self::KIND_HTML_CLOSE;
            
$text substr($rawText3);
        } else {
            throw new 
\Exception("Invalid comment");
        }
        return 
$this->setKind($kind)->setText($text);
    }
    
    
/**
     * Sets leading comments array
     * 
     * @param Comment[] $comments Comments array
     * 
     * @return $this
     */
    
public function setLeadingComments($comments)
    {
        
//Comments cannot be attached to other comments
        
return $this;
    }
    
    
/**
     * Sets trailing comments array
     * 
     * @param Comment[] $comments Comments array
     * 
     * @return $this
     */
    
public function setTrailingComments($comments)
    {
        
//Comments cannot be attached to other comments
        
return $this;
    }
    
    
/**
     * Returns a serializable version of the node
     * 
     * @return array
     */
    
#[\ReturnTypeWillChange]
    public function 
jsonSerialize()
    {
        
$ret parent::jsonSerialize();
        unset(
$ret["leadingComments"]);
        unset(
$ret["trailingComments"]);
        return 
$ret;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a sequence of expressions.
 * For example: a, b
 * 
 * @author Marco Marchiò <[email protected]>
 */
class SequenceExpression  extends Node implements Expression
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"expressions" => true
    
);
    
    
/**
     * Expressions array
     * 
     * @var Expression[]
     */
    
protected $expressions = array();
    
    
/**
     * Returns the expressions array
     * 
     * @return Expression[]
     */
    
public function getExpressions()
    {
        return 
$this->expressions;
    }
    
    
/**
     * Sets the expressions array
     * 
     * @param Expression[] $expressions Expressions array
     * 
     * @return $this
     */
    
public function setExpressions($expressions)
    {
        
$this->assertArrayOf($expressions"Expression");
        
$this->expressions $expressions;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents an object literal.
 * For example: {a: 1, b: 2, c: 3}
 * 
 * @author Marco Marchiò <[email protected]>
 */
class ObjectExpression extends Node implements Expression
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"properties" => true
    
);
    
    
/**
     * Object properties
     * 
     * @var Property[] 
     */
    
protected $properties = array();
    
    
/**
     * Returns object properties
     * 
     * @return Property[] 
     */
    
public function getProperties()
    {
        return 
$this->properties;
    }
    
    
/**
     * Sets object properties
     * 
     * @param Property[] $properties Object properties
     * 
     * @return $this
     */
    
public function setProperties($properties)
    {
        
$this->assertArrayOf($properties, array("Property""SpreadElement"));
        
$this->properties $properties;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * Abstract class that export and import specifiers nodes must extend.
 * 
 * @author Marco Marchiò <[email protected]>
 * 
 * @abstract
 */
abstract class ModuleSpecifier extends Node
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"local" => true
    
);
    
    
/**
     * Local identifier
     * 
     * @var Identifier|StringLiteral
     */
    
protected $local;
    
    
/**
     * Returns the local identifier
     * 
     * @return Identifier|StringLiteral
     */
    
public function getLocal()
    {
        return 
$this->local;
    }
    
    
/**
     * Sets the local identifier
     * 
     * @param Identifier|StringLiteral $local Local identifier
     * 
     * @return $this
     */
    
public function setLocal($local)
    {
        
$this->assertType($local, array("Identifier""StringLiteral"));
        
$this->local $local;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a conditional expression.
 * For example: test() ? ok() : fail()
 * 
 * @author Marco Marchiò <[email protected]>
 */
class ConditionalExpression extends Node implements Expression
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"test" => true,
        
"consequent" => true,
        
"alternate" => true
    
);
    
    
/**
     * The test expression
     * 
     * @var Expression 
     */
    
protected $test;
    
    
/**
     * The consequent expression
     * 
     * @var Expression 
     */
    
protected $consequent;
    
    
/**
     * The alternate expression
     * 
     * @var Expression 
     */
    
protected $alternate;
    
    
/**
     * Returns the test expression
     * 
     * @return Expression
     */
    
public function getTest()
    {
        return 
$this->test;
    }
    
    
/**
     * Sets the test expression
     * 
     * @param Expression $test Test expression
     * 
     * @return $this
     */
    
public function setTest(Expression $test)
    {
        
$this->test $test;
        return 
$this;
    }
    
    
/**
     * Returns the consequent expression
     * 
     * @return Expression
     */
    
public function getConsequent()
    {
        return 
$this->consequent;
    }
    
    
/**
     * Sets the consequent expression
     * 
     * @param Expression $consequent Consequent expression
     * 
     * @return $this
     */
    
public function setConsequent(Expression $consequent)
    {
        
$this->consequent $consequent;
        return 
$this;
    }
    
    
/**
     * Returns the alternate expression
     * 
     * @return Expression
     */
    
public function getAlternate()
    {
        return 
$this->alternate;
    }
    
    
/**
     * Sets the alternate expression
     * 
     * @param Expression $alternate Alternate expression
     * 
     * @return $this
     */
    
public function setAlternate(Expression $alternate)
    {
        
$this->alternate $alternate;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents an update expression.
 * For example: a++
 * 
 * @author Marco Marchiò <[email protected]>
 */
class UpdateExpression extends Node implements Expression
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"argument" => true,
        
"operator" => false,
        
"prefix" => false
    
);
    
    
/**
     * Expression's operator
     * 
     * @var string
     */
    
protected $operator;
    
    
/**
     * Prefix flag that is true when the operator precedes the argument
     * 
     * @var bool
     */
    
protected $prefix false;
    
    
/**
     * Expression's argument
     * 
     * @var Expression 
     */
    
protected $argument;
    
    
/**
     * Returns the expression's operator
     * 
     * @return string
     */
    
public function getOperator()
    {
        return 
$this->operator;
    }
    
    
/**
     * Sets the expression's operator
     * 
     * @param string $operator Operator
     * 
     * @return $this
     */
    
public function setOperator($operator)
    {
        
$this->operator $operator;
        return 
$this;
    }
    
    
/**
     * Returns the prefix flag that is true when the operator precedes the
     * argument
     * 
     * @return bool
     */
    
public function getPrefix()
    {
        return 
$this->prefix;
    }
    
    
/**
     * Sets the prefix flag that is true when the operator precedes the
     * argument
     * 
     * @param bool $prefix Prefix flag
     * 
     * @return $this
     */
    
public function setPrefix($prefix)
    {
        
$this->prefix $prefix;
        return 
$this;
    }
    
    
/**
     * Returns the expression's argument
     * 
     * @return Expression
     */
    
public function getArgument()
    {
        return 
$this->argument;
    }
    
    
/**
     * Sets the expression's argument
     * 
     * @param Expression $argument Argument
     * 
     * @return $this
     */
    
public function setArgument(Expression $argument)
    {
        
$this->argument $argument;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents an import expression (dynamic import).
 * 
 * @author Marco Marchiò <[email protected]>
 */
class ImportExpression extends Node implements Expression
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"source" => true
    
);
    
    
/**
     * The catch clause parameter
     * 
     * @var Expression
     */
    
protected $source;
    
    
/**
     * Returns the import source
     * 
     * @return Expression
     */
    
public function getSource()
    {
        return 
$this->source;
    }
    
    
/**
     * Sets the import source
     * 
     * @param Expression $source Import source
     * 
     * @return $this
     */
    
public function setSource(Expression $source)
    {
        
$this->source $source;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * Interface that every declaration node must implement.
 * 
 * @author Marco Marchiò <[email protected]>
 */
interface Declaration extends Statement
{
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a property in an object literal.
 * For example "b" in: a = {b: 1}
 * 
 * @author Marco Marchiò <[email protected]>
 */
class Property extends Node
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"key" => true,
        
"value" => true,
        
"kind" => false,
        
"method" => false,
        
"shorthand" => false,
        
"computed" => false
    
);
    
    
//Kind constants
    /**
     * The default kind for properties
     */
    
const KIND_INIT "init";
    
    
/**
     * Getter property
     */
    
const KIND_GET "get";
    
    
/**
     * Setter property
     */
    
const KIND_SET "set";
    
    
/**
     * Property key
     * 
     * @var Expression 
     */
    
protected $key;
    
    
/**
     * Property value
     * 
     * @var Expression 
     */
    
protected $value;
    
    
/**
     * Property kind that is one of the kind constants
     * 
     * @var string
     */
    
protected $kind self::KIND_INIT;
    
    
/**
     * Property method flag that is true when the property is a method
     * 
     * @var bool 
     */
    
protected $method false;
    
    
/**
     * Property shorthand flag that is true when the property is declared
     * using an identifier and without a value
     * 
     * @var bool 
     */
    
protected $shorthand false;
    
    
/**
     * Property computed flag that is true when the property is declared using
     * the square brackets syntax
     * 
     * @var bool 
     */
    
protected $computed false;
    
    
/**
     * Returns the property key
     * 
     * @return Expression
     */
    
public function getKey()
    {
        return 
$this->key;
    }
    
    
/**
     * Sets the property key
     * 
     * @param Expression $key Property key
     * 
     * @return $this
     */
    
public function setKey(Expression $key)
    {
        
$this->key $key;
        return 
$this;
    }
    
    
/**
     * Returns the property value
     * 
     * @return Expression
     */
    
public function getValue()
    {
        return 
$this->value;
    }
    
    
/**
     * Sets the property value
     * 
     * @param Expression $value Property value
     * 
     * @return $this
     */
    
public function setValue($value)
    {
        
$this->assertType($value"Expression");
        
$this->value $value;
        return 
$this;
    }
    
    
/**
     * Returns the property kind that is one of the kind constants
     * 
     * @return string
     */
    
public function getKind()
    {
        return 
$this->kind;
    }
    
    
/**
     * Sets the property kind that is one of the kind constants
     * 
     * @param string $kind Property kind
     * 
     * @return $this
     */
    
public function setKind($kind)
    {
        
$this->kind $kind;
        return 
$this;
    }
    
    
/**
     * Returns the property method flag that is true when the property is a
     * method
     * 
     * @return bool
     */
    
public function getMethod()
    {
        return 
$this->method;
    }
    
    
/**
     * Sets the property method flag that is true when the property is a method
     * 
     * @param bool $method Method flag
     * 
     * @return $this
     */
    
public function setMethod($method)
    {
        
$this->method = (bool) $method;
        return 
$this;
    }
    
    
/**
     * Returns the property shorthand flag that is true when the property
     * is declared using an identifier and without a value
     * 
     * @return bool 
     */
    
public function getShorthand()
    {
        return 
$this->shorthand;
    }
    
    
/**
     * Sets the property shorthand flag that is true when the property
     * is declared using an identifier and without a value
     * 
     * @param bool $shorthand Property shorthand flag
     * 
     * @return $this
     */
    
public function setShorthand($shorthand)
    {
        
$this->shorthand = (bool) $shorthand;
        return 
$this;
    }
    
    
/**
     * Returns the property computed flag that is true when the property is
     * declared using the square brackets syntax
     * 
     * @return bool
     */
    
public function getComputed()
    {
        return 
$this->computed;
    }
    
    
/**
     * Sets the property computed flag that is true when the property is
     * declared using the square brackets syntax
     * 
     * @param bool $computed Property computed flag
     * 
     * @return $this
     */
    
public function setComputed($computed)
    {
        
$this->computed = (bool) $computed;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a member expression.
 * For example: foo.bar
 * 
 * @author Marco Marchiò <[email protected]>
 */
class MemberExpression extends ChainElement implements Pattern
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"object" => true,
        
"property" => true,
        
"computed" => false
    
);
    
    
/**
     * Expression's object
     * 
     * @var Expression|Super 
     */
    
protected $object;
    
    
/**
     * Expression's property
     * 
     * @var Expression|PrivateIdentifier
     */
    
protected $property;
    
    
/**
     * Computed flag that is true if the property is declared using square
     * brackets syntax
     * 
     * @var bool 
     */
    
protected $computed false;
    
    
/**
     * Returns the expression's object
     * 
     * @return Expression|Super 
     */
    
public function getObject()
    {
        return 
$this->object;
    }
    
    
/**
     * Sets the expression's object
     * 
     * @param Expression|Super $object Object
     * 
     * @return $this
     */
    
public function setObject($object)
    {
        
$this->assertType($object, array("Expression""Super"));
        
$this->object $object;
        return 
$this;
    }
    
    
/**
     * Returns the expression's property
     * 
     * @return Expression|PrivateIdentifier
     */
    
public function getProperty()
    {
        return 
$this->property;
    }
    
    
/**
     * Sets the expression's property
     * 
     * @param Expression|PrivateIdentifier $property Property
     * 
     * @return $this
     */
    
public function setProperty($property)
    {
        
$this->assertType($property, array("Expression""PrivateIdentifier"));
        
$this->property $property;
        return 
$this;
    }
    
    
/**
     * Returns the computed flag that is true if the property is declared
     * using square brackets syntax
     * 
     * @return bool
     */
    
public function getComputed()
    {
        return 
$this->computed;
    }
    
    
/**
     * Sets the computed flag that is true if the property is declared
     * using square brackets syntax
     * 
     * @param bool $computed Computed flag
     * 
     * @return $this
     */
    
public function setComputed($computed)
    {
        
$this->computed = (bool) $computed;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a function expression
 * For example: var test = function () {}
 * 
 * @author Marco Marchiò <[email protected]>
 */
class FunctionExpression extends Function_ implements Expression
{
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a class expression
 * For example: test = class {}
 * 
 * @author Marco Marchiò <[email protected]>
 */
class ClassExpression extends Class_ implements Expression
{
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents an assignment in a binding context.
 * For example "a = b" in: var {a = b} = c
 * 
 * @author Marco Marchiò <[email protected]>
 */
class AssignmentPattern extends Node implements Pattern
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"left" => true,
        
"right" => true
    
);
    
    
/**
     * The left node of the assignment
     * 
     * @var Pattern
     */
    
protected $left;
    
    
/**
     * The right node of the assignment
     * 
     * @var Expression
     */
    
protected $right;
    
    
/**
     * Returns the left node of the assignment
     * 
     * @return Pattern
     */
    
public function getLeft()
    {
        return 
$this->left;
    }
    
    
/**
     * Sets the left node of the assignment
     * 
     * @param Pattern $left Left node
     * 
     * @return $this
     */
    
public function setLeft(Pattern $left)
    {
        
$this->left $left;
        return 
$this;
    }
    
    
/**
     * Returns the right node of the assignment
     * 
     * @return Expression
     */
    
public function getRight()
    {
        return 
$this->right;
    }
    
    
/**
     * Sets the right node of the assignment
     * 
     * @param Expression $right Right node
     * 
     * @return $this
     */
    
public function setRight(Expression $right)
    {
        
$this->right $right;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents the return statement inside functions.
 * For example: return a + 1
 * 
 * @author Marco Marchiò <[email protected]>
 */
class ReturnStatement extends Node implements Statement
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"argument" => true
    
);
    
    
/**
     * Optional expression after the return keyword
     * 
     * @var Expression 
     */
    
protected $argument;
    
    
/**
     * Returns the expression after the return keyword
     * 
     * @return Expression
     */
    
public function getArgument()
    {
        return 
$this->argument;
    }
    
    
/**
     * Sets the expression after the return keyword
     * 
     * @param Expression $argument The expression to return
     * 
     * @return $this
     */
    
public function setArgument($argument)
    {
        
$this->assertType($argument"Expression"true);
        
$this->argument $argument;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents an assignment expression.
 * For example: a = b
 * 
 * @author Marco Marchiò <[email protected]>
 */
class AssignmentExpression extends Node implements Expression
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"left" => true,
        
"operator" => false,
        
"right" => true
    
);
    
    
/**
     * The assignment operator
     * 
     * @var string 
     */
    
protected $operator;
    
    
/**
     * The left node of the assignment
     * 
     * @var Pattern|Expression
     */
    
protected $left;
    
    
/**
     * The right node of the assignment
     * 
     * @var Expression 
     */
    
protected $right;
    
    
/**
     * Returns the assignment operator
     * 
     * @return string
     */
    
public function getOperator()
    {
        return 
$this->operator;
    }
    
    
/**
     * Sets the assignment operator
     * 
     * @param string $operator Assignment operator
     * 
     * @return $this
     */
    
public function setOperator($operator)
    {
        
$this->operator $operator;
        return 
$this;
    }
    
    
/**
     * Returns the left node of the assignment
     * 
     * @return Pattern|Expression 
     */
    
public function getLeft()
    {
        return 
$this->left;
    }
    
    
/**
     * Sets the left node of the assignment
     * 
     * @param Pattern|Expression $left The node to set
     * 
     * @return $this
     */
    
public function setLeft($left)
    {
        
$this->assertType($left, array("Pattern""Expression"));
        
$this->left $left;
        return 
$this;
    }
    
    
/**
     * Returns the right node of the assignment
     * 
     * @return Expression 
     */
    
public function getRight()
    {
        return 
$this->right;
    }
    
    
/**
     * Sets the right node of the assignment
     * 
     * @param Expression $right The node to set
     * 
     * @return $this
     */
    
public function setRight(Expression $right)
    {
        
$this->right $right;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a property in an object binding pattern.
 * For example "a" in: var {a} = b
 * 
 * @author Marco Marchiò <[email protected]>
 */
class AssignmentProperty extends Property
{
    
/**
     * Returns node's type
     * 
     * @return string
     */
    
public function getType()
    {
        return 
"Property";
    }
    
    
/**
     * Sets the property value
     * 
     * @param Pattern $value Property value
     * 
     * @return $this
     */
    
public function setValue($value)
    {
        
$this->assertType($value"Pattern");
        
$this->value $value;
        return 
$this;
    }
    
    
/**
     * Sets the property kind that is one of the kind constants
     * 
     * @param string $kind Property kind
     * 
     * @return $this
     * 
     * @codeCoverageIgnore
     */
    
public function setKind($kind)
    {
        return 
$this;
    }
    
    
/**
     * Sets the property method flag that is true when the property is a method
     * 
     * @param bool $method Method flag
     * 
     * @return $this
     * 
     * @codeCoverageIgnore
     */
    
public function setMethod($method)
    {
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * A node that represents a block of code wrapped in curly braces.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class BlockStatement extends Node implements Statement
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"body" => true
    
);
    
    
/**
     * Block's body
     * 
     * @var Statement[] 
     */
    
protected $body = array();
    
    
/**
     * Returns block's body
     * 
     * @return Statement[]
     */
    
public function getBody()
    {
        return 
$this->body;
    }
    
    
/**
     * Sets block's body
     * 
     * @param Statement[] $body Array of Statements that are the body of the
     *                          block
     * 
     * @return $this
     */
    
public function setBody($body)
    {
        
$this->assertArrayOf($body"Statement");
        
$this->body $body;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * Abstract class for classes.
 * 
 * @author Marco Marchiò <[email protected]>
 */
abstract class Class_ extends Node
{
    
/**
     * Map of node properties
     * 
     * @var array 
     */
    
protected $propertiesMap = array(
        
"id" => true,
        
"superClass" => true,
        
"body" => true
    
);
    
    
/**
     * Class name
     * 
     * @var Identifier 
     */
    
protected $id;
    
    
/**
     * Extended class
     * 
     * @var Expression 
     */
    
protected $superClass;
    
    
/**
     * Class body
     * 
     * @var ClassBody 
     */
    
protected $body;
    
    
/**
     * Returns class name
     * 
     * @return Identifier
     */
    
public function getId()
    {
        return 
$this->id;
    }
    
    
/**
     * Sets class name
     * 
     * @param Identifier $id Class name
     * 
     * @return $this
     */
    
public function setId($id)
    {
        
$this->assertType($id"Identifier"true);
        
$this->id $id;
        return 
$this;
    }
    
    
/**
     * Returns extended class
     * 
     * @return Expression
     */
    
public function getSuperClass()
    {
        return 
$this->superClass;
    }
    
    
/**
     * Sets extended class
     * 
     * @param Expression $superClass Extended class
     * 
     * @return $this
     */
    
public function setSuperClass($superClass)
    {
        
$this->assertType($superClass"Expression"true);
        
$this->superClass $superClass;
        return 
$this;
    }
    
    
/**
     * Returns class body
     * 
     * @return ClassBody
     */
    
public function getBody()
    {
        return 
$this->body;
    }
    
    
/**
     * Sets class body
     * 
     * @param ClassBody $body Class body
     * 
     * @return $this
     */
    
public function setBody($body)
    {
        
$this->assertType($body"ClassBody");
        
$this->body $body;
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax;

/**
 * Parser class
 * 
 * @author Marco Marchiò <[email protected]>
 */
class Parser extends ParserAbstract
{
    use 
JSX\Parser;
    
    
//Identifier parsing mode constants
    /**
     * Everything is allowed as identifier, including keywords, null and booleans
     */
    
const ID_ALLOW_ALL 1;
    
    
/**
     * Keywords, null and booleans are not allowed in any situation
     */
    
const ID_ALLOW_NOTHING 2;
    
    
/**
     * Keywords, null and booleans are not allowed in any situation, future
     * reserved words are allowed if not in strict mode. Keywords that depend on
     * parser context are evaluated only if the parser context allows them.
     */
    
const ID_MIXED 3;
    
    
/**
     * Binding identifier parsing rule
     * 
     * @var int 
     */
    
protected static $bindingIdentifier self::ID_MIXED;
    
    
/**
     * Labelled identifier parsing rule
     * 
     * @var int 
     */
    
protected static $labelledIdentifier self::ID_MIXED;
    
    
/**
     * Identifier reference parsing rule
     * 
     * @var int 
     */
    
protected static $identifierReference self::ID_MIXED;
    
    
/**
     * Identifier name parsing rule
     * 
     * @var int 
     */
    
protected static $identifierName self::ID_ALLOW_ALL;
    
    
/**
     * Imported binding parsing rule
     * 
     * @var int 
     */
    
protected static $importedBinding self::ID_ALLOW_NOTHING;
    
    
/**
     * Assignment operators
     * 
     * @var array 
     */
    
protected $assignmentOperators = array(
        
"=""+=""-=""*=""/=""%=""<<="">>="">>>=""&=""^=",
        
"|=""**=""&&=""||=""??="
    
);
    
    
/**
     * Logical and binary operators
     * 
     * @var array 
     */
    
protected $logicalBinaryOperators = array(
        
"??" => 0,
        
"||" => 0,
        
"&&" => 1,
        
"|" => 2,
        
"^" => 3,
        
"&" => 4,
        
"===" => 5"!==" => 5"==" => 5"!=" => 5,
        
"<=" => 6">=" => 6"<" => 6">" => 6,
        
"instanceof" => 6"in" => 6,
        
">>>" => 7"<<" => 7">>" => 7,
        
"+" => 8"-" => 8,
        
"*" => 9"/" => 9"%" => 9,
        
"**" => 10
    
);
    
    
/**
     * Unary operators
     * 
     * @var array 
     */
    
protected $unaryOperators = array(
        
"delete""void""typeof""++""--""+""-""~""!"
    
);
    
    
/**
     * Postfix operators
     * 
     * @var array 
     */
    
protected $postfixOperators = array("--""++");
    
    
/**
     * Array of keywords that depends on a context property
     * 
     * @var array 
     */
    
protected $contextKeywords = array(
        
"yield" => "allowYield",
        
"await" => "allowAwait"
    
);
    
    
/**
     * Initializes parser context
     * 
     * @return void
     */
    
protected function initContext()
    {
        
$context = array(
            
"allowReturn" => false,
            
"allowIn" => false,
            
"allowYield" => false,
            
"allowAwait" => false
        
);
        
//If async/await is not enabled remove the
        //relative context properties
        
if (!$this->features->asyncAwait) {
            unset(
$context["allowAwait"]);
            unset(
$this->contextKeywords["await"]);
        }
        
$this->context = (object) $context;
    }
    
    
/**
     * Post initialize operations
     * 
     * @return void
     */
    
protected function postInit()
    {
        
//Remove exponentiation operator if the feature
        //is not enabled
        
if (!$this->features->exponentiationOperator) {
            
Utils::removeArrayValue(
                
$this->assignmentOperators,
                
"**="
            
);
            unset(
$this->logicalBinaryOperators["**"]);
        }

        
//Remove coalescing operator if the feature
        //is not enabled
        
if (!$this->features->coalescingOperator) {
            unset(
$this->logicalBinaryOperators["??"]);
        }

        
//Remove logical assignment operators if the
        //feature is not enabled
        
if (!$this->features->logicalAssignmentOperators) {
            foreach (array(
"&&=""||=""??=") as $op) {
                
Utils::removeArrayValue(
                    
$this->assignmentOperators,
                    
$op
                
);
            }
        }
    }
    
    
/**
     * Parses the source
     * 
     * @return Node\Program
     */
    
public function parse()
    {
        if (
$this->sourceType === \Peast\Peast::SOURCE_TYPE_MODULE) {
            
$this->scanner->setStrictMode(true);
            
$body $this->parseModuleItemList();
        } else {
            
$body $this->parseStatementList(true);
        }
        
        
$node $this->createNode(
            
"Program"$body ?: $this->scanner->getPosition()
        );
        
$node->setSourceType($this->sourceType);
        if (
$body) {
            
$node->setBody($body);
        }
        
$program $this->completeNode($node);
        
        if (
$this->scanner->getToken()) {
            
$this->error();
        }
        
        
//Execute scanner end operations
        
$this->scanner->consumeEnd();
        
        
//Emit the EndParsing event and pass the resulting program node as
        //event data
        
$this->eventsEmitter && $this->eventsEmitter->fire(
            
"EndParsing", array($program)
        );
        
        return 
$program;
    }
    
    
/**
     * Converts an expression node to a pattern node
     * 
     * @param Node\Node $node The node to convert
     * 
     * @return Node\Node
     */
    
protected function expressionToPattern($node)
    {
        if (
$node instanceof Node\ArrayExpression) {
            
            
$loc $node->location;
            
$elems = array();
            foreach (
$node->getElements() as $elem) {
                
$elems[] = $this->expressionToPattern($elem);
            }
                
            
$retNode $this->createNode("ArrayPattern"$loc->start);
            
$retNode->setElements($elems);
            
$this->completeNode($retNode$loc->end);
            
        } elseif (
$node instanceof Node\ObjectExpression) {
            
            
$loc $node->location;
            
$props = array();
            foreach (
$node->getProperties() as $prop) {
                
$props[] = $this->expressionToPattern($prop);
            }
                
            
$retNode $this->createNode("ObjectPattern"$loc->start);
            
$retNode->setProperties($props);
            
$this->completeNode($retNode$loc->end);
            
        } elseif (
$node instanceof Node\Property) {
            
            
$loc $node->location;
            
$retNode $this->createNode(
                
"AssignmentProperty"$loc->start
            
);
            
// If it's a shorthand property convert the value to an assignment
            // pattern if necessary
            
$value $node->getValue();
            
$key $node->getKey();
            if (
$value && $node->getShorthand() &&
                !
$value instanceof Node\AssignmentExpression &&
                (!
$value instanceof Node\Identifier || (
                
$key instanceof Node\Identifier && $key->getName() !== $value->getName()
                ))) {
                
$loc $node->location;
                
$valNode $this->createNode("AssignmentPattern"$loc->start);
                
$valNode->setLeft($key);
                
$valNode->setRight($value);
                
$this->completeNode($valNode$loc->end);
                
$value $valNode;
            } else {
                
$value $this->expressionToPattern($value);
            }
            
$retNode->setValue($value);
            
$retNode->setKey($key);
            
$retNode->setMethod($node->getMethod());
            
$retNode->setShorthand($node->getShorthand());
            
$retNode->setComputed($node->getComputed());
            
$this->completeNode($retNode$loc->end);
            
        } elseif (
$node instanceof Node\SpreadElement) {
            
            
$loc $node->location;
            
$retNode $this->createNode("RestElement"$loc->start);
            
$retNode->setArgument(
                
$this->expressionToPattern($node->getArgument())
            );
            
$this->completeNode($retNode$loc->end);
            
        } elseif (
$node instanceof Node\AssignmentExpression) {
            
            
$loc $node->location;
            
$retNode $this->createNode("AssignmentPattern"$loc->start);
            
$retNode->setLeft($this->expressionToPattern($node->getLeft()));
            
$retNode->setRight($node->getRight());
            
$this->completeNode($retNode$loc->end);
            
        } else {
            
$retNode $node;
        }
        return 
$retNode;
    }
    
    
/**
     * Parses a statement list
     * 
     * @param bool $parseDirectivePrologues True to parse directive prologues
     * 
     * @return Node\Node[]|null
     */
    
protected function parseStatementList(
        
$parseDirectivePrologues false
    
) {
        
$items = array();
        
        
//Get directive prologues and check if strict mode is present
        
if ($parseDirectivePrologues) {
            
$oldStrictMode $this->scanner->getStrictMode();
            if (
$directives $this->parseDirectivePrologues()) {
                
$items array_merge($items$directives[0]);
                
//If "use strict" is present enable scanner strict mode
                
if (in_array("use strict"$directives[1])) {
                    
$this->scanner->setStrictMode(true);
                }
            }
        }
        
        while (
$item $this->parseStatementListItem()) {
            
$items[] = $item;
        }
        
        
//Apply previous strict mode
        
if ($parseDirectivePrologues) {
            
$this->scanner->setStrictMode($oldStrictMode);
        }
        
        return 
count($items) ? $items null;
    }
    
    
/**
     * Parses a statement list item
     * 
     * @return Node\Statement|Node\Declaration|null
     */
    
protected function parseStatementListItem()
    {
        if (
$declaration $this->parseDeclaration()) {
            return 
$declaration;
        } elseif (
$statement $this->parseStatement()) {
            return 
$statement;
        }
        return 
null;
    }
    
    
/**
     * Parses a statement
     * 
     * @return Node\Statement|null
     */
    
protected function parseStatement()
    {
        
//Here the token value is checked for performance so that functions won't be
        //called if not necessary
        
$token $this->scanner->getToken();
        if (!
$token) {
            return 
null;
        }
        
$val $token->value;
        if (
$val === "{" && $statement $this->parseBlock()) {
            return 
$statement;
        } elseif (
$val === "var" && $statement $this->parseVariableStatement()) {
            return 
$statement;
        } elseif (
$val === ";" && $statement $this->parseEmptyStatement()) {
            return 
$statement;
        } elseif (
$val === "if" && $statement $this->parseIfStatement()) {
            return 
$statement;
        } elseif (
            (
$val === "for" || $val === "while" || $val === "do" || $val === "switch") &&
            
$statement $this->parseBreakableStatement()
        ) {
            return 
$statement;
        } elseif (
$val == "continue" && $statement $this->parseContinueStatement()) {
            return 
$statement;
        } elseif (
$val === "break" && $statement $this->parseBreakStatement()) {
            return 
$statement;
        } elseif (
            
$this->context->allowReturn && $val === "return" &&
            
$statement $this->parseReturnStatement()
        ) {
            return 
$statement;
        } elseif (
$val === "with" && $statement $this->parseWithStatement()) {
            return 
$statement;
        } elseif (
$val === "throw" && $statement $this->parseThrowStatement()) {
            return 
$statement;
        } elseif (
$val === "try" && $statement $this->parseTryStatement()) {
            return 
$statement;
        } elseif (
$val === "debugger" && $statement $this->parseDebuggerStatement()) {
            return 
$statement;
        } elseif (
$statement $this->parseLabelledStatement()) {
            return 
$statement;
        } elseif (
$statement $this->parseExpressionStatement()) {
            return 
$statement;
        }
        return 
null;
    }
    
    
/**
     * Parses a declaration
     * 
     * @return Node\Declaration|null
     */
    
protected function parseDeclaration()
    {
        
//Here the token value is checked for performance so that functions won't be
        //called if not necessary
        
$token $this->scanner->getToken();
        if (!
$token) {
            return 
null;
        }
        
$val $token->value;
        if (
$declaration $this->parseFunctionOrGeneratorDeclaration()) {
            return 
$declaration;
        } elseif (
$val === "class" && $declaration $this->parseClassDeclaration()) {
            return 
$declaration;
        } elseif (
            (
$val === "let" || $val === "const") &&
            
$declaration $this->isolateContext(
                array(
"allowIn" => true), "parseLexicalDeclaration"
            
)
        ) {
            return 
$declaration;
        }
        return 
null;
    }
    
    
/**
     * Parses a breakable statement
     * 
     * @return Node\Node|null
     */
    
protected function parseBreakableStatement()
    {
        if (
$statement $this->parseIterationStatement()) {
            return 
$statement;
        } elseif (
$statement $this->parseSwitchStatement()) {
            return 
$statement;
        }
        return 
null;
    }
    
    
/**
     * Parses a block statement
     * 
     * @return Node\BlockStatement|null
     */
    
protected function parseBlock()
    {
        if (
$token $this->scanner->consume("{")) {
            
            
$statements $this->parseStatementList();
            if (
$this->scanner->consume("}")) {
                
$node $this->createNode("BlockStatement"$token);
                if (
$statements) {
                    
$node->setBody($statements);
                }
                return 
$this->completeNode($node);
            }
            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses a module item list
     * 
     * @return Node\Node[]|null
     */
    
protected function parseModuleItemList()
    {
        
$items = array();
        while (
$item $this->parseModuleItem()) {
            
$items[] = $item;
        }
        return 
count($items) ? $items null;
    }
    
    
/**
     * Parses an empty statement
     * 
     * @return Node\EmptyStatement|null
     */
    
protected function parseEmptyStatement()
    {
        if (
$token $this->scanner->consume(";")) {
            
$node $this->createNode("EmptyStatement"$token);
            return 
$this->completeNode($node);
        }
        return 
null;
    }
    
    
/**
     * Parses a debugger statement
     * 
     * @return Node\DebuggerStatement|null
     */
    
protected function parseDebuggerStatement()
    {
        if (
$token $this->scanner->consume("debugger")) {
            
$node $this->createNode("DebuggerStatement"$token);
            
$this->assertEndOfStatement();
            return 
$this->completeNode($node);
        }
        return 
null;
    }
    
    
/**
     * Parses an if statement
     * 
     * @return Node\IfStatement|null
     */
    
protected function parseIfStatement()
    {
        if (
$token $this->scanner->consume("if")) {
            
            if (
$this->scanner->consume("(") &&
                (
$test $this->isolateContext(
                    array(
"allowIn" => true), "parseExpression"
                
)) &&
                
$this->scanner->consume(")") &&
                (
                    (
$consequent $this->parseStatement()) ||
                    (!
$this->scanner->getStrictMode() &&
                    
$consequent $this->parseFunctionOrGeneratorDeclaration(
                        
falsefalse
                    
))
                )
            ) {
                
                
$node $this->createNode("IfStatement"$token);
                
$node->setTest($test);
                
$node->setConsequent($consequent);
                
                if (
$this->scanner->consume("else")) {
                    if ((
$alternate $this->parseStatement()) ||
                        (!
$this->scanner->getStrictMode() &&
                        
$alternate $this->parseFunctionOrGeneratorDeclaration(
                            
falsefalse
                        
))
                    ) {
                        
$node->setAlternate($alternate);
                        return 
$this->completeNode($node);
                    }
                } else {
                    return 
$this->completeNode($node);
                }
            }
            
            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses a try-catch statement
     * 
     * @return Node\TryStatement|null
     */
    
protected function parseTryStatement()
    {
        if (
$token $this->scanner->consume("try")) {
            
            if (
$block $this->parseBlock()) {
                
                
$node $this->createNode("TryStatement"$token);
                
$node->setBlock($block);

                if (
$handler $this->parseCatch()) {
                    
$node->setHandler($handler);
                }

                if (
$finalizer $this->parseFinally()) {
                    
$node->setFinalizer($finalizer);
                }

                if (
$handler || $finalizer) {
                    return 
$this->completeNode($node);
                }
            }
            
            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses the catch block of a try-catch statement
     * 
     * @return Node\CatchClause|null
     */
    
protected function parseCatch()
    {
        if (
$token $this->scanner->consume("catch")) {

            
$node $this->createNode("CatchClause"$token);

            if (
$this->scanner->consume("(")) {
                if (!(
$param $this->parseCatchParameter()) ||
                    !
$this->scanner->consume(")")) {
                    
$this->error();
                }
                
$node->setParam($param);
            } elseif (!
$this->features->optionalCatchBinding) {
                
$this->error();
            }

            if (!(
$body $this->parseBlock())) {
                
$this->error();
            }

            
$node->setBody($body);

            return 
$this->completeNode($node);
        }
        return 
null;
    }
    
    
/**
     * Parses the catch parameter of a catch block in a try-catch statement
     * 
     * @return Node\Node|null
     */
    
protected function parseCatchParameter()
    {
        if (
$param $this->parseIdentifier(static::$bindingIdentifier)) {
            return 
$param;
        } elseif (
$param $this->parseBindingPattern()) {
            return 
$param;
        }
        return 
null;
    }
    
    
/**
     * Parses a finally block in a try-catch statement
     * 
     * @return Node\BlockStatement|null
     */
    
protected function parseFinally()
    {
        if (
$this->scanner->consume("finally")) {
            
            if (
$block $this->parseBlock()) {
                return 
$block;
            }
            
            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses a continue statement
     * 
     * @return Node\ContinueStatement|null
     */
    
protected function parseContinueStatement()
    {
        if (
$token $this->scanner->consume("continue")) {
            
            
$node $this->createNode("ContinueStatement"$token);
            
            if (
$this->scanner->noLineTerminators() &&
                (
$label $this->parseIdentifier(static::$labelledIdentifier))
            ) {
                
$node->setLabel($label);
                
$this->assertEndOfStatement();
            } else {
                
$this->scanner->consume(";");
            }
            
            return 
$this->completeNode($node);
        }
        return 
null;
    }
    
    
/**
     * Parses a break statement
     * 
     * @return Node\BreakStatement|null
     */
    
protected function parseBreakStatement()
    {
        if (
$token $this->scanner->consume("break")) {
            
            
$node $this->createNode("BreakStatement"$token);
            
            if (
$this->scanner->noLineTerminators() &&
                (
$label $this->parseIdentifier(static::$labelledIdentifier))) {
                
$node->setLabel($label);
                
$this->assertEndOfStatement();
            } else {
                
$this->scanner->consume(";");
            }
            
            return 
$this->completeNode($node);
        }
        return 
null;
    }
    
    
/**
     * Parses a return statement
     * 
     * @return Node\ReturnStatement|null
     */
    
protected function parseReturnStatement()
    {
        if (
$token $this->scanner->consume("return")) {
            
            
$node $this->createNode("ReturnStatement"$token);
            
            if (
$this->scanner->noLineTerminators()) {
                
$argument $this->isolateContext(
                    array(
"allowIn" => true), "parseExpression"
                
);
                if (
$argument) {
                    
$node->setArgument($argument);
                }
            }
            
            
$this->assertEndOfStatement();
            
            return 
$this->completeNode($node);
        }
        return 
null;
    }
    
    
/**
     * Parses a labelled statement
     * 
     * @return Node\LabeledStatement|null
     */
    
protected function parseLabelledStatement()
    {
        if (
$label $this->parseIdentifier(static::$labelledIdentifier":")) {
            
            
$this->scanner->consume(":");
                
            if ((
$body $this->parseStatement()) ||
                (
$body $this->parseFunctionOrGeneratorDeclaration(
                    
falsefalse
                
))
            ) {
                
                
//Labelled functions are not allowed in strict mode 
                
if ($body instanceof Node\FunctionDeclaration &&
                    
$this->scanner->getStrictMode()) {
                    
$this->error(
                        
"Labelled functions are not allowed in strict mode"
                    
);
                }

                
$node $this->createNode("LabeledStatement"$label);
                
$node->setLabel($label);
                
$node->setBody($body);
                return 
$this->completeNode($node);

            }

            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses a throw statement
     * 
     * @return Node\ThrowStatement|null
     */
    
protected function parseThrowStatement()
    {
        if (
$token $this->scanner->consume("throw")) {
            
            if (
$this->scanner->noLineTerminators() &&
                (
$argument $this->isolateContext(
                    array(
"allowIn" => true), "parseExpression"
                
))
            ) {
                
                
$this->assertEndOfStatement();
                
$node $this->createNode("ThrowStatement"$token);
                
$node->setArgument($argument);
                return 
$this->completeNode($node);
            }
            
            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses a with statement
     * 
     * @return Node\WithStatement|null
     */
    
protected function parseWithStatement()
    {
        if (
$token $this->scanner->consume("with")) {
            
            if (
$this->scanner->consume("(") &&
                (
$object $this->isolateContext(
                    array(
"allowIn" => true), "parseExpression"
                
)) &&
                
$this->scanner->consume(")") &&
                
$body $this->parseStatement()
            ) {
            
                
$node $this->createNode("WithStatement"$token);
                
$node->setObject($object);
                
$node->setBody($body);
                return 
$this->completeNode($node);
            }
            
            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses a switch statement
     * 
     * @return Node\SwitchStatement|null
     */
    
protected function parseSwitchStatement()
    {
        if (
$token $this->scanner->consume("switch")) {
            
            if (
$this->scanner->consume("(") &&
                (
$discriminant $this->isolateContext(
                    array(
"allowIn" => true), "parseExpression"
                
)) &&
                
$this->scanner->consume(")") &&
                (
$cases $this->parseCaseBlock()) !== null
            
) {
            
                
$node $this->createNode("SwitchStatement"$token);
                
$node->setDiscriminant($discriminant);
                
$node->setCases($cases);
                return 
$this->completeNode($node);
            }
            
            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses the content of a switch statement
     * 
     * @return Node\SwitchCase[]|null
     */
    
protected function parseCaseBlock()
    {
        if (
$this->scanner->consume("{")) {
            
            
$parsedCasesAll = array(
                
$this->parseCaseClauses(),
                
$this->parseDefaultClause(),
                
$this->parseCaseClauses()
            );
            
            if (
$this->scanner->consume("}")) {
                
$cases = array();
                foreach (
$parsedCasesAll as $parsedCases) {
                    if (
$parsedCases) {
                        if (
is_array($parsedCases)) {
                            
$cases array_merge($cases$parsedCases);
                        } else {
                            
$cases[] = $parsedCases;
                        }
                    }
                }
                return 
$cases;
            } elseif (
$this->parseDefaultClause()) {
                
$this->error(
                    
"Multiple default clause in switch statement"
                
);
            } else {
                
$this->error();
            }
        }
        return 
null;
    }
    
    
/**
     * Parses cases in a switch statement
     * 
     * @return Node\SwitchCase[]|null
     */
    
protected function parseCaseClauses()
    {
        
$cases = array();
        while (
$case $this->parseCaseClause()) {
            
$cases[] = $case;
        }
        return 
count($cases) ? $cases null;
    }
    
    
/**
     * Parses a case in a switch statement
     * 
     * @return Node\SwitchCase|null
     */
    
protected function parseCaseClause()
    {
        if (
$token $this->scanner->consume("case")) {
            
            if ((
$test $this->isolateContext(
                    array(
"allowIn" => true), "parseExpression"
                
)) &&
                
$this->scanner->consume(":")
            ) {

                
$node $this->createNode("SwitchCase"$token);
                
$node->setTest($test);

                if (
$consequent $this->parseStatementList()) {
                    
$node->setConsequent($consequent);
                }

                return 
$this->completeNode($node);
            }
            
            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses default case in a switch statement
     * 
     * @return Node\SwitchCase|null
     */
    
protected function parseDefaultClause()
    {
        if (
$token $this->scanner->consume("default")) {
            
            if (
$this->scanner->consume(":")) {

                
$node $this->createNode("SwitchCase"$token);
            
                if (
$consequent $this->parseStatementList()) {
                    
$node->setConsequent($consequent);
                }

                return 
$this->completeNode($node);
            }
            
            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses an expression statement
     * 
     * @return Node\ExpressionStatement|null
     */
    
protected function parseExpressionStatement()
    {
        
$lookaheadTokens = array("{""function""class", array("let""["));
        if (
$this->features->asyncAwait) {
            
array_splice(
                
$lookaheadTokens30,
                array(array(
"async"true))
            );
        }
        if (!
$this->scanner->isBefore($lookaheadTokenstrue) &&
            
$expression $this->isolateContext(
                array(
"allowIn" => true), "parseExpression"
            
)
        ) {
            
            
$this->assertEndOfStatement();
            
$node $this->createNode("ExpressionStatement"$expression);
            
$node->setExpression($expression);
            return 
$this->completeNode($node);
        }
        return 
null;
    }
    
    
/**
     * Parses a do-while statement
     * 
     * @return Node\DoWhileStatement|null
     */
    
protected function parseDoWhileStatement()
    {
        if (
$token $this->scanner->consume("do")) {
            
            if ((
$body $this->parseStatement()) &&
                
$this->scanner->consume("while") &&
                
$this->scanner->consume("(") &&
                (
$test $this->isolateContext(
                    array(
"allowIn" => true), "parseExpression"
                
)) &&
                
$this->scanner->consume(")")
            ) {
                    
                
$node $this->createNode("DoWhileStatement"$token);
                
$node->setBody($body);
                
$node->setTest($test);
                
$node $this->completeNode($node);
                
$this->scanner->consume(";");
                return 
$node;
            }
            
            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses a while statement
     * 
     * @return Node\WhileStatement|null
     */
    
protected function parseWhileStatement()
    {
        if (
$token $this->scanner->consume("while")) {
            
            if (
$this->scanner->consume("(") &&
                (
$test $this->isolateContext(
                    array(
"allowIn" => true), "parseExpression"
                
)) &&
                
$this->scanner->consume(")") &&
                
$body $this->parseStatement()
            ) {
                    
                
$node $this->createNode("WhileStatement"$token);
                
$node->setTest($test);
                
$node->setBody($body);
                return 
$this->completeNode($node);
            }
            
            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses a for(var ...) statement
     * 
     * @param Token $forToken Token that corresponds to the "for" keyword
     * 
     * @return Node\Node|null
     */
    
protected function parseForVarStatement($forToken)
    {
        if (!(
$varToken $this->scanner->consume("var"))) {
            return 
null;
        }

        
$state $this->scanner->getState();

        if ((
$decl $this->isolateContext(
                array(
"allowIn" => false), "parseVariableDeclarationList"
            
)) &&
            (
$varEndPosition $this->scanner->getPosition()) &&
            
$this->scanner->consume(";")
        ) {

            
$init $this->createNode(
                
"VariableDeclaration"$varToken
            
);
            
$init->setKind($init::KIND_VAR);
            
$init->setDeclarations($decl);
            
$init $this->completeNode($init$varEndPosition);

            
$test $this->isolateContext(
                array(
"allowIn" => true), "parseExpression"
            
);

            if (
$this->scanner->consume(";")) {

                
$update $this->isolateContext(
                    array(
"allowIn" => true), "parseExpression"
                
);

                if (
$this->scanner->consume(")") &&
                    
$body $this->parseStatement()
                ) {

                    
$node $this->createNode("ForStatement"$forToken);
                    
$node->setInit($init);
                    
$node->setTest($test);
                    
$node->setUpdate($update);
                    
$node->setBody($body);
                    return 
$this->completeNode($node);
                }
            }
        } else {

            
$this->scanner->setState($state);

            if (
$decl $this->parseForBinding()) {

                
$init null;
                if (
$this->features->forInInitializer &&
                    
$decl->getId()->getType() === "Identifier") {
                    
$init $this->parseInitializer();
                }

                if (
$init) {
                    
$decl->setInit($init);
                    
$decl->location->end $init->location->end;
                }

                
$left $this->createNode("VariableDeclaration"$varToken);
                
$left->setKind($left::KIND_VAR);
                
$left->setDeclarations(array($decl));
                
$left $this->completeNode($left);

                if (
$this->scanner->consume("in")) {

                    if (
$init && $this->scanner->getStrictMode()) {
                        
$this->error(
                            
"For-in variable initializer not allowed in " .
                            
"strict mode"
                        
);
                    }

                    if ((
$right $this->isolateContext(
                            array(
"allowIn" => true), "parseExpression"
                        
)) &&
                        
$this->scanner->consume(")") &&
                        
$body $this->parseStatement()
                    ) {

                        
$node $this->createNode(
                            
"ForInStatement"$forToken
                        
);
                        
$node->setLeft($left);
                        
$node->setRight($right);
                        
$node->setBody($body);
                        return 
$this->completeNode($node);
                    }
                } elseif (!
$init && $this->scanner->consume("of")) {

                    if ((
$right $this->isolateContext(
                            array(
"allowIn" => true), "parseAssignmentExpression"
                        
)) &&
                        
$this->scanner->consume(")") &&
                        
$body $this->parseStatement()
                    ) {

                        
$node $this->createNode(
                            
"ForOfStatement"$forToken
                        
);
                        
$node->setLeft($left);
                        
$node->setRight($right);
                        
$node->setBody($body);
                        return 
$this->completeNode($node);
                    }
                }
            }
        }

        
$this->error();
    }
    
    
/**
     * Parses a for(let ...) or for(const ...) statement
     * 
     * @param Token $forToken Token that corresponds to the "for" keyword
     * 
     * @return Node\Node|null
     */
    
protected function parseForLetConstStatement($forToken)
    {
        
$afterBracketState $this->scanner->getState();
        if (!(
$init $this->parseForDeclaration())) {
            return 
null;
        }
            
        if (
$this->scanner->consume("in")) {
            if ((
$right $this->isolateContext(
                    array(
"allowIn" => true), "parseExpression"
                
)) &&
                
$this->scanner->consume(")") &&
                
$body $this->parseStatement()
            ) {
                
                
$node $this->createNode("ForInStatement"$forToken);
                
$node->setLeft($init);
                
$node->setRight($right);
                
$node->setBody($body);
                return 
$this->completeNode($node);
            }
        } elseif (
$this->scanner->consume("of")) {
            if ((
$right $this->isolateContext(
                    array(
"allowIn" => true), "parseAssignmentExpression"
                
)) &&
                
$this->scanner->consume(")") &&
                
$body $this->parseStatement()
            ) {
                
                
$node $this->createNode("ForOfStatement"$forToken);
                
$node->setLeft($init);
                
$node->setRight($right);
                
$node->setBody($body);
                return 
$this->completeNode($node);
            }
        } else {
            
            
$this->scanner->setState($afterBracketState);
            if (
$init $this->isolateContext(
                    array(
"allowIn" => false), "parseLexicalDeclaration"
                
)
            ) {
                
                
$test $this->isolateContext(
                    array(
"allowIn" => true), "parseExpression"
                
);
                if (
$this->scanner->consume(";")) {
                        
                    
$update $this->isolateContext(
                        array(
"allowIn" => true), "parseExpression"
                    
);
                    
                    if (
$this->scanner->consume(")") &&
                        
$body $this->parseStatement()
                    ) {
                        
                        
$node $this->createNode("ForStatement"$forToken);
                        
$node->setInit($init);
                        
$node->setTest($test);
                        
$node->setUpdate($update);
                        
$node->setBody($body);
                        return 
$this->completeNode($node);
                    }
                }
            }
        }
        
        
$this->error();
    }
    
    
/**
     * Parses a for statement that does not start with var, let or const
     * 
     * @param Token $forToken Token that corresponds to the "for" keyword
     * @param bool  $hasAwait True if "for" is followed by "await"
     * 
     * @return Node\Node|null
     */
    
protected function parseForNotVarLetConstStatement($forToken$hasAwait)
    {
        
$state $this->scanner->getState();
        
$notBeforeSB = !$this->scanner->isBefore(array(array("let""[")), true);
        
        if (
$notBeforeSB &&
            ((
$init $this->isolateContext(
                array(
"allowIn" => false), "parseExpression"
            
)) || true) &&
            
$this->scanner->consume(";")
        ) {
        
            
$test $this->isolateContext(
                array(
"allowIn" => true), "parseExpression"
            
);
            
            if (
$this->scanner->consume(";")) {
                    
                
$update $this->isolateContext(
                    array(
"allowIn" => true), "parseExpression"
                
);
                
                if (
$this->scanner->consume(")") &&
                    
$body $this->parseStatement()
                ) {
                    
                    
$node $this->createNode("ForStatement"$forToken);
                    
$node->setInit($init);
                    
$node->setTest($test);
                    
$node->setUpdate($update);
                    
$node->setBody($body);
                    return 
$this->completeNode($node);
                }
            }
        } else {
            
            
$this->scanner->setState($state);
            
$beforeLetAsyncOf $this->scanner->isBefore(array("let", array("async""of")), true);
            
$left $this->parseLeftHandSideExpression();

            if (
$left && $left->getType() === "ChainExpression") {
                
$this->error(
                    
"Optional chain can't appear in left-hand side"
                
);
            }

            
$left $this->expressionToPattern($left);
            
            if (
$notBeforeSB && $left && $this->scanner->consume("in")) {
                
                if ((
$right $this->isolateContext(
                        array(
"allowIn" => true), "parseExpression"
                    
)) &&
                    
$this->scanner->consume(")") &&
                    
$body $this->parseStatement()
                ) {
                    
                    
$node $this->createNode("ForInStatement"$forToken);
                    
$node->setLeft($left);
                    
$node->setRight($right);
                    
$node->setBody($body);
                    return 
$this->completeNode($node);
                }
            } elseif ((
$hasAwait || !$beforeLetAsyncOf) &&
                
$left && $this->scanner->consume("of")
            ) {
                
                if ((
$right $this->isolateContext(
                        array(
"allowIn" => true),
                        
"parseAssignmentExpression"
                    
)) &&
                    
$this->scanner->consume(")") &&
                    
$body $this->parseStatement()
                ) {
                    
                    
$node $this->createNode("ForOfStatement"$forToken);
                    
$node->setLeft($left);
                    
$node->setRight($right);
                    
$node->setBody($body);
                    return 
$this->completeNode($node);
                }
            }
        }
        
        
$this->error();
    }
    
    
/**
     * Parses do-while, while, for, for-in and for-of statements
     * 
     * @return Node\Node|null
     */
    
protected function parseIterationStatement()
    {
        if (
$node $this->parseWhileStatement()) {
            return 
$node;
        } elseif (
$node $this->parseDoWhileStatement()) {
            return 
$node;
        } elseif (
$startForToken $this->scanner->consume("for")) {

            
$forAwait false;
            if (
$this->features->asyncIterationGenerators &&
                
$this->context->allowAwait &&
                
$this->scanner->consume("await")
            ) {
                
$forAwait true;
            }

            if (
$this->scanner->consume("(") && (
                (
$node $this->parseForVarStatement($startForToken)) ||
                (
$node $this->parseForLetConstStatement($startForToken)) ||
                (
$node $this->parseForNotVarLetConstStatement($startForToken$forAwait)))
            ) {
                if (
$forAwait) {
                    if (!
$node instanceof Node\ForOfStatement) {
                        
$this->error(
                            
"Async iteration is allowed only with for-of statements",
                            
$startForToken->location->start
                        
);
                    }
                    
$node->setAwait(true);
                }
                return 
$node;
            }

            
$this->error();
        }

        return 
null;
    }

    
/**
     * Checks if an async function can start from the current position. Returns
     * the async token or null if not found
     *
     * @param bool $checkFn If false it won't check if the async keyword is
     *                      followed by "function"
     *
     * @return Token
     */
    
protected function checkAsyncFunctionStart($checkFn true)
    {
        return (
$asyncToken $this->scanner->getToken()) &&
        
$asyncToken->value === "async" &&
        (
            !
$checkFn ||
            ((
$nextToken $this->scanner->getNextToken()) &&
                
$nextToken->value === "function")
        ) &&
        
$this->scanner->noLineTerminators(true) ?
            
$asyncToken :
            
null;
    }
    
    
/**
     * Parses function or generator declaration
     * 
     * @param bool $default        Default mode
     * @param bool $allowGenerator True to allow parsing of generators
     * 
     * @return Node\FunctionDeclaration|null
     */
    
protected function parseFunctionOrGeneratorDeclaration(
        
$default false$allowGenerator true
    
) {
        
$async null;
        if (
$this->features->asyncAwait &&
            (
$async $this->checkAsyncFunctionStart())) {
            
$this->scanner->consumeToken();
            if (!
$this->features->asyncIterationGenerators) {
                
$allowGenerator false;
            }
        }
        if (
$token $this->scanner->consume("function")) {

            
$generator $allowGenerator && $this->scanner->consume("*");
            
$id $this->parseIdentifier(static::$bindingIdentifier);

            if (
$generator || $async) {
                
$flags = array(null);
                if (
$generator) {
                    
$flags["allowYield"] = true;
                }
                if (
$async) {
                    
$flags["allowAwait"] = true;
                }
            } else {
                
$flags null;
            }

            if ((
$default || $id) &&
                
$this->scanner->consume("(") &&
                (
$params $this->isolateContext(
                    
$flags,
                    
"parseFormalParameterList"
                
)) !== null &&
                
$this->scanner->consume(")") &&
                (
$tokenBodyStart $this->scanner->consume("{")) &&
                ((
$body $this->isolateContext(
                        
$flags,
                        
"parseFunctionBody"
                    
)) || true) &&
                
$this->scanner->consume("}")
            ) {

                
$body->location->start $tokenBodyStart->location->start;
                
$body->location->end $this->scanner->getPosition();
                
$node $this->createNode(
                    
"FunctionDeclaration",
                    
$async ?: $token
                
);
                if (
$id) {
                    
$node->setId($id);
                }
                
$node->setParams($params);
                
$node->setBody($body);
                
$node->setGenerator($generator);
                
$node->setAsync((bool) $async);
                return 
$this->completeNode($node);
            }

            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses function or generator expression
     * 
     * @return Node\FunctionExpression|null
     */
    
protected function parseFunctionOrGeneratorExpression()
    {
        
$allowGenerator true;
        
$async false;
        if (
$this->features->asyncAwait &&
            (
$async $this->checkAsyncFunctionStart())) {
            
$this->scanner->consumeToken();
            if (!
$this->features->asyncIterationGenerators) {
                
$allowGenerator false;
            }
        }
        if (
$token $this->scanner->consume("function")) {

            
$generator $allowGenerator && $this->scanner->consume("*");

            if (
$generator || $async) {
                
$flags = array(null);
                if (
$generator) {
                    
$flags["allowYield"] = true;
                }
                if (
$async) {
                    
$flags["allowAwait"] = true;
                }
            } else {
                
$flags null;
            }

            
$id $this->isolateContext(
                
$flags,
                
"parseIdentifier",
                array(static::
$bindingIdentifier)
            );

            if (
$this->scanner->consume("(") &&
                (
$params $this->isolateContext(
                    
$flags,
                    
"parseFormalParameterList"
                
)) !== null &&
                
$this->scanner->consume(")") &&
                (
$tokenBodyStart $this->scanner->consume("{")) &&
                ((
$body $this->isolateContext(
                        
$flags,
                        
"parseFunctionBody"
                    
)) || true) &&
                
$this->scanner->consume("}")
            ) {

                
$body->location->start $tokenBodyStart->location->start;
                
$body->location->end $this->scanner->getPosition();
                
$node $this->createNode(
                    
"FunctionExpression",
                    
$async ?: $token
                
);
                
$node->setId($id);
                
$node->setParams($params);
                
$node->setBody($body);
                
$node->setGenerator($generator);
                
$node->setAsync((bool) $async);
                return 
$this->completeNode($node);
            }

            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses yield statement
     * 
     * @return Node\YieldExpression|null
     */
    
protected function parseYieldExpression()
    {
        if (
$token $this->scanner->consume("yield")) {
            
            
$node $this->createNode("YieldExpression"$token);
            if (
$this->scanner->noLineTerminators()) {
                
                
$delegate $this->scanner->consume("*");
                
$argument $this->isolateContext(
                    array(
"allowYield" => true), "parseAssignmentExpression"
                
);
                if (
$argument) {
                    
$node->setArgument($argument);
                    
$node->setDelegate($delegate);
                }
            }
            
            return 
$this->completeNode($node);
        }
        return 
null;
    }
    
    
/**
     * Parses a parameter list
     * 
     * @return Node\Node[]|null
     */
    
protected function parseFormalParameterList()
    {
        
$hasComma false;
        
$list = array();
        while (
            (
$param $this->parseBindingRestElement()) ||
            
$param $this->parseBindingElement()
        ) {
            
$hasComma false;
            
$list[] = $param;
            if (
$param->getType() === "RestElement") {
                break;
            } elseif (
$this->scanner->consume(",")) {
                
$hasComma true;
            } else {
                break;
            }
        }
        if (
$hasComma &&
            !
$this->features->trailingCommaFunctionCallDeclaration) {
            
$this->error();
        }
        return 
$list;
    }
    
    
/**
     * Parses a function body
     * 
     * @return Node\BlockStatement[]|null
     */
    
protected function parseFunctionBody()
    {
        
$body $this->isolateContext(
            array(
"allowReturn" => true),
            
"parseStatementList",
            array(
true)
        );
        
$node $this->createNode(
            
"BlockStatement"$body ?: $this->scanner->getPosition()
        );
        if (
$body) {
            
$node->setBody($body);
        }
        return 
$this->completeNode($node);
    }
    
    
/**
     * Parses a class declaration
     * 
     * @param bool $default Default mode
     * 
     * @return Node\ClassDeclaration|null
     */
    
protected function parseClassDeclaration($default false)
    {
        if (
$token $this->scanner->consume("class")) {
            
            
//Class declarations are strict mode by default
            
$prevStrict $this->scanner->getStrictMode();
            
$this->scanner->setStrictMode(true);
            
            
$id $this->parseIdentifier(static::$bindingIdentifier);
            if ((
$default || $id) &&
                
$tail $this->parseClassTail()
            ) {
                
                
$node $this->createNode("ClassDeclaration"$token);
                if (
$id) {
                    
$node->setId($id);
                }
                if (
$tail[0]) {
                    
$node->setSuperClass($tail[0]);
                }
                
$node->setBody($tail[1]);
                
$this->scanner->setStrictMode($prevStrict);
                return 
$this->completeNode($node);
            }
            
            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses a class expression
     * 
     * @return Node\ClassExpression|null
     */
    
protected function parseClassExpression()
    {
        if (
$token $this->scanner->consume("class")) {
            
            
//Class expressions are strict mode by default
            
$prevStrict $this->scanner->getStrictMode();
            
$this->scanner->setStrictMode(true);
            
            
$id $this->parseIdentifier(static::$bindingIdentifier);
            
$tail $this->parseClassTail();
            
$node $this->createNode("ClassExpression"$token);
            if (
$id) {
                
$node->setId($id);
            }
            if (
$tail[0]) {
                
$node->setSuperClass($tail[0]);
            }
            
$node->setBody($tail[1]);
            
$this->scanner->setStrictMode($prevStrict);
            return 
$this->completeNode($node);
        }
        return 
null;
    }
    
    
/**
     * Parses the code that comes after the class keyword and class name. The
     * return value is an array where the first item is the extended class, if
     * any, and the second value is the class body
     * 
     * @return array|null
     */
    
protected function parseClassTail()
    {
        
$heritage $this->parseClassHeritage();
        if (
$token $this->scanner->consume("{")) {
            
            
$body $this->parseClassBody();
            if (
$this->scanner->consume("}")) {
                
$body->location->start $token->location->start;
                
$body->location->end $this->scanner->getPosition();
                return array(
$heritage$body);
            }
        }
        
$this->error();
    }
    
    
/**
     * Parses the class extends part
     * 
     * @return Node\Node|null
     */
    
protected function parseClassHeritage()
    {
        if (
$this->scanner->consume("extends")) {
            
            if (
$superClass $this->parseLeftHandSideExpression()) {
                return 
$superClass;
            }
            
            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses the class body
     * 
     * @return Node\ClassBody|null
     */
    
protected function parseClassBody()
    {
        
$body $this->parseClassElementList();
        
$node $this->createNode(
            
"ClassBody"$body ?: $this->scanner->getPosition()
        );
        if (
$body) {
            
$node->setBody($body);
        }
        return 
$this->completeNode($node);
    }
    
    
/**
     * Parses class elements list
     * 
     * @return Node\MethodDefinition[]|null
     */
    
protected function parseClassElementList()
    {
        
$items = array();
        while (
$item $this->parseClassElement()) {
            if (
$item !== true) {
                
$items[] = $item;
            }
        }
        return 
count($items) ? $items null;
    }
    
    
/**
     * Parses a class elements
     * 
     * @return Node\MethodDefinition|Node\PropertyDefinition|Node\StaticBlock|bool|null
     */
    
protected function parseClassElement()
    {
        if (
$this->scanner->consume(";")) {
            return 
true;
        }
        if (
$this->features->classStaticBlock &&
            
$this->scanner->isBefore(array(array("static""{")), true)
        ) {
            return 
$this->parseClassStaticBlock();
        }
        
$staticToken null;
        
$state $this->scanner->getState();
        
//This code handles the case where "static" is the method name
        
if (!$this->scanner->isBefore(array(array("static""(")), true)) {
            
$staticToken $this->scanner->consume("static");
        }
        if (
$def $this->parseMethodDefinition()) {
            if (
$staticToken) {
                
$def->setStatic(true);
                
$def->location->start $staticToken->location->start;
            }
            return 
$def;
        } else {
            if (
$this->features->classFields) {
                if (
$field $this->parseFieldDefinition()) {
                    if (
$staticToken) {
                        
$field->setStatic(true);
                        
$field->location->start $staticToken->location->start;
                    }
                } elseif (
$staticToken) {
                    
//Handle the case when "static" is the field name
                    
$this->scanner->setState($state);
                    
$field $this->parseFieldDefinition();
                }
                return 
$field;
            } elseif (
$staticToken) {
                
$this->error();
            }
        }
        
        return 
null;
    }
    
    
/**
     * Parses a let or const declaration
     * 
     * @return Node\VariableDeclaration|null
     */
    
protected function parseLexicalDeclaration()
    {
        
$state $this->scanner->getState();
        if (
$token $this->scanner->consumeOneOf(array("let""const"))) {
            
            
$declarations $this->charSeparatedListOf(
                
"parseVariableDeclaration"
            
);
            
            if (
$declarations) {
                
$this->assertEndOfStatement();
                
$node $this->createNode("VariableDeclaration"$token);
                
$node->setKind($token->value);
                
$node->setDeclarations($declarations);
                return 
$this->completeNode($node);
            }
            
            
// "let" can be used as variable name in non-strict mode
            
if ($this->scanner->getStrictMode() || $token->value !== "let") {
                
$this->error();
            } else {
                
$this->scanner->setState($state);
            }
        }
        return 
null;
    }
    
    
/**
     * Parses a var declaration
     * 
     * @return Node\VariableDeclaration|null
     */
    
protected function parseVariableStatement()
    {
        if (
$token $this->scanner->consume("var")) {
            
            
$declarations $this->isolateContext(
                array(
"allowIn" => true), "parseVariableDeclarationList"
            
);
            if (
$declarations) {
                
$this->assertEndOfStatement();
                
$node $this->createNode("VariableDeclaration"$token);
                
$node->setKind($node::KIND_VAR);
                
$node->setDeclarations($declarations);
                return 
$this->completeNode($node);
            }
            
            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses an variable declarations
     * 
     * @return Node\VariableDeclarator[]|null
     */
    
protected function parseVariableDeclarationList()
    {
        return 
$this->charSeparatedListOf(
            
"parseVariableDeclaration"
        
);
    }
    
    
/**
     * Parses a variable declarations
     * 
     * @return Node\VariableDeclarator|null
     */
    
protected function parseVariableDeclaration()
    {
        if (
$id $this->parseIdentifier(static::$bindingIdentifier)) {
            
            
$node $this->createNode("VariableDeclarator"$id);
            
$node->setId($id);
            if (
$init $this->parseInitializer()) {
                
$node->setInit($init);
            }
            return 
$this->completeNode($node);
            
        } elseif (
$id $this->parseBindingPattern()) {
            
            if (
$init $this->parseInitializer()) {
                
$node $this->createNode("VariableDeclarator"$id);
                
$node->setId($id);
                
$node->setInit($init);
                return 
$this->completeNode($node);
            }
            
        }
        return 
null;
    }
    
    
/**
     * Parses a let or const declaration in a for statement definition
     * 
     * @return Node\VariableDeclaration|null
     */
    
protected function parseForDeclaration()
    {
        
$state $this->scanner->getState();
        if (
$token $this->scanner->consumeOneOf(array("let""const"))) {
            
            if (
$declaration $this->parseForBinding()) {

                
$node $this->createNode("VariableDeclaration"$token);
                
$node->setKind($token->value);
                
$node->setDeclarations(array($declaration));
                return 
$this->completeNode($node);
            }
            
            
// "let" can be used as variable name in non-strict mode
            
if ($this->scanner->getStrictMode() || $token->value !== "let") {
                
$this->error();
            } else {
                
$this->scanner->setState($state);
            }
        }
        return 
null;
    }
    
    
/**
     * Parses a binding pattern or an identifier that come after a const or let
     * declaration in a for statement definition
     * 
     * @return Node\VariableDeclarator|null
     */
    
protected function parseForBinding()
    {
        if ((
$id $this->parseIdentifier(static::$bindingIdentifier)) ||
            (
$id $this->parseBindingPattern())
        ) {
            
            
$node $this->createNode("VariableDeclarator"$id);
            
$node->setId($id);
            return 
$this->completeNode($node);
        }
        return 
null;
    }
    
    
/**
     * Parses a module item
     * 
     * @return Node\Node|null
     */
    
protected function parseModuleItem()
    {
        if (
$item $this->parseImportDeclaration()) {
            return 
$item;
        } elseif (
$item $this->parseExportDeclaration()) {
            return 
$item;
        } elseif (
            
$item $this->isolateContext(
                array(
                    
"allowYield" => false,
                    
"allowReturn" => false,
                    
"allowAwait" => $this->features->topLevelAwait
                
),
                
"parseStatementListItem"
            
)
        ) {
            return 
$item;
        }
        return 
null;
    }
    
    
/**
     * Parses the from keyword and the following string in import and export
     * declarations
     * 
     * @return Node\StringLiteral|null
     */
    
protected function parseFromClause()
    {
        if (
$this->scanner->consume("from")) {
            if (
$spec $this->parseStringLiteral()) {
                return 
$spec;
            }
            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses an export declaration
     * 
     * @return Node\ModuleDeclaration|null
     */
    
protected function parseExportDeclaration()
    {
        if (
$token $this->scanner->consume("export")) {
            
            if (
$this->scanner->consume("*")) {

                
$exported null;
                if (
$this->features->exportedNameInExportAll &&
                    
$this->scanner->consume("as")) {
                    
$exported $this->parseModuleExportName();
                    if (!
$exported) {
                        
$this->error();
                    }
                }
                
                if (
$source $this->parseFromClause()) {
                    
$this->assertEndOfStatement();
                    
$node $this->createNode("ExportAllDeclaration"$token);
                    
$node->setSource($source);
                    
$node->setExported($exported);
                    return 
$this->completeNode($node);
                }
                
            } elseif (
$this->scanner->consume("default")) {
                
$lookaheadTokens = array("function""class");
                if (
$this->features->asyncAwait) {
                    
$lookaheadTokens[] = array("async"true);
                }
                if ((
$declaration $this->isolateContext(
                        array(
"allowAwait" => $this->features->topLevelAwait),
                        
"parseFunctionOrGeneratorDeclaration",
                        array(
true)
                    )) ||
                    (
$declaration $this->isolateContext(
                        array(
"allowAwait" => $this->features->topLevelAwait),
                        
"parseClassDeclaration",
                        array(
true)
                    ))
                ) {
                    
                    
$node $this->createNode("ExportDefaultDeclaration"$token);
                    
$node->setDeclaration($declaration);
                    return 
$this->completeNode($node);
                    
                } elseif (!
$this->scanner->isBefore(
                        
$lookaheadTokens,
                        
$this->features->asyncAwait
                    
) &&
                    (
$declaration $this->isolateContext(
                        array(
"allowIn" => true"allowAwait" => $this->features->topLevelAwait),
                        
"parseAssignmentExpression"
                    
))
                ) {
                    
                    
$this->assertEndOfStatement();
                    
$node $this->createNode(
                        
"ExportDefaultDeclaration"$token
                    
);
                    
$node->setDeclaration($declaration);
                    return 
$this->completeNode($node);
                }
                
            } elseif ((
$specifiers $this->parseExportClause()) !== null) {
                
                
$node $this->createNode("ExportNamedDeclaration"$token);
                
$node->setSpecifiers($specifiers);
                if (
$source $this->parseFromClause()) {
                    
$node->setSource($source);
                }
                
$this->assertEndOfStatement();
                return 
$this->completeNode($node);

            } elseif (
                (
$dec $this->isolateContext(
                    array(
"allowAwait" => $this->features->topLevelAwait),
                    
"parseVariableStatement"
                
)) ||
                
$dec $this->isolateContext(
                    array(
"allowAwait" => $this->features->topLevelAwait),
                    
"parseDeclaration"
                
)
            ) {

                
$node $this->createNode("ExportNamedDeclaration"$token);
                
$node->setDeclaration($dec);
                return 
$this->completeNode($node);
            }
            
            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses an export clause
     * 
     * @return Node\ExportSpecifier[]|null
     */
    
protected function parseExportClause()
    {
        if (
$this->scanner->consume("{")) {
            
            
$list = array();
            while (
$spec $this->parseExportSpecifier()) {
                
$list[] = $spec;
                if (!
$this->scanner->consume(",")) {
                    break;
                }
            }
            
            if (
$this->scanner->consume("}")) {
                return 
$list;
            }
            
            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses an export specifier
     * 
     * @return Node\ExportSpecifier|null
     */
    
protected function parseExportSpecifier()
    {
        if (
$local $this->parseModuleExportName()) {
            
            
$node $this->createNode("ExportSpecifier"$local);
            
$node->setLocal($local);
            
            if (
$this->scanner->consume("as")) {
                
                if (
$exported $this->parseModuleExportName()) {
                    
$node->setExported($exported);
                    return 
$this->completeNode($node);
                }
                
                
$this->error();
            } else {
                
$node->setExported($local);
                return 
$this->completeNode($node);
            }
        }
        return 
null;
    }

    
/**
     * Parses an export name
     * 
     * @return Node\Identifier|Node\StringLiteral|null
     */
    
protected function parseModuleExportName()
    {
        if (
$name $this->parseIdentifier(static::$identifierName)) {
            return 
$name;
        } elseif (
$this->features->arbitraryModuleNSNames &&
            (
$name $this->parseStringLiteral())
        ) {
            return 
$name;
        }
        return 
null;
    }
    
    
/**
     * Parses an import declaration
     * 
     * @return Node\ModuleDeclaration|null
     */
    
protected function parseImportDeclaration()
    {
        
//Delay parsing of dynamic import so that it is handled
        //by the relative method
        
if ($this->features->dynamicImport &&
            
$this->scanner->isBefore(array(array("import""(")), true)) {
            return 
null;
        }
        
//Delay parsing of import.meta so that it is handled
        //by the relative method
        
if ($this->features->importMeta &&
            
$this->scanner->isBefore(array(array("import"".")), true)) {
            return 
null;
        }
        if (
$token $this->scanner->consume("import")) {
            
            if (
$source $this->parseStringLiteral()) {
                
                
$this->assertEndOfStatement();
                
$node $this->createNode("ImportDeclaration"$token);
                
$node->setSource($source);
                return 
$this->completeNode($node);
                
            } elseif ((
$specifiers $this->parseImportClause()) !== null &&
                
$source $this->parseFromClause()
            ) {
                
                
$this->assertEndOfStatement();
                
$node $this->createNode("ImportDeclaration"$token);
                
$node->setSpecifiers($specifiers);
                
$node->setSource($source);
                
                return 
$this->completeNode($node);
            }
            
            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses an import clause
     * 
     * @return array|null
     */
    
protected function parseImportClause()
    {
        if (
$spec $this->parseNameSpaceImport()) {
            return array(
$spec);
        } elseif ((
$specs $this->parseNamedImports()) !== null) {
            return 
$specs;
        } elseif (
$spec $this->parseIdentifier(static::$importedBinding)) {
            
            
$node $this->createNode("ImportDefaultSpecifier"$spec);
            
$node->setLocal($spec);
            
$ret = array($this->completeNode($node));
            
            if (
$this->scanner->consume(",")) {
                
                if (
$spec $this->parseNameSpaceImport()) {
                    
$ret[] = $spec;
                    return 
$ret;
                } elseif ((
$specs $this->parseNamedImports()) !== null) {
                    return 
array_merge($ret$specs);
                }
                
                
$this->error();
            } else {
                return 
$ret;
            }
        }
        return 
null;
    }
    
    
/**
     * Parses a namespace import
     * 
     * @return Node\ImportNamespaceSpecifier|null
     */
    
protected function parseNameSpaceImport()
    {
        if (
$token $this->scanner->consume("*")) {
            
            if (
$this->scanner->consume("as") &&
                
$local $this->parseIdentifier(static::$identifierReference)
            ) {
                
$node $this->createNode("ImportNamespaceSpecifier"$token);
                
$node->setLocal($local);
                return 
$this->completeNode($node);  
            }
            
            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses a named imports
     * 
     * @return Node\ImportSpecifier[]|null
     */
    
protected function parseNamedImports()
    {
        if (
$this->scanner->consume("{")) {
            
            
$list = array();
            while (
$spec $this->parseImportSpecifier()) {
                
$list[] = $spec;
                if (!
$this->scanner->consume(",")) {
                    break;
                }
            }
            
            if (
$this->scanner->consume("}")) {
                return 
$list;
            }
            
            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses an import specifier
     * 
     * @return Node\ImportSpecifier|null
     */
    
protected function parseImportSpecifier()
    {
        
$requiredAs false;
        
$imported $this->parseIdentifier(static::$importedBinding);
        if (!
$imported) {
            
$imported $this->parseModuleExportName();
            if (!
$imported) {
                return 
null;
            }
            
$requiredAs true;
        }
        
        
$node $this->createNode("ImportSpecifier"$imported);
        
$node->setImported($imported);
        
        if (
$this->scanner->consume("as")) {
            
            if (!(
$local $this->parseIdentifier(static::$importedBinding))) {
                
$this->error();
            }
            
            
$node->setLocal($local);
            
        } elseif (
$requiredAs) {
            
$this->error();
        } else {
            
$node->setLocal($imported);
        }
        
        return 
$this->completeNode($node);
    }
    
    
/**
     * Parses a binding pattern
     * 
     * @return Node\ArrayPattern|Node\ObjectPattern|null
     */
    
protected function parseBindingPattern()
    {
        if (
$pattern $this->parseObjectBindingPattern()) {
            return 
$pattern;
        } elseif (
$pattern $this->parseArrayBindingPattern()) {
            return 
$pattern;
        }
        return 
null;
    }
    
    
/**
     * Parses an elisions sequence. It returns the number of elisions or null
     * if no elision has been found
     * 
     * @return int
     */
    
protected function parseElision()
    {
        
$count 0;
        while (
$this->scanner->consume(",")) {
            
$count ++;
        }
        return 
$count ?: null;
    }
    
    
/**
     * Parses an array binding pattern
     * 
     * @return Node\ArrayPattern|null
     */
    
protected function parseArrayBindingPattern()
    {
        if (
$token $this->scanner->consume("[")) {
            
            
$elements = array();
            while (
true) {
                if (
$elision $this->parseElision()) {
                    
$elements array_merge(
                        
$elementsarray_fill(0$elisionnull)
                    );
                }
                if (
$element $this->parseBindingElement()) {
                    
$elements[] = $element;
                    if (!
$this->scanner->consume(",")) {
                        break;
                    }
                } elseif (
$rest $this->parseBindingRestElement()) {
                    
$elements[] = $rest;
                    break;
                } else {
                    break;
                }
            }
            
            if (
$this->scanner->consume("]")) {
                
$node $this->createNode("ArrayPattern"$token);
                
$node->setElements($elements);
                return 
$this->completeNode($node);
            }
        }
        return 
null;
    }
    
    
/**
     * Parses a rest element
     * 
     * @return Node\RestElement|null
     */
    
protected function parseBindingRestElement()
    {
        if (
$token $this->scanner->consume("...")) {
            
            if ((
$argument $this->parseIdentifier(static::$bindingIdentifier)) ||
                (
$argument $this->parseBindingPattern())) {
                
$node $this->createNode("RestElement"$token);
                
$node->setArgument($argument);
                return 
$this->completeNode($node);
            }
            
            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses a binding element
     * 
     * @return Node\AssignmentPattern|Node\Identifier|null
     */
    
protected function parseBindingElement()
    {
        if (
$el $this->parseSingleNameBinding()) {
            return 
$el;
        } elseif (
$left $this->parseBindingPattern()) {
            
            
$right $this->isolateContext(
                array(
"allowIn" => true), "parseInitializer"
            
);
            if (
$right) {
                
$node $this->createNode("AssignmentPattern"$left);
                
$node->setLeft($left);
                
$node->setRight($right);
                return 
$this->completeNode($node);
            } else {
                return 
$left;
            }
        }
        return 
null;
    }
    
    
/**
     * Parses single name binding
     * 
     * @return Node\AssignmentPattern|Node\Identifier|null
     */
    
protected function parseSingleNameBinding()
    {
        if (
$left $this->parseIdentifier(static::$bindingIdentifier)) {
            
$right $this->isolateContext(
                array(
"allowIn" => true), "parseInitializer"
            
);
            if (
$right) {
                
$node $this->createNode("AssignmentPattern"$left);
                
$node->setLeft($left);
                
$node->setRight($right);
                return 
$this->completeNode($node);
            } else {
                return 
$left;
            }
        }
        return 
null;
    }
    
    
/**
     * Parses a property name. The returned value is an array where there first
     * element is the property name and the second element is a boolean
     * indicating if it's a computed property
     * 
     * @return array|null
     */
    
protected function parsePropertyName()
    {
        if (
$token $this->scanner->consume("[")) {
            
            if ((
$name $this->isolateContext(
                    array(
"allowIn" => true), "parseAssignmentExpression"
                
)) &&
                
$this->scanner->consume("]")
            ) {
                return array(
$nametrue$token);
            }
            
            
$this->error();
        } elseif (
$name $this->parseIdentifier(static::$identifierName)) {
            return array(
$namefalse);
        } elseif (
$name $this->parseStringLiteral()) {
            return array(
$namefalse);
        } elseif (
$name $this->parseNumericLiteral()) {
            return array(
$namefalse);
        }
        return 
null;
    }

    
/**
     * Parses a property name. The returned value is an array where there first
     * element is the property name and the second element is a boolean
     * indicating if it's a computed property
     * 
     * @return array|null
     */
    
protected function parseClassElementName()
    {
        if (
            
$this->features->privateMethodsAndFields &&
            (
$name $this->parsePrivateIdentifier())
        ) {
            return array(
$namefalse);
        }
        return 
$this->parsePropertyName();
    }

    
/**
     * Parses a field definition
     * 
     * @return Node\StaticBlock
     */
    
protected function parseClassStaticBlock()
    {
        
$staticToken $this->scanner->consume("static");
        
$this->scanner->consume("{");
        
$statements $this->isolateContext(
            array(
"allowAwait" => true), "parseStatementList"
        
);
        if (
$this->scanner->consume("}")) {
            
$node $this->createNode("StaticBlock"$staticToken);
            if (
$statements) {
                
$node->setBody($statements);
            }
            return 
$this->completeNode($node);
        }
        
$this->error();
    }

    
/**
     * Parses a field definition
     * 
     * @return Node\PropertyDefinition|null
     */
    
protected function parseFieldDefinition()
    {
        
$state $this->scanner->getState();
        if (
$prop $this->parseClassElementName()) {
            
$value $this->isolateContext(
                array(
"allowIn" => true), "parseInitializer"
            
);
            
$this->assertEndOfStatement();
            
$node $this->createNode("PropertyDefinition"$prop);
            
$node->setKey($prop[0]);
            if (
$value) {
                
$node->setValue($value);
            }
            
$node->setComputed($prop[1]);
            return 
$this->completeNode($node);
        }
        
$this->scanner->setState($state);
        return 
null;
    }
    
    
/**
     * Parses a method definition
     * 
     * @return Node\MethodDefinition|null
     */
    
protected function parseMethodDefinition()
    {
        
$state $this->scanner->getState();
        
$generator $error $async false;
        
$position null;
        
$kind Node\MethodDefinition::KIND_METHOD;
        if (
$token $this->scanner->consume("get")) {
            
$position $token;
            
$kind Node\MethodDefinition::KIND_GET;
        } elseif (
$token $this->scanner->consume("set")) {
            
$position $token;
            
$kind Node\MethodDefinition::KIND_SET;
        } elseif (
$token $this->scanner->consume("*")) {
            
$position $token;
            
$error true;
            
$generator true;
        } elseif (
$this->features->asyncAwait &&
                 (
$token $this->checkAsyncFunctionStart(false))) {
            
$this->scanner->consumeToken();
            
$position $token;
            
$error true;
            
$async true;
            if (
$this->features->asyncIterationGenerators &&
                (
$this->scanner->consume("*"))) {
                
$generator true;
            }
        }

        
//Handle the case where get and set are methods name and not the
        //definition of a getter/setter
        
if ($kind !== Node\MethodDefinition::KIND_METHOD &&
            
$this->scanner->consume("(")
        ) {
            
$this->scanner->setState($state);
            
$kind Node\MethodDefinition::KIND_METHOD;
            
$error false;
        }

        if (
$prop $this->parseClassElementName()) {

            if (!
$position) {
                
$position = isset($prop[2]) ? $prop[2] : $prop[0];
            }
            if (
$tokenFn $this->scanner->consume("(")) {

                if (
$generator || $async) {
                    
$flags = array(null);
                    if (
$generator) {
                        
$flags["allowYield"] = true;
                    }
                    if (
$async) {
                        
$flags["allowAwait"] = true;
                    }
                } else {
                    
$flags null;
                }

                
$error true;
                
$params = array();
                if (
$kind === Node\MethodDefinition::KIND_SET) {
                    
$params $this->isolateContext(
                        
null"parseBindingElement"
                    
);
                    if (
$params) {
                        
$params = array($params);
                    }
                } elseif (
$kind === Node\MethodDefinition::KIND_METHOD) {
                    
$params $this->isolateContext(
                        
$flags"parseFormalParameterList"
                    
);
                }

                if (
$params !== null &&
                    
$this->scanner->consume(")") &&
                    (
$tokenBodyStart $this->scanner->consume("{")) &&
                    ((
$body $this->isolateContext(
                        
$flags"parseFunctionBody"
                    
)) || true) &&
                    
$this->scanner->consume("}")
                ) {

                    if (
$prop[0] instanceof Node\Identifier &&
                        
$prop[0]->getName() === "constructor"
                    
) {
                        
$kind Node\MethodDefinition::KIND_CONSTRUCTOR;
                    }

                    
$body->location->start $tokenBodyStart->location->start;
                    
$body->location->end $this->scanner->getPosition();

                    
$nodeFn $this->createNode("FunctionExpression"$tokenFn);
                    
$nodeFn->setParams($params);
                    
$nodeFn->setBody($body);
                    
$nodeFn->setGenerator($generator);
                    
$nodeFn->setAsync($async);

                    
$node $this->createNode("MethodDefinition"$position);
                    
$node->setKey($prop[0]);
                    
$node->setValue($this->completeNode($nodeFn));
                    
$node->setKind($kind);
                    
$node->setComputed($prop[1]);
                    return 
$this->completeNode($node);
                }
            }
        }

        if (
$error) {
            
$this->error();
        } else {
            
$this->scanner->setState($state);
        }
        return 
null;
    }
    
    
/**
     * Parses parameters in an arrow function. If the parameters are wrapped in
     * round brackets, the returned value is an array where the first element
     * is the parameters list and the second element is the open round brackets,
     * this is needed to know the start position
     * 
     * @return Node\Identifier|array|null
     */
    
protected function parseArrowParameters()
    {
        if (
$param $this->parseIdentifier(static::$bindingIdentifier"=>")) {
            return 
$param;
        } elseif (
$token $this->scanner->consume("(")) {
            
            
$params $this->parseFormalParameterList();
            
            if (
$params !== null && $this->scanner->consume(")")) {
                return array(
$params$token);
            }
        }
        return 
null;
    }

    
/**
     * Parses the body of an arrow function. The returned value is an array
     * where the first element is the function body and the second element is
     * a boolean indicating if the body is wrapped in curly braces
     *
     * @param bool  $async  Async body mode
     *
     * @return array|null
     */
    
protected function parseConciseBody($async false)
    {
        if (
$token $this->scanner->consume("{")) {

            if ((
$body $this->isolateContext(
                    
$async ? array(null"allowAwait" => true) : null,
                    
"parseFunctionBody"
                
)) &&
                
$this->scanner->consume("}")
            ) {
                
$body->location->start $token->location->start;
                
$body->location->end $this->scanner->getPosition();
                return array(
$bodyfalse);
            }

            
$this->error();
        } elseif (!
$this->scanner->isBefore(array("{")) &&
            
$body $this->isolateContext(
                
$this->features->asyncAwait ?
                array(
"allowYield" => false"allowAwait" => $async) :
                array(
"allowYield" => false),
                
"parseAssignmentExpression"
            
)
        ) {
            return array(
$bodytrue);
        }
        return 
null;
    }
    
    
/**
     * Parses an arrow function
     * 
     * @return Node\ArrowFunctionExpression|null
     */
    
protected function parseArrowFunction()
    {
        
$state $this->scanner->getState();
        
$async false;
        if (
$this->features->asyncAwait &&
            (
$async $this->checkAsyncFunctionStart(false))) {
            
$this->scanner->consumeToken();
        }
        if ((
$params $this->parseArrowParameters()) !== null) {

            if (
$this->scanner->noLineTerminators() &&
                
$this->scanner->consume("=>")
            ) {

                if (
$body $this->parseConciseBody((bool) $async)) {
                    if (
is_array($params)) {
                        
$pos $params[1];
                        
$params $params[0];
                    } else {
                        
$pos $params;
                        
$params = array($params);
                    }
                    if (
$async) {
                        
$pos $async;
                    }
                    
$node $this->createNode("ArrowFunctionExpression"$pos);
                    
$node->setParams($params);
                    
$node->setBody($body[0]);
                    
$node->setExpression($body[1]);
                    
$node->setAsync((bool) $async);
                    return 
$this->completeNode($node);
                }

                
$this->error();
            }
        }
        
$this->scanner->setState($state);
        return 
null;
    }
    
    
/**
     * Parses an object literal
     * 
     * @return Node\ObjectExpression|null
     */
    
protected function parseObjectLiteral()
    {
        if (
$token $this->scanner->consume("{")) {
            
            
$properties = array();
            while (
$prop $this->parsePropertyDefinition()) {
                
$properties[] = $prop;
                if (!
$this->scanner->consume(",")) {
                    break;
                }
            }
            
            if (
$this->scanner->consume("}")) {
                
                
$node $this->createNode("ObjectExpression"$token);
                if (
$properties) {
                    
$node->setProperties($properties);
                }
                return 
$this->completeNode($node);
            }
            
            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses a property in an object literal
     * 
     * @return Node\Property|null
     */
    
protected function parsePropertyDefinition()
    {
        if (
$this->features->restSpreadProperties &&
            (
$prop $this->parseSpreadElement())) {
            return 
$prop;
        }

        
$state $this->scanner->getState();
        if ((
$property $this->parsePropertyName()) &&
            
$this->scanner->consume(":")
        ) {
            
$value $this->isolateContext(
                array(
"allowIn" => true), "parseAssignmentExpression"
            
);
            if (
$value) {
                
$startPos = isset($property[2]) ? $property[2] : $property[0];
                
$node $this->createNode("Property"$startPos);
                
$node->setKey($property[0]);
                
$node->setValue($value);
                
$node->setComputed($property[1]);
                return 
$this->completeNode($node);
            }

            
$this->error();
            
        }
        
        
$this->scanner->setState($state);
        if (
$property $this->parseMethodDefinition()) {

            
$node $this->createNode("Property"$property);
            
$node->setKey($property->getKey());
            
$node->setValue($property->getValue());
            
$node->setComputed($property->getComputed());
            
$kind $property->getKind();
            if (
$kind !== Node\MethodDefinition::KIND_GET &&
                
$kind !== Node\MethodDefinition::KIND_SET
            
) {
                
$node->setMethod(true);
                
$node->setKind(Node\Property::KIND_INIT);
            } else {
                
$node->setKind($kind);
            }
            return 
$this->completeNode($node);
            
        } elseif (
$key $this->parseIdentifier(static::$identifierReference)) {
            
            
$node $this->createNode("Property"$key);
            
$node->setShorthand(true);
            
$node->setKey($key);
            
$value $this->isolateContext(
                array(
"allowIn" => true), "parseInitializer"
            
);
            
$node->setValue($value ?: $key);
            return 
$this->completeNode($node);
            
        }
        return 
null;
    }
    
    
/**
     * Parses an initializer
     * 
     * @return Node\Node|null
     */
    
protected function parseInitializer()
    {
        if (
$this->scanner->consume("=")) {
            
            if (
$value $this->parseAssignmentExpression()) {
                return 
$value;
            }
            
            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses an object binding pattern
     * 
     * @return Node\ObjectPattern|null
     */
    
protected function parseObjectBindingPattern()
    {
        
$state $this->scanner->getState();
        if (
$token $this->scanner->consume("{")) {

            
$properties = array();
            while (
$prop $this->parseBindingProperty()) {
                
$properties[] = $prop;
                if (!
$this->scanner->consume(",")) {
                    break;
                }
            }

            if (
$this->features->restSpreadProperties &&
                (
$rest $this->parseRestProperty())) {
                
$properties[] = $rest;
            }

            if (
$this->scanner->consume("}")) {
                
$node $this->createNode("ObjectPattern"$token);
                if (
$properties) {
                    
$node->setProperties($properties);
                }
                return 
$this->completeNode($node);
            }

            
$this->scanner->setState($state);
        }
        return 
null;
    }

    
/**
     * Parses a rest property
     *
     * @return Node\RestElement|null
     */
    
protected function parseRestProperty()
    {
        
$state $this->scanner->getState();
        if (
$token $this->scanner->consume("...")) {

            if (
$argument $this->parseIdentifier(static::$bindingIdentifier)) {
                
$node $this->createNode("RestElement"$token);
                
$node->setArgument($argument);
                return 
$this->completeNode($node);
            }

            
$this->scanner->setState($state);
        }
        return 
null;
    }
    
    
/**
     * Parses a property in an object binding pattern
     * 
     * @return Node\AssignmentProperty|null
     */
    
protected function parseBindingProperty()
    {
        
$state $this->scanner->getState();
        if ((
$key $this->parsePropertyName()) &&
            
$this->scanner->consume(":")
        ) {
            
            if (
$value $this->parseBindingElement()) {
                
$startPos = isset($key[2]) ? $key[2] : $key[0];
                
$node $this->createNode("AssignmentProperty"$startPos);
                
$node->setKey($key[0]);
                
$node->setComputed($key[1]);
                
$node->setValue($value);
                return 
$this->completeNode($node);
            }
            
            
$this->scanner->setState($state);
            return 
null;
        }
            
        
$this->scanner->setState($state);
        if (
$property $this->parseSingleNameBinding()) {
            
            
$node $this->createNode("AssignmentProperty"$property);
            
$node->setShorthand(true);
            if (
$property instanceof Node\AssignmentPattern) {
                
$node->setKey($property->getLeft());
            } else {
                
$node->setKey($property);
            }
            
$node->setValue($property);
            return 
$this->completeNode($node);
        }
        return 
null;
    }
    
    
/**
     * Parses an expression
     * 
     * @return Node\Node|null
     */
    
protected function parseExpression()
    {
        
$list $this->charSeparatedListOf("parseAssignmentExpression");
        
        if (!
$list) {
            return 
null;
        } elseif (
count($list) === 1) {
            return 
$list[0];
        } else {
            
$node $this->createNode("SequenceExpression"$list);
            
$node->setExpressions($list);
            return 
$this->completeNode($node);
        }
    }
    
    
/**
     * Parses an assignment expression
     * 
     * @return Node\Node|null
     */
    
protected function parseAssignmentExpression()
    {
        if (
$expr $this->parseArrowFunction()) {
            return 
$expr;
        } elseif (
$this->context->allowYield && $expr $this->parseYieldExpression()) {
            return 
$expr;
        } elseif (
$expr $this->parseConditionalExpression()) {
            
            
$exprTypes = array(
                
"ConditionalExpression""LogicalExpression",
                
"BinaryExpression""UpdateExpression""UnaryExpression"
            
);
            
            if (!
in_array($expr->getType(), $exprTypes)) {
                
                
$operators $this->assignmentOperators;
                if (
$operator $this->scanner->consumeOneOf($operators)) {

                    if (
$expr->getType() === "ChainExpression") {
                        
$this->error(
                            
"Optional chain can't appear in left-hand side"
                        
);
                    }

                    
$right $this->parseAssignmentExpression();
                    
                    if (
$right) {
                        
$node $this->createNode(
                            
"AssignmentExpression"$expr
                        
);
                        
$node->setLeft($this->expressionToPattern($expr));
                        
$node->setOperator($operator->value);
                        
$node->setRight($right);
                        return 
$this->completeNode($node);
                    }
                    
$this->error();
                }
            }
            return 
$expr;
        }
        return 
null;
    }
    
    
/**
     * Parses a conditional expression
     * 
     * @return Node\Node|null
     */
    
protected function parseConditionalExpression()
    {
        if (
$test $this->parseLogicalBinaryExpression()) {
            
            if (
$this->scanner->consume("?")) {
                
                
$consequent $this->isolateContext(
                    array(
"allowIn" => true), "parseAssignmentExpression"
                
);
                if (
$consequent && $this->scanner->consume(":") &&
                    
$alternate $this->parseAssignmentExpression()
                ) {
                
                    
$node $this->createNode("ConditionalExpression"$test);
                    
$node->setTest($test);
                    
$node->setConsequent($consequent);
                    
$node->setAlternate($alternate);
                    return 
$this->completeNode($node);
                }
                
                
$this->error();
            } else {
                return 
$test;
            }
        }
        return 
null;
    }
    
    
/**
     * Parses a logical or a binary expression
     * 
     * @return Node\Node|null
     */
    
protected function parseLogicalBinaryExpression()
    {
        
$operators $this->logicalBinaryOperators;
        if (!
$this->context->allowIn) {
            unset(
$operators["in"]);
        }
        
        if (!(
$exp $this->parseUnaryExpression())) {
            if (
                !
$this->features->classFieldsPrivateIn ||
                !
$this->context->allowIn
            
) {
                return 
null;
            }
            
//Support "#private in x" syntax
            
$state $this->scanner->getState();
            if (
                !(
$exp $this->parsePrivateIdentifier()) ||
                !
$this->scanner->isBefore(array("in"))
            ) {
                if (
$exp) {
                    
$this->scanner->setState($state);
                }
                return 
null;
            }
        }
        
        
$list = array($exp);
        
$coalescingFound $andOrFound false;
        while (
$token $this->scanner->consumeOneOf(array_keys($operators))) {
            
$op $token->value;
            
// Coalescing and logical expressions can't be used together
            
if ($op === "??") {
                
$coalescingFound true;
            } elseif (
$op === "&&" || $op === "||") {
                
$andOrFound true;
            }
            if (
$coalescingFound && $andOrFound) {
                
$this->error(
                    
"Logical expressions must be wrapped in parentheses when " .
                    
"inside coalesce expressions"
                
);
            }
            if (!(
$exp $this->parseUnaryExpression())) {
                
$this->error();
            }
            
$list[] = $op;
            
$list[] = $exp;
        }
        
        
$len count($list);
        if (
$len 1) {
            
$maxGrade max($operators);
            for (
$grade $maxGrade$grade >= 0$grade--) {
                
$class $grade "LogicalExpression" "BinaryExpression";
                
$r2l $grade === 10;
                
//Exponentiation operator must be parsed right to left
                
if ($r2l) {
                    
$i $len 2;
                    
$step = -2;
                } else {
                    
$i 1;
                    
$step 2;
                }
                for (; (
$r2l && $i 0) || (!$r2l && $i $len); $i += $step) {
                    if (
$operators[$list[$i]] === $grade) {
                        
$node $this->createNode($class$list[$i 1]);
                        
$node->setLeft($list[$i 1]);
                        
$node->setOperator($list[$i]);
                        
$node->setRight($list[$i 1]);
                        
$node $this->completeNode(
                            
$node$list[$i 1]->location->end
                        
);
                        
array_splice($list$i 13, array($node));
                        if (!
$r2l) {
                            
$i -= $step;
                        }
                        
$len count($list);
                    }
                }
            }
        }
        return 
$list[0];
    }
    
    
/**
     * Parses a unary expression
     * 
     * @return Node\Node|null
     */
    
protected function parseUnaryExpression()
    {
        
$operators $this->unaryOperators;
        if (
$this->features->asyncAwait && $this->context->allowAwait) {
            
$operators[] = "await";
        }
        if (
$expr $this->parsePostfixExpression()) {
            return 
$expr;
        } elseif (
$token $this->scanner->consumeOneOf($operators)) {
            if (
$argument $this->parseUnaryExpression()) {

                
$op $token->value;

                
//Deleting a variable without accessing its properties is a
                //syntax error in strict mode
                
if ($op === "delete" &&
                    
$this->scanner->getStrictMode() &&
                    
$argument instanceof Node\Identifier) {
                    
$this->error(
                        
"Deleting an unqualified identifier is not allowed in strict mode"
                    
);
                }

                if (
$this->features->asyncAwait && $op === "await") {
                    
$node $this->createNode("AwaitExpression"$token);
                } else {
                    if (
$op === "++" || $op === "--") {
                        if (
$argument->getType() === "ChainExpression") {
                            
$this->error(
                                
"Optional chain can't appear in left-hand side"
                            
);
                        }
                        
$node $this->createNode("UpdateExpression"$token);
                        
$node->setPrefix(true);
                    } else {
                        
$node $this->createNode("UnaryExpression"$token);
                    }
                    
$node->setOperator($op);
                }
                
$node->setArgument($argument);
                return 
$this->completeNode($node);
            }

            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses a postfix expression
     * 
     * @return Node\Node|null
     */
    
protected function parsePostfixExpression()
    {
        if (
$argument $this->parseLeftHandSideExpression()) {

            if (
$this->scanner->noLineTerminators() &&
                
$token $this->scanner->consumeOneOf($this->postfixOperators)
            ) {

                if (
$argument->getType() === "ChainExpression") {
                    
$this->error(
                        
"Optional chain can't appear in left-hand side"
                    
);
                }
                
                
$node $this->createNode("UpdateExpression"$argument);
                
$node->setOperator($token->value);
                
$node->setArgument($argument);
                return 
$this->completeNode($node);
            }
            
            return 
$argument;
        }
        return 
null;
    }
    
    
/**
     * Parses a left hand side expression
     * 
     * @return Node\Node|null
     */
    
protected function parseLeftHandSideExpression()
    {
        
$object null;
        
$newTokens = array();
        
        
//Parse all occurrences of "new"
        
if ($this->scanner->isBefore(array("new"))) {
            while (
$newToken $this->scanner->consume("new")) {
                if (
$this->scanner->consume(".")) {
                    
//new.target
                    
if (!$this->scanner->consume("target")) {
                        
$this->error();
                    }
                    
$node $this->createNode("MetaProperty"$newToken);
                    
$node->setMeta("new");
                    
$node->setProperty("target");
                    
$object $this->completeNode($node);
                    break;
                }
                
$newTokens[] = $newToken;
            }
        } elseif (
$this->features->importMeta &&
            
$this->sourceType === \Peast\Peast::SOURCE_TYPE_MODULE &&
            
$this->scanner->isBefore(array(array("import"".")), true)
        ) {
            
//import.meta
            
$importToken $this->scanner->consume("import");
            
$this->scanner->consume(".");
            if (!
$this->scanner->consume("meta")) {
                
$this->error();
            }
            
$node $this->createNode("MetaProperty"$importToken);
            
$node->setMeta("import");
            
$node->setProperty("meta");
            
$object $this->completeNode($node);
        }
        
        
$newTokensCount count($newTokens);
        
        if (!
$object &&
            !(
$object $this->parseSuperPropertyOrCall()) &&
            !(
$this->features->dynamicImport &&
                (
$object $this->parseImportCall())
            ) &&
            !(
$object $this->parsePrimaryExpression())
        ) {
            
            if (
$newTokensCount) {
                
$this->error();
            }
            return 
null;
        }
        
        
$valid true;
        
$optionalChain false;
        
$properties = array();
        while (
true) {
            
$optional false;
            if (
$opToken $this->scanner->consumeOneOf(array("?.""."))) {
                
$isOptChain $opToken->value == "?.";
                if (
$isOptChain) {
                    
$optionalChain $optional true;
                }
                if (
                    (
$this->features->privateMethodsAndFields && ($property $this->parsePrivateIdentifier())) ||
                    (
$property $this->parseIdentifier(static::$identifierName))
                ) {
                    
$valid true;
                    
$properties[] = array(
                        
"type"=> "id",
                        
"info" => $property,
                        
"optional" => $optional
                    
);
                    continue;
                } else {
                    
$valid false;
                    if (!
$isOptChain) {
                        break;
                    }
                }
            }
            if (
$this->scanner->consume("[")) {
                if ((
$property $this->isolateContext(
                        array(
"allowIn" => true), "parseExpression"
                    
)) &&
                    
$this->scanner->consume("]")
                ) {
                    
$valid true;
                    
$properties[] = array(
                        
"type" => "computed",
                        
"info" => array(
                            
$property$this->scanner->getPosition()
                        ),
                        
"optional" => $optional
                    
);
                } else {
                    
$valid false;
                    break;
                }
            } elseif (
$property $this->parseTemplateLiteral(true)) {
                if (
$optionalChain) {
                    
$this->error(
                        
"Optional chain can't appear in tagged template expressions"
                    
);
                }
                
$valid true;
                
$properties[] = array(
                    
"type"=> "template",
                    
"info" => $property,
                    
"optional" => $optional
                
);
            } elseif ((
$args $this->parseArguments()) !== null) {
                
$valid true;
                
$properties[] = array(
                    
"type"=> "args",
                    
"info" => array($args$this->scanner->getPosition()),
                    
"optional" => $optional
                
);
            } else {
                break;
            }
        }
        
        
$propCount count($properties);
        
        if (!
$valid) {
            
$this->error();
        } elseif (!
$propCount && !$newTokensCount) {
            return 
$object;
        }
        
        
$node null;
        
$endPos $object->location->end;
        
$optionalChainStarted false;
        foreach (
$properties as $i => $property) {
            
$lastNode $node ?: $object;
            if (
$property["optional"]) {
                
$optionalChainStarted true;
            }
            if (
$property["type"] === "args") {
                if (
$newTokensCount) {
                    if (
$optionalChainStarted) {
                        
$this->error(
                            
"Optional chain can't appear in new expressions"
                        
);
                    }
                    
$node $this->createNode(
                        
"NewExpression"array_pop($newTokens)
                    );
                    
$newTokensCount--;
                } else {
                    
$node $this->createNode("CallExpression"$lastNode);
                    
$node->setOptional($property["optional"]);
                }
                
$node->setCallee($lastNode);
                
$node->setArguments($property["info"][0]);
                
$endPos $property["info"][1];
            } elseif (
$property["type"] === "id") {
                
$node $this->createNode("MemberExpression"$lastNode);
                
$node->setObject($lastNode);
                
$node->setOptional($property["optional"]);
                
$node->setProperty($property["info"]);
                
$endPos $property["info"]->location->end;
            } elseif (
$property["type"] === "computed") {
                
$node $this->createNode("MemberExpression"$lastNode);
                
$node->setObject($lastNode);
                
$node->setProperty($property["info"][0]);
                
$node->setOptional($property["optional"]);
                
$node->setComputed(true);
                
$endPos $property["info"][1];
            } elseif (
$property["type"] === "template") {
                
$node $this->createNode("TaggedTemplateExpression"$object);
                
$node->setTag($lastNode);
                
$node->setQuasi($property["info"]);
                
$endPos $property["info"]->location->end;
            }
            
$node $this->completeNode($node$endPos);
        }
        
        
//Wrap the result in multiple NewExpression if there are "new" tokens
        
if ($newTokensCount) {
            for (
$i $newTokensCount 1$i >= 0$i--) {
                
$lastNode $node ?: $object;
                
$node $this->createNode("NewExpression"$newTokens[$i]);
                
$node->setCallee($lastNode);
                
$node $this->completeNode($node);
            }
        }

        
//Wrap the result in a chain expression if required
        
if ($optionalChain) {
            
$prevNode $node;
            
$node $this->createNode("ChainExpression"$prevNode);
            
$node->setExpression($prevNode);
            
$node $this->completeNode($node);
        }
        
        return 
$node;
    }
    
    
/**
     * Parses a spread element
     * 
     * @return Node\SpreadElement|null
     */
    
protected function parseSpreadElement()
    {
        if (
$token $this->scanner->consume("...")) {
            
            
$argument $this->isolateContext(
                array(
"allowIn" => true), "parseAssignmentExpression"
            
);
            if (
$argument) {
                
$node $this->createNode("SpreadElement"$token);
                
$node->setArgument($argument);
                return 
$this->completeNode($node);
            }
            
            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses an array literal
     * 
     * @return Node\ArrayExpression|null
     */
    
protected function parseArrayLiteral()
    {
        if (
$token $this->scanner->consume("[")) {
            
            
$elements = array();
            while (
true) {
                if (
$elision $this->parseElision()) {
                    
$elements array_merge(
                        
$elementsarray_fill(0$elisionnull)
                    );
                }
                if ((
$element $this->parseSpreadElement()) ||
                    (
$element $this->isolateContext(
                        array(
"allowIn" => true), "parseAssignmentExpression"
                    
))
                ) {
                    
$elements[] = $element;
                    if (!
$this->scanner->consume(",")) {
                        break;
                    }
                } else {
                    break;
                }
            }
            
            if (
$this->scanner->consume("]")) {
                
$node $this->createNode("ArrayExpression"$token);
                
$node->setElements($elements);
                return 
$this->completeNode($node);
            }
            
            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses an arguments list wrapped in round brackets
     * 
     * @return array|null
     */
    
protected function parseArguments()
    {
        if (
$this->scanner->consume("(")) {
            
            if ((
$args $this->parseArgumentList()) !== null &&
                
$this->scanner->consume(")")
            ) {
                return 
$args;
            }
            
            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses an arguments list
     * 
     * @return array|null
     */
    
protected function parseArgumentList()
    {
        
$list = array();
        
$hasComma false;
        while (
true) {
            
$spread $this->scanner->consume("...");
            
$exp $this->isolateContext(
                array(
"allowIn" => true), "parseAssignmentExpression"
            
);

            if (!
$exp) {
                
//If there's no expression and the spread dots have been found
                //or there is a trailing comma that is not allowed, throw an
                //error
                
if ($spread ||
                    (
$hasComma &&
                    !
$this->features->trailingCommaFunctionCallDeclaration)) {
                    
$this->error();
                }
                break;
            }

            if (
$spread) {
                
$node $this->createNode("SpreadElement"$spread);
                
$node->setArgument($exp);
                
$list[] = $this->completeNode($node);
            } else {
                
$list[] = $exp;
            }

            if (!
$this->scanner->consume(",")) {
                break;
            }
            
$hasComma true;
        }
        return 
$list;
    }
    
    
/**
     * Parses a super call or a super property
     * 
     * @return Node\Node|null
     */
    
protected function parseSuperPropertyOrCall()
    {
        if (
$token $this->scanner->consume("super")) {
            
            
$super $this->completeNode($this->createNode("Super"$token));
            
            if ((
$args $this->parseArguments()) !== null) {
                
$node $this->createNode("CallExpression"$token);
                
$node->setArguments($args);
                
$node->setCallee($super);
                return 
$this->completeNode($node);
            }
            
            
$node $this->createNode("MemberExpression"$token);
            
$node->setObject($super);
            
            if (
$this->scanner->consume(".")) {
                
                if (
$property $this->parseIdentifier(static::$identifierName)) {
                    
$node->setProperty($property);
                    return 
$this->completeNode($node);
                }
            } elseif (
$this->scanner->consume("[") &&
                (
$property $this->isolateContext(
                    array(
"allowIn" => true), "parseExpression"
                
)) &&
                
$this->scanner->consume("]")
            ) {
                
                
$node->setProperty($property);
                
$node->setComputed(true);
                return 
$this->completeNode($node);
            }
            
            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Parses a primary expression
     * 
     * @return Node\Node|null
     */
    
protected function parsePrimaryExpression()
    {
        if (
$token $this->scanner->consume("this")) {
            
$node $this->createNode("ThisExpression"$token);
            return 
$this->completeNode($node);
        } elseif (
$exp $this->parseFunctionOrGeneratorExpression()) {
            return 
$exp;
        } elseif (
$exp $this->parseClassExpression()) {
            return 
$exp;
        } elseif (
$exp $this->parseIdentifier(static::$identifierReference)) {
            return 
$exp;
        } elseif (
$exp $this->parseLiteral()) {
            return 
$exp;
        } elseif (
$exp $this->parseArrayLiteral()) {
            return 
$exp;
        } elseif (
$exp $this->parseObjectLiteral()) {
            return 
$exp;
        } elseif (
$exp $this->parseRegularExpressionLiteral()) {
            return 
$exp;
        } elseif (
$exp $this->parseTemplateLiteral()) {
            return 
$exp;
        } elseif (
$this->jsx && ($exp $this->parseJSXFragment())) {
            return 
$exp;
        } elseif (
$this->jsx && ($exp $this->parseJSXElement())) {
            return 
$exp;
        } elseif (
$token $this->scanner->consume("(")) {
            
            if ((
$exp $this->isolateContext(
                    array(
"allowIn" => true), "parseExpression"
                
)) &&
                
$this->scanner->consume(")")
            ) {
                
                
$node $this->createNode("ParenthesizedExpression"$token);
                
$node->setExpression($exp);
                return 
$this->completeNode($node);
            }
            
            
$this->error();
        }
        return 
null;
    }

    
/**
     * Parses a private identifier
     * 
     * @return Node\PrivateIdentifier|null
     */
    
protected function parsePrivateIdentifier()
    {
        
$token $this->scanner->getToken();
        if (!
$token || $token->type !== Token::TYPE_PRIVATE_IDENTIFIER) {
            return 
null;
        }
        
$this->scanner->consumeToken();
        
$node $this->createNode("PrivateIdentifier"$token);
        
$node->setName(substr($token->value1));
        return 
$this->completeNode($node);
    }
    
    
/**
     * Parses an identifier
     * 
     * @param int   $mode       Parsing mode, one of the id parsing mode
     *                          constants
     * @param string $after     If a string is passed in this parameter, the
     *                          identifier is parsed only if precedes this string
     * 
     * @return Node\Identifier|null
     */
    
protected function parseIdentifier($mode$after null)
    {
        
$token $this->scanner->getToken();
        if (!
$token) {
            return 
null;
        }
        if (
$after !== null) {
            
$next $this->scanner->getNextToken();
            if (!
$next || $next->value !== $after) {
                return 
null;
            }
        }
        
$type $token->type;
        switch (
$type) {
            case 
Token::TYPE_BOOLEAN_LITERAL:
            case 
Token::TYPE_NULL_LITERAL:
                if (
$mode !== self::ID_ALLOW_ALL) {
                    return 
null;
                }
            break;
            case 
Token::TYPE_KEYWORD:
                if (
$mode === self::ID_ALLOW_NOTHING) {
                    return 
null;
                } elseif (
$mode === self::ID_MIXED &&
                    
$this->scanner->isStrictModeKeyword($token)
                ) {
                    return 
null;
                }
            break;
            default:
                if (
$type !== Token::TYPE_IDENTIFIER) {
                    return 
null;
                }
            break;
        }
        
        
//Exclude keywords that depend on parser context
        
$value $token->value;
        if (
$mode === self::ID_MIXED &&
            isset(
$this->contextKeywords[$value]) &&
            
$this->context->{$this->contextKeywords[$value]}
        ) {
            return 
null;
        }
        
        
$this->scanner->consumeToken();
        
$node $this->createNode("Identifier"$token);
        
$node->setRawName($value);
        return 
$this->completeNode($node);
    }
    
    
/**
     * Parses a literal
     * 
     * @return Node\Literal|null
     */
    
protected function parseLiteral()
    {
        if (
$token $this->scanner->getToken()) {
            if (
$token->type === Token::TYPE_NULL_LITERAL) {
                
$this->scanner->consumeToken();
                
$node $this->createNode("NullLiteral"$token);
                return 
$this->completeNode($node);
            } elseif (
$token->type === Token::TYPE_BOOLEAN_LITERAL) {
                
$this->scanner->consumeToken();
                
$node $this->createNode("BooleanLiteral"$token);
                
$node->setRaw($token->value);
                return 
$this->completeNode($node);
            } elseif (
$literal $this->parseStringLiteral()) {
                return 
$literal;
            } elseif (
$literal $this->parseNumericLiteral()) {
                return 
$literal;
            }
        }
        return 
null;
    }
    
    
/**
     * Parses a string literal
     * 
     * @return Node\StringLiteral|null
     */
    
protected function parseStringLiteral()
    {
        
$token $this->scanner->getToken();
        if (
$token && $token->type === Token::TYPE_STRING_LITERAL) {
            
$val $token->value;
            
$this->checkInvalidEscapeSequences($val);
            
$this->scanner->consumeToken();
            
$node $this->createNode("StringLiteral"$token);
            
$node->setRaw($val);
            return 
$this->completeNode($node);
        }
        return 
null;
    }
    
    
/**
     * Parses a numeric literal
     * 
     * @return Node\NumericLiteral|Node\BigIntLiteral|null
     */
    
protected function parseNumericLiteral()
    {
        
$token $this->scanner->getToken();
        if (
$token && $token->type === Token::TYPE_NUMERIC_LITERAL) {
            
$val $token->value;
            
$this->checkInvalidEscapeSequences($valtrue);
            
$this->scanner->consumeToken();
            
$node $this->createNode("NumericLiteral"$token);
            
$node->setRaw($val);
            return 
$this->completeNode($node);
        } elseif (
$token && $token->type === Token::TYPE_BIGINT_LITERAL) {
            
$val $token->value;
            
$this->checkInvalidEscapeSequences($valtrue);
            
$this->scanner->consumeToken();
            
$node $this->createNode("BigIntLiteral"$token);
            
$node->setRaw($val);
            return 
$this->completeNode($node);
        }
        return 
null;
    }
    
    
/**
     * Parses a template literal
     * 
     * @param bool $tagged True if the template is tagged
     * 
     * @return Node\Literal|null
     */
    
protected function parseTemplateLiteral($tagged false)
    {
        
$token $this->scanner->getToken();
        
        if (!
$token || $token->type !== Token::TYPE_TEMPLATE) {
            return 
null;
        }
        
        
//Do not parse templates parts
        
$val $token->value;
        if (
$val[0] !== "`") {
            return 
null;
        }
        
        
$quasis $expressions = array();
        
$valid false;
        do {
            
$this->scanner->consumeToken();
            
$val $token->value;
            
$this->checkInvalidEscapeSequences($valfalsetrue$tagged);
            
$lastChar substr($val, -1);
            
            
$quasi $this->createNode("TemplateElement"$token);
            
$quasi->setRawValue($val);
            if (
$lastChar === "`") {
                
$quasi->setTail(true);
                
$quasis[] = $this->completeNode($quasi);
                
$valid true;
                break;
            } else {
                
$quasis[] = $this->completeNode($quasi);
                
$exp $this->isolateContext(
                    array(
"allowIn" => true), "parseExpression"
                
);
                if (
$exp) {
                    
$expressions[] = $exp;
                } else {
                    
$valid false;
                    break;
                }
            }
            
            
$token $this->scanner->getToken();
        } while (
$token && $token->type === Token::TYPE_TEMPLATE);
        
        if (
$valid) {
            
$node $this->createNode("TemplateLiteral"$quasis[0]);
            
$node->setQuasis($quasis);
            
$node->setExpressions($expressions);
            return 
$this->completeNode($node);
        }
        
        
$this->error();
    }
    
    
/**
     * Parses a regular expression literal
     * 
     * @return Node\Literal|null
     */
    
protected function parseRegularExpressionLiteral()
    {
        if (
$token $this->scanner->reconsumeCurrentTokenAsRegexp()) {
            
$this->scanner->consumeToken();
            
$node $this->createNode("RegExpLiteral"$token);
            
$node->setRaw($token->value);
            return 
$this->completeNode($node);
        }
        return 
null;
    }
    
    
/**
     * Parse directive prologues. The result is an array where the first element
     * is the array of parsed nodes and the second element is the array of
     * directive prologues values
     * 
     * @return array|null
     */
    
protected function parseDirectivePrologues()
    {
        
$directives $nodes = array();
        while ((
$token $this->scanner->getToken()) &&
            
$token->type === Token::TYPE_STRING_LITERAL
        
) {
            
$directive substr($token->value1, -1);
            if (
$directive === "use strict") {
                
$directives[] = $directive;
                
$directiveNode $this->parseStringLiteral();
                
$this->assertEndOfStatement();
                
$node $this->createNode("ExpressionStatement"$directiveNode);
                
$node->setExpression($directiveNode);
                
$nodes[] = $this->completeNode($node);
            } else {
                break;
            }
        }
        return 
count($nodes) ? array($nodes$directives) : null;
    }

    
/**
     * Parses an import call
     *
     * @return Node\Node|null
     */
    
protected function parseImportCall()
    {
        if ((
$token $this->scanner->consume("import")) &&
            
$this->scanner->consume("(")) {

            if ((
$source $this->isolateContext(
                    array(
"allowIn" => true), "parseAssignmentExpression"
                
)) &&
                
$this->scanner->consume(")")
            ) {
                
$node $this->createNode("ImportExpression"$token);
                
$node->setSource($source);
                return 
$this->completeNode($node);
            }

            
$this->error();
        }
        return 
null;
    }
    
    
/**
     * Checks if the given string or number contains invalid escape sequences
     * 
     * @param string  $val                      Value to check
     * @param bool    $number                   True if the value is a number
     * @param bool    $forceLegacyOctalCheck    True to force legacy octal
     *                                          form check
     * @param bool    $taggedTemplate           True if the value is a tagged
     *                                          template
     * 
     * @return void
     */
    
protected function checkInvalidEscapeSequences(
        
$val$number false$forceLegacyOctalCheck false,
        
$taggedTemplate false
    
) {
        if (
$this->features->skipEscapeSeqCheckInTaggedTemplates &&
            
$taggedTemplate) {
            return;
        }
        
$checkLegacyOctal $forceLegacyOctalCheck || $this->scanner->getStrictMode();
        if (
$number) {
            if (
$val && $val[0] === "0" && preg_match("#^0[0-9_]+$#"$val)) {
                if (
$checkLegacyOctal) {
                    
$this->error(
                        
"Octal literals are not allowed in strict mode"
                    
);
                }
                if (
$this->features->numericLiteralSeparator &&
                    
strpos($val'_') !== false
                
) {
                    
$this->error(
                        
"Numeric separators are not allowed in legacy octal numbers"
                    
);
                }
            }
        } elseif (
strpos($val"\\") !== false) {
            
$hex "0-9a-fA-F";
            
$invalidSyntax = array(
                
"x[$hex]?[^$hex]",
                
"x[$hex]?$",
                
"u\{\}",
                
"u\{(?:[$hex]*[^$hex\}]+)+[$hex]*\}",
                
"u\{[^\}]*$",
                
"u(?!{)[$hex]{0,3}[^$hex\{]",
                
"u[$hex]{0,3}$"
            
);
            if (
$checkLegacyOctal) {
                
$invalidSyntax[] = "\d{2}";
                
$invalidSyntax[] = "[1-7]";
                
$invalidSyntax[] = "0[89]";
            }
            
$reg "#(\\\\+)(" implode("|"$invalidSyntax) . ")#";
            if (
preg_match_all($reg$val$matchesPREG_SET_ORDER)) {
                foreach (
$matches as $match) {
                    if (
strlen($match[1]) % 2) {
                        
$first $match[2][0];
                        if (
$first === "u") {
                            
$err "Malformed unicode escape sequence";
                        } elseif (
$first === "x") {
                            
$err "Malformed hexadecimal escape sequence";
                        } else {
                            
$err "Octal literals are not allowed in strict mode";
                        }
                        
$this->error($err);
                    }
                }
            }
        }
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax;

/**
 * Parser features class
 * 
 * @author Marco Marchiò <[email protected]>
 * 
 * @codeCoverageIgnore
 */
class Features
{
    
/**
     * Exponentiation operator
     *
     * @var bool
     */
    
public $exponentiationOperator false;

    
/**
     * Async/await
     *
     * @var bool
     */
    
public $asyncAwait false;

    
/**
     * Trailing comma in function calls and declarations
     *
     * @var bool
     */
    
public $trailingCommaFunctionCallDeclaration false;

    
/**
     * For-in initializer
     *
     * @var bool
     */
    
public $forInInitializer false;

    
/**
     * Async iteration and generators
     *
     * @var bool
     */
    
public $asyncIterationGenerators false;

    
/**
     * Rest/spread properties
     *
     * @var bool
     */
    
public $restSpreadProperties false;

    
/**
     * Skip escape sequences checks in tagged template
     *
     * @var bool
     */
    
public $skipEscapeSeqCheckInTaggedTemplates false;

    
/**
     * Optional catch binding
     *
     * @var bool
     */
    
public $optionalCatchBinding false;

    
/**
     * Paragraph and line separator in strings
     *
     * @var bool
     */
    
public $paragraphLineSepInStrings false;

    
/**
     * Dynamic import
     *
     * @var bool
     */
    
public $dynamicImport false;

    
/**
     * BigInt literals
     *
     * @var bool
     */
    
public $bigInt false;

    
/**
     * Exported name for export all declarations
     *
     * @var bool
     */
    
public $exportedNameInExportAll false;

    
/**
     * Import.meta
     *
     * @var bool
     */
    
public $importMeta false;

    
/**
     * Coalescing operator
     *
     * @var bool
     */
    
public $coalescingOperator false;

    
/**
     * Optional chaining
     *
     * @var bool
     */
    
public $optionalChaining false;

    
/**
     * Logical assignment operators
     *
     * @var bool
     */
    
public $logicalAssignmentOperators false;

    
/**
     * Numeric literal separator
     *
     * @var bool
     */
    
public $numericLiteralSeparator false;

    
/**
     * Private methods and fields
     *
     * @var bool
     */
    
public $privateMethodsAndFields false;

    
/**
     * Class fields
     *
     * @var bool
     */
    
public $classFields false;

    
/**
     * "in" operator for private fields
     *
     * @var bool
     */
    
public $classFieldsPrivateIn false;

    
/**
     * Top level await
     *
     * @var bool
     */
    
public $topLevelAwait false;

    
/**
     * Class static block
     *
     * @var bool
     */
    
public $classStaticBlock false;

    
/**
     * Arbitrary module namespace identifier names
     *
     * @var bool
     */
    
public $arbitraryModuleNSNames false;
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax;

/**
 * A token emitted by the tokenizer.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class Token implements \JSONSerializable
{
    
//Type constants
    /**
     * Boolean literal
     */
    
const TYPE_BOOLEAN_LITERAL "Boolean";
    
    
/**
     * Identifier
     */
    
const TYPE_IDENTIFIER "Identifier";
    
    
/**
     * Private identifier
     */
    
const TYPE_PRIVATE_IDENTIFIER "PrivateIdentifier";
    
    
/**
     * Keyword
     */
    
const TYPE_KEYWORD "Keyword";
    
    
/**
     * Null literal
     */
    
const TYPE_NULL_LITERAL "Null";
    
    
/**
     * Numeric literal
     */
    
const TYPE_NUMERIC_LITERAL "Numeric";
    
    
/**
     * BigInt literal
     */
    
const TYPE_BIGINT_LITERAL "BigInt";
    
    
/**
     * Punctuator
     */
    
const TYPE_PUNCTUATOR "Punctuator";

    
//This constant is kept only for backward compatibility since it was
    //first written with a typo
    
const TYPE_PUNCTUTATOR "Punctuator";
    
    
/**
     * String literal
     */
    
const TYPE_STRING_LITERAL "String";
    
    
/**
     * Regular expression
     */
    
const TYPE_REGULAR_EXPRESSION "RegularExpression";
    
    
/**
     * Template
     */
    
const TYPE_TEMPLATE "Template";
    
    
/**
     * Comment
     */
    
const TYPE_COMMENT "Comment";
    
    
/**
     * JSX text
     */
    
const TYPE_JSX_TEXT "JSXText";
    
    
/**
     * JSX identifier
     */
    
const TYPE_JSX_IDENTIFIER "JSXIdentifier";
    
    
/**
     * Tokens' type that is one of the type constants
     * 
     * @var string 
     */
    
public $type;
    
    
/**
     * Token's value
     * 
     * @var string 
     */
    
public $value;
    
    
/**
     * Token's location in the source code
     * 
     * @var SourceLocation 
     */
    
public $location;
    
    
/**
     * Class constructor
     * 
     * @param string $type  Token's type
     * @param string $value Token's value
     */
    
public function __construct($type$value)
    {
        
$this->type $type;
        
$this->value $value;
        
$this->location = new SourceLocation();
    }

    
/**
     * Returns the token's type
     *
     * @return string
     */
    
public function getType()
    {
        return 
$this->type;
    }

    
/**
     * Returns the token's value
     *
     * @return string
     */
    
public function getValue()
    {
        return 
$this->value;
    }
    
    
/**
     * Returns the token's location in the source code
     * 
     * @return SourceLocation
     */
    
public function getLocation()
    {
        return 
$this->location;
    }
    
    
/**
     * Sets the start position of the token in the source code
     * 
     * @param Position $position Start position
     * 
     * @return $this
     */
    
public function setStartPosition(Position $position)
    {
        
$this->location->start $position;
        return 
$this;
    }
    
    
/**
     * Sets the end position of the token in the source code
     * 
     * @param Position $position End position
     * 
     * @return $this
     */
    
public function setEndPosition(Position $position)
    {
        
$this->location->end $position;
        return 
$this;
    }
    
    
/**
     * Returns a serializable version of the node
     * 
     * @return array
     */
    
#[\ReturnTypeWillChange]
    public function 
jsonSerialize()
    {
        return array(
            
"type" => $this->type,
            
"value" => $this->value,
            
"location" => $this->location
        
);
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax;

/**
 * This class represents the position in the source code.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class Position implements \JSONSerializable
{
    
/**
     * Source line
     * 
     * @var int 
     */
    
protected $line;
    
    
/**
     * Source column
     * 
     * @var int 
     */
    
protected $column;
    
    
/**
     * Source index
     * 
     * @var int 
     */
    
protected $index;
    
    
/**
     * Class constructor
     * 
     * @param int $line   Source line
     * @param int $column Source column
     * @param int $index  Source index
     */
    
function __construct($line$column$index)
    {
        
$this->line $line;
        
$this->column $column;
        
$this->index $index;
    }
    
    
/**
     * Returns the source line
     * 
     * @return int
     */
    
public function getLine()
    {
        return 
$this->line;
    }
    
    
/**
     * Returns the source column
     * 
     * @return int
     */
    
public function getColumn()
    {
        return 
$this->column;
    }
    
    
/**
     * Returns the source index
     * 
     * @return int
     */
    
public function getIndex()
    {
        return 
$this->index;
    }
    
    
/**
     * Returns a serializable version of the object
     * 
     * @return array
     */
    
#[\ReturnTypeWillChange]
    public function 
jsonSerialize()
    {
        return array(
            
"line" => $this->getLine(),
            
"column" => $this->getColumn(),
            
"index" => $this->getIndex()
        );
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax;

/**
 * Events emitter class. An instance of this class is used by Parser and Scanner
 * to emit events and attach listeners to them
 * 
 * @author Marco Marchiò <[email protected]>
 */
class EventsEmitter
{
    
/**
     * Events registry array
     *
     * @var array
     */
    
protected $eventsRegistry = array();
    
    
/**
     * Attaches a listener function to the given event
     * 
     * @param string    $event      Event name
     * @param callable  $listener   Listener function
     * 
     * @return $this
     */
    
public function addListener($event$listener)
    {
        if (!isset(
$this->eventsRegistry[$event])) {
            
$this->eventsRegistry[$event] = array();
        }
        
$this->eventsRegistry[$event][] = $listener;
        return 
$this;
    }
    
    
/**
     * Fires an event
     * 
     * @param string    $event  Event name
     * @param array     $args   Arguments to pass to functions attached to the
     *                          event
     * 
     * @return $this
     */
    
public function fire($event$args = array())
    {
        if (isset(
$this->eventsRegistry[$event])) {
            foreach (
$this->eventsRegistry[$event] as $listener) {
                
call_user_func_array($listener$args);
            }
        }
        return 
$this;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax;

/**
 * Syntax exception class. Syntax errors in the source are thrown using this
 * using this exception class.
 * 
 * @author Marco Marchiò <[email protected]>
 * 
 * @codeCoverageIgnore
 */
class Exception extends \Exception
{
    
/**
     * Error position
     * 
     * @var Position
     */
    
protected $position;
    
    
/**
     * Class constructor
     * 
     * @param string   $message  Error message
     * @param Position $position Error position
     */
    
public function __construct($messagePosition $position)
    {
        
parent::__construct($message);
        
$this->position $position;
    }
    
    
/**
     * Returns the error position
     * 
     * @return Position
     */
    
public function getPosition()
    {
        return 
$this->position;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\ES2019;

/**
 * ES2019 features class
 * 
 * @author Marco Marchiò <[email protected]>
 * 
 * @codeCoverageIgnore
 */
class Features extends \Peast\Syntax\ES2018\Features
{
    
/**
     * Optional catch binding
     *
     * @var bool
     */
    
public $optionalCatchBinding true;

    
/**
     * Paragraph and line separator in strings
     *
     * @var bool
     */
    
public $paragraphLineSepInStrings true;
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax;

/**
 * Utilities class.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class Utils
{
    
/**
     * Converts a string to an array of UTF-8 characters
     * 
     * @param string $str            String to convert
     * @param bool   $strictEncoding If false and the string contains invalid
     *                               UTF-8 characters, it will replace those
     *                               characters with the one defined in the
     *                               mbstring.substitute_character setting
     * 
     * @return array
     * 
     * @throws EncodingException
     */
    
static public function stringToUTF8Array($str$strictEncoding true)
    {
        if (
$str === "") {
            return array();
        }
        
$ret preg_split('//u'$str, -1PREG_SPLIT_NO_EMPTY);
        if (
preg_last_error() === PREG_BAD_UTF8_ERROR) {
            if (!
$strictEncoding) {
                
$str mb_convert_encoding($str'UTF-8''UTF-8');
                
$ret self::stringToUTF8Array($strfalse);
            } else {
                throw new 
EncodingException("String contains invalid UTF-8");
            }
        }
        return 
$ret;
    }
    
    
/**
     * Converts an unicode code point to UTF-8
     * 
     * @param int $num Unicode code point
     * 
     * @return string
     * 
     * @codeCoverageIgnore
     */
    
static public function unicodeToUtf8($num)
    {
        
//From: http://stackoverflow.com/questions/1805802/php-convert-unicode-codepoint-to-utf-8#answer-7153133
        
if ($num <= 0x7F) {
            return 
chr($num);
        } elseif (
$num <= 0x7FF) {
            return 
chr(($num >> 6) + 192) .
                   
chr(($num 63) + 128);
        } elseif (
$num <= 0xFFFF) {
            return 
chr(($num >> 12) + 224) .
                   
chr((($num >> 6) & 63) + 128) .
                   
chr(($num 63) + 128);
        } elseif (
$num <= 0x1FFFFF) {
            return 
chr(($num >> 18) + 240) .
                   
chr((($num >> 12) & 63) + 128) .
                   
chr((($num >> 6) & 63) + 128) .
                   
chr(($num 63) + 128);
        }
        return 
'';
    }
    
    
/**
     * Compiled line terminators cache
     * 
     * @var array 
     */
    
protected static $lineTerminatorsCache;
    
    
/**
     * Returns line terminators array
     * 
     * @return array
     */
    
protected static function getLineTerminators()
    {
        if (!
self::$lineTerminatorsCache) {
            
self::$lineTerminatorsCache = array();
            foreach (
Scanner::$lineTerminatorsChars as $char) {
                
self::$lineTerminatorsCache[] = is_int($char) ?
                                                
self::unicodeToUtf8($char) :
                                                
$char;
            }
        }
        return 
self::$lineTerminatorsCache;
    }

    
/**
     * Converts a surrogate pair of Unicode code points to UTF-8
     * 
     * @param string $first  First Unicode code point
     * @param string $second Second Unicode code point
     * 
     * @return string
     * 
     * @codeCoverageIgnore
     */
    
static public function surrogatePairToUtf8($first$second)
    {
        
//From: https://stackoverflow.com/questions/39226593/how-to-convert-utf16-surrogate-pairs-to-equivalent-hex-codepoint-in-php
        
$value = ((hexdec($first) & 0x3ff) << 10) | (hexdec($second) & 0x3ff);
        return 
self::unicodeToUtf8($value 0x10000);
    }
    
    
/**
     * This function takes a string as it appears in the source code and returns
     * an unquoted version of it
     * 
     * @param string $str The string to unquote
     * 
     * @return string
     */
    
static public function unquoteLiteralString($str)
    {
        
//Remove quotes
        
$str substr($str1, -1);
        
        
//Return immediately if the escape character is missing
        
if (strpos($str"\\") === false) {
            return 
$str;
        }
        
        
$lineTerminators self::getLineTerminators();
        
        
//Surrogate pairs regex
        
$surrogatePairsReg sprintf(
            
'u(?:%1$s|\{%1$s\})\\\\u(?:%2$s|\{%2$s\})',
            
"[dD][89abAB][0-9a-fA-F]{2}""[dD][c-fC-F][0-9a-fA-F]{2}"
        
);
        
        
//Handle escapes
        
$patterns = array(
            
$surrogatePairsReg,
            
"u\{[a-fA-F0-9]+\}",
            
"u[a-fA-F0-9]{4}",
            
"x[a-fA-F0-9]{2}",
            
"0[0-7]{2}",
            
"[1-7][0-7]",
            
"."
        
);
        
$reg "/\\\\(" implode("|"$patterns) . ")/s";
        
$simpleSequence = array(
            
"n" => "\n",
            
"f" => "\f",
            
"r" => "\r",
            
"t" => "\t",
            
"v" => "\v",
            
"b" => "\x8"
        
);
        
$replacement = function ($m) use ($simpleSequence$lineTerminators) {
            
$type $m[1][0];
            if (isset(
$simpleSequence[$type])) {
                
// \n, \r, \t ...
                
return $simpleSequence[$type];
            } elseif (
$type === "u" || $type === "x") {
                
//Invalid unicode or hexadecimal sequences
                
if (strlen($m[1]) === 1) {
                    return 
"\\$type";
                }
                
// Surrogate pair
                
if ($type === "u" && strpos($m[1], "\\") !== false) {
                    
$points explode("\\"$m[1]);
                    return 
Utils::surrogatePairToUtf8(
                        
str_replace(array("{""}""u"), ""$points[0]),
                        
str_replace(array("{""}""u"), ""$points[1])
                    );
                }
                
// \uFFFF, \u{FFFF}, \xFF
                
$code substr($m[1], 1);
                
$code str_replace(array("{""}"), ""$code);
                return 
Utils::unicodeToUtf8(hexdec($code));
            } elseif (
$type >= "0" && $type <= "7") {
                
//Invalid octal sequences
                
if (strlen($m[1]) === 1) {
                    return 
"\\$type";
                }
                
// \123
                
return Utils::unicodeToUtf8(octdec($m[1]));
            } elseif (
in_array($m[1], $lineTerminators)) {
                
// Escaped line terminators
                
return "";
            } else {
                
// Escaped characters
                
return $m[1];
            }
        };
        return 
preg_replace_callback($reg$replacement$str);
    }
    
    
/**
     * This function converts a string to a quoted javascript string
     * 
     * @param string $str   String to quote
     * @param string $quote Quote character
     * 
     * @return string
     */
    
static public function quoteLiteralString($str$quote)
    {
        
$escape self::getLineTerminators();
        
$escape[] = $quote;
        
$escape[] = "\\\\";
        
$reg "/(" implode("|"$escape) . ")/";
        
$str preg_replace($reg"\\\\$1"$str);
        return 
$quote $str $quote;
    }
    
    
/**
     * Returns the properties map for the given node
     * 
     * @param mixed $node Node or class to consider
     * 
     * @return array
     */
    
static protected function getPropertiesMap($node)
    {
        static 
$cache = array();
        
        if (
$node instanceof \ReflectionClass) {
            
$className $node->getName();
        } else {
            
$className get_class($node);
        }
        
        if (!isset(
$cache[$className])) {
            
$class = new \ReflectionClass($className);
            
$parent $class->getParentClass();
            
$props $parent self::getPropertiesMap($parent) : array();
            
$defaults $class->getDefaultProperties();
            if (isset(
$defaults["propertiesMap"])) {
                
$props array_merge($props$defaults["propertiesMap"]);
            }
            
$cache[$className] = $props;
        }
        return 
$cache[$className];
    }
    
    
/**
     * Returns the properties list for the given node
     * 
     * @param Node\Node $node        Node to consider
     * @param bool      $traversable If true it returns only traversable properties
     * 
     * @return array
     */
    
static public function getNodeProperties(Node\Node $node$traversable false)
    {
        
$props self::getPropertiesMap($node);
        return 
array_map(
            function (
$prop) {
                
$ucProp ucfirst($prop);
                return array(
                    
"name" => $prop,
                    
"getter" => "get$ucProp",
                    
"setter" => "set$ucProp"
                
);
            },
            
array_keys($traversable array_filter($props) : $props)
        );
    }

    
/**
     * Returns an expanded version of the traversable node properties.
     * The return of the function is an array of node properties
     * values with arrays flattened
     *
     * @param Node\Node $node Node
     *
     * @return array
     */
    
static public function getExpandedNodeProperties(Node\Node $node)
    {
        
$ret = array();
        
$props self::getNodeProperties($nodetrue);
        foreach (
$props as $prop) {
            
$val $node->{$prop["getter"]}();
            if (
is_array($val)) {
                
$ret array_merge($ret$val);
            } else {
                
$ret[] = $val;
            }
        }
        return 
$ret;
    }

    
/**
     * Delete an array element by value
     * 
     * @param array $array Array
     * @param mixed $val   Value to remove
     * 
     * @return void
     */
    
static public function removeArrayValue(&$array$val)
    {
        
array_splice($arrayarray_search($val$array), 1);
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax;

/**
 * This class represents a location in the source code with start and end
 * position.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class SourceLocation implements \JSONSerializable
{
    
/**
     * Start position
     * 
     * @var Position 
     */
    
public $start;
    
    
/**
     * End position
     * 
     * @var Position 
     */
    
public $end;
    
    
/**
     * Returns the start position
     * 
     * @return Position
     */
    
public function getStart()
    {
        return 
$this->start;
    }
    
    
/**
     * Sets the start position
     * 
     * @param Position $position Start position
     * 
     * @return $this
     */
    
public function setStart(Position $position)
    {
        
$this->start $position;
        return 
$this;
    }
    
    
/**
     * Returns the end position
     * 
     * @return Position
     */
    
public function getEnd()
    {
        return 
$this->end;
    }
    
    
/**
     * Sets the end position
     * 
     * @param Position $position End position
     * 
     * @return $this
     */
    
public function setEnd(Position $position)
    {
        
$this->end $position;
        return 
$this;
    }
    
    
/**
     * Returns a serializable version of the object
     * 
     * @return array
     */
    
#[\ReturnTypeWillChange]
    public function 
jsonSerialize()
    {
        return array(
            
"start" => $this->start,
            
"end" => $this->end
        
);
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax;

/**
 * Longest Sequence Matcher. Utility class used by the scanner to consume
 * the longest sequence of character given a set of allowed characters sequences.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class LSM
{
    
/**
     * Internal sequences map
     * 
     * @var array 
     */
    
protected $map = array();
    
    
/**
     * Encoding handle flag
     * 
     * @var bool 
     */
    
protected $handleEncoding false;
    
    
/**
     * Class constructor
     *
     * @param array $sequences      Allowed characters sequences
     * @param bool  $handleEncoding True to handle encoding when matching
     */
    
function __construct($sequences$handleEncoding false)
    {
        
$this->handleEncoding $handleEncoding;
        foreach (
$sequences as $s) {
            
$this->add($s);
        }
    }
    
    
/**
     * Adds a sequence
     * 
     * @param string $sequence Sequence to add
     * 
     * @return $this
     */
    
public function add($sequence)
    {
        if (
$this->handleEncoding) {
            
$s Utils::stringToUTF8Array($sequence);
            
$first $s[0];
            
$len count($s);
        } else {    
            
$first $sequence[0];
            
$len strlen($sequence);
        }
        if (!isset(
$this->map[$first])) {
            
$this->map[$first] = array(
                
"maxLen" => $len,
                
"map" => array($sequence)
            );
        } else {
            
$this->map[$first]["map"][] = $sequence;
            
$this->map[$first]["maxLen"] = max($this->map[$first]["maxLen"], $len);
        }
        return 
$this;
    }
    
    
/**
     * Removes a sequence
     * 
     * @param string $sequence Sequence to remove
     * 
     * @return $this
     */
    
public function remove($sequence)
    {
        if (
$this->handleEncoding) {
            
$s Utils::stringToUTF8Array($sequence);
            
$first $s[0];
        } else {
            
$first $sequence[0];
        }
        if (isset(
$this->map[$first])) {
            
$len $this->handleEncoding count($s) : strlen($sequence);
            
$this->map[$first]["map"] = array_diff(
                
$this->map[$first]["map"], array($sequence)
            );
            if (!
count($this->map[$first]["map"])) {
                unset(
$this->map[$first]);
            } elseif (
$this->map[$first]["maxLen"] === $len) {
                
// Recalculate the max length if necessary
                
foreach ($this->map[$first]["map"] as $m) {
                    
$this->map[$first]["maxLen"] = max(
                        
$this->map[$first]["maxLen"],
                        
strlen($m)
                    );
                }
            }
        }
        return 
$this;
    }
    
    
/**
     * Executes the match. It returns an array where the first element is the
     * number of consumed characters and the second element is the match. If
     * no match is found it returns null.
     * 
     * @param Scanner   $scanner    Scanner instance
     * @param int       $index      Current index
     * @param string    $char       Current character
     * 
     * @return array|null
     */
    
public function match($scanner$index$char)
    {
        
$consumed 1;
        
$bestMatch null;
        if (isset(
$this->map[$char])) {
            
//If the character is present in the map and it has a max length of
            //1, match immediately
            
if ($this->map[$char]["maxLen"] === 1) {
                
$bestMatch = array($consumed$char);
            } else {
                
//Otherwise consume a number of characters equal to the max
                //length and find the longest match
                
$buffer $char;
                
$map $this->map[$char]["map"];
                
$maxLen $this->map[$char]["maxLen"];
                do {
                    if (
in_array($buffer$map)) {
                        
$bestMatch = array($consumed$buffer);
                    }
                    
$nextChar $scanner->charAt($index $consumed);
                    if (
$nextChar === null) {
                        break;
                    }
                    
$buffer .= $nextChar;
                    
$consumed++;
                } while (
$consumed <= $maxLen);
            }
        }
        return 
$bestMatch;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax;

/**
 * Encoding exception class. Encoding errors while scanning the source are
 * thrown using this using this exception class.
 * 
 * @author Marco Marchiò <[email protected]>
 * 
 * @codeCoverageIgnore
 */
class EncodingException extends \Exception
{
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\ES2018;

/**
 * ES2018 features class
 * 
 * @author Marco Marchiò <[email protected]>
 * 
 * @codeCoverageIgnore
 */
class Features extends \Peast\Syntax\ES2017\Features
{
    
/**
     * Async iteration and generators
     *
     * @var bool
     */
    
public $asyncIterationGenerators true;

    
/**
     * Rest/spread properties
     *
     * @var bool
     */
    
public $restSpreadProperties true;

    
/**
     * Skip escape sequences checks in tagged template
     *
     * @var bool
     */
    
public $skipEscapeSeqCheckInTaggedTemplates true;
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\ES2017;

/**
 * ES2017 features class
 * 
 * @author Marco Marchiò <[email protected]>
 * 
 * @codeCoverageIgnore
 */
class Features extends \Peast\Syntax\ES2016\Features
{
    
/**
     * Async/await
     *
     * @var bool
     */
    
public $asyncAwait true;

    
/**
     * Trailing comma in function calls and declarations
     *
     * @var bool
     */
    
public $trailingCommaFunctionCallDeclaration true;

    
/**
     * For-in initializer
     *
     * @var bool
     */
    
public $forInInitializer true;
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\ES2016;

/**
 * ES2016 features class
 * 
 * @author Marco Marchiò <[email protected]>
 * 
 * @codeCoverageIgnore
 */
class Features extends \Peast\Syntax\ES2015\Features
{
    
/**
     * Exponentiation operator
     *
     * @var bool
     */
    
public $exponentiationOperator true;
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\ES2020;

/**
 * ES2020 features class
 * 
 * @author Marco Marchiò <[email protected]>
 * 
 * @codeCoverageIgnore
 */
class Features extends \Peast\Syntax\ES2019\Features
{
    
/**
     * Dynamic import
     *
     * @var bool
     */
    
public $dynamicImport true;

    
/**
     * BigInt literals
     *
     * @var bool
     */
    
public $bigInt true;

    
/**
     * Exported name for export all declarations
     *
     * @var bool
     */
    
public $exportedNameInExportAll true;

    
/**
     * Import.meta
     *
     * @var bool
     */
    
public $importMeta true;

    
/**
     * Coalescing operator
     *
     * @var bool
     */
    
public $coalescingOperator true;

    
/**
     * Optional chaining
     *
     * @var bool
     */
    
public $optionalChaining true;
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Formatter;

/**
 * Compact formatter.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class Expanded extends Base
{
    
/**
     * Boolean that indicates if open curly brackets in code blocks must be
     * on a new line
     * 
     * @var bool
     */
    
protected $newLineBeforeCurlyBracket true;
    
    
/**
     * Boolean that indicates if content inside round brackets must be
     * surrounded by spaces
     * 
     * @var bool
     */
    
protected $spacesInsideRoundBrackets true;
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Formatter;

/**
 * Compact formatter.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class Compact extends Base
{
    
/**
     * New line character
     * 
     * @var string
     */
    
protected $newLine "";
    
    
/**
     * Indentation character
     * 
     * @var string
     */
    
protected $indentation "";
    
    
/**
     * Boolean that indicates if operators must be surrounded by spaces
     * 
     * @var bool
     */
    
protected $spacesAroundOperators false;
    
    
/**
     * Boolean that indicates if blocks of code must be wrapped in curly
     * brackets also if they contain only one instruction
     * 
     * @var bool
     */
    
protected $alwaysWrapBlocks false;
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Formatter;

/**
 * Base class for formatters, all the formatters must extend this class.
 * 
 * @author Marco Marchiò <[email protected]>
 * 
 * @abstract
 */
abstract class Base
{
    
/**
     * New line character
     * 
     * @var string
     */
    
protected $newLine "\n";
    
    
/**
     * Indentation character
     * 
     * @var string
     */
    
protected $indentation "\t";
    
    
/**
     * Boolean that indicates if open curly brackets in code blocks must be
     * on a new line
     * 
     * @var bool
     */
    
protected $newLineBeforeCurlyBracket false;
    
    
/**
     * Boolean that indicates if blocks of code must be wrapped in curly
     * brackets also if they contain only one instruction
     * 
     * @var bool
     */
    
protected $alwaysWrapBlocks true;
    
    
/**
     * Boolean that indicates if operators must be surrounded by spaces
     * 
     * @var bool
     */
    
protected $spacesAroundOperators true;
    
    
/**
     * Boolean that indicates if content inside round brackets must be
     * surrounded by spaces
     * 
     * @var bool
     */
    
protected $spacesInsideRoundBrackets false;
    
    
/**
     * Returns the new line character
     * 
     * @return string
     */
    
public function getNewLine()
    {
        return 
$this->newLine;
    }
    
    
/**
     * Returns the indentation character
     * 
     * @return string
     */
    
public function getIndentation()
    {
        return 
$this->indentation;
    }
    
    
/**
     * Returns a boolean that indicates if open curly brackets in code blocks
     * must be on a new line
     * 
     * @return bool
     */
    
public function getNewLineBeforeCurlyBracket()
    {
        return 
$this->newLineBeforeCurlyBracket;
    }
    
    
/**
     * Returns a boolean that indicates if blocks of code must be wrapped in
     * curly brackets also if they contain only one instruction
     * 
     * @return bool
     */
    
public function getAlwaysWrapBlocks()
    {
        return 
$this->alwaysWrapBlocks;
    }
    
    
/**
     * Returns a boolean that indicates if operators must be surrounded by
     * spaces
     * 
     * @return bool
     */
    
public function getSpacesAroundOperator()
    {
        return 
$this->spacesAroundOperators;
    }
    
    
/**
     * Returns a boolean that indicates if content inside round brackets must be
     * surrounded by spaces
     * 
     * @return bool
     */
    
public function getSpacesInsideRoundBrackets()
    {
        return 
$this->spacesInsideRoundBrackets;
    }
}<?
php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <[email protected]>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Formatter;

/**
 * Pretty print formatter.
 * 
 * @author Marco Marchiò <[email protected]>
 */
class PrettyPrint extends Base
{
}
## NAME

    
{{name}}

{{
#shortdesc}}
## DESCRIPTION

    
{{shortdesc}}

{{/
shortdesc}}
## SYNOPSIS

    
{{synopsis}}

{{
#alias}}
## ALIAS

    
{{alias}}

{{/
alias}}
{{
#has-subcommands}}
## SUBCOMMANDS

{{#subcommands}}
    
{{.}}
{{/
subcommands}}

{{/
has-subcommands}}
{{
#is_subcommand}}


{{/is_subcommand}}
{{
#has_subcommands}}


{{/has_subcommands}}
{{
#has_parameters}}
## GLOBAL PARAMETERS

{{#parameters}}
  
{{synopsis}}
      {{
desc}}

{{/
parameters}}
{{/
has_parameters}}
{{
#root_command}}
  
Run 'wp help <command>' to get more information on a specific command.

{{/
root_command}}
<?
php
/**
 * The base configuration for WordPress
 *
 * The wp-config.php creation script uses this file during the installation.
 * You don't have to use the web site, you can copy this file to "wp-config.php"
 * and fill in the values.
 *
 * This file contains the following configurations:
 *
 * * Database settings
 * * Secret keys
 * * Database table prefix
 * * Localized language
 * * ABSPATH
 *
 * @link https://wordpress.org/support/article/editing-wp-config-php/
 *
 * @package WordPress
 */

// ** Database settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define'DB_NAME''{{dbname}}' );

/** Database username */
define'DB_USER''{{dbuser}}' );

/** Database password */
define'DB_PASSWORD''{{dbpass}}' );

/** Database hostname */
define'DB_HOST''{{dbhost}}' );

/** Database charset to use in creating database tables. */
define'DB_CHARSET''{{dbcharset}}' );

/** The database collate type. Don't change this if in doubt. */
define'DB_COLLATE''{{dbcollate}}' );

/**#@+
 * Authentication unique keys and salts.
 *
 * Change these to different unique phrases! You can generate these using
 * the {@link https://api.wordpress.org/secret-key/1.1/salt/ WordPress.org secret-key service}.
 *
 * You can change these at any point in time to invalidate all existing cookies.
 * This will force all users to have to log in again.
 *
 * @since 2.6.0
 */
{{#keys-and-salts}}
define'AUTH_KEY',          '{{auth-key}}' );
define'SECURE_AUTH_KEY',   '{{secure-auth-key}}' );
define'LOGGED_IN_KEY',     '{{logged-in-key}}' );
define'NONCE_KEY',         '{{nonce-key}}' );
define'AUTH_SALT',         '{{auth-salt}}' );
define'SECURE_AUTH_SALT',  '{{secure-auth-salt}}' );
define'LOGGED_IN_SALT',    '{{logged-in-salt}}' );
define'NONCE_SALT',        '{{nonce-salt}}' );
define'WP_CACHE_KEY_SALT''{{wp-cache-key-salt}}' );
{{/
keys-and-salts}}
{{
keys-and-salts-alt}}

/**#@-*/

/**
 * WordPress database table prefix.
 *
 * You can have multiple installations in one database if you give each
 * a unique prefix. Only numbers, letters, and underscores please!
 */
$table_prefix '{{dbprefix}}';

/**
 * For developers: WordPress debugging mode.
 *
 * Change this to true to enable the display of notices during development.
 * It is strongly recommended that plugin and theme developers use WP_DEBUG
 * in their development environments.
 *
 * For information on other constants that can be used for debugging,
 * visit the documentation.
 *
 * @link https://wordpress.org/support/article/debugging-in-wordpress/
 */
define'WP_DEBUG'false );

{{
#add-wplang}}
/**
 * WordPress localized language, defaults to English.
 *
 * Change this to localize WordPress. A corresponding MO file for the chosen
 * language must be installed to wp-content/languages. For example, install
 * de_DE.mo to wp-content/languages and set WPLANG to 'de_DE' to enable German
 * language support.
 */
define'WPLANG''{{locale}}' );
{{/
add-wplang}}

/* Add any custom values between this line and the "stop editing" line. */

{{extra-php}}

/* That's all, stop editing! Happy publishing. */

/** Absolute path to the WordPress directory. */
if ( ! defined'ABSPATH' ) ) {
    
define'ABSPATH'__DIR__ '/' );
}

/** Sets up WordPress vars and included files. */
require_once ABSPATH 'wp-settings.php';
WordPress version: {{wp-version}}
Database revision: {{db-version}}
TinyMCE version:   {{mce-version}}
Package language:  {{local-package}}
Theme %9{{slug}}%n details:
    
Name: {{name}}
    
Status: {{status}}%n
    Version
: {{version}}
    
Author: {{author}}
Plugin %9{{slug}}%n details:
    
Name: {{name}}
    
Status: {{status}}%n
    Version
: {{version}}
    
Author: {{author}}
    
Description: {{description}}
Theme %9{{slug}}%n details:
    
Name: {{name}}
    
Status: {{status}}%n
    Version
: {{version}}
    
Author: {{author}}
<?
xml version="1.0"?>
<phpunit
    bootstrap="tests/bootstrap.php"
    backupGlobals="false"
    colors="true"
    convertErrorsToExceptions="true"
    convertNoticesToExceptions="true"
    convertWarningsToExceptions="true"
    >
    <testsuites>
        <testsuite>
            <directory prefix="test-" suffix=".php">./tests/</directory>
            <exclude>./tests/test-sample.php</exclude>
        </testsuite>
    </testsuites>
</phpunit>
.DS_Store
phpcs.xml
phpunit.xml
Thumbs.db
wp-cli.local.yml
node_modules/
*.sql
*.tar.gz
*.zip

{
  "name": "{{plugin_slug}}",
  "version": "0.1.0",
  "main": "Gruntfile.js",
  "author": "{{plugin_author}}",
  "scripts" : {
    "start" : "grunt default" ,
    "readme" : "grunt readme",
    "i18n" : "grunt i18n"
  },
  "devDependencies": {
    "grunt": "~0.4.5",
    "grunt-wp-i18n": "~0.5.0",
    "grunt-wp-readme-to-markdown": "~1.0.0"
  }
}
<?php
/**
 * PHPUnit bootstrap file.
 *
 * @package {{theme_package}}
 */

if ( PHP_MAJOR_VERSION >= ) {
    echo 
"The scaffolded tests cannot currently be run on PHP 8.0+. See https://github.com/wp-cli/scaffold-command/issues/285" PHP_EOL// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
    
exit( );
}

$_tests_dir getenv'WP_TESTS_DIR' );

if ( ! 
$_tests_dir ) {
    
$_tests_dir rtrimsys_get_temp_dir(), '/\\' ) . '/wordpress-tests-lib';
}

if ( ! 
file_exists$_tests_dir '/includes/functions.php' ) ) {
    echo 
"Could not find {$_tests_dir}/includes/functions.php, have you run bin/install-wp-tests.sh ?" PHP_EOL;
    exit( 
);
}

// Give access to tests_add_filter() function.
require_once "{$_tests_dir}/includes/functions.php";

/**
 * Registers theme.
 */
function _register_theme() {

    
$theme_dir     dirname__DIR__ );
    
$current_theme basename$theme_dir );
    
$theme_root    dirname$theme_dir );

    
add_filter'theme_root', function () use ( $theme_root ) {
        return 
$theme_root;
    } );

    
register_theme_directory$theme_root );

    
add_filter'pre_option_template', function () use ( $current_theme ) {
        return 
$current_theme;
    } );

    
add_filter'pre_option_stylesheet', function () use ( $current_theme ) {
        return 
$current_theme;
    } );
}

tests_add_filter'muplugins_loaded''_register_theme' );

// Start up the WP testing environment.
require "{$_tests_dir}/includes/bootstrap.php";
<?
php
/**
 * Class SampleTest
 *
 * @package {{plugin_package}}
 */

/**
 * Sample test case.
 */
class SampleTest extends WP_UnitTestCase {

    
/**
     * A single example test.
     */
    
public function test_sample() {
        
// Replace this with some actual testing code.
        
$this->assertTruetrue );
    }
}
<?
php

/**
 * Registers the `{{machine_name}}` taxonomy,
 * for use with {{post_types}}.
 */
function {{machine_name}}_init() {
{{
output}}
}

add_action'init''{{machine_name}}_init' );

/**
 * Sets the post updated messages for the `{{machine_name}}` taxonomy.
 *
 * @param  array $messages Post updated messages.
 * @return array Messages for the `{{machine_name}}` taxonomy.
 */
function {{machine_name}}_updated_messages$messages ) {

    
$messages['{{slug}}'] = [
        
=> ''// Unused. Messages start at index 1.
        
=> __'{{label_ucfirst}} added.''{{textdomain}}' ),
        
=> __'{{label_ucfirst}} deleted.''{{textdomain}}' ),
        
=> __'{{label_ucfirst}} updated.''{{textdomain}}' ),
        
=> __'{{label_ucfirst}} not added.''{{textdomain}}' ),
        
=> __'{{label_ucfirst}} not updated.''{{textdomain}}' ),
        
=> __'{{label_plural_ucfirst}} deleted.''{{textdomain}}' ),
    ];

    return 
$messages;
}

add_filter'term_updated_messages''{{machine_name}}_updated_messages' );
<?
php
/**
 * Plugin Name:     {{plugin_name}}
 * Plugin URI:      {{plugin_uri}}
 * Description:     {{plugin_description}}
 * Author:          {{plugin_author}}
 * Author URI:      {{plugin_author_uri}}
 * Text Domain:     {{textdomain}}
 * Domain Path:     /languages
 * Version:         0.1.0
 *
 * @package         {{plugin_package}}
 */

// Your code starts here.
<?xml version="1.0"?>
<ruleset name="WordPress Coding Standards based custom ruleset for your plugin">
    <description>Generally-applicable sniffs for WordPress plugins.</description>

    <!-- What to scan -->
    <file>.</file>
    <exclude-pattern>/vendor/</exclude-pattern>
    <exclude-pattern>/node_modules/</exclude-pattern>

    <!-- How to scan -->
    <!-- Usage instructions: https://github.com/squizlabs/PHP_CodeSniffer/wiki/Usage -->
    <!-- Annotated ruleset: https://github.com/squizlabs/PHP_CodeSniffer/wiki/Annotated-ruleset.xml -->
    <arg value="sp"/> <!-- Show sniff and progress -->
    <arg name="basepath" value="./"/><!-- Strip the file paths down to the relevant bit -->
    <arg name="colors"/>
    <arg name="extensions" value="php"/>
    <arg name="parallel" value="8"/><!-- Enables parallel processing when available for faster results. -->

    <!-- Rules: Check PHP version compatibility -->
    <!-- https://github.com/PHPCompatibility/PHPCompatibility#sniffing-your-code-for-compatibility-with-specific-php-versions -->
    <config name="testVersion" value="5.6-"/>
    <!-- https://github.com/PHPCompatibility/PHPCompatibilityWP -->
    <rule ref="PHPCompatibilityWP"/>

    <!-- Rules: WordPress Coding Standards -->
    <!-- https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards -->
    <!-- https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties -->
    <config name="minimum_supported_wp_version" value="4.6"/>
    <rule ref="WordPress"/>
    <rule ref="WordPress.NamingConventions.PrefixAllGlobals">
        <properties>
            <!-- Value: replace the function, class, and variable prefixes used. Separate multiple prefixes with a comma. -->
            <property name="prefixes" type="array" value="my-plugin"/>
        </properties>
    </rule>
    <rule ref="WordPress.WP.I18n">
        <properties>
            <!-- Value: replace the text domain used. -->
            <property name="text_domain" type="array" value="my-plugin"/>
        </properties>
    </rule>
    <rule ref="WordPress.WhiteSpace.ControlStructureSpacing">
        <properties>
            <property name="blank_line_check" value="true"/>
        </properties>
    </rule>
</ruleset>
module.exports = function( grunt ) {

    'use strict';

    // Project configuration
    grunt.initConfig( {

        pkg: grunt.file.readJSON( 'package.json' ),

        addtextdomain: {
            options: {
                textdomain: '{{textdomain}}',
            },
            update_all_domains: {
                options: {
                    updateDomains: true
                },
                src: [ '*.php', '**/*.php', '!\.git/**/*', '!bin/**/*', '!node_modules/**/*', '!tests/**/*' ]
            }
        },

        wp_readme_to_markdown: {
            your_target: {
                files: {
                    'README.md': 'readme.txt'
                }
            },
        },

        makepot: {
            target: {
                options: {
                    domainPath: '/languages',
                    exclude: [ '\.git/*', 'bin/*', 'node_modules/*', 'tests/*' ],
                    mainFile: '{{plugin_slug}}.php',
                    potFilename: '{{plugin_slug}}.pot',
                    potHeaders: {
                        poedit: true,
                        'x-poedit-keywordslist': true
                    },
                    type: 'wp-plugin',
                    updateTimestamp: true
                }
            }
        },
    } );

    grunt.loadNpmTasks( 'grunt-wp-i18n' );
    grunt.loadNpmTasks( 'grunt-wp-readme-to-markdown' );
    grunt.registerTask( 'default', [ 'i18n','readme' ] );
    grunt.registerTask( 'i18n', ['addtextdomain', 'makepot'] );
    grunt.registerTask( 'readme', ['wp_readme_to_markdown'] );

    grunt.util.linefeed = '\n';

};
<?php
/**
 * Class SampleTest
 *
 * @package {{theme_package}}
 */

/**
 * Sample test case.
 */
class SampleTest extends WP_UnitTestCase {

    
/**
     * A single example test.
     */
    
public function test_sample() {
        
// Replace this with some actual testing code.
        
$this->assertTruetrue );
    }
}
    
register_taxonomy'{{slug}}', [ {{post_types}} ], [
        
'hierarchical'          => false,
        
'public'                => true,
        
'show_in_nav_menus'     => true,
        
'show_ui'               => true,
        
'show_admin_column'     => false,
        
'query_var'             => true,
        
'rewrite'               => true,
        
'capabilities'          => [
            
'manage_terms' => 'edit_posts',
            
'edit_terms'   => 'edit_posts',
            
'delete_terms' => 'edit_posts',
            
'assign_terms' => 'edit_posts',
        ],
        
'labels'                => [
            
'name'                       => __'{{label_plural_ucfirst}}''{{textdomain}}' ),
            
'singular_name'              => _x'{{label_ucfirst}}''taxonomy general name''{{textdomain}}' ),
            
'search_items'               => __'Search {{label_plural_ucfirst}}''{{textdomain}}' ),
            
'popular_items'              => __'Popular {{label_plural_ucfirst}}''{{textdomain}}' ),
            
'all_items'                  => __'All {{label_plural_ucfirst}}''{{textdomain}}' ),
            
'parent_item'                => __'Parent {{label_ucfirst}}''{{textdomain}}' ),
            
'parent_item_colon'          => __'Parent {{label_ucfirst}}:''{{textdomain}}' ),
            
'edit_item'                  => __'Edit {{label_ucfirst}}''{{textdomain}}' ),
            
'update_item'                => __'Update {{label_ucfirst}}''{{textdomain}}' ),
            
'view_item'                  => __'View {{label_ucfirst}}''{{textdomain}}' ),
            
'add_new_item'               => __'Add New {{label_ucfirst}}''{{textdomain}}' ),
            
'new_item_name'              => __'New {{label_ucfirst}}''{{textdomain}}' ),
            
'separate_items_with_commas' => __'Separate {{label_plural}} with commas''{{textdomain}}' ),
            
'add_or_remove_items'        => __'Add or remove {{label_plural}}''{{textdomain}}' ),
            
'choose_from_most_used'      => __'Choose from the most used {{label_plural}}''{{textdomain}}' ),
            
'not_found'                  => __'No {{label_plural}} found.''{{textdomain}}' ),
            
'no_terms'                   => __'No {{label_plural}}''{{textdomain}}' ),
            
'menu_name'                  => __'{{label_plural_ucfirst}}''{{textdomain}}' ),
            
'items_list_navigation'      => __'{{label_plural_ucfirst}} list navigation''{{textdomain}}' ),
            
'items_list'                 => __'{{label_plural_ucfirst}} list''{{textdomain}}' ),
            
'most_used'                  => _x'Most Used''{{slug}}''{{textdomain}}' ),
            
'back_to_items'              => __'&larr; Back to {{label_plural_ucfirst}}''{{textdomain}}' ),
        ],
        
'show_in_rest'          => true,
        
'rest_base'             => '{{slug}}',
        
'rest_controller_class' => 'WP_REST_Terms_Controller',
    ] );
# This file is for unifying the coding style for different editors and IDEs
# editorconfig.org

# WordPress Coding Standards
# https://make.wordpress.org/core/handbook/coding-standards/

root true

[*]
charset utf-8
end_of_line 
lf
insert_final_newline 
true
trim_trailing_whitespace 
true
indent_style 
tab
indent_size 
4

[{.jshintrc,*.json,*.yml}]
indent_style space
indent_size 
2

[{*.txt,wp-config-sample.php}]
end_of_line crlf
/**
 * The following styles get applied inside the editor only.
 *
 * Replace them with your own styles or remove the file completely.
 */

.wp-block-{{namespace}}-{{slug}} {
    
border1px dotted #f00;
}
/**
 * The following styles get applied both on the front of your site and in the editor.
 *
 * Replace them with your own styles or remove the file completely.
 */

.wp-block-{{namespace}}-{{slug}} {
    
background-color#000;
    
color#fff;
    
padding2px;
}
workflows:
  
version2
  main
:
    
jobs:
      - 
php56-build
      
php70-build
      
php71-build
      
php72-build
      
php73-build
      
php74-build

version
2

job
-references:
  
mysql_image: &mysql_image
    circleci
/mysql:5.6

  setup_environment
: &setup_environment
    name
"Setup Environment Variables"
    
command: |
      echo 
"export PATH=$HOME/.composer/vendor/bin:$PATH>> $BASH_ENV
      source 
/home/circleci/.bashrc

  install_dependencies
: &install_dependencies
    name
"Install Dependencies"
    
command: |
      
sudo apt-get update && sudo apt-get install subversion
      sudo 
-E docker-php-ext-install mysqli
      sudo sh 
-"printf '\ndeb http://ftp.us.debian.org/debian sid main\n' >> /etc/apt/sources.list"
      
sudo apt-get update && sudo apt-get install mysql-client-5.7

  php_job
: &php_job
    environment
:
      - 
WP_TESTS_DIR"/tmp/wordpress-tests-lib"
      
WP_CORE_DIR"/tmp/wordpress/"
    
steps:
      - 
checkout
      
run: *setup_environment
      
run: *install_dependencies
      
run:
          
name"Run Tests"
          
command: |
            
composer global require "phpunit/phpunit=5.7.*"
            
composer global require wp-coding-standards/wpcs
            phpcs 
--config-set installed_paths $HOME/.composer/vendor/wp-coding-standards/wpcs
            phpcs
            rm 
-rf $WP_TESTS_DIR $WP_CORE_DIR
            bash bin
/install-wp-tests.sh wordpress_test root '' 127.0.0.1 latest
            phpunit
            WP_MULTISITE
=1 phpunit

jobs
:
  
php56-build:
    <<: *
php_job
    docker
:
      - 
imagecircleci/php:5.6
      
image: *mysql_image
    steps
:
      - 
checkout
      
run: *setup_environment
      
run: *install_dependencies
      
run:
          
name"Run Tests"
          
command: |
            
composer global require "phpunit/phpunit=5.7.*"
            
composer global require wp-coding-standards/wpcs
            phpcs 
--config-set installed_paths $HOME/.composer/vendor/wp-coding-standards/wpcs
            phpcs
            SKIP_DB_CREATE
=false
            
{{#wp_versions_to_test}}
            
rm -rf $WP_TESTS_DIR $WP_CORE_DIR
            bash bin
/install-wp-tests.sh wordpress_test root '' 127.0.0.1 {{.}} $SKIP_DB_CREATE
            phpunit
            WP_MULTISITE
=1 phpunit
            SKIP_DB_CREATE
=true
            
{{/wp_versions_to_test}}

  
php70-build:
    <<: *
php_job
    docker
:
      - 
imagecircleci/php:7.0
      
image: *mysql_image

  php71
-build:
    <<: *
php_job
    docker
:
      - 
imagecircleci/php:7.1
      
image: *mysql_image

  php72
-build:
    <<: *
php_job
    docker
:
      - 
imagecircleci/php:7.2
      
image: *mysql_image

  php73
-build:
    <<: *
php_job
    docker
:
      - 
imagecircleci/php:7.3
      
image: *mysql_image

  php74
-build:
    <<: *
php_job
    docker
:
      - 
imagecircleci/php:7.4
      
image: *mysql_image
# A set of files you probably don't want in your WordPress.org distribution
.babelrc
.deployignore
.distignore
.editorconfig
.eslintignore
.eslintrc
.git
.gitignore
.github
.gitlab-ci.yml
.travis.yml
.DS_Store
Thumbs
.db
behat
.yml
bitbucket
-pipelines.yml
bin
.circleci/config.yml
composer
.json
composer
.lock
dependencies
.yml
Gruntfile
.js
package
.json
package
-lock.json
phpunit
.xml
phpunit
.xml.dist
multisite
.xml
multisite
.xml.dist
.phpcs.xml
phpcs
.xml
.phpcs.xml.dist
phpcs
.xml.dist
README
.md
webpack
.config.js
wp
-cli.local.yml
yarn
.lock
tests
vendor
node_modules
*.sql
*.tar.gz
*.zip
( function( wp ) {
    
/**
     * Registers a new block provided a unique name and an object defining its behavior.
     * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/#registering-a-block
     */
    
var registerBlockType wp.blocks.registerBlockType;
    
/**
     * Returns a new element of given type. Element is an abstraction layer atop React.
     * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/packages/packages-element/
     */
    
var el wp.element.createElement;
    
/**
     * Retrieves the translation of text.
     * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/packages/packages-i18n/
     */
    
var __ wp.i18n.__;

    
/**
     * Every block starts by registering a new block type definition.
     * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/#registering-a-block
     */
    
registerBlockType'{{namespace}}/{{slug}}', {
        
/**
         * This is the display title for your block, which can be translated with `i18n` functions.
         * The block inserter will show this name.
         */
        
title__'{{title_ucfirst_js}}''{{namespace}}' ),

        {{
#dashicon}}
        /**
         * An icon property should be specified to make it easier to identify a block.
         * These can be any of WordPress’ Dashicons, or a custom svg element.
         */
        
icon'{{dashicon}}',

        {{/
dashicon}}
        
/**
         * Blocks are grouped into categories to help users browse and discover them.
         * The categories provided by core are `common`, `embed`, `formatting`, `layout` and `widgets`.
         */
        
category'{{category}}',

        
/**
         * Optional block extended support features.
         */
        
supports: {
            
// Removes support for an HTML mode.
            
htmlfalse,
        },

        
/**
         * The edit function describes the structure of your block in the context of the editor.
         * This represents what the editor will render when the block is used.
         * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/block-edit-save/#edit
         *
         * @param {Object} [props] Properties passed from the editor.
         * @return {Element}       Element to render.
         */
        
edit: function( props ) {
            return 
el(
                
'p',
                { 
classNameprops.className },
                
__'Hello from the editor!''{{namespace}}' )
            );
        },

        
/**
         * The save function defines the way in which the different attributes should be combined
         * into the final markup, which is then serialized by Gutenberg into `post_content`.
         * @see https://wordpress.org/gutenberg/handbook/designers-developers/developers/block-api/block-edit-save/#save
         *
         * @return {Element}       Element to render.
         */
        
save: function() {
            return 
el(
                
'p',
                {},
                
__'Hello from the saved content!''{{namespace}}' )
            );
        }
    } );
} )(
    
window.wp
);
pipelines:
  default:
    - 
step:
        
imagephp:5.6
        name
"PHP 5.6"
        
script:
          
# Install Dependencies
          
docker-php-ext-install mysqli
          
apt-get update && apt-get install -y subversion --no-install-recommends

          
# Install PHPCS
          
curl -/usr/local/bin/phpcs -fSL https://squizlabs.github.io/PHP_CodeSniffer/phpcs.phar && chmod +x /usr/local/bin/phpcs
          
phpcs --version

          
# Install WordPress Coding Standards
          
WPCS_VERSION=0.14.1
          
curl -o wpcs.tar.gz -fSL "https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/archive/${WPCS_VERSION}.tar.gz"
          
mkdir -/var/wpcs && tar -xzf wpcs.tar.gz --directory /var/wpcs --strip-components 1 && rm wpcs.tar.gz
          
phpcs --config-set show_progress 1 && phpcs --config-set colors 1 && phpcs --config-set installed_paths /var/wpcs

          
## Run PHPCS
          
phpcs

          
# Install PHPUnit
          
PHPUNIT_VERSION=5.7.27
          
curl -/usr/local/bin/phpunit "https://phar.phpunit.de/phpunit-${PHPUNIT_VERSION}.phar" && chmod +/usr/local/bin/phpunit
          
phpunit --version

          
## Run PHPUnit
          
bash bin/install-wp-tests.sh wordpress_tests root root 127.0.0.1 latest true
          
phpunit
        services
:
          - 
database

    
step:
        
imagephp:7.0
        name
"PHP 7.0"
        
script:
          
# Install Dependencies
          
docker-php-ext-install mysqli
          
apt-get update && apt-get install -y subversion --no-install-recommends

          
# Install PHPCS
          
curl -/usr/local/bin/phpcs -fSL https://squizlabs.github.io/PHP_CodeSniffer/phpcs.phar && chmod +x /usr/local/bin/phpcs
          
phpcs --version

          
# Install WordPress Coding Standards
          
WPCS_VERSION=0.14.1
          
curl -o wpcs.tar.gz -fSL "https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/archive/${WPCS_VERSION}.tar.gz"
          
mkdir -/var/wpcs && tar -xzf wpcs.tar.gz --directory /var/wpcs --strip-components 1 && rm wpcs.tar.gz
          
phpcs --config-set show_progress 1 && phpcs --config-set colors 1 && phpcs --config-set installed_paths /var/wpcs

          
## Run PHPCS
          
phpcs

          
# Install PHPUnit
          
PHPUNIT_VERSION=6.5.6
          
curl -/usr/local/bin/phpunit "https://phar.phpunit.de/phpunit-${PHPUNIT_VERSION}.phar" && chmod +/usr/local/bin/phpunit
          
phpunit --version

          
## Run PHPUnit
          
bash bin/install-wp-tests.sh wordpress_tests root root 127.0.0.1 latest true
          
phpunit
        services
:
          - 
database

    
step:
        
imagephp:7.1
        name
"PHP 7.1"
        
script:
          
# Install Dependencies
          
docker-php-ext-install mysqli
          
apt-get update && apt-get install -y subversion --no-install-recommends

          
# Install PHPCS
          
curl -/usr/local/bin/phpcs -fSL https://squizlabs.github.io/PHP_CodeSniffer/phpcs.phar && chmod +x /usr/local/bin/phpcs
          
phpcs --version

          
# Install WordPress Coding Standards
          
WPCS_VERSION=0.14.1
          
curl -o wpcs.tar.gz -fSL "https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/archive/${WPCS_VERSION}.tar.gz"
          
mkdir -/var/wpcs && tar -xzf wpcs.tar.gz --directory /var/wpcs --strip-components 1 && rm wpcs.tar.gz
          
phpcs --config-set show_progress 1 && phpcs --config-set colors 1 && phpcs --config-set installed_paths /var/wpcs

          
## Run PHPCS
          
phpcs

          
# Install PHPUnit
          
PHPUNIT_VERSION=6.5.6
          
curl -/usr/local/bin/phpunit "https://phar.phpunit.de/phpunit-${PHPUNIT_VERSION}.phar" && chmod +/usr/local/bin/phpunit
          
phpunit --version

          
## Run PHPUnit
          
bash bin/install-wp-tests.sh wordpress_tests root root 127.0.0.1 latest true
          
phpunit
        services
:
          - 
database

    
step:
        
imagephp:7.2
        name
"PHP 7.2"
        
script:
          
# Install Dependencies
          
docker-php-ext-install mysqli
          
apt-get update && apt-get install -y subversion --no-install-recommends

          
# Install PHPCS
          
curl -/usr/local/bin/phpcs -fSL https://squizlabs.github.io/PHP_CodeSniffer/phpcs.phar && chmod +x /usr/local/bin/phpcs
          
phpcs --version

          
# Install WordPress Coding Standards
          
WPCS_VERSION=0.14.1
          
curl -o wpcs.tar.gz -fSL "https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/archive/${WPCS_VERSION}.tar.gz"
          
mkdir -/var/wpcs && tar -xzf wpcs.tar.gz --directory /var/wpcs --strip-components 1 && rm wpcs.tar.gz
          
phpcs --config-set show_progress 1 && phpcs --config-set colors 1 && phpcs --config-set installed_paths /var/wpcs

          
## Run PHPCS
          
phpcs

          
# Install PHPUnit
          
PHPUNIT_VERSION=6.5.6
          
curl -/usr/local/bin/phpunit "https://phar.phpunit.de/phpunit-${PHPUNIT_VERSION}.phar" && chmod +/usr/local/bin/phpunit
          
phpunit --version

          
## Run PHPUnit
          
bash bin/install-wp-tests.sh wordpress_tests root root 127.0.0.1 latest true
          
phpunit
        services
:
          - 
database

definitions
:
  
services:
    
database:
      
imagemysql:latest
      environment
:
        
MYSQL_DATABASE'wordpress_tests'
        
MYSQL_ROOT_PASSWORD'root'
<?php

/**
 * Registers the `{{machine_name}}` post type.
 */
function {{machine_name}}_init() {
{{
output}}
}

add_action'init''{{machine_name}}_init' );

/**
 * Sets the post updated messages for the `{{machine_name}}` post type.
 *
 * @param  array $messages Post updated messages.
 * @return array Messages for the `{{machine_name}}` post type.
 */
function {{machine_name}}_updated_messages$messages ) {
    global 
$post;

    
$permalink get_permalink$post );

    
$messages['{{slug}}'] = [
        
0  => ''// Unused. Messages start at index 1.
        /* translators: %s: post permalink */
        
1  => sprintf__'{{label_ucfirst}} updated. <a target="_blank" href="%s">View {{label}}</a>''{{textdomain}}' ), esc_url$permalink ) ),
        
2  => __'Custom field updated.''{{textdomain}}' ),
        
3  => __'Custom field deleted.''{{textdomain}}' ),
        
4  => __'{{label_ucfirst}} updated.''{{textdomain}}' ),
        
/* translators: %s: date and time of the revision */
        
5  => isset( $_GET['revision'] ) ? sprintf__'{{label_ucfirst}} restored to revision from %s''{{textdomain}}' ), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false// phpcs:ignore WordPress.Security.NonceVerification.Recommended
        /* translators: %s: post permalink */
        
6  => sprintf__'{{label_ucfirst}} published. <a href="%s">View {{label}}</a>''{{textdomain}}' ), esc_url$permalink ) ),
        
7  => __'{{label_ucfirst}} saved.''{{textdomain}}' ),
        
/* translators: %s: post permalink */
        
8  => sprintf__'{{label_ucfirst}} submitted. <a target="_blank" href="%s">Preview {{label}}</a>''{{textdomain}}' ), esc_urladd_query_arg'preview''true'$permalink ) ) ),
        
/* translators: 1: Publish box date format, see https://secure.php.net/date 2: Post permalink */
        
9  => sprintf__'{{label_ucfirst}} scheduled for: <strong>%1$s</strong>. <a target="_blank" href="%2$s">Preview {{label}}</a>''{{textdomain}}' ), date_i18n__'M j, Y @ G:i''{{textdomain}}' ), strtotime$post->post_date ) ), esc_url$permalink ) ),
        
/* translators: %s: post permalink */
        
10 => sprintf__'{{label_ucfirst}} draft updated. <a target="_blank" href="%s">Preview {{label}}</a>''{{textdomain}}' ), esc_urladd_query_arg'preview''true'$permalink ) ) ),
    ];

    return 
$messages;
}

add_filter'post_updated_messages''{{machine_name}}_updated_messages' );

/**
 * Sets the bulk post updated messages for the `{{machine_name}}` post type.
 *
 * @param  array $bulk_messages Arrays of messages, each keyed by the corresponding post type. Messages are
 *                              keyed with 'updated', 'locked', 'deleted', 'trashed', and 'untrashed'.
 * @param  int[] $bulk_counts   Array of item counts for each message, used to build internationalized strings.
 * @return array Bulk messages for the `{{machine_name}}` post type.
 */
function {{machine_name}}_bulk_updated_messages$bulk_messages$bulk_counts ) {
    global 
$post;

    
$bulk_messages['{{slug}}'] = [
        
/* translators: %s: Number of {{label_plural}}. */
        
'updated'   => _n'%s {{label}} updated.''%s {{label_plural}} updated.'$bulk_counts['updated'], '{{textdomain}}' ),
        
'locked'    => ( === $bulk_counts['locked'] ) ? __'1 {{label}} not updated, somebody is editing it.''{{textdomain}}' ) :
                        
/* translators: %s: Number of {{label_plural}}. */
                        
_n'%s {{label}} not updated, somebody is editing it.''%s {{label_plural}} not updated, somebody is editing them.'$bulk_counts['locked'], '{{textdomain}}' ),
        
/* translators: %s: Number of {{label_plural}}. */
        
'deleted'   => _n'%s {{label}} permanently deleted.''%s {{label_plural}} permanently deleted.'$bulk_counts['deleted'], '{{textdomain}}' ),
        
/* translators: %s: Number of {{label_plural}}. */
        
'trashed'   => _n'%s {{label}} moved to the Trash.''%s {{label_plural}} moved to the Trash.'$bulk_counts['trashed'], '{{textdomain}}' ),
        
/* translators: %s: Number of {{label_plural}}. */
        
'untrashed' => _n'%s {{label}} restored from the Trash.''%s {{label_plural}} restored from the Trash.'$bulk_counts['untrashed'], '{{textdomain}}' ),
    ];

    return 
$bulk_messages;
}

add_filter'bulk_post_updated_messages''{{machine_name}}_bulk_updated_messages'10);
<?
php
/**
 * PHPUnit bootstrap file.
 *
 * @package {{plugin_package}}
 */

$_tests_dir getenv'WP_TESTS_DIR' );

if ( ! 
$_tests_dir ) {
    
$_tests_dir rtrimsys_get_temp_dir(), '/\\' ) . '/wordpress-tests-lib';
}

// Forward custom PHPUnit Polyfills configuration to PHPUnit bootstrap file.
$_phpunit_polyfills_path getenv'WP_TESTS_PHPUNIT_POLYFILLS_PATH' );
if ( 
false !== $_phpunit_polyfills_path ) {
    
define'WP_TESTS_PHPUNIT_POLYFILLS_PATH'$_phpunit_polyfills_path );
}

if ( ! 
file_exists"{$_tests_dir}/includes/functions.php" ) ) {
    echo 
"Could not find {$_tests_dir}/includes/functions.php, have you run bin/install-wp-tests.sh ?" PHP_EOL// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
    
exit( );
}

// Give access to tests_add_filter() function.
require_once "{$_tests_dir}/includes/functions.php";

/**
 * Manually load the plugin being tested.
 */
function _manually_load_plugin() {
    require 
dirnamedirname__FILE__ ) ) . '/{{plugin_slug}}.php';
}

tests_add_filter'muplugins_loaded''_manually_load_plugin' );

// Start up the WP testing environment.
require "{$_tests_dir}/includes/bootstrap.php";
variables:
  
# Configure mysql service (https://hub.docker.com/_/mysql/)
  
MYSQL_DATABASEwordpress_tests
  MYSQL_ROOT_PASSWORD
mysql

before_script
:
  
# Install dependencies
  
  # update the docker
  
apt-get clean
  
apt-get -yqq update

  
# instll the required packages for the running CI tests
  
apt-get -yqqf install zip unzip subversion mysql-client libmysqlclient-dev --fix-missing

  
# PHP extensions
  
docker-php-ext-enable mbstring mcrypt mysqli pdo_mysql intl gd zip bz2

  
# Set up WordPress tests
  
bash bin/install-wp-tests.sh wordpress_tests root mysql mysql latest true

  
# Install PHPCS and WPCS
  
composer global require "squizlabs/php_codesniffer=*"
  
composer global require "wp-coding-standards/wpcs"
  
phpcs --config-set installed_paths $HOME/.composer/vendor/wp-coding-standards/wpcs

PHPunit
:PHP5.3:MySQL:
  
imagetetraweb/php:5.3
  services
:
    - 
mysql:5.6
  script
:
  - 
phpcs
  
phpunit

PHPunit
:PHP5.6:MySQL:
  
imagetetraweb/php:5.6
  services
:
    - 
mysql:5.6
  script
:
  - 
phpcs
  
phpunit
  
PHPunit
:PHP7.0:MySQL:
  
imagetetraweb/php:7.0
  services
:
    - 
mysql:5.6
  script
:
  - 
phpcs
  
phpunit

PHPunit
:PHP7.1:MySQL:
  
imagetetraweb/php:7.1
  services
:
    - 
mysql:5.6
  script
:
  - 
phpcs
  
phpunit

PHPunit
:PHP7.2:MySQL:
  
imagetetraweb/php:7.2
  services
:
    - 
mysql:5.6
  script
:
  - 
phpcs
  
phpunit
#!/usr/bin/env bash

if [ $# -lt 3 ]; then
    
echo "usage: $0 <db-name> <db-user> <db-pass> [db-host] [wp-version] [skip-database-creation]"
    
exit 1
fi

DB_NAME
=$1
DB_USER
=$2
DB_PASS
=$3
DB_HOST
=${4-localhost}
WP_VERSION=${5-latest}
SKIP_DB_CREATE=${6-false}

TMPDIR=${TMPDIR-/tmp}
TMPDIR=$(echo $TMPDIR sed -"s/\/$//")
WP_TESTS_DIR=${WP_TESTS_DIR-$TMPDIR/wordpress-tests-lib}
WP_CORE_DIR=${WP_CORE_DIR-$TMPDIR/wordpress}

download() {
    if [ `
which curl` ]; then
        curl 
-"$1" "$2";
    
elif [ `which wget` ]; then
        wget 
-nv -"$2" "$1"
    
fi
}

if [[ 
$WP_VERSION =~ ^[0-9]+\.[0-9]+\-(beta|RC)[0-9]+$ ]]; then
    WP_BRANCH
=${WP_VERSION%\-*}
    
WP_TESTS_TAG="branches/$WP_BRANCH"

elif [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then
    WP_TESTS_TAG
="branches/$WP_VERSION"
elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then
    
if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then
        
# version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x
        
WP_TESTS_TAG="tags/${WP_VERSION%??}"
    
else
        
WP_TESTS_TAG="tags/$WP_VERSION"
    
fi
elif 
[[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then
    WP_TESTS_TAG
="trunk"
else
    
# http serves a single offer, whereas https serves multiple. we only want one
    
download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json
    
grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json
    LATEST_VERSION
=$(grep -'"version":"[^"]*' /tmp/wp-latest.json sed 's/"version":"//')
    if [[ -
"$LATEST_VERSION]]; then
        
echo "Latest WordPress version could not be found"
        
exit 1
    fi
    WP_TESTS_TAG
="tags/$LATEST_VERSION"
fi
set 
-ex

install_wp
() {

    if [ -
d $WP_CORE_DIR ]; then
        
return;
    
fi

    mkdir 
-p $WP_CORE_DIR

    
if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then
        mkdir 
-p $TMPDIR/wordpress-trunk
        rm 
-rf $TMPDIR/wordpress-trunk/*
        svn export --quiet https://core.svn.wordpress.org/trunk $TMPDIR/wordpress-trunk/wordpress
        mv $TMPDIR/wordpress-trunk/wordpress/* $WP_CORE_DIR
    else
        if [ $WP_VERSION == 'latest' ]; then
            local ARCHIVE_NAME='latest'
        elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+ ]]; then
            # https serves multiple offers, whereas http serves single.
            download https://api.wordpress.org/core/version-check/1.7/ $TMPDIR/wp-latest.json
            if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then
                # version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x
                LATEST_VERSION=${WP_VERSION%??}
            else
                # otherwise, scan the releases and get the most up to date minor version of the major release
                local VERSION_ESCAPED=`echo $WP_VERSION | sed 's/\./\\\\./g'`
                LATEST_VERSION=$(grep -o '"version":"'$VERSION_ESCAPED'[^"]*' $TMPDIR/wp-latest.json | sed 's/"version":"//' | head -1)
            fi
            if [[ -z "$LATEST_VERSION" ]]; then
                local ARCHIVE_NAME="wordpress-$WP_VERSION"
            else
                local ARCHIVE_NAME="wordpress-$LATEST_VERSION"
            fi
        else
            local ARCHIVE_NAME="wordpress-$WP_VERSION"
        fi
        download https://wordpress.org/${ARCHIVE_NAME}.tar.gz  $TMPDIR/wordpress.tar.gz
        tar --strip-components=1 -zxmf $TMPDIR/wordpress.tar.gz -C $WP_CORE_DIR
    fi

    download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php
}

install_test_suite() {
    # portable in-place argument for both GNU sed and Mac OSX sed
    if [[ $(uname -s) == 'Darwin' ]]; then
        local ioption='-i.bak'
    else
        local ioption='-i'
    fi

    # set up testing suite if it doesn't yet exist
    if [ ! -d $WP_TESTS_DIR ]; then
        # set up testing suite
        mkdir -p $WP_TESTS_DIR
        rm -rf $WP_TESTS_DIR/{includes,data}
        svn export --quiet --ignore-externals https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes
        svn export --quiet --ignore-externals https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data
    fi

    if [ ! -f wp-tests-config.php ]; then
        download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php
        # remove all forward slashes in the end
        WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::")
        sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php
        sed $ioption "s:__DIR__ . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php
        sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php
        sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php
        sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php
        sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php
    fi

}

recreate_db() {
    shopt -s nocasematch
    if [[ $1 =~ ^(y|yes)$ ]]
    then
        mysqladmin drop $DB_NAME -f --user="$DB_USER" --password="$DB_PASS"$EXTRA
        create_db
        echo "Recreated the database ($DB_NAME)."
    else
        echo "Leaving the existing database ($DB_NAME) in place."
    fi
    shopt -u nocasematch
}

create_db() {
    mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA
}

install_db() {

    if [ ${SKIP_DB_CREATE} = "true" ]; then
        return 0
    fi

    # parse DB_HOST for port or socket references
    local PARTS=(${DB_HOST//\:/ })
    local DB_HOSTNAME=${PARTS[0]};
    local DB_SOCK_OR_PORT=${PARTS[1]};
    local EXTRA=""

    if ! [ -z $DB_HOSTNAME ] ; then
        if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then
            EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp"
        elif ! [ -z $DB_SOCK_OR_PORT ] ; then
            EXTRA=" --socket=$DB_SOCK_OR_PORT"
        elif ! [ -z $DB_HOSTNAME ] ; then
            EXTRA=" --host=$DB_HOSTNAME --protocol=tcp"
        fi
    fi

    # create database
    if [ $(mysql --user="$DB_USER" --password="$DB_PASS"$EXTRA --execute='show databases;' | grep ^$DB_NAME$) ]
    then
        echo "Reinstalling will delete the existing test database ($DB_NAME)"
        read -p 'Are you sure you want to proceed? [y/N]: ' DELETE_EXISTING_DB
        recreate_db $DELETE_EXISTING_DB
    else
        create_db
    fi
}

install_wp
install_test_suite
install_db
=== {{plugin_name}} ===
Contributors: (this should be a list of wordpress.org userid's)
Donate link: https://example.com/
Tags: comments, spam
Requires at least: 4.5
Tested up to: {{plugin_tested_up_to}}
Requires PHP: 5.6
Stable tag: 0.1.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Here is a short description of the plugin.  This should be no more than 150 characters.  No markup here.

== Description ==

This is the long description.  No limit, and you can use Markdown (as well as in the following sections).

For backwards compatibility, if this section is missing, the full length of the short description will be used, and
Markdown parsed.

A few notes about the sections above:

*   "Contributors" is a comma separated list of wp.org/wp-plugins.org usernames
*   "Tags" is a comma separated list of tags that apply to the plugin
*   "Requires at least" is the lowest version that the plugin will work on
*   "Tested up to" is the highest version that you've *successfully used to test the plugin*. Note that it might work on
higher versions... this is just the highest one you've verified.
*   Stable tag should indicate the Subversion "tag" of the latest stable version, or "trunk," if you use `/trunk/` for
stable.

    Note that the `readme.txt` of the stable tag is the one that is considered the defining one for the plugin, so
if the `/trunk/readme.txt` file says that the stable tag is `4.3`, then it is `/tags/4.3/readme.txt` that'll be used
for displaying information about the plugin.  In this situation, the only thing considered from the trunk `readme.txt`
is the stable tag pointer.  Thus, if you develop in trunk, you can update the trunk `readme.txt` to reflect changes in
your in-development version, without having that information incorrectly disclosed about the current stable version
that lacks those changes -- as long as the trunk's `readme.txt` points to the correct stable tag.

    If no stable tag is provided, it is assumed that trunk is stable, but you should specify "trunk" if that's where
you put the stable version, in order to eliminate any doubt.

== Installation ==

This section describes how to install the plugin and get it working.

e.g.

1. Upload `plugin-name.php` to the `/wp-content/plugins/` directory
1. Activate the plugin through the 'Plugins' menu in WordPress
1. Place `<?php do_action('plugin_name_hook'); ?>` in your templates

== Frequently Asked Questions ==

= A question that someone might have =

An answer to that question.

= What about foo bar? =

Answer to foo bar dilemma.

== Screenshots ==

1. This screen shot description corresponds to screenshot-1.(png|jpg|jpeg|gif). Note that the screenshot is taken from
the /assets directory or the directory that contains the stable readme.txt (tags or trunk). Screenshots in the /assets
directory take precedence. For example, `/assets/screenshot-1.png` would win over `/tags/4.3/screenshot-1.png`
(or jpg, jpeg, gif).
2. This is the second screen shot

== Changelog ==

= 1.0 =
* A change since the previous version.
* Another change.

= 0.5 =
* List versions from most recent at top to oldest at bottom.

== Upgrade Notice ==

= 1.0 =
Upgrade notices describe the reason a user should upgrade.  No more than 300 characters.

= 0.5 =
This version fixes a security related bug.  Upgrade immediately.

== Arbitrary section ==

You may provide arbitrary sections, in the same format as the ones above.  This may be of use for extremely complicated
plugins where more information needs to be conveyed that doesn't fit into the categories of "description" or
"installation."  Arbitrary sections will be shown below the built-in sections outlined above.

== A brief Markdown Example ==

Ordered list:

1. Some feature
1. Another feature
1. Something else about the plugin

Unordered list:

* something
* something else
* third thing

Here's a link to [WordPress](https://wordpress.org/ "Your favorite software") and one to [Markdown's Syntax Documentation][markdown syntax].
Titles are optional, naturally.

[markdown syntax]: https://daringfireball.net/projects/markdown/syntax
            "Markdown is what the parser uses to process much of the readme file"

Markdown uses email style notation for blockquotes and I've been told:
> Asterisks for *emphasis*. Double it up  for **strong**.

`<?php code(); // goes in backticks ?>`
/*
Theme Name:     {{theme_name}}
Theme URI:      {{theme_uri}}
Description:    {{description}}
Author:         {{author}}
Author URI:     {{author_uri}}
Template:       {{parent_theme}}
Version:        0.1.0
*/
<?php
/**
 * {{theme_name}} Theme functions and definitions.
 *
 * @link https://developer.wordpress.org/themes/basics/theme-functions/
 *
 * @package {{slug}}
 */

add_action'wp_enqueue_scripts''{{parent_theme_function_safe}}_parent_theme_enqueue_styles' );

/**
 * Enqueue scripts and styles.
 */
function {{parent_theme_function_safe}}_parent_theme_enqueue_styles() {
    
wp_enqueue_style'{{parent_theme}}-style'get_template_directory_uri() . '/style.css' );
    
wp_enqueue_style'{{slug}}-style',
        
get_stylesheet_directory_uri() . '/style.css',
        [ 
'{{parent_theme}}-style' ]
    );
}
    
register_post_type(
        
'{{slug}}',
        [
            
'labels'                => [
                
'name'                  => __'{{label_plural_ucfirst}}''{{textdomain}}' ),
                
'singular_name'         => __'{{label_ucfirst}}''{{textdomain}}' ),
                
'all_items'             => __'All {{label_plural_ucfirst}}''{{textdomain}}' ),
                
'archives'              => __'{{label_ucfirst}} Archives''{{textdomain}}' ),
                
'attributes'            => __'{{label_ucfirst}} Attributes''{{textdomain}}' ),
                
'insert_into_item'      => __'Insert into {{label}}''{{textdomain}}' ),
                
'uploaded_to_this_item' => __'Uploaded to this {{label}}''{{textdomain}}' ),
                
'featured_image'        => _x'Featured Image''{{slug}}''{{textdomain}}' ),
                
'set_featured_image'    => _x'Set featured image''{{slug}}''{{textdomain}}' ),
                
'remove_featured_image' => _x'Remove featured image''{{slug}}''{{textdomain}}' ),
                
'use_featured_image'    => _x'Use as featured image''{{slug}}''{{textdomain}}' ),
                
'filter_items_list'     => __'Filter {{label_plural}} list''{{textdomain}}' ),
                
'items_list_navigation' => __'{{label_plural_ucfirst}} list navigation''{{textdomain}}' ),
                
'items_list'            => __'{{label_plural_ucfirst}} list''{{textdomain}}' ),
                
'new_item'              => __'New {{label_ucfirst}}''{{textdomain}}' ),
                
'add_new'               => __'Add New''{{textdomain}}' ),
                
'add_new_item'          => __'Add New {{label_ucfirst}}''{{textdomain}}' ),
                
'edit_item'             => __'Edit {{label_ucfirst}}''{{textdomain}}' ),
                
'view_item'             => __'View {{label_ucfirst}}''{{textdomain}}' ),
                
'view_items'            => __'View {{label_plural_ucfirst}}''{{textdomain}}' ),
                
'search_items'          => __'Search {{label_plural}}''{{textdomain}}' ),
                
'not_found'             => __'No {{label_plural}} found''{{textdomain}}' ),
                
'not_found_in_trash'    => __'No {{label_plural}} found in trash''{{textdomain}}' ),
                
'parent_item_colon'     => __'Parent {{label_ucfirst}}:''{{textdomain}}' ),
                
'menu_name'             => __'{{label_plural_ucfirst}}''{{textdomain}}' ),
            ],
            
'public'                => true,
            
'hierarchical'          => false,
            
'show_ui'               => true,
            
'show_in_nav_menus'     => true,
            
'supports'              => [ 'title''editor' ],
            
'has_archive'           => true,
            
'rewrite'               => true,
            
'query_var'             => true,
            
'menu_position'         => null,
            
'menu_icon'             => 'dashicons-{{dashicon}}',
            
'show_in_rest'          => true,
            
'rest_base'             => '{{slug}}',
            
'rest_controller_class' => 'WP_REST_Posts_Controller',
        ]
    );
sudofalse
dist
trusty

language
php

notifications
:
  
email:
    
on_successnever
    on_failure
change

branches
:
  
only:
    - 
master

cache
:
  
directories:
    - 
$HOME/.composer/cache

matrix
:
  include:
    - 
php7.4
      env
WP_VERSION=latest
    
php7.3
      env
WP_VERSION=latest
    
php7.2
      env
WP_VERSION=latest
    
php7.1
      env
WP_VERSION=latest
    
php7.0
      env
WP_VERSION=latest
    
{{#wp_versions_to_test}}
    
php5.6
      env
WP_VERSION={{.}}
    {{/
wp_versions_to_test}}
    - 
php5.6
      env
WP_TRAVISCI=phpcs

before_script
:
  - 
export PATH="$HOME/.composer/vendor/bin:$PATH"
  
- |
    if [ -
~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini ]; then
      phpenv config
-rm xdebug.ini
    
else
      echo 
"xdebug.ini does not exist"
    
fi
  
- |
    if [[ ! -
"$WP_VERSION]] ; then
      bash bin
/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION
      composer 
global require "phpunit/phpunit=4.8.*|5.7.*"
    
fi
  
- |
    if [[ 
"$WP_TRAVISCI== "phpcs" ]] ; then
      composer 
global require wp-coding-standards/wpcs
      composer 
global require phpcompatibility/php-compatibility
      composer 
global require phpcompatibility/phpcompatibility-paragonie
      composer 
global require phpcompatibility/phpcompatibility-wp
      phpcs 
--config-set installed_paths $HOME/.composer/vendor/wp-coding-standards/wpcs,$HOME/.composer/vendor/phpcompatibility/php-compatibility,$HOME/.composer/vendor/phpcompatibility/phpcompatibility-paragonie,$HOME/.composer/vendor/phpcompatibility/phpcompatibility-wp
    fi

script
:
  - |
    if [[ ! -
"$WP_VERSION]] ; then
      phpunit
      WP_MULTISITE
=1 phpunit
    fi
  
- |
    if [[ 
"$WP_TRAVISCI== "phpcs" ]] ; then
      phpcs
    fi
<?php

// autoload.php @generated by Composer

require_once __DIR__ '/composer/autoload_real.php';

return 
ComposerAutoloaderInit04114110af87f5624b3580eba43b8770::getLoader();
Copyright (cNils AdermannJordi Boggiano

Permission is hereby granted
free of chargeto 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, copymodifymergepublishdistributesublicense, and/or sell
copies of the Software
, and to permit persons to whom the Software is furnished
to 
do sosubject 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 KINDEXPRESS OR
IMPLIEDINCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENTIN NO EVENT SHALL THE
AUTHORS 
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIMDAMAGES OR OTHER
LIABILITY
WHETHER IN AN ACTION OF CONTRACTTORT OR OTHERWISEARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE
.
{
    
"$schema""https://json-schema.org/draft-04/schema#",
    
"title""Package",
    
"type""object",
    
"properties": {
        
"name": {
            
"type""string",
            
"description""Package name, including 'vendor-name/' prefix.",
            
"pattern""^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9](([_.]?|-{0,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""^v?\\d+(\\.\\d+){0,3}|^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"
                
}
            }
        },
        
"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"
                    
}
                }
            }
        },
        
"_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."
                
}
            }
        },
        
"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.",
                    
"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"]
                    }
                },
                
"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 [\"git\", \"https\", \"http\"].",
                    
"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\":\"<token>\"}.",
                    
"additionalProperties": {
                        
"type""string"
                    
}
                },
                
"gitlab-token": {
                    
"type""object",
                    
"description""An object of domain name => gitlab private tokens, typically {\"gitlab.com\":\"<token>\"}.",
                    
"additionalProperties": {
                        
"type""string"
                    
}
                },
                
"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"
                    
}
                },
                
"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."
                
}
            }
        },
        
"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"
            
}
        }
    },
    
"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": {
                        
"symlink": { "type": ["boolean""null"] }
                    },
                    
"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": {
                    
"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"
                        
}
                    }
                }
            },
            
"additionalProperties"true
        
}
    }
}
##
## Bundle of CA Root Certificates
##
## Certificate data from Mozilla as of: Tue Jan 19 04:12:04 2021 GMT
##
## 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://hg.mozilla.org/releases/mozilla-release/raw-file/default/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.28.
## SHA256: 3bdc63d1de27058fec943a999a2a8a01fcc6806a611b19221a7727d3d9bbbdfd
##


GlobalSign Root CA
==================
-----
BEGIN CERTIFICATE-----
MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx
GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds
b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV
BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD
VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa
DuaZjc6j40
+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc
THAHoT0KMM0VjU
/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb
Kk3Fp1S4bInMm
/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP
c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX
gzT
/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH
/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF
AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj
Y1Ub8rrvrTnhQ7k4o
+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG
j
/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH
hm4qxFYxldBniYUr
+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC
X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi
/EjJKSZp4A==
-----
END CERTIFICATE-----

GlobalSign Root CA R2
=======================
-----
BEGIN CERTIFICATE-----
MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4GA1UECxMXR2xv
YmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh
bFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT
aWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln
bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6
+Lm8omUVCxKs+IVSbC9N/hHD6
ErPLv4dfxn
+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8eoLrvozp
s6yWJQeXSpkqBy
+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklqtTleiDTsvHgMCJiEbKjN
S7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzdC9XZzPnqJworc5HGnRusyMvo4KD0L5CL
TfuwNhv2GXqF4G3yYROIXJ
/gkwpRl4pazq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6C
ygPCm48CAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH
/BAUwAwEB/zAdBgNVHQ4E
FgQUm
+IHV2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5nbG9i
YWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG3lm0mi3f3BmGLjAN
BgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4GsJ0
/WwbgcQ3izDJr86iw8bmEbTUsp
9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO291xNBrBVNpGP
+DTKqttVCL1OmLNIG+6KYnX3ZHu
01yiPqFbQfXf5WRDLenVOavSot
+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG7
9G
+dwfCMNYxdAfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7
TBj0
/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==
-----
END CERTIFICATE-----

Entrust.net Premium 2048 Secure Server CA
=========================================
-----
BEGIN CERTIFICATE-----
MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u
ZXQxQDA
+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp
bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV
BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx
NzUwNTFaFw0yOTA3MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3
d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl
MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u
ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL
Gp18EzoOH1u3Hs
/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr
hRSGlVuXMlBvPci6Zgzj
/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW
nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9
+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi
VBcAkCaTvA5JaJG
/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo0IwQDAOBgNVHQ8BAf8E
BAMCAQYwDwYDVR0TAQH
/BAUwAwEB/zAdBgNVHQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJ
KoZIhvcNAQEFBQADggEBADubj1abMOdTmXx6eadNl9cZlZD7Bh
/KM3xGY4+WZiT6QBshJ8rmcnPy
T
/4xmf3IDExoU8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf
zX1XEC
+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5bu/8j72gZyxKT
J1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7
+bYQLCIt+jerXmCHG8+c8eS9e
nNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv
/ErfF6adulZkMV8gzURZVE=
-----
END CERTIFICATE-----

Baltimore CyberTrust Root
=========================
-----
BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJRTESMBAGA1UE
ChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYDVQQDExlCYWx0aW1vcmUgQ3li
ZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoXDTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMC
SUUxEjAQBgNVBAoTCUJhbHRpbW9yZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFs
dGltb3JlIEN5YmVyVHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKME
uyKrmD1X6CZymrV51Cni4eiVgLGw41uOKymaZN
+hXe2wCQVt2yguzmKiYv60iNoS6zjrIZ3AQSsB
UnuId9Mcj8e6uYi1agnnc
+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeKmpYcqWe4PwzV9/lSEy/C
G9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSuXmD
+tqYF/LTdB1kC1FkYmGP1pWPgkAx9
XbIGevOF6uvUA65ehD5f
/xXtabz5OTZydc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjpr
l3RjM71oGDHweI12v
/yejl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoI
VDaGezq1BE3wMBIGA1UdEwEB
/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEB
BQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF
+vaKSm2eT929hkTI7gQCvlYpNRh
cL0EYWoSihfVCr3FvDB81ukMJY2GQE
/szKN+OMY3EU/t3WgxjkzSswF07r51XgdIGn9w/xZchMB5
hbgF
/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsa
Y71k5h
+3zvDyny67G7fyUIhzksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9H
RCwBXbsdtTLSR9I4LtD
+gdwyah617jzV/OeBHRnDJELqYzmp
-----END CERTIFICATE-----

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-----

Comodo AAA Services root
========================
-----
BEGIN CERTIFICATE-----
MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS
R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg
TGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAw
MFoXDTI4MTIzMTIzNTk1OVowezELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hl
c3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNV
BAMMGEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr
+GB+O5AL686tdUIoWMQuaBtDFcCLNSS1UY8y2bmhG
C1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe3M
/vg4aijJRPn2jymJBGhCfHdr/jzDUs
i14HZGWCwEiwqJH5YZ92IFCokcdmtet4YgNW8IoaE
+oxox6gmf049vYnMlhvB/VruPsUK6+3qszW
Y19zjNoFmag4qMsXeDZRrOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjH
Ypy
+g8cmez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQUoBEK
Iz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH
/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wewYDVR0f
BHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20vQUFBQ2VydGlmaWNhdGVTZXJ2aWNl
cy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29tb2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2Vz
LmNybDANBgkqhkiG9w0BAQUFAAOCAQEACFb8AvCb6P
+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm
7l3sAg9g1o1QGE8mTgHj5rCl7r
+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz
Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC
/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2G9w84FoVxp7Z
8VlIMCFlA2zs6SFz7JsDoeA3raAVGI
/6ugLOpyypEBMs1OUIJqsil2D4kF501KKaU73yqWjgom7C
12yxow
+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==
-----
END CERTIFICATE-----

QuoVadis Root CA
================
-----
BEGIN CERTIFICATE-----
MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJCTTEZMBcGA1UE
ChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0
eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAz
MTkxODMzMzNaFw0yMTAzMTcxODMzMzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRp
cyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQD
EyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAv2G1lVO6V
/z68mcLOhrfEYBklbTRvM16z/Ypli4kVEAkOPcahdxYTMuk
J0KX0J
+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2DrOpm2RgbaIr1VxqYuvXtdj182d6UajtL
F8HVj71lODqV0D1VNk7feVcxKh7YWWVJWCCYfqtffp
/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeL
YzcS19Dsw3sgQUSj7cugF
+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWen
AScOospUxbF6lR1xHkopigPcakXBpBlebzbNw6Kwt
/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCCAk4w
PQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVvdmFkaXNvZmZzaG9y
ZS5jb20wDwYDVR0TAQH
/BAUwAwEB/zCCARoGA1UdIASCAREwggENMIIBCQYJKwYBBAG+WAABMIH7
MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNlIG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmlj
YXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJs
ZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh
Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYIKwYBBQUHAgEW
Fmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3TKbkGGew5Oanwl4Rqy
+/fMIGu
BgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rqy
+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkw
FwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0
aG9yaXR5MS4wLAYDVQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6
tlCLMA4GA1UdDwEB
/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSkfnIYj9lo
fFIk3WdvOXrEql494liwTXCYhGHoG
+NpGA7O+0dQoE7/8CQfvbLO9Sf87C9TqnN7Az10buYWnuul
LsS
/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1RcHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2x
gI4JVrmcGmD
+XcHXetwReNDWXcG31a0ymQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi
5upZIof4l
/UO/erMkqQWxFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi
5nrQNiOKSnQ2
+Q==
-----
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-----

Security Communication Root CA
==============================
-----
BEGIN CERTIFICATE-----
MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP
U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw
HhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMP
U0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEw
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzs
/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw
8yl89f
/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJDKaVv0uM
DPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV
+7AwFb9Ms+k2Y7CI9eNqPPYJayX
5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6
+HGhWZq/NQV3Is00qVUarH9oe4kA92819uZKAnDfd
DJZkndwi92SL32HeFZRSFaB9UslLqCHJxrHty8OVYNEP8Ktw
+N/LTX7s1vqr2b1/VPKl6Xn62dZ2
JChzAgMBAAGjPzA9MB0GA1UdDgQWBBSgc0mZaNyFW2XjmygvV5
+9M7wHSDALBgNVHQ8EBAMCAQYw
DwYDVR0TAQH
/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vGkl3g
0dNq
/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfrUj94nK9NrvjVT8+a
mCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo
+zenaSMQVy+n5Bw+SUEmK3TGXX8npN6o7WWWXlDLJ
s58
+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJUJRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ
6rBK
+1YWc26sTfcioU+tHXotRSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAi
FL39vmwLAw
==
-----
END CERTIFICATE-----

Sonera Class 2 Root CA
======================
-----
BEGIN CERTIFICATE-----
MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEPMA0GA1UEChMG
U29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAxMDQwNjA3Mjk0MFoXDTIxMDQw
NjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNVBAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJh
IENsYXNzMiBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE
+hY3
/Ei9vX+ALTU74W+oZ6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybT
dXnt5oTjV
+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s3TmVToMG
f
+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2EjvOr7nQKV0ba5cTppCD8P
tOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu8nYybieDwnPz3BjotJPqdURrBGAgcVeH
nfO
+oJAjPYok4doh28MCAwEAAaMzMDEwDwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITT
XjwwCwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt
0jSv9zilzqsWuasvfDXLrNAPtEwr
/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/3DEI
cbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvDFNr450kkkdAdavph
Oe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh
+Re/n570K6Tk6ezAyNlNzZRZxe7EJQY670XcSx
EtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2ZrUYrAqmVCY0M9IbwdR
/GjqOC6oybtv8TyWf2TLH
llpwrN9M
-----END CERTIFICATE-----

XRamp Global CA Root
====================
-----
BEGIN CERTIFICATE-----
MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UE
BhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2Vj
dXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB
dXRob3JpdHkwHhcNMDQxMTAxMTcxNDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMx
HjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkg
U2VydmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
dHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS638eMpSe2OAtp87ZOqCwu
IR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCPKZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMx
foArtYzAQDsRhtDLooY2YKTVMIJt2W7QDxIEM5dfT2Fa8OT5kavnHTu86M
/0ay00fOJIYRyO82FE
zG
+gSqmUsE3a56k0enI4qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqs
AxcZZPRaJSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh
/MHJfNViPvry
xS3T
/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud
EwEB
/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASsjVy16bYbMDYGA1UdHwQvMC0wK6Ap
oCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMC
AQEwDQYJKoZIhvcNAQEFBQADggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc
/Kh4ZzXxHfARvbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt
qZ4Bfj8pzgCT3
/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLaIR9NmXmd4c8n
nxCbHIgNsIpkQTG4DmyQJKSbXHGPurt
+HBvbaoAPIbzp26a3QPSyi6mx5O+aGtA9aZnuqCij4Tyz
8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQO
+7ETPTsJ3xCwnR8gooJybQDJbw=
-----
END CERTIFICATE-----

Go Daddy Class 2 CA
===================
-----
BEGIN CERTIFICATE-----
MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMY
VGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRp
ZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkG
A1UEBhMCVVMxITAfBgNVBAoTGFRoZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28g
RGFkZHkgQ2xhc3MgMiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQAD
ggENADCCAQgCggEBAN6d1
+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCAPVYYYwhv
2vLM0D9
/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6wwdhFJ2+qN1j3hybX2C32
qRe3H3I2TqYXP2WYktsqbl2i
/ojgC95/5Y0V4evLOtXiEqITLdiOr18SPaAIBQi2XKVlOARFmR6j
YGB0xUGlcmIbYsUfb18aQr4CUWWoriMYavx4A6lNf4DD
+qta/KFApMoZFv6yyO9ecw3ud72a9nmY
vLEHZ6IVDd2gWMZEewo
+YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0O
BBYEFNLEsNKR1EwRcbNhyz2h
/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h/t2o
atTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMu
MTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwG
A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wim
PQoZ
+YeAEW5p5JYXMP80kWNyOO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKt
I3lpjbi2Tc7PTMozI
+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ
HmyW74cNxA9hi63ugyuV
+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mERdEr/VxqHD3VI
Ls9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5CufReYNnyicsbkqWletNw
+vHX/b
vZ8
=
-----
END CERTIFICATE-----

Starfield Class 2 CA
====================
-----
BEGIN CERTIFICATE-----
MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzElMCMGA1UEChMc
U3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZpZWxkIENsYXNzIDIg
Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQwNjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBo
MQswCQYDVQQGEwJVUzElMCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAG
A1UECxMpU3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqG
SIb3DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj
+6XGmBIWtDBFk385N78gDGIc/oav7PKaf8MOh2tTY
bitTkPskpD6E8J7oX
+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN+lq2cwQlZut3f+dZxkqZ
JRRU6ybH838Z1TBwj6
+wRir/resp7defqgSHo9T5iaU0X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVm
epsZGD3
/cVE8MC5fvj13c7JdBmzDI1aaK4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSN
F4Azbl5KXZnJHoe0nRrA1W4TNSNe35tfPe
/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HF
MIHCMB0GA1UdDgQWBBS
/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fRzt0f
hvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0YXJmaWVsZCBUZWNo
bm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBDbGFzcyAyIENlcnRpZmljYXRpb24g
QXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGs
afPzWdqbAYcaT1epoXkJKtv3L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP
+yz3SFmH8lU+nLM
PUxA2IGvd56Deruix
/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl
xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynpVSJYACPq4xJD
KVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEYWQPJIrSPnNVeKtelttQKbfi3
QBFGmh95DmK
/D5fs4C8fF5Q=
-----
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-----

DST Root CA X3
==============
-----
BEGIN CERTIFICATE-----
MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/MSQwIgYDVQQK
ExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMTDkRTVCBSb290IENBIFgzMB4X
DTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVowPzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1
cmUgVHJ1c3QgQ28uMRcwFQYDVQQDEw5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAN
+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmT
rE4Orz5Iy2Xu
/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEqOLl5CjH9
UL2AZd
+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9bxiqKqy69cK3FCxolkHRy
xXtqqzTWMIn
/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40d
utolucbY38EVAjqr2m7xPi71XAicPNaDaeQQmxkqtilX4
+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0T
AQH
/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQ
MA0GCSqGSIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or
+Dxz9LwwmglSBd49lZRNI+DT69ikug
dB
/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXrAvHRAosZy5Q6XkjE
GB5YGV8eAlrwDPGxrancWYaLbumR9YbK
+rlmM6pZW87ipxZzR8srzJmwN0jP41ZL9c8PDHIyh8bw
RLtTcm1D9SZImlJnt1ir
/md2cXjbDaJWFBM5JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubS
fZGL
+T0yjWW06XyxV3bqxbYoOb8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
-----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-----

SwissSign Silver CA G2
========================
-----
BEGIN CERTIFICATE-----
MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCQ0gxFTAT
BgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMB4X
DTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0NlowRzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3
aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG
9w0BAQEFAAOCAg8AMIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x
/e8N+dOcbpLj6VzHVxumK4DV644
N0MvFz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5
/ASLHvGiTSf5YXu6t+WiE7brYT7QbNHm
+/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieFnbAVlDLaYQ1HTWBCrpJH
6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH6ATK72oxh9TAtvmUcXtnZLi2kUpCe2Uu
MGoM9ZDulebyzYLs2aFK7PayS
+VFheZteJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5h
qAaEuSh6XzjZG6k4sIN
/c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5
FZGkECwJMoBgs5PAKrYYC51
+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRHHTBs
ROopN4WSaGa8gzj
+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTfjNFusB3hB48IHpmc
celM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb65i
/4z3GcRm25xBWNOHkDRUjvxF3X
CO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOBrDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH
/
BAUwAwEB/zAdBgNVHQ4EFgQUF6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRB
tjpbO8tFnb0cwpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0
cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBAHPGgeAn0i0P
4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShpWJHckRE1qTodvBqlYJ7YH39F
kWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9
/L
3XWgwF15kIwb4FDm3jH
+mHtwX6WQ2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx
/uNncqCxv1yL5PqZIseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFa
DGi8aRl5xB9
+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2Xem1ZqSqP
e97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQRdAtq
/gsD/KNVV4n+Ssuu
WxcFyPKNIzFTONItaj
+CuY0IavdeQXRuwxF+B6wpYJE/OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJ
DIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY
+hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ub
DgEj8Z
+7fNzcbBGXJbLytGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u
-----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-----

Network Solutions Certificate Authority
=======================================
-----
BEGIN CERTIFICATE-----
MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQG
EwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydOZXR3b3Jr
IFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMx
MjM1OTU5WjBiMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu
MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e
+foS0zwzc7MEL7xx
jOWftiJgPl9dzgn
/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPPOCwGJgl6cvf6UDL4wpPT
aaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rlmGNpSAW
+Lv8ztumXWWn4Zxmuk2GWRBXT
crA
/vGp97Eh/jcOrqnErU2lBUzS1sLnFBgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc
/Qzpf14Dl847ABSHJ3A4qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMB
AAGjgZcwgZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB
/wQEAwIBBjAP
BgNVHRMBAf8EBTADAQH
/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwubmV0c29sc3NsLmNv
bS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3JpdHkuY3JsMA0GCSqGSIb3DQEBBQUA
A4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc86fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q
4LqILPxFzBiwmZVRDuwduIj
/h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/
GGUsyfJj4akH/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv
wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHNpGxlaKFJdlxD
ydi8NmdspZS11My5vWo1ViHe2MPr
+8ukYEywVaCge1ey
-----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-----

Cybertrust Global Root
======================
-----
BEGIN CERTIFICATE-----
MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYGA1UEChMPQ3li
ZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBSb290MB4XDTA2MTIxNTA4
MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQD
ExZDeWJlcnRydXN0IEdsb2JhbCBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+Mi8vRRQZhP/8NN57CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW
0ozSJ8bi
/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2yHLtgwEZL
AfVJrn5GitB0jaEMAs7u
/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iPt3sMpTjr3kfb1V05/Iin
89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNzFtApD0mpSPCzqrdsxacwOUBdrsTiXSZT
8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAYXSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB
/wQEAwIBBjAP
BgNVHRMBAf8EBTADAQH
/MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2
MDSgMqAwhi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3JsMB8G
A1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUAA4IBAQBW7wojoFRO
lZfJ
+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMjWqd8BfP9IjsO0QbE2zZMcwSO5bAi
5MXzLqXZI
+O4Tkogp24CJJ8iYGd7ix1yCcUxXOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2
hO0j9n0Hq0V
+09+zv+mKts2oomcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+T
X3EJIrduPuocA06dGiBh
+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW
WL1WMRJOEcgh4LMRkWXbtKaIOM5V
-----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-----

GeoTrust Primary Certification Authority G2
=============================================
-----
BEGIN CERTIFICATE-----
MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDELMAkGA1UEBhMC
VVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA3IEdlb1RydXN0IElu
Yy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBD
ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1
OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg
MjAwNyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNVBAMTLUdl
b1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjB2MBAGByqGSM49AgEG
BSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcLSo17VDs6bl8VAsBQps8lL33KSLjHUGMc
KiEIfJo22Av
+0SbFWDEwKCXzXV2juLaltJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYD
VR0TAQH
/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+
EVXVMAoGCCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGTqQ7m
ndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF
+hnMJbn0bWtsuRBmOiBuczrD6ogRLQy7rQkgu2
npaqBA
+K
-----END CERTIFICATE-----

VeriSign Universal Root Certification Authority
===============================================
-----
BEGIN CERTIFICATE-----
MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCBvTELMAkGA1UE
BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO
ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk
IHVzZSBvbmx5MTgwNgYDVQQDEy9WZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9u
IEF1dGhvcml0eTAeFw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJV
UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv
cmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl
IG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNhbCBSb290IENlcnRpZmljYXRpb24gQXV0
aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb
/1hajCMj
1mCOkdeQmIN65lgZOIzF9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO
+k1KxCHfKWGP
MiJhgsWHH26MfF8WIFFE0XBPV
+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+HLL72
9fdC4uW
/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN/BMReYTtXlT2NJ8I
AfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPTrJ9VAMf2CGqUuV
/c4DPxhGD5WycR
tPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1UdEwEB
/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0G
CCsGAQUFBwEMBGEwX6FdoFswWTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI
/l0xqGrI2O
a8PPgGrUSBgsexkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud
DgQWBBS2d
/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4sAPmLGd75JR3
Y8xuTPl9Dg3cyLk1uXBPY
/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+seQxIcaBlVZaDrHC1LGmWazx
Y8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y
+Oz4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTx
P
/jgdFcrGJ2BtMQo2pSXpXDrrB2+BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+P
wGZsY6rp2aQW9IHRlRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v
/D/w1WIg0vvBZIGcfK4
mJO37M2CYfE45k
+XmCpajQ==
-----
END CERTIFICATE-----

NetLock Arany (Class GoldFő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-----

Hongkong Post Root CA 1
=======================
-----
BEGIN CERTIFICATE-----
MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoT
DUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMB4XDTAzMDUx
NTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkGA1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25n
IFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEArP84tulmAknjorThkPlAj3n54r15
/gK97iSSHSL22oVyaf7XPwnU3ZG1
ApzQjVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEnPzlTCeqr
auh0ssJlXI6
/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjhZY4bXSNmO7ilMlHIhqqh
qZ5
/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9nnV0ttgCXjqQesBCNnLsak3c78QA3xMY
V18meMjWCnl3v
/evt3a5pQuEF10Q6m/hq5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNV
HRMBAf8ECDAGAQH
/AgEDMA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7i
h9legYsCmEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI37pio
l7Yutmcn1KZJ
/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clBoiMBdDhViw+5Lmei
IAQ32pwL0xch4I
+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJsEhTkYY2sEJCehFC78JZvRZ+K88ps
T
/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpOfMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilT
c4afU9hDDl3WY4JxHYB0yvbiAmvZWg
==
-----
END CERTIFICATE-----

SecureSign RootCA11
===================
-----
BEGIN CERTIFICATE-----
MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDErMCkGA1UEChMi
SmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoGA1UEAxMTU2VjdXJlU2lnbiBS
b290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSsw
KQYDVQQKEyJKYXBhbiBDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1
cmVTaWduIFJvb3RDQTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
/XeqpRyQBTvL
TJszi1oURaTnkBbR31fSIRCkF
/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8h9uuywGO
wvNmxoVF9ALGOrVisq
/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOVMdrAG/LuYpmGYz+/3ZMq
g6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9UK31BQ1FT0Zx
/Sg+U/sE2C3XZR1KG/rP
O7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni8McDWc
/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitA
bpSACW22s293bzUIUPsCh8U
+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZX
t94wDgYDVR0PAQH
/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAKCh
OBZmLqdWHyGcBvod7bkixTgm2E5P7KN
/ed5GIaGHd48HCJqypMWvDzKYC3xmKbabfSVSSUOrTC4r
bnpwrxYO4wJs
+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQX5Ucv+2rIrVls4W6ng+4reV6G4pQ
Oh29Dbx7VFALuUKvVaAYga1lme
++5Jy/xIWrQbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01
y8hSyn
+B/tlr0/cR7SXf+Of5pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061
lgeLKBObjBmNQSdJQO7e5iNEOdyhIta6A
/I=
-----
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-----

Autoridad de Certificacion Firmaprofesional CIF A62634068
=========================================================
-----
BEGIN CERTIFICATE-----
MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UEBhMCRVMxQjBA
BgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2
MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEyMzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIw
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+JIwuwIDAQABo4HvMIHsMBIGA1Ud
EwEB
/wQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNH
DhpkLzCBpgYDVR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp
cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBvACAAZABlACAA
bABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBlAGwAbwBuAGEAIAAwADgAMAAx
ADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF661LJLWhAqvdpYhKsg9VSytXjDvlMd3
+xDLx
51tkljYyGOylMnfX40S2wBEqgLk9am58m9Ot
/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qk
R71kMrv2JYSiJ0L1ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaP
T481PyWzOdxjKpBrIF
/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS3a/DTg4f
Jl4N3LON7NWBcN7STyQF82xO9UxJZo3R
/9ILJUFI/lGExkKvgATP0H5kSeTy36LssUzAKh3ntLFl
osS88Zj0qnAHY7S42jtM
+kAiMFsRpvAFDsYCA0irhpuF3dvd6qJ2gHN99ZwExEWN57kci57q13XR
crHedUTnQn3iV2t93Jm8PYMo6oCTjcVMZcFwgbg4
/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoR
saS8I8nkvof
/uZS2+F0gStRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTD
KCOM
/iczQ0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQBjLMi
6Et8Vcad
+qMUu2WFbm5PEn4KPJ2V
-----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-----

Chambers of Commerce Root 2008
================================
-----
BEGIN CERTIFICATE-----
MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYDVQQGEwJFVTFD
MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv
bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu
QS4xKTAnBgNVBAMTIENoYW1iZXJzIG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEy
Mjk1MFoXDTM4MDczMTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNl
ZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQF
EwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJl
cnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
AQCvAMtwNyuAWko6bHiUfaN
/Gh/2NdW928sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKA
XuFixrYp4YFs8r
/lfTJqVKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorj
h40G072QDuKZoRuGDtqaCrsLYVAGUvGef3bsyw
/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR5gN/
ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfLZEFHcpOrUMPrCXZk
NNI5t3YRCQ12RcSprj1qr7V9ZS
+UWBDsXHyvfuK2GNnQm05aSd+pZgvMPMZ4fKecHePOjlO+Bd5g
D2vlGts
/4+EhySnB8esHnFIbAURRPHsl18TlUlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331
lubKgdaX8ZSD6e2wsWsSaR6s
+12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ
0wlf2eOKNcx5Wk0ZN5K3xMGtr
/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj
ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K
+OJ9dC1vX7gUMQSibMjmhAxhduub+84Mxh2
EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH
/BAgwBgEB/wIBDDAdBgNVHQ4EFgQU+SSsD7K1+HnA+mCI
G8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU
+SSsD7K1+HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJ
BgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNh
bWVyZmlybWEuY29tL2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENh
bWVyZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDiC
CQCj2kJ
+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUH
AgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAJASryI1
wqM58C7e6bXpeHxIvj99RZJe6dqxGfwWPJ
+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH
3qLPaYRgM
+gQDROpI9CF5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbU
RWpGqOt1glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9
/jvjA9iSnyu0/VU+I22mlaHFoI6
M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO
+rcf+g769HsJtg1pDDFOqxXnrN2pSB7+R5KBWIBpih1
YJeSDW4
+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MDxvbxrN8y8NmBGuScvfaAFPDRLLmF
9dijscilIeUcE5fuDr3fKanvNFNb0
+RqE4QGtjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcK
zBIKinmwPQN
/aUv0NCB9szTqjktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvG
nrDQWzilm1DefhiYtUU79nm06PcaewaD
+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg
OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZd0jQ
-----END CERTIFICATE-----

Global 
Chambersign Root 2008
==============================
-----
BEGIN CERTIFICATE-----
MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYDVQQGEwJFVTFD
MEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNv
bS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMu
QS4xJzAlBgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMx
NDBaFw0zODA3MzExMjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUg
Y3VycmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJ
QTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD
aGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDf
VtPkOpt2RbQT2
//BthmLN0EYlVJH6xedKYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXf
XjaOcNFccUMd2drvXNL7G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0
ZJJ0YPP2zxhPYLIj0Mc7zmFLmY
/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4ddPB
/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyGHoiMvvKRhI9lNNgA
TH23MRdaKXoKGCQwoze1eqkBfSbW
+Q6OWfH9GzO1KTsXO0G2Id3UwD2ln58fQ1DJu7xsepeY7s2M
H
/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3VyJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfe
Ox2YItaswTXbo6Al
/3K1dh3ebeksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSF
HTynyQbehP9r6GsaPMWis0L7iwk
+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh
wZG7SMA0j0GMS0zbaRL
/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsogzCtLkykPAgMB
AAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH
/AgEMMB0GA1UdDgQWBBS5CcqcHtvTbDprru1U8VuT
BjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDprru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UE
BhMCRVUxQzBBBgNVBAcTOk1hZHJpZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJm
aXJtYS5jb20vYWRkcmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJm
aXJtYSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiCCQDJzdPp
1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0
dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZIhvcNAQEFBQADggIBAICIf3DekijZBZRG
/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZUohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6
ReAJ3spED8IXDneRRXozX1
+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/s
dZ7LoR
/xfxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVza2Mg
9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yydYhz2rXzdpjEetrHH
foUm
+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMdSqlapskD7+3056huirRXhOukP9Du
qqqHW2Pok
+JrqNS4cnhrG+055F3Lm6qH1U9OAP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETr
P3iZ8ntxPjzxmKfFGBI
/5rsoM0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVq
c5iJWzouE4gev8CSlDQb4ye3ix5vQv
/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z
09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp
/B
-----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-----

EC-ACC
======
-----
BEGIN CERTIFICATE-----
MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB8zELMAkGA1UE
BhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2VydGlmaWNhY2lvIChOSUYgUS0w
ODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1YmxpY3MgZGUgQ2VydGlmaWNhY2lvMTUwMwYD
VQQLEyxWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAoYykwMzE1MDMGA1UE
CxMsSmVyYXJxdWlhIEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRhbGFuZXMxDzANBgNVBAMT
BkVDLUFDQzAeFw0wMzAxMDcyMzAwMDBaFw0zMTAxMDcyMjU5NTlaMIHzMQswCQYDVQQGEwJFUzE7
MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8gKE5JRiBRLTA4MDExNzYt
SSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBDZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZl
Z2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQubmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJh
cnF1aWEgRW50aXRhdHMgZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUND
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyLHT
+KXQpWIR4NA9h0X84NzJB5R85iK
w5K4
/0CQBXCHYMkAqbWUZRkiFRfCQ2xmRJoNBD45b6VLeqpjt4pEndljkYRm4CgPukLjbo73FCeT
ae6RDqNfDrHrZqJyTxIThmV6PttPB
/SnCWDaOkKZx7J/sxaVHMf5NLWUhdWZXqBIoH7nF2W4onW4
HvPlQn2v7fOKSGRdghST2MDk
/7NQcvJ29rNdQlB50JQ+awwAvthrDk4q7D7SzIKiGGUzE3eeml0a
E9jD2z3Il3rucO2n5nzbcc8tlGLfbdb1OL4
/pYUKGbio2Al1QnDE6u/LDsg0qBIimAy4E5S2S+zw
0JDnJwIDAQABo4HjMIHgMB0GA1UdEQQWMBSBEmVjX2FjY0BjYXRjZXJ0Lm5ldDAPBgNVHRMBAf8E
BTADAQH
/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoMOLRKo3pUW/l4Ba0fF4opvpXY0wfwYD
VR0gBHgwdjB0BgsrBgEEAfV4AQMBCjBlMCwGCCsGAQUFBwIBFiBodHRwczovL3d3dy5jYXRjZXJ0
Lm5ldC92ZXJhcnJlbDA1BggrBgEFBQcCAjApGidWZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5l
dC92ZXJhcnJlbCAwDQYJKoZIhvcNAQEFBQADggEBAKBIW4IB9k1IuDlVNZyAelOZ1Vr
/sXE7zDkJ
lF7W2u
++AVtd0x7Y/X1PzaBB4DSTv8vihpw3kpBWHNzrKQXlxJ7HNd+KDM3FIUPpqojlNcAZQmNa
Al6kSBg6hW
/cnbw/nZzBh7h6YQjpdwt/cKt63dmXLGQehb+8dJahw3oS7AwaboMMPOhyRp/7SNVe
l
+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOSAgu+TGbrIP65y7WZf+a2
E
/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xlnJ2lYJU6Un/10asIbvPuW/mIPX64b24D
5EI
=
-----
END CERTIFICATE-----

Hellenic Academic and Research Institutions RootCA 2011
=======================================================
-----
BEGIN CERTIFICATE-----
MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1IxRDBCBgNVBAoT
O0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9y
aXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z
IFJvb3RDQSAyMDExMB4XDTExMTIwNjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYT
AkdSMUQwQgYDVQQKEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25z
IENlcnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2VhcmNo
IEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AKlTAOMupvaO
+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPzdYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI
1Q
+kOilENbgH9mgdVc04UfCMJDGFr4PJfel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa
71HFK9
+WXesyHgLacEnsbgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u
8yBRQlqD75O6aRXxYp2fmTmCobd0LovUxQt7L
/DICto9eQqakxylKHJzkUOap9FNhYS5qXSPFEDH
3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh
+UCAwEAAaOBiTCBhjAPBgNVHRMBAf8EBTADAQH/
MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp5dgTBCPuQSUwRwYDVR0eBEAwPqA8
MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQub3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQu
b3JnMA0GCSqGSIb3DQEBBQUAA4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVt
XdMiKahsog2p6z0GW5k6x8zDmjR
/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8
TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA
+2KIaWuE4ZJwzNzvoc7dIsXRSZMFpGD
/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8AcysNnq/onN694/BtZqhFLKPM58N
7yLcZnuEvUUXBj08yrl3NI
/K6s8/MT7jiOOASSXIl7WdmplNsDz4SgCbZN2fOUvRJ9e4
-----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-----

Trustis FPS Root CA
===================
-----
BEGIN CERTIFICATE-----
MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQG
EwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQLExNUcnVzdGlzIEZQUyBSb290
IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTExMzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNV
BAoTD1RydXN0aXMgTGltaXRlZDEcMBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc
+gIMPpqdZh8mQ
RUN
+AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihHiTHcDnlk
H5nSW7r
+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjjvSkCqPoc4Vu5g6hBSLwa
cY3nYuUtsuvffM
/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zt
o3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlBOrTh4
/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEA
AaNTMFEwDwYDVR0TAQH
/BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAd
BgNVHQ4EFgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y
//01GX2c
GE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmWzaD+vkAMXBJV+JOC
yinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP41BIy
+Q7DsdwyhEQsb8tGD+pmQQ9P
8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZEf1T1o
+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHV
l
/9D7S3B2l0pKoU/rGXuhg8FjZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYl
iB6XzCGcKQENZetX2fNXlrtIzYE
=
-----
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-----

E-Tugra Certification Authority
===============================
-----
BEGIN CERTIFICATE-----
MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNVBAYTAlRSMQ8w
DQYDVQQHDAZBbmthcmExQDA
+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamls
ZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN
ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMw
NTEyMDk0OFoXDTIzMDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmEx
QDA
+BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxl
cmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQD
DB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
MIICCgKCAgEA4vU
/kwVRHoViVF56C/UYB4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vd
hQd2h8y
/L5VMzH2nPbxHD5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5K
CKpbknSFQ9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEoq1
+g
ElIwcxmOj
+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3Dk14opz8n8Y4e0ypQ
BaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcHfC425lAcP9tDJMW
/hkd5s3kc91r0
E
+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsutdEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gz
rt48Ue7LE3wBf4QOXVGUnhMMti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF
//vkUAq
jqFGOjGY5RH8zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn
rFpct1pHXFXOVbQicVtbC
/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUXU8u3Zg5mTPj5
dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6Jyr
+zE7S6E5UMA8GA1UdEwEB
/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEG
MA0GCSqGSIb3DQEBCwUAA4ICAQAFNzr0TbdF4kV1JI
+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAK
kEh47U6YA5n
+KGCRHTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jO
XKqYGwXgAEZKgoClM4so3O0409
/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c77NCR807
VRMGsAnb
/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3+GbHeJAAFS6LrVE1Uweo
a2iu
+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WKvJUawSg5TB9D0pH0clmKuVb8P7Sd2nCc
dlqMQ1DujjByTd
//SffGqWfZbawCEeI6FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEV
KV0jq9BgoRJP3vQXzTLlyb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gT
Dx4JnW2PAJ8C2sH6H3p6CcRK5ogql5
+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpDy4Q0
8ijE6m30Ku
/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8dNL/+I5c30jn6PQ0G
C7TbO6Orb1wdtn7os4I07QZcJA
==
-----
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 R4
===========================
-----
BEGIN CERTIFICATE-----
MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEkMCIGA1UECxMb
R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD
EwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoXDTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMb
R2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQD
EwpHbG9iYWxTaWduMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprl
OQcJFspjsbmG
+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAwDgYDVR0P
AQH
/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61FuOJAf/sKbvu+M8k8o4TV
MAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGXkPoUVy0D7O48027KqGx2vKLeuwIgJ6iF
JzWbVsaj8kfSt24bAgAXqmemFZHe
+pTsewv4n4Q=
-----
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-----

Staat der Nederlanden Root CA G3
==================================
-----
BEGIN CERTIFICATE-----
MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJOTDEeMBwGA1UE
CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFhdCBkZXIgTmVkZXJsYW5kZW4g
Um9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloXDTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMC
TkwxHjAcBgNVBAoMFVN0YWF0IGRlciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5l
ZGVybGFuZGVuIFJvb3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4y
olQPcPssXFnrbMSkUeiFKrPMSjTysF
/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WWIkYFsO2t
x1ueKt6c
/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqXxz8ecAgwoNzFs21v0IJy
EavSgWhZghe3eJJg
+szeP4TrjTgzkApyI/o1zCZxMdFyKJLZWyNtZrVtB0LrpjPOktvA9mxjeM3K
Tj215VKb8b475lRgsGYeCasH
/lSJEULR9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUur
mkVLoR9BvUhTFXFkC4az5S6
+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU5
1nus6
+N86U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7Ngzp
07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHPbMk7ccHViLVlvMDo
FxcHErVc0qsgk7TmgoNwNsXNo42ti
+yjwUOH5kPiNL6VizXtBznaqB16nzaeErAMZRKQFWDZJkBE
41ZgpRDUajz9QdwOWke275dhdU
/Z/seyHdTtXUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMB
AAGjQjBAMA8GA1UdEwEB
/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleu
yjWcLhL75LpdINyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB
/d6+Oea31BD
U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwpLiniyMMB8jPq
KqrMCQj3ZWfGzd
/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8Ipf3YF3qKS9Ysr1YvY2WTxB1
v0h7PVGHoTx0IsL8B3
+A3MSs/mrBcDCw6Y5p4ixpgZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA
8KCWAg8zxXHzniN9lLf9OtMJgwYh
/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b
8KKaa8MFSu1BYBQw0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko
+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0r
mj1AfsbAddJu
+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq4BZ+Extq
1z7XnvwtdbLBFNUjA9tbbws
+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR1VmiiXTTn74eS9fGbbeI
JG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0
/QFH1T/U67cjF68IeHRaVesd+QnGTbksV
tzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM94B7IWcnMFk
=
-----
END CERTIFICATE-----

Staat der Nederlanden EV Root CA
================================
-----
BEGIN CERTIFICATE-----
MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJOTDEeMBwGA1UE
CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFhdCBkZXIgTmVkZXJsYW5kZW4g
RVYgUm9vdCBDQTAeFw0xMDEyMDgxMTE5MjlaFw0yMjEyMDgxMTEwMjhaMFgxCzAJBgNVBAYTAk5M
MR4wHAYDVQQKDBVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRl
cmxhbmRlbiBFViBSb290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA48d
+ifkk
SzrSM4M1LGns3Amk41GoJSt5uAg94JG6hIXGhaTK5skuU6TJJB79VWZxXSzFYGgEt9nCUiY4iKTW
O0Cmws0
/zZiTs1QUWJZV1VD+hq2kY39ch/aO5ieSZxeSAgMs3NZmdO3dZ//BYY1jTw+bbRcwJu+r
0h8QoPnFfxZpgQNH7R5ojXKhTbImxrpsX23Wr9GxE46prfNeaXUmGD5BKyF/7otdBwadQ8QpCiv8
Kj6GyzyDOvnJDdrFmeK8eEEzduG
/L13lpJhQDBXd4Pqcfzho0LKmeqfRMb1+ilgnQ7O6M5HTp5gV
XJrm0w912fxBmJc
+qiXbj5IusHsMX/FjqTf5m3VpTCgmJdrV8hJwRVXj33NeN/UhbJCONVrJ0yPr
08C
+eKxCKFhmpUZtcALXEPlLVPxdhkqHz3/KRawRWrUgUY0viEeXOcDPusBCAUCZSCELa6fS/ZbV
0b5GnUngC6agIk440ME8MLxwjyx1zNDFjFE7PZQIZCZhfbnDZY8UnCHQqv0XcgOPvZuM5l5Tnrmd
74K74bzickFbIZTTRTeU0d8JOV3nI6qaHcptqAqGhYqCvkIH1vI4gnPah1vlPNOePqc7nvQDs
/nx
fRN0Av
+7oeX6AHkcpmZBiFxgV6YuCcS6/ZrPpx9Aw7vMWgpVSzs4dlG4Y4uElBbmVvMCAwEAAaNC
MEAwDwYDVR0TAQH
/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP6rAJCYniT8qcwa
ivsnuL8wbqg7MA0GCSqGSIb3DQEBCwUAA4ICAQDPdyxuVr5Os7aEAJSrR8kN0nbHhp8dB9O2tLsI
eK9p0gtJ3jPFrK3CiAJ9Brc1AsFgyb
/E6JTe1NOpEyVa/m6irn0F3H3zbPB+po3u2dfOWBfoqSmu
c0iH55vKbimhZF8ZE
/euBhD/UcabTVUlT5OZEAFTdfETzsemQUHSv4ilf0X8rLiltTMMgsT7B/Zq
5SWEXwbKwYY5EdtYzXc7LMJMD16a4
/CrPmEbUCTCwPTxGfARKbalGAKb12NMcIxHowNDXLldRqAN
b
/9Zjr7dn3LDWyvfjFvO5QxGbJKyCqNMVEIYFRIYvdr8unRu/8G2oGTYqV9Vrp9canaW2HNnh/tN
f1zuacpzEPuKqf2evTY4SUmH9A4U8OmHuD
+nT3pajnnUk+S7aFKErGzp85hwVXIy+TSrK0m1zSBi
5Dp6Z2Orltxtrpfs
/J92VoguZs9btsmksNcFuuEnL5O7Jiqik7Ab846+HUCjuTaPPoIaGl6I6lD4
WeKDRikL40Rc4ZW2aZCaFG
+XroHPaO+Zmr615+F/+PoTRxZMzG0IQOeLeG9QgkRQP2YGiqtDhFZK
DyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8FVdMpEbB4IMeDExNH08GGeL5qPQ6gqGy
eUN51q1veieQA6TqJIc
/2b3Z6fJfUEkc7uzXLg==
-----
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-----

TrustCor RootCert CA-1
======================
-----
BEGIN CERTIFICATE-----
MIIEMDCCAxigAwIBAgIJANqb7HHzA7AZMA0GCSqGSIb3DQEBCwUAMIGkMQswCQYDVQQGEwJQQTEP
MA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3Ig
U3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3Jp
dHkxHzAdBgNVBAMMFlRydXN0Q29yIFJvb3RDZXJ0IENBLTEwHhcNMTYwMjA0MTIzMjE2WhcNMjkx
MjMxMTcyMzE2WjCBpDELMAkGA1UEBhMCUEExDzANBgNVBAgMBlBhbmFtYTEUMBIGA1UEBwwLUGFu
YW1hIENpdHkxJDAiBgNVBAoMG1RydXN0Q29yIFN5c3RlbXMgUy4gZGUgUi5MLjEnMCUGA1UECwwe
VHJ1c3RDb3IgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MR8wHQYDVQQDDBZUcnVzdENvciBSb290Q2Vy
dCBDQS0xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv463leLCJhJrMxnHQFgKq1mq
jQCj
/IDHUHuO1CAmujIS2CNUSSUQIpidRtLByZ5OGy4sDjjzGiVoHKZaBeYei0i/mJZ0PmnK6bV4
pQa81QBeCQryJ3pS
/C3Vseq0iWEk8xoT26nPUu0MJLq5nux+AHT6k61sKZKuUbS701e/s/OojZz0
JEsq1pme9J7
+wH5COucLlVPat2gOkEz7cD+PSiyU8ybdY2mplNgQTsVHCJCZGxdNuWxu72CVEY4h
gLW9oHPY0LJ3xEXqWib7ZnZ2
+AYfYW0PVcWDtxBWcgYHpfOxGgMFZA6dWorWhnAbJN7+KIor0Gqw
/Hqi3LJ5DotlDwIDAQABo2MwYTAdBgNVHQ4EFgQU7mtJPHo/DeOxCbeKyKsZn3MzUOcwHwYDVR0j
BBgwFoAU7mtJPHo
/DeOxCbeKyKsZn3MzUOcwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
AYYwDQYJKoZIhvcNAQELBQADggEBACUY1JGPE
+6PHh0RU9otRCkZoB5rMZ5NDp6tPVxBb5UrJKF5
mDo4Nvu7Zp5I
/5CQ7z3UuJu0h3U/IJvOcs+hVcFNZKIZBqEHMwwLKeXx6quj7LUKdJDHfXLy11yf
ke
+Ri7fc7Waiz45mO7yfOgLgJ90WmMCV1Aqk5IGadZQ1nJBfiDcGrVmVCrDRZ9MZyonnMlo2HD6C
qFqTvsbQZJG2z9m2GM
/bftJlo6bEjhcxwft+dtvTheNYsnd6djtsL1Ac59v2Z3kf9YKVmgenFK+P
3CghZwnS1k1aHBkcjndcw5QkPTJrS37UeJSDvjdNzl
/HHk484IkzlQsPpTLWPFp5LBk=
-----
END CERTIFICATE-----

TrustCor RootCert CA-2
======================
-----
BEGIN CERTIFICATE-----
MIIGLzCCBBegAwIBAgIIJaHfyjPLWQIwDQYJKoZIhvcNAQELBQAwgaQxCzAJBgNVBAYTAlBBMQ8w
DQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5MSQwIgYDVQQKDBtUcnVzdENvciBT
eXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRydXN0Q29yIENlcnRpZmljYXRlIEF1dGhvcml0
eTEfMB0GA1UEAwwWVHJ1c3RDb3IgUm9vdENlcnQgQ0EtMjAeFw0xNjAyMDQxMjMyMjNaFw0zNDEy
MzExNzI2MzlaMIGkMQswCQYDVQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5h
bWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5U
cnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRydXN0Q29yIFJvb3RDZXJ0
IENBLTIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCnIG7CKqJiJJWQdsg4foDSq8Gb
ZQWU9MEKENUCrO2fk8eHyLAnK0IMPQo
+QVqedd2NyuCb7GgypGmSaIwLgQ5WoD4a3SwlFIIvl9Nk
RvRUqdw6VC0xK5mC8tkq1
+9xALgxpL56JAfDQiDyitSSBBtlVkxs1Pu2YVpHI7TYabS3OtB0PAx1
oYxOdqHp2yqlO
/rOsP9+aij9JxzIsekp8VduZLTQwRVtDr4uDkbIXvRR/u8OYzo7cbrPb1nKDOOb
XUm4TOJXsZiKQlecdu
/vvdFoqNL0Cbt3Nb4lggjEFixEIFapRBF37120Hapeaz6LMvYHL1cEksr1
/p3C6eizjkxLAjHZ5DxIgif3GIJ2SDpxsROhOdUuxTTCHWKF3wP+TfSvPd9cW436cOGlfifHhi5q
jxLGhF5DUVCcGZt45vz27Ud
+ez1m7xMTiF88oWP7+ayHNZ/zgp6kPwqcMWmLmaSISo5uZk3vFsQP
eSghYA2FFn3XVDjxklb9tTNMg9zXEJ9L
/cb4Qr26fHMC4P99zVvh1Kxhe1fVSntb1IVYJ12/+Ctg
rKAmrhQhJ8Z3mjOAPF5GP
/fDsaOGM8boXg25NSyqRsGFAnWAoOsk+xWq5Gd/bnc/9ASKL3x74xdh
8N0JqSDIvgmk0H5Ew7IwSjiqqewYmgeCK9u4nBit2uBGF6zPXQIDAQABo2MwYTAdBgNVHQ4EFgQU
2f4hQG6UnrybPZx9mCAZ5YwwYrIwHwYDVR0jBBgwFoAU2f4hQG6UnrybPZx9mCAZ5YwwYrIwDwYD
VR0TAQH
/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBAJ5Fngw7tu/h
Osh80QA9z
+LqBrWyOrsGS2h60COXdKcs8AjYeVrXWoSK2BKaG9l9XE1wxaX5q+WjiYndAfrs3fnp
kpfbsEZC89NiqpX
+MWcUaViQCqoL7jcjx1BRtPV+nuN79+TMQjItSQzL/0kMmx40/W5ulop5A7Zv
2wnL
/V9lFDfhOPXzYRZY5LVtDQsEGz9QLX+zx3oaFoBg+Iof6Rsqxvm6ARppv9JYx1RXCI/hOWB3
S6xZhBqI8d3LT3jX5
+EzLfzuQfogsL7L9ziUwOHQhQ+77Sxzq+3+knYaZH9bDTMJBzN7Bj8RpFxw
PIXAz
+OQqIN3+tvmxYxoZxBnpVIt8MSZj3+/0WvitUfW2dCFmU2Umw9Lje4AWkcdEQOsQRivh7dv
DDqPys
/cA8GiCcjl/YBeyGBCARsaU1q7N6a3vLqE6R5sGtRk2tRD/pOLS/IseRYQ1JMLiI+h2IYU
RpFHmygk71dSTlxCnKr3Sewn6EAes6aJInKc9Q0ztFijMDvd1GpUk74aTfOTlPf8hAs
/hCBcNANE
xdqtvArBAs8e5ZTZ845b2EzwnexhF7sUMlQMAimTHpKG9n
/v55IFDlndmQguLvqcAFLTxWYp5KeX
RKQOKIETNcX2b2TmQcTVL8w0RSXPQQCWPUouwpaYT05KnJe32x
+SMsj/D1Fu1uwJ
-----END CERTIFICATE-----

TrustCor ECA-1
==============
-----
BEGIN CERTIFICATE-----
MIIEIDCCAwigAwIBAgIJAISCLF8cYtBAMA0GCSqGSIb3DQEBCwUAMIGcMQswCQYDVQQGEwJQQTEP
MA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEkMCIGA1UECgwbVHJ1c3RDb3Ig
U3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3Jp
dHkxFzAVBgNVBAMMDlRydXN0Q29yIEVDQS0xMB4XDTE2MDIwNDEyMzIzM1oXDTI5MTIzMTE3Mjgw
N1owgZwxCzAJBgNVBAYTAlBBMQ8wDQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5
MSQwIgYDVQQKDBtUcnVzdENvciBTeXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRydXN0Q29y
IENlcnRpZmljYXRlIEF1dGhvcml0eTEXMBUGA1UEAwwOVHJ1c3RDb3IgRUNBLTEwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPj
+ARtZ+odnbb3w9U73NjKYKtR8aja+3+XzP4Q1HpGjOR
MRegdMTUpwHmspI
+ap3tDvl0mEDTPwOABoJA6LHip1GnHYMma6ve+heRK9jGrB6xnhkB1Zem6g23
xFUfJ3zSCNV2HykVh0A53ThFEXXQmqc04L
/NyFIduUd+Dbi7xgz2c1cWWn5DkR9VOsZtRASqnKmc
p0yJF4OuowReUoCLHhIlERnXDH19MURB6tuvsBzvgdAsxZohmz3tQjtQJvLsznFhBmIhVE5
/wZ0+
fyCMgMsq2JdiyIMzkX2woloPV+g7zPIlstR8L+xNxqE6FXrntl019fZISjZFZtS6mFjBAgMBAAGj
YzBhMB0GA1UdDgQWBBREnkj1zG1I1KBLf
/5ZJC+Dl5mahjAfBgNVHSMEGDAWgBREnkj1zG1I1KBL
f
/5ZJC+Dl5mahjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsF
AAOCAQEABT41XBVwm8nHc2FvcivUwo
/yQ10CzsSUuZQRg2dd4mdsdXa/uwyqNsatR5Nj3B5+1t4u
/ukZMjgDfxT2AHMsWbEhBuH7rBiVDKP/mZb3Kyeb1STMHd3BOuCYRLDE5D53sXOpZCz2HAF8P11F
hcCF5yWPldwX8zyfGm6wyuMdKulMY
/okYWLW2n62HGz1Ah3UKt1VkOsqEUc8Ll50soIipX1TH0Xs
J5F95yIW6MBoNtjG8U
+ARDL54dHRHareqKucBK+tIA5kmE2la8BIWJZpTdwHjFGTot+fDz2LYLSC
jaoITmJF4PkL0uDgPFveXHEnJcLmA4GLEFPjx1WitJ
/X5g==
-----
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-----

GTS Root R1
===========
-----
BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgIQbkepxUtHDA3sM9CJuRz04TANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQG
EwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJv
b3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAG
A1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L
+UPreVp0A8of2C+X0yBoJx
9vaMf
/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7wCl7r
aKb0xlpHDU0QM
+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjwTcLCeoiKu7rPWRnW
r4
+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0PfyblqAj+lug8aJRT7oM6iCsVlgmy4HqM
LnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly
4cpk9
+aCEI3oncKKiPo4Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr
06zqkUspzBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf
/VjsPOS+C12LOORc92
wO1AK
/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70paDPvOmbsB4om
3xPXV2V4J95eSRQAogB
/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrNVjzRlwW5y0vtOUucxD/SVRNu
JLDWcfr0wbrM7Rv1
/oFB2ACYPTrIrnqYNxgFlQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD
VR0TAQH
/BAUwAwEB/zAdBgNVHQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEM
BQADggIBADiWCu49tJYeX
++dnAsznyvgyv3SjgofQXSlfKqE1OXyHuY3UjKcC9FhHb8owbZEKTV1
d5iyfNm9dKyKaOOpMQkpAWBz40d8U6iQSifvS9efk
+eCNs6aaAyC58/UEBZvXw6ZXPYfcX3v73sv
fuo21pdwCxXu11xWajOl40k4DLh9
+42FpLFZXvRq4d2h9mREruZRgyFmxhE+885H7pwoHyXa/6xm
ld01D1zvICxi
/ZG6qcz8WpyTgYMpl0p8WnK0OdC3d8t5/Wk6kjftbjhlRn7pYL15iJdfOBL07q9b
gsiG1eGZbYwE8na6SfZu6W0eX6DvJ4J2QPim01hcDyxC2kLGe4g0x8HYRZvBPsVhHdljUEn2NIVq
4BjFbkerQUIpm
/ZgDdIx02OYI5NaAIFItO/Nis3Jz5nu2Z6qNuFoS3FJFDYoOj0dzpqPJeaAcWEr
tXvM
+SUWgeExX6GjfhaknBZqlxi9dnKlC54dNuYvoS++cJEPqOba+MSSQGwlfnuzCdyyF62ARPBo
pY
+Udf90WuioAnwMCeKpSwughQtiue+hMZL77/ZRBIls6Kl0obsXs7X9SQ98POyDGCBDTtWTurQ0
sR8WNh8M5mQ5Fkzc4P4dyKliPUDqysU0ArSuiYgzNdwsE3PYJ
/HQcu51OyLemGhmW/HGY0dVHLql
CFF1pkgl
-----END CERTIFICATE-----

GTS Root R2
===========
-----
BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgIQbkepxlqz5yDFMJo/aFLybzANBgkqhkiG9w0BAQwFADBHMQswCQYDVQQG
EwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJv
b3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAG
A1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDO3v2m
++zsFDQ8BwZabFn3GTXd98GdVarTzTuk
k3LvCvptnfbwhYBboUhSnznFt
+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY6Dlo
7JUle3ah5mm5hRm9iYz
+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAuMC6C/Pq8tBcKSOWI
m8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7kRXuJVfeKH2JShBKzwkCX44ofR5Gm
dFrS
+LFjKBC4swm4VndAoiaYecb+3yXuPuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbu
ak7MkogwTZq9TwtImoS1mKPV
+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscsz
cTJGr61K8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV
+mSSeh2FnIxZyuWfoqjx5RW
Ir9qS34BIbIjMt
/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKaG73Vululycsl
aVNVJ1zgyjbLiGH7HrfQy
+4W+9OmTN6SpdTi3/UGVN4unUu0kzCqgc7dGtxRcw1PcOnlthYhGXmy
5okLdWTK1au8CcEYof
/UVKGFPP0UJAOyh9OktwIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD
VR0TAQH
/BAUwAwEB/zAdBgNVHQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEM
BQADggIBALZp8KZ3/p7uC4Gt4cCpx/k1HUCCq+YEtN/L9x0Pg/B+E02NjO7jMyLDOfxA325BS0JT
vhaI8dI4XsRomRyYUpOM52jtG2pzegVATX9lO9ZY8c6DR2Dj
/5epnGB3GFW1fgiTz9D2PGcDFWEJ
+YF59exTpJ/JjwGLc8R3dtyDovUMSRqodt6Sm2T4syzFJ9MHwAiApJiS4wGWAqoC7o87xdFtCjMw
c3i5T1QWvwsHoaRc5svJXISPD
+AVdyx+Jn7axEvbpxZ3B7DNdehyQtaVhJ2Gg/LkkM0JR9SLA3Da
WsYDQvTtN6LwG1BUSw7YhN4ZKJmBR64JGz9I0cNv4rBgF
/XuIwKl2gBbbZCr7qLpGzvpx0QnRY5r
n
/WkhLx3+WuXrD5RRaIRpsyF7gpo8j5QOHokYh4XIDdtak23CZvJ/KRY9bb7nE4Yu5UC56Gtmwfu
Nmsk0jmGwZODUNKBRqhfYlcsu2xkiAhu7xNUX90txGdj08
+JN7+dIPT7eoOboB6BAFDC5AwiWVIQ
7UNWhwD4FFKnHYuTjKJNRn8nxnGbJN7k2oaLDX5rIMHAnuFl2GqjpuiFizoHCBy69Y9Vmhh1fuXs
gWbRIXOhNUQLgD1bnF5vKheW0YMjiGZt5obicDIvUiLnyOd
/xCxgXS/Dr55FBcOEArf9LAhST4Ld
o
/DUhgkC
-----END CERTIFICATE-----

GTS Root R3
===========
-----
BEGIN CERTIFICATE-----
MIICDDCCAZGgAwIBAgIQbkepx2ypcyRAiQ8DVd2NHTAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJV
UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg
UjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE
ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcq
hkjOPQIBBgUrgQQAIgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x
/JlFyb+Kf1qPKzEUU
Rout736GjOyxfi
//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL24Cej
QjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTB8Sa6oC2uhYHP
0
/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEAgFukfCPAlaUs3L6JbyO5o91lAFJekazInXJ0
glMLfalAvWhgxeG4VDvBNhcl2MG9AjEAnjWSdIUlUfUk7GRSJFClH9voy8l27OyCbvWFGFPouOOa
KaqW04MjyaR7YbPMAuhd
-----END CERTIFICATE-----

GTS Root R4
===========
-----
BEGIN CERTIFICATE-----
MIICCjCCAZGgAwIBAgIQbkepyIuUtui7OyrYorLBmTAKBggqhkjOPQQDAzBHMQswCQYDVQQGEwJV
UzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3Qg
UjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UE
ChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcq
hkjOPQIBBgUrgQQAIgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc
/eJkTBa
6zzuhXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai
/lxKvRHYqj
QjBAMA4GA1UdDwEB
/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSATNbrdP9JNqPV
2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNnADBkAjBqUFJ0CMRw3J5QdCHojXohw0
+WbhXRIjVhLfoI
N
+4Zba3bssx9BzT1YBkstTTZbyACMANxsbqjYAuG7ZoIapVon+Kz4ZNkfF6Tpt95LY2F45TPI11x
zPKwTdb
+mciUqXWi4w==
-----
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-----

Entrust Root Certification Authority G4
=========================================
-----
BEGIN CERTIFICATE-----
MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAwgb4xCzAJBgNV
BAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3Qu
bmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1
dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1
dGhvcml0eSAtIEc0MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYT
AlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0
L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhv
cml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhv
cml0eSAtIEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD
/pZJH4i3D
umSXbcr3DbVZwbPLqGgZ2K
+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV
3imz
/f3ET+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j5pds
8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAMC1rlLAHGVK
/XqsEQ
e9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73TDtTUXm6Hnmo9RR3RXRv06QqsYJn7
ibT
/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNXwbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5X
xNMhIWNlUpEbsZmOeX7m640A2Vqq6nPopIICR5b
+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV
7rtNOzK
+mndmnqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8
dWbrAuMINClKxuMrLzOg2qOGpRKX
/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwlN4y6mACXi0mW
Hv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9
/E15dWf10hkNjc0kCAwEAAaNCMEAwDwYDVR0T
AQH
/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9n
MA0GCSqGSIb3DQEBCwUAA4ICAQAS5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD
+MlZ9POrYs4Q
jbRaZIxowLByQzTSGwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL
/FmFz0Jomee8O5ZDIBf9PD3Vht
7LGrhFV0d4QEJ1JrhkzO3bll
/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/B7NTeLUK
YvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX
/Zr1hstWO1uIAeV8KEsD+UmDfLJ/fOPt
jqF
/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbwH5Lk6rWS02FREAutp9lfx1/cH6NcjKF+
m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKW
RGhXxNUzzxkvFMSUHHuk2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC
+QsDJMlhVoSFLUmQjA
JOgc47OlIQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk5F6G
+TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuYn/PIjhs4ViFqUZPT
kcpG2om3PVODLAgfi49T3f
+sHw==
-----
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-----

Thawte Server CA
================
-----
BEGIN CERTIFICATE-----
MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT
DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3Vs
dGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UE
AxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5j
b20wHhcNOTYwODAxMDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNV
BAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29u
c3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcG
A1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0
ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANOkUG7I
/1Zr5s9dtuoMaHVHoqrC2oQl
/Kj0R1HahbUgdJSGHg91yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg7
1CcEJRCXL
+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGjEzAR
MA8GA1UdEwEB
/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG7oWDTSEwjsrZqG9J
GubaUeNgcGyEYRGhGshIPllDfU
+VPaGLtwtimHp1it2ITk6eQNuozDJ0uW8NxuOzRAvZim+aKZuZ
GCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZqdq5snUb9kLy78fyGPmJvKP
/iiMucEc=
-----
END CERTIFICATE-----

Thawte Premium Server CA
========================
-----
BEGIN CERTIFICATE-----
MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkExFTATBgNVBAgT
DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3Vs
dGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UE
AxMYVGhhd3RlIFByZW1pdW0gU2VydmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZl
ckB0aGF3dGUuY29tMB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYT
AlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsGA1UEChMU
VGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2
aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNlcnZlciBDQTEoMCYGCSqGSIb3DQEJARYZ
cHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2
aovXwlue2oFBYo847kkEVdbQ7xwblRZH7xhINTpS9CtqBo87L
+pW46+GjZ4X9560ZXUCTe/LCaIh
Udib0GfQug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j
+ao6hnO2RlNYyIkFvYMRuHM/
qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQAm
SCwWwlj66BZ0DKqqX1Q
/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUIhfzJATj/Tb7yFkJD57taRvvBxhEf
8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JMpAwSremkrj
/xw0llmozFyD4lt5SZu5IycQfwhl7t
UCemDaYj
+bvLpgcUQg==
-----
END CERTIFICATE-----

Verisign Class Public Primary Certification Authority
=======================================================
-----
BEGIN CERTIFICATE-----
MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkGA1UEBhMCVVMx
FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmltYXJ5
IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVow
XzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAz
IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhEBarsAx94
f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG
+YD/isI19wKTakyYbnsZogy1Ol
hec9vn2a
/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0GCSqGSIb3DQEBAgUAA4GBALtMEivPLCYA
TxQT3ab7
/AoRhIzzKBxnki98tsX63/Dolbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59Ah
WM1pF
+NEHJwZRDmJXNycAA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2Omuf
Tqj
/ZA1k
-----END CERTIFICATE-----

Verisign Class Public Primary Certification Authority G2
============================================================
-----
BEGIN CERTIFICATE-----
MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJBgNVBAYTAlVT
MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMgUHJpbWFy
eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln
biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz
dCBOZXR3b3JrMB4XDTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVT
MRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMgUHJpbWFy
eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2ln
biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVz
dCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCO
FoUgRm1HP9SFIIThbbP4pO0M8RcPO
/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71
lSk8UOg013gfqLptQ5GVj0VXXn7F
+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwIDAQAB
MA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSkU01UbSuvDV1Ai2TT
1
+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7iF6YM40AIOw7n60RzKprxaZLvcRTD
Oaxxp5EJb
+RxBrO6WVcmeQD2+A2iMzAo1KpYoJ2daZH9
-----END CERTIFICATE-----
wp-cli/wp-cli 2.6.0 2022-01-25T23:55:14+00:00
composer
/ca-bundle 1.3.1 4c679186f2aca4ab6a0f1b0b9cf9252decb44d0b
composer
/composer 2.2.5 22c41ef275c7bb64fa28fb2c0871a39666832cb9
composer
/metadata-minifier 1.0.0 c549d23829536f0d0e984aaabbf02af91f443207
composer
/pcre 1.0.1 67a32d7d6f9f560b726ab25a061b38ff3a80c560
composer
/semver 3.2.7 deac27056b57e46faf136fae7b449eeaa71661ee
composer
/spdx-licenses 1.5.6 a30d487169d799745ca7280bc90fdfa693536901
composer
/xdebug-handler 2.0.4 0c1a3925ec58a4ec98e992b9c7d171e9e184be0a
gettext
/gettext v4.8.6 bbeb8f4d3077663739aecb4551b22e720c0e9efe
gettext
/languages 2.9.0 ed56dd2c7f4024cc953ed180d25f02f2640e3ffa
justinrainbow
/json-schema 5.2.11 2ab6744b7296ded80f8cc4f9509abbff393399aa
mck89
/peast v1.13.11 78c57966f3da5f223636ea0417d71ac6ff61e47f
mustache
/mustache v2.14.1 579ffa5c96e1d292c060b3dd62811ff01ad8c24e
nb
/oxymel v0.1.0 cbe626ef55d5c4cc9b5e6e3904b395861ea76e3c
psr
/log 1.1.4 d49695b909c3b7628b6289db5479a1c204601f11
react
/promise v2.8.0 f3cff96a19736714524ca0dd1d4130de73dbbbc4
rmccue
/requests v1.8.1 82e6936366eac3af4d836c18b9d8c31028fe4cd5
seld
/jsonlint 1.8.3 9ad6ce79c342fbd44df10ea95511a1b24dee5b57
seld
/phar-utils 1.2.0 9f3452c93ff423469c0d56450431562ca423dcee
symfony
/console v3.4.47 a10b1da6fc93080c180bba7219b5ff5b7518fe81
symfony
/debug v3.4.47 ab42889de57fdfcfcc0759ab102e2fd4ea72dcae
symfony
/filesystem v3.4.47 e58d7841cddfed6e846829040dca2cca0ebbbbb3
symfony
/finder v3.4.47 b6b6ad3db3edb1b4b1c1896b1975fb684994de6e
symfony
/polyfill-ctype v1.19.0 aed596913b70fae57be53d86faa2e9ef85a2297b
symfony
/polyfill-mbstring v1.19.0 b5f7b932ee6fa802fc792eabd77c4c88084517ce
symfony
/process v3.4.47 b8648cf1d5af12a44a51d07ef9bf980921f15fca
wp
-cli/cache-command v2.0.9 05378440d8c6d4d2a1a5e5cbc1ba92a5e4bf1c40
wp
-cli/checksum-command v2.1.2 ec59a24af2ca97b770a4709b0a1c241eeb4b4cff
wp
-cli/config-command v2.1.3 cdabbc47dae464a93b10361b9a18e84cf4e72fe2
wp
-cli/core-command v2.1.1 a75d052ea000b4f0ec14106c110836b376e95a4f
wp
-cli/cron-command v2.1.0 bb9fd9645e9a5276d024a59affeda3e6aa8530be
wp
-cli/db-command v2.0.20 709f58c30d178afcdecaf56068ca9f5e985ed6b9
wp
-cli/embed-command v2.0.11 00a901a66aecb4da94a8dace610eb1135fc82386
wp
-cli/entity-command v2.2.1 d7d08b05c67651abde5d570851e46498a164cb34
wp
-cli/eval-command v2.1.2 5213040ec2167b2748f2689ff6fe24b92a064a90
wp
-cli/export-command v2.0.11 8dd137e0c739a59bb3d3de684a219dbb34473e11
wp
-cli/extension-command v2.1.4 6401d7ea51084fac40010c2fb305be640675f385
wp
-cli/i18n-command v2.2.13 77ece9e2c914bb56ea72b9ee9f00556dece07b3f
wp
-cli/import-command v2.0.8 a092e3abcca843f1fabf2e9b706a912ae075355f
wp
-cli/language-command v2.0.12 bbdba69179fc8df597928587111500c8ade40a38
wp
-cli/maintenance-mode-command v2.0.8 e65505c973ea9349257a4f33ac9edc78db0b189a
wp
-cli/media-command v2.0.12 7cbf32c7fa68631619f85a9bea0fb80fca119ba6
wp
-cli/mustangostang-spyc 0.6.3 6aa0b4da69ce9e9a2c8402dab8d43cf32c581cc7
wp
-cli/package-command v2.2.2 36afdee21d022e6270867aa0cbfef6f453041814
wp
-cli/php-cli-tools v0.11.13 a2866855ac1abc53005c102e901553ad5772dc04
wp
-cli/rewrite-command v2.0.10 562a0a5a0d51be000de87d7a8a870de13383ecd6
wp
-cli/role-command v2.0.9 9abd93952565935084160bc3be49dfb2483bb0b6
wp
-cli/scaffold-command v2.0.16 6d92fb363b8ed7473af7f12cf342aaf9d2c96e81
wp
-cli/search-replace-command v2.0.16 dbf21560fd91710b2900f5631448657d28f2b380
wp
-cli/server-command v2.0.10 50c81f45f1cf09bc0a52e3582b3e56d27ca3c33c
wp
-cli/shell-command v2.0.11 28a7de3134c9f059900d8fa4aea1d7d618481454
wp
-cli/super-admin-command v2.0.10 e6707f3acfff089d19c5c55bba0fd66cd7d6c2fa
wp
-cli/widget-command v2.1.7 6aedab77f1cd2a0f511b62110c244c32b84dd429
wp
-cli/wp-cli v2.6.0 dee13c2baf6bf972484a63f8b8dab48f7220f095
wp
-cli/wp-config-transformer v1.3.0 2e90eefc6b8f5166f53aa5414fd8f1a572164ef12.6.0H#e�N�'I�7��6
��GBMB

All system for education purposes only. For more tools: Telegram @jackleet

Mr.X Private Shell

Logo
-
New File | New Folder
Command
SQL