%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /bin/
Upload File :
Create Path :
Current File : //bin/wp

#!/usr/bin/env php
<?php
Phar::mapPhar();
include 'phar://wp-cli.phar/php/boot-phar.php';
__HALT_COMPILER(); ?>
;^wp-cli.phar#vendor/wp-cli/wp-cli/php/compat.phpq@��]q�E�(vendor/wp-cli/wp-cli/php/config-spec.php�@��]�q���"vendor/wp-cli/wp-cli/php/utils.php(�@��](�LC�q�'vendor/wp-cli/wp-cli/php/dispatcher.phpR@��]R���$vendor/wp-cli/wp-cli/php/boot-fs.php�@��]���A�6vendor/wp-cli/wp-cli/php/WP_CLI/WpHttpCacheManager.php�@��]�E۫k�*vendor/wp-cli/wp-cli/php/WP_CLI/Runner.php��@��]����u�<vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/RunnerInstance.php�@��]��{��Hvendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/DeclareAbstractBaseCommand.phpA@��]A�@<@�Evendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/DefineProtectedCommands.php�@��]�ٶXw�Gvendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/IncludeFallbackAutoloader.php�@��]����\�Bvendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/LoadUtilityFunctions.php)@��])TYMݴHvendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/IncludeFrameworkAutoloader.php�@��]��t?ߴ<vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/AutoloaderStep.php	@��]	���<vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/LoadDispatcher.php5@��]5U��4�:vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/LaunchRunner.php(@��](�z�\�Fvendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/RegisterDeferredCommands.php�@��]��.�Ǵ>vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/InitializeLogger.php@��](m�J�Avendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/LoadRequiredCommand.php�@��]� �#��>vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/DeclareMainClass.php@��]�Tj�Gvendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/RegisterFrameworkCommands.php8@��]8�PxO�Dvendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/InitializeColorization.phpL@��]L��'=�Fvendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/IncludePackageAutoloader.php�@��]��w���<vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/BootstrapState.php�@��]�n�$�=vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/ConfigureRunner.php5@��]5�߶-�;vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/BootstrapStep.php�@��]�A7/�(vendor/wp-cli/wp-cli/php/WP_CLI/NoOp.php�@��]�D�1�-vendor/wp-cli/wp-cli/php/WP_CLI/FileCache.php(!@��](!Nj^��1vendor/wp-cli/wp-cli/php/WP_CLI/Fetchers/Base.php�@��]�y9��-vendor/wp-cli/wp-cli/php/WP_CLI/DocParser.php7@��]7fdl��-vendor/wp-cli/wp-cli/php/WP_CLI/Formatter.php�%@��]�%X&��.vendor/wp-cli/wp-cli/php/WP_CLI/ProcessRun.php�@��]��(�~�Avendor/wp-cli/wp-cli/php/WP_CLI/PackageManagerEventSubscriber.php�@��]�ݑྴJvendor/wp-cli/wp-cli/php/WP_CLI/Compat/Min_PHP_5_6/FeedbackMethodTrait.php�@��]��4�p�Jvendor/wp-cli/wp-cli/php/WP_CLI/Compat/Min_PHP_5_4/FeedbackMethodTrait.php@��]ܠ�Ӵ>vendor/wp-cli/wp-cli/php/WP_CLI/Compat/FeedbackMethodTrait.php�@��]��閴3vendor/wp-cli/wp-cli/php/WP_CLI/Loggers/Regular.php
@��]
����1vendor/wp-cli/wp-cli/php/WP_CLI/Loggers/Quiet.php�@��]��k���0vendor/wp-cli/wp-cli/php/WP_CLI/Loggers/Base.php�@��]��햴5vendor/wp-cli/wp-cli/php/WP_CLI/Loggers/Execution.php@@��]@B��B�0vendor/wp-cli/wp-cli/php/WP_CLI/UpgraderSkin.php=@��]=�A�	�/vendor/wp-cli/wp-cli/php/WP_CLI/Completions.php�@��]��S��.vendor/wp-cli/wp-cli/php/WP_CLI/Autoloader.phpK@��]K�v��>vendor/wp-cli/wp-cli/php/WP_CLI/Dispatcher/CommandAddition.php#@��]#�躛�?vendor/wp-cli/wp-cli/php/WP_CLI/Dispatcher/CommandNamespace.php�@��]��z��?vendor/wp-cli/wp-cli/php/WP_CLI/Dispatcher/CompositeCommand.php�@��]�Zv�,�=vendor/wp-cli/wp-cli/php/WP_CLI/Dispatcher/CommandFactory.php/"@��]/"���.�:vendor/wp-cli/wp-cli/php/WP_CLI/Dispatcher/RootCommand.php^@��]^xD{�9vendor/wp-cli/wp-cli/php/WP_CLI/Dispatcher/Subcommand.php:2@��]:2�5f�2vendor/wp-cli/wp-cli/php/WP_CLI/SynopsisParser.php�@��]��xM��1vendor/wp-cli/wp-cli/php/WP_CLI/ExitException.phpD@��]D�s/ߴ5vendor/wp-cli/wp-cli/php/WP_CLI/SynopsisValidator.php1@��]1��]��0vendor/wp-cli/wp-cli/php/WP_CLI/Configurator.php+&@��]+&d٧F�.vendor/wp-cli/wp-cli/php/WP_CLI/ComposerIO.phpK@��]K��+vendor/wp-cli/wp-cli/php/WP_CLI/Process.php}@��]}!��Ǵ-vendor/wp-cli/wp-cli/php/WP_CLI/Inflector.php+@@��]+@���7�-vendor/wp-cli/wp-cli/php/WP_CLI/Extractor.phpW'@��]W'I��7�3vendor/wp-cli/wp-cli/php/WP_CLI/Iterators/Query.php@��]YoA��7vendor/wp-cli/wp-cli/php/WP_CLI/Iterators/Transform.php�@��]��Ŀ��3vendor/wp-cli/wp-cli/php/WP_CLI/Iterators/Table.php}
@��]}
�[A�7vendor/wp-cli/wp-cli/php/WP_CLI/Iterators/Exception.phpR@��]R�Y�7�1vendor/wp-cli/wp-cli/php/WP_CLI/Iterators/CSV.phpm@��]mYo6��&vendor/wp-cli/wp-cli/php/bootstrap.php@��]����#vendor/wp-cli/wp-cli/php/wp-cli.php @��] �x�c�*vendor/wp-cli/wp-cli/php/commands/help.php�@��]�lD��)vendor/wp-cli/wp-cli/php/commands/cli.php�@��]�jn���6vendor/wp-cli/wp-cli/php/commands/src/Help_Command.phpg@��]g���"�;vendor/wp-cli/wp-cli/php/commands/src/CLI_Alias_Command.phpd2@��]d2���4�;vendor/wp-cli/wp-cli/php/commands/src/CLI_Cache_Command.php�@��]��n?�5vendor/wp-cli/wp-cli/php/commands/src/CLI_Command.php�J@��]�J4����1vendor/wp-cli/wp-cli/php/class-wp-cli-command.php�@��]��*��,vendor/wp-cli/wp-cli/php/wp-settings-cli.phpQB@��]QB��"�)vendor/wp-cli/wp-cli/php/class-wp-cli.phpɞ@��]ɞ@ᠴ%vendor/wp-cli/wp-cli/php/utils-wp.php�5@��]�5A\��php/boot-phar.php9@��]9Y��s�0vendor/mustache/mustache/bin/build_bootstrap.php;@��];����3vendor/mustache/mustache/src/Mustache/Tokenizer.php�)@��]�)�	�=vendor/mustache/mustache/src/Mustache/Logger/StreamLogger.php�@��]����J�?vendor/mustache/mustache/src/Mustache/Logger/AbstractLogger.phpv@��]vn�Y6�0vendor/mustache/mustache/src/Mustache/Logger.php$
@��]$
�@Y9�6vendor/mustache/mustache/src/Mustache/LambdaHelper.php�@��]��pմ0vendor/mustache/mustache/src/Mustache/Parser.php�*@��]�*�nG�4vendor/mustache/mustache/src/Mustache/Autoloader.php@��]=c�ִ@vendor/mustache/mustache/src/Mustache/Loader/CascadingLoader.php�@��]����>vendor/mustache/mustache/src/Mustache/Loader/MutableLoader.php�@��]���g�=vendor/mustache/mustache/src/Mustache/Loader/InlineLoader.php�@��]�\�	�=vendor/mustache/mustache/src/Mustache/Loader/StringLoader.php�@��]�<%��Avendor/mustache/mustache/src/Mustache/Loader/FilesystemLoader.php�@��]�L$y��Kvendor/mustache/mustache/src/Mustache/Loader/ProductionFilesystemLoader.phpf@��]f����<vendor/mustache/mustache/src/Mustache/Loader/ArrayLoader.php�@��]��!��?vendor/mustache/mustache/src/Mustache/Cache/FilesystemCache.phph@��]h(}4�9vendor/mustache/mustache/src/Mustache/Cache/NoopCache.phpA@��]A���ٴ=vendor/mustache/mustache/src/Mustache/Cache/AbstractCache.php�@��]�V߀۴1vendor/mustache/mustache/src/Mustache/Context.php�@��]���D�2vendor/mustache/mustache/src/Mustache/Template.php�@��]�8��0vendor/mustache/mustache/src/Mustache/Engine.php�c@��]�c�R���2vendor/mustache/mustache/src/Mustache/Compiler.php|X@��]|XVs���:vendor/mustache/mustache/src/Mustache/HelperCollection.php�@��]���Ӵ/vendor/mustache/mustache/src/Mustache/Cache.php�@��]�%	H
�3vendor/mustache/mustache/src/Mustache/Exception.phpR@��]R]u��0vendor/mustache/mustache/src/Mustache/Loader.phpB@��]BQ��y�0vendor/mustache/mustache/src/Mustache/Source.php@��]b׶m�Avendor/mustache/mustache/src/Mustache/Source/FilesystemSource.phpj@��]j���@�Bvendor/mustache/mustache/src/Mustache/Exception/LogicException.php�@��]��}A��Jvendor/mustache/mustache/src/Mustache/Exception/UnknownHelperException.php�@��]��H�j�Cvendor/mustache/mustache/src/Mustache/Exception/SyntaxException.php�@��]��N���Jvendor/mustache/mustache/src/Mustache/Exception/UnknownFilterException.php�@��]��f�޴Lvendor/mustache/mustache/src/Mustache/Exception/InvalidArgumentException.php�@��]���Q�Dvendor/mustache/mustache/src/Mustache/Exception/RuntimeException.php�@��]�1]�ӴLvendor/mustache/mustache/src/Mustache/Exception/UnknownTemplateException.php�@��]�-�[�2vendor/rmccue/requests/bin/create_pear_package.php#@��]# !쯴7vendor/rmccue/requests/library/Requests/IDNAEncoder.php=,@��]=,��J�4vendor/rmccue/requests/library/Requests/Response.php�	@��]�	��9=�0vendor/rmccue/requests/library/Requests/IPv6.php?@��]?��6vendor/rmccue/requests/library/Requests/Auth/Basic.php�@��]���z��6vendor/rmccue/requests/library/Requests/Cookie/Jar.php�@��]�!	Wg�?vendor/rmccue/requests/library/Requests/Transport/fsockopen.php�0@��]�0��:vendor/rmccue/requests/library/Requests/Transport/cURL.php�;@��]�;�8Lߴ1vendor/rmccue/requests/library/Requests/Hooks.phpw@��]w�Tִ/vendor/rmccue/requests/library/Requests/IRI.php3o@��]3o�D��1vendor/rmccue/requests/library/Requests/Proxy.php-@��]-���<vendor/rmccue/requests/library/Requests/Response/Headers.phpM@��]M�~�ʹ6vendor/rmccue/requests/library/Requests/Proxy/HTTP.php�
@��]�
���3vendor/rmccue/requests/library/Requests/Session.php�@��]��x´/vendor/rmccue/requests/library/Requests/SSL.php�@��]��<o�5vendor/rmccue/requests/library/Requests/Transport.php�@��]���H��2vendor/rmccue/requests/library/Requests/Hooker.php�@��]���é�0vendor/rmccue/requests/library/Requests/Auth.php*@��]*�K�I�5vendor/rmccue/requests/library/Requests/Exception.php@��]�kdJ�2vendor/rmccue/requests/library/Requests/Cookie.php62@��]62VJ6x�Mvendor/rmccue/requests/library/Requests/Utility/CaseInsensitiveDictionary.php�@��]������Dvendor/rmccue/requests/library/Requests/Utility/FilteredIterator.php=@��]=??>vendor/rmccue/requests/library/Requests/Exception/HTTP/414.php�@��]�y���>vendor/rmccue/requests/library/Requests/Exception/HTTP/412.php�@��]�RU�J�>vendor/rmccue/requests/library/Requests/Exception/HTTP/401.php�@��]����>vendor/rmccue/requests/library/Requests/Exception/HTTP/416.php�@��]��1��>vendor/rmccue/requests/library/Requests/Exception/HTTP/402.php�@��]���U��>vendor/rmccue/requests/library/Requests/Exception/HTTP/503.php�@��]�*����>vendor/rmccue/requests/library/Requests/Exception/HTTP/403.php}@��]}��^%�>vendor/rmccue/requests/library/Requests/Exception/HTTP/505.php�@��]���"�>vendor/rmccue/requests/library/Requests/Exception/HTTP/501.php�@��]�c/�>vendor/rmccue/requests/library/Requests/Exception/HTTP/411.php�@��]��uI�>vendor/rmccue/requests/library/Requests/Exception/HTTP/404.php}@��]}�k��>vendor/rmccue/requests/library/Requests/Exception/HTTP/305.php~@��]~4-O��>vendor/rmccue/requests/library/Requests/Exception/HTTP/418.php�@��]�?�/l�>vendor/rmccue/requests/library/Requests/Exception/HTTP/428.php�@��]�ْ/�>vendor/rmccue/requests/library/Requests/Exception/HTTP/406.php�@��]���*�>vendor/rmccue/requests/library/Requests/Exception/HTTP/413.php�@��]�6�^%�>vendor/rmccue/requests/library/Requests/Exception/HTTP/306.php�@��]�M�o��>vendor/rmccue/requests/library/Requests/Exception/HTTP/429.php%@��]%]�fn�>vendor/rmccue/requests/library/Requests/Exception/HTTP/409.phpz@��]z���>vendor/rmccue/requests/library/Requests/Exception/HTTP/400.php�@��]�v�>vendor/rmccue/requests/library/Requests/Exception/HTTP/417.php�@��]�;����>vendor/rmccue/requests/library/Requests/Exception/HTTP/502.php�@��]���b��>vendor/rmccue/requests/library/Requests/Exception/HTTP/304.php�@��]�H9��>vendor/rmccue/requests/library/Requests/Exception/HTTP/405.php�@��]����]�>vendor/rmccue/requests/library/Requests/Exception/HTTP/408.php�@��]��&M��>vendor/rmccue/requests/library/Requests/Exception/HTTP/504.php�@��]�m�͍�>vendor/rmccue/requests/library/Requests/Exception/HTTP/415.php�@��]���9�>vendor/rmccue/requests/library/Requests/Exception/HTTP/407.php�@��]�b�iִBvendor/rmccue/requests/library/Requests/Exception/HTTP/Unknown.phpc@��]c"v��>vendor/rmccue/requests/library/Requests/Exception/HTTP/500.php�@��]�}�l\�>vendor/rmccue/requests/library/Requests/Exception/HTTP/431.php@��]3JѴ>vendor/rmccue/requests/library/Requests/Exception/HTTP/511.php@��]� D�>vendor/rmccue/requests/library/Requests/Exception/HTTP/410.phpn@��]n�|Z�Dvendor/rmccue/requests/library/Requests/Exception/Transport/cURL.php�@��]��`��:vendor/rmccue/requests/library/Requests/Exception/HTTP.php�@��]��S冴?vendor/rmccue/requests/library/Requests/Exception/Transport.phpJ@��]J�9[��+vendor/rmccue/requests/library/Requests.php^t@��]^tD
�?�"vendor/composer/autoload_files.phpw@��]w�)�Ӵ#vendor/composer/autoload_static.php�W@��]�W�r��3vendor/composer/composer/src/Composer/IO/BaseIO.php@��]u{w�6vendor/composer/composer/src/Composer/IO/ConsoleIO.php?'@��]?'��bO�8vendor/composer/composer/src/Composer/IO/IOInterface.php4@��]4����5vendor/composer/composer/src/Composer/IO/BufferIO.php3
@��]3
�W��3vendor/composer/composer/src/Composer/IO/NullIO.php	@��]	����=vendor/composer/composer/src/Composer/SelfUpdate/Versions.php\@��]\��l��9vendor/composer/composer/src/Composer/SelfUpdate/Keys.phpj@��]j��A�Evendor/composer/composer/src/Composer/Console/HtmlOutputFormatter.php
@��]
�R�ܴ=vendor/composer/composer/src/Composer/Console/Application.phpO@��]O���Rvendor/composer/composer/src/Composer/EventDispatcher/ScriptExecutionException.php�@��]�\!'ƴRvendor/composer/composer/src/Composer/EventDispatcher/EventSubscriberInterface.php@��]�#g�Ivendor/composer/composer/src/Composer/EventDispatcher/EventDispatcher.phpgV@��]gV����?vendor/composer/composer/src/Composer/EventDispatcher/Event.phpf@��]fKxմFvendor/composer/composer/src/Composer/Config/ConfigSourceInterface.php�@��]��Q�-�Avendor/composer/composer/src/Composer/Config/JsonConfigSource.php� @��]� jL�\�Mvendor/composer/composer/src/Composer/Question/StrictConfirmationQuestion.phpk
@��]k
��|F�Dvendor/composer/composer/src/Composer/Autoload/AutoloadGenerator.php܈@��]܈�}��Dvendor/composer/composer/src/Composer/Autoload/ClassMapGenerator.php�"@��]�"��,r�>vendor/composer/composer/src/Composer/Autoload/ClassLoader.php�4@��]�4�z���7vendor/composer/composer/src/Composer/XdebugHandler.phpM@��]M.��3vendor/composer/composer/src/Composer/Installer.php�@��]�DS"Ǵ=vendor/composer/composer/src/Composer/Script/PackageEvent.php�@��]�����6vendor/composer/composer/src/Composer/Script/Event.php�@��]�H��=vendor/composer/composer/src/Composer/Script/CommandEvent.php�@��]����Ǵ=vendor/composer/composer/src/Composer/Script/ScriptEvents.php�@��]�N�ׯ�Fvendor/composer/composer/src/Composer/Json/JsonValidationException.php�@��]���Jȴ>vendor/composer/composer/src/Composer/Json/JsonManipulator.php�K@��]�K��^�7vendor/composer/composer/src/Composer/Json/JsonFile.php�%@��]�%�#Ǵ<vendor/composer/composer/src/Composer/Json/JsonFormatter.php�@��]��|⸴:vendor/composer/composer/src/Composer/Util/SpdxLicense.php:@��]:�R��<vendor/composer/composer/src/Composer/Util/PackageSorter.php�	@��]�	x-��5vendor/composer/composer/src/Composer/Util/GitLab.php4@��]4&���9vendor/composer/composer/src/Composer/Util/Filesystem.php9W@��]9W�ܿд7vendor/composer/composer/src/Composer/Util/Silencer.phpv@��]v�G��;vendor/composer/composer/src/Composer/Util/ErrorHandler.phpC	@��]C	����>vendor/composer/composer/src/Composer/Util/ProcessExecutor.php@��]�Y�p�=vendor/composer/composer/src/Composer/Util/ComposerMirror.php�@��]����Q�7vendor/composer/composer/src/Composer/Util/Platform.php(
@��](
��R�8vendor/composer/composer/src/Composer/Util/Bitbucket.php�!@��]�!��9��Cvendor/composer/composer/src/Composer/Util/StreamContextFactory.php@��]���2vendor/composer/composer/src/Composer/Util/Zip.php�@��]�����7vendor/composer/composer/src/Composer/Util/Perforce.phpeD@��]eD3n�C�2vendor/composer/composer/src/Composer/Util/Git.php�C@��]�CtxD{�8vendor/composer/composer/src/Composer/Util/IniHelper.php`@��]`���X�2vendor/composer/composer/src/Composer/Util/Svn.php9'@��]9'竚ʴ9vendor/composer/composer/src/Composer/Util/AuthHelper.php�@��]��F�5vendor/composer/composer/src/Composer/Util/GitHub.php�@��]���.�2vendor/composer/composer/src/Composer/Util/Url.phpX@��]X��c�>vendor/composer/composer/src/Composer/Util/ConfigValidator.phpy@��]y_F��?vendor/composer/composer/src/Composer/Util/RemoteFilesystem.php�@��]�����1vendor/composer/composer/src/Composer/Util/Hg.phpC@��]Cs���8vendor/composer/composer/src/Composer/Util/TlsHelper.php�@��]������=vendor/composer/composer/src/Composer/Util/NoProxyPattern.phpK@��]K���C�Lvendor/composer/composer/src/Composer/DependencyResolver/PolicyInterface.php�@��]�����Hvendor/composer/composer/src/Composer/DependencyResolver/Transaction.php-@��]-Ȭ�~�Mvendor/composer/composer/src/Composer/DependencyResolver/RuleSetGenerator.phpv7@��]v7���Kvendor/composer/composer/src/Composer/DependencyResolver/RuleWatchGraph.php�@��]����/�Avendor/composer/composer/src/Composer/DependencyResolver/Rule.php�$@��]�$n�~ѴAvendor/composer/composer/src/Composer/DependencyResolver/Pool.php�7@��]�7��;�Hvendor/composer/composer/src/Composer/DependencyResolver/GenericRule.phpz@��]zrp�Jvendor/composer/composer/src/Composer/DependencyResolver/RuleWatchNode.php�
@��]�
�����Dvendor/composer/composer/src/Composer/DependencyResolver/Problem.php;%@��];%�#�Fvendor/composer/composer/src/Composer/DependencyResolver/Decisions.php�@��]���/�Cvendor/composer/composer/src/Composer/DependencyResolver/Solver.php�k@��]�ky�v�Ovendor/composer/composer/src/Composer/DependencyResolver/SolverBugException.php�@��]��8|4�Jvendor/composer/composer/src/Composer/DependencyResolver/DefaultPolicy.phpy$@��]y$FhC�Dvendor/composer/composer/src/Composer/DependencyResolver/RuleSet.php<@��]<���Z�Dvendor/composer/composer/src/Composer/DependencyResolver/Request.php�@��]��~�6�Kvendor/composer/composer/src/Composer/DependencyResolver/RuleWatchChain.phpz@��]zd]_C�Tvendor/composer/composer/src/Composer/DependencyResolver/SolverProblemsException.php�
@��]�
�',�Lvendor/composer/composer/src/Composer/DependencyResolver/RuleSetIterator.php	@��]	��Jc�Wvendor/composer/composer/src/Composer/DependencyResolver/Operation/InstallOperation.phpK@��]K��Q�Vvendor/composer/composer/src/Composer/DependencyResolver/Operation/UpdateOperation.php.@��].	%pg�dvendor/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php�@��]���x@�Yvendor/composer/composer/src/Composer/DependencyResolver/Operation/UninstallOperation.phpS@��]S�B�T�Yvendor/composer/composer/src/Composer/DependencyResolver/Operation/OperationInterface.php#@��]#�u��Vvendor/composer/composer/src/Composer/DependencyResolver/Operation/SolverOperation.php�@��]���݊�bvendor/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php�@��]��D��Jvendor/composer/composer/src/Composer/DependencyResolver/Rule2Literals.php�
@��]�
M��ɴBvendor/composer/composer/src/Composer/Package/RootAliasPackage.php=@��]=��6�Pvendor/composer/composer/src/Composer/Package/Archiver/ComposerExcludeFilter.phpZ@��]ZHInw�Jvendor/composer/composer/src/Composer/Package/Archiver/HgExcludeFilter.php�
@��]�
ұS�Lvendor/composer/composer/src/Composer/Package/Archiver/ArchiverInterface.php�@��]�lة�Pvendor/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFilter.php�@��]�Դ=�Lvendor/composer/composer/src/Composer/Package/Archiver/BaseExcludeFilter.php�@��]���x�Fvendor/composer/composer/src/Composer/Package/Archiver/ZipArchiver.php�@��]�����Kvendor/composer/composer/src/Composer/Package/Archiver/GitExcludeFilter.php9@��]9�$���Ivendor/composer/composer/src/Composer/Package/Archiver/ArchiveManager.php<@��]<����Pvendor/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFinder.phpK@��]KI|/��Gvendor/composer/composer/src/Composer/Package/Archiver/PharArchiver.phpF@��]F�L	�Xvendor/composer/composer/src/Composer/Package/LinkConstraint/LinkConstraintInterface.php�@��]��䑞�Rvendor/composer/composer/src/Composer/Package/LinkConstraint/VersionConstraint.php�@��]�4��O�Svendor/composer/composer/src/Composer/Package/LinkConstraint/SpecificConstraint.php�@��]���<�Pvendor/composer/composer/src/Composer/Package/LinkConstraint/EmptyConstraint.php�@��]��3��Pvendor/composer/composer/src/Composer/Package/LinkConstraint/MultiConstraint.php�@��]���[5�Hvendor/composer/composer/src/Composer/Package/Version/VersionGuesser.php.@��].C����Gvendor/composer/composer/src/Composer/Package/Version/VersionParser.php�	@��]�	����Ivendor/composer/composer/src/Composer/Package/Version/VersionSelector.php�@��]�FBo'�Bvendor/composer/composer/src/Composer/Package/PackageInterface.phpl%@��]l%�~��9vendor/composer/composer/src/Composer/Package/Package.php1@��]1��p(�Dvendor/composer/composer/src/Composer/Package/Dumper/ArrayDumper.phpX@��]X`Z%��Pvendor/composer/composer/src/Composer/Package/Loader/InvalidPackageException.php�@��]�IQ�شCvendor/composer/composer/src/Composer/Package/Loader/JsonLoader.php\@��]\��pj�Dvendor/composer/composer/src/Composer/Package/Loader/ArrayLoader.php�,@��]�,��UƴHvendor/composer/composer/src/Composer/Package/Loader/LoaderInterface.phpB@��]B*�J��Jvendor/composer/composer/src/Composer/Package/Loader/RootPackageLoader.phpq'@��]q'�86ߴNvendor/composer/composer/src/Composer/Package/Loader/ValidatingArrayLoader.php�V@��]�V#"��Cvendor/composer/composer/src/Composer/Package/Comparer/Comparer.php4@��]4�r=��Avendor/composer/composer/src/Composer/Package/CompletePackage.php:@��]:�"w�=vendor/composer/composer/src/Composer/Package/RootPackage.phpD
@��]D
�-/�=vendor/composer/composer/src/Composer/Package/BasePackage.php�@��]��vO��>vendor/composer/composer/src/Composer/Package/AliasPackage.phpQ$@��]Q$�ZJ�8vendor/composer/composer/src/Composer/Package/Locker.phpi9@��]i9����Fvendor/composer/composer/src/Composer/Package/RootPackageInterface.php�@��]�'
�f�Jvendor/composer/composer/src/Composer/Package/CompletePackageInterface.php�@��]���r�6vendor/composer/composer/src/Composer/Package/Link.php�@��]�0�V�2vendor/composer/composer/src/Composer/Compiler.phpi(@��]i(9�W´2vendor/composer/composer/src/Composer/Composer.php�@��]����R�/vendor/composer/composer/src/Composer/Cache.phpM@��]M�\�U�Cvendor/composer/composer/src/Composer/Command/ClearCacheCommand.phpg@��]g�(�Jvendor/composer/composer/src/Composer/Command/CheckPlatformReqsCommand.php�@��]�>lc�Avendor/composer/composer/src/Composer/Command/OutdatedCommand.phpI@��]I�A�?vendor/composer/composer/src/Composer/Command/ConfigCommand.phpVo@��]Vo�E�=vendor/composer/composer/src/Composer/Command/InitCommand.php�z@��]�z]����=vendor/composer/composer/src/Composer/Command/ShowCommand.php]�@��]]�+:Ҵ@vendor/composer/composer/src/Composer/Command/DependsCommand.phpt@��]tz��f�@vendor/composer/composer/src/Composer/Command/RequireCommand.php�1@��]�1I�N�=vendor/composer/composer/src/Composer/Command/HomeCommand.phpu@��]uD��i�>vendor/composer/composer/src/Composer/Command/AboutCommand.php�@��]�'�|�Avendor/composer/composer/src/Composer/Command/ValidateCommand.php6@��]6!$1d�Gvendor/composer/composer/src/Composer/Command/BaseDependencyCommand.php-'@��]-'8Pt�Evendor/composer/composer/src/Composer/Command/DumpAutoloadCommand.phph@��]h���@vendor/composer/composer/src/Composer/Command/ArchiveCommand.php@��]>��
�=vendor/composer/composer/src/Composer/Command/ExecCommand.php�@��]���p<�Avendor/composer/composer/src/Composer/Command/LicensesCommand.php�@��]���2�Bvendor/composer/composer/src/Composer/Command/ProhibitsCommand.php�@��]����ٴ@vendor/composer/composer/src/Composer/Command/InstallCommand.php%@��]%\�4��?vendor/composer/composer/src/Composer/Command/GlobalCommand.php
@��]
�f�1�=vendor/composer/composer/src/Composer/Command/BaseCommand.php�@��]�B�i�?vendor/composer/composer/src/Composer/Command/StatusCommand.php� @��]� �!��Bvendor/composer/composer/src/Composer/Command/RunScriptCommand.php�@��]�o2|q�Fvendor/composer/composer/src/Composer/Command/CreateProjectCommand.phpE@��]E�f�:�?vendor/composer/composer/src/Composer/Command/UpdateCommand.php�+@��]�+͞�f�?vendor/composer/composer/src/Composer/Command/SearchCommand.php�@��]�K�5ӴCvendor/composer/composer/src/Composer/Command/SelfUpdateCommand.php�B@��]�BE_ѴAvendor/composer/composer/src/Composer/Command/SuggestsCommand.php�@��]�J}�#�?vendor/composer/composer/src/Composer/Command/RemoveCommand.php�@��]��U���Dvendor/composer/composer/src/Composer/Command/ScriptAliasCommand.php�@��]��fO��Avendor/composer/composer/src/Composer/Command/DiagnoseCommand.phpkn@��]kn/a�]�8vendor/composer/composer/src/Composer/Plugin/Capable.php�@��]�'�}�Cvendor/composer/composer/src/Composer/Plugin/PreCommandRunEvent.phpM@��]MÒ@	�@vendor/composer/composer/src/Composer/Plugin/PluginInterface.php*@��]*u�*ִEvendor/composer/composer/src/Composer/Plugin/PreFileDownloadEvent.php7@��]7JD�=vendor/composer/composer/src/Composer/Plugin/PluginEvents.php�@��]��@��=vendor/composer/composer/src/Composer/Plugin/CommandEvent.php�@��]�#� �Kvendor/composer/composer/src/Composer/Plugin/Capability/CommandProvider.php_@��]_�μ�Fvendor/composer/composer/src/Composer/Plugin/Capability/Capability.php�@��]�Y9�3�>vendor/composer/composer/src/Composer/Plugin/PluginManager.php�>@��]�>K����1vendor/composer/composer/src/Composer/Factory.phpp_@��]p_~��Jvendor/composer/composer/src/Composer/Downloader/ChangeReportInterface.php�@��]��)Ÿ�Evendor/composer/composer/src/Composer/Downloader/FossilDownloader.php�@��]�k"�o�Gvendor/composer/composer/src/Composer/Downloader/TransportException.php�@��]�y�#e�Rvendor/composer/composer/src/Composer/Downloader/VcsCapableDownloaderInterface.php@��]���Bvendor/composer/composer/src/Composer/Downloader/RarDownloader.php&@��]&��´Bvendor/composer/composer/src/Composer/Downloader/GitDownloader.php�P@��]�PMB�6�Bvendor/composer/composer/src/Composer/Downloader/SvnDownloader.php�@��]�]�C�Cvendor/composer/composer/src/Composer/Downloader/GzipDownloader.php*
@��]*
rw�Bvendor/composer/composer/src/Composer/Downloader/TarDownloader.php�@��]� ��f�Lvendor/composer/composer/src/Composer/Downloader/DvcsDownloaderInterface.php@��]�v�(�Fvendor/composer/composer/src/Composer/Downloader/ArchiveDownloader.php$@��]$��ͦ�Dvendor/composer/composer/src/Composer/Downloader/DownloadManager.phpX)@��]X)�J�Hvendor/composer/composer/src/Composer/Downloader/DownloaderInterface.php�@��]�Rz^�Bvendor/composer/composer/src/Composer/Downloader/ZipDownloader.php�!@��]�!�tS��Ivendor/composer/composer/src/Composer/Downloader/PearPackageExtractor.php�-@��]�-W����Cvendor/composer/composer/src/Composer/Downloader/PharDownloader.php�@��]���x6�Cvendor/composer/composer/src/Composer/Downloader/FileDownloader.php�,@��]�,ə!c�Bvendor/composer/composer/src/Composer/Downloader/VcsDownloader.php�)@��]�)�X6��Avendor/composer/composer/src/Composer/Downloader/HgDownloader.php�@��]�bMܴHvendor/composer/composer/src/Composer/Downloader/FilesystemException.php�@��]����Gvendor/composer/composer/src/Composer/Downloader/PerforceDownloader.php@��]��Avendor/composer/composer/src/Composer/Downloader/XzDownloader.php�@��]��-DJ�Cvendor/composer/composer/src/Composer/Downloader/PathDownloader.phpG#@��]G#�I$��0vendor/composer/composer/src/Composer/Config.php5@@��]5@�3��Bvendor/composer/composer/src/Composer/Installer/InstallerEvent.php�@��]���Cvendor/composer/composer/src/Composer/Installer/PluginInstaller.php
@��]
��c�Avendor/composer/composer/src/Composer/Installer/PackageEvents.php�@��]�Z�V��@vendor/composer/composer/src/Composer/Installer/PackageEvent.phpT@��]T[��ڴFvendor/composer/composer/src/Composer/Installer/InstallerInterface.php�	@��]�	{�`��Cvendor/composer/composer/src/Composer/Installer/BinaryInstaller.phpf@��]f��{�Kvendor/composer/composer/src/Composer/Installer/BinaryPresenceInterface.php�@��]����Gvendor/composer/composer/src/Composer/Installer/InstallationManager.php*@��]*�nwD�Dvendor/composer/composer/src/Composer/Installer/LibraryInstaller.php�@��]�+�_��Dvendor/composer/composer/src/Composer/Installer/ProjectInstaller.php/
@��]/
�#D�Hvendor/composer/composer/src/Composer/Installer/MetapackageInstaller.php�
@��]�
W%��Gvendor/composer/composer/src/Composer/Installer/PearBinaryInstaller.phpT@��]T�|�Avendor/composer/composer/src/Composer/Installer/PearInstaller.php6@��]6x��Avendor/composer/composer/src/Composer/Installer/NoopInstaller.phpn@��]n1�a��Cvendor/composer/composer/src/Composer/Installer/InstallerEvents.php@��]��.!�Mvendor/composer/composer/src/Composer/Installer/SuggestedPackagesReporter.php�@��]�ډ��Bvendor/composer/composer/src/Composer/Exception/NoSslException.php�@��]��g��Bvendor/composer/composer/src/Composer/Repository/VcsRepository.php�<@��]�<O���Rvendor/composer/composer/src/Composer/Repository/InstalledFilesystemRepository.php�@��]��Y�Gvendor/composer/composer/src/Composer/Repository/ArtifactRepository.phph@��]h����Jvendor/composer/composer/src/Composer/Repository/VersionCacheInterface.phpz@��]z�m%ݴDvendor/composer/composer/src/Composer/Repository/ArrayRepository.phpV@��]VaN��Hvendor/composer/composer/src/Composer/Repository/CompositeRepository.phpW@��]W�2��Kvendor/composer/composer/src/Composer/Repository/Vcs/GitBitbucketDriver.phpM	@��]M	h9m��Kvendor/composer/composer/src/Composer/Repository/Vcs/VcsDriverInterface.php~@��]~����Hvendor/composer/composer/src/Composer/Repository/Vcs/BitbucketDriver.php�5@��]�5��YĴBvendor/composer/composer/src/Composer/Repository/Vcs/GitDriver.php�@��]���Y�Jvendor/composer/composer/src/Composer/Repository/Vcs/HgBitbucketDriver.phpG	@��]G	*ڿN�Bvendor/composer/composer/src/Composer/Repository/Vcs/SvnDriver.php�/@��]�/�A���Evendor/composer/composer/src/Composer/Repository/Vcs/GitLabDriver.php�<@��]�<��Evendor/composer/composer/src/Composer/Repository/Vcs/GitHubDriver.php�=@��]�=D���Bvendor/composer/composer/src/Composer/Repository/Vcs/VcsDriver.php^@��]^t��l�Evendor/composer/composer/src/Composer/Repository/Vcs/FossilDriver.php'@��]'��"c�Gvendor/composer/composer/src/Composer/Repository/Vcs/PerforceDriver.phpZ@��]Z�Z�Avendor/composer/composer/src/Composer/Repository/Vcs/HgDriver.phpY@��]Y�[r.�Gvendor/composer/composer/src/Composer/Repository/ComposerRepository.phpK}@��]K}�ۜ�Ivendor/composer/composer/src/Composer/Repository/FilesystemRepository.php�	@��]�	a�f�Pvendor/composer/composer/src/Composer/Repository/RepositorySecurityException.php�@��]�<9�Gvendor/composer/composer/src/Composer/Repository/PlatformRepository.php2@��]2S��Pvendor/composer/composer/src/Composer/Repository/WritableRepositoryInterface.php�@��]�ugb��Cvendor/composer/composer/src/Composer/Repository/BaseRepository.php�#@��]�#��6��Cvendor/composer/composer/src/Composer/Repository/PearRepository.php'"@��]'"뛅ǴTvendor/composer/composer/src/Composer/Repository/ConfigurableRepositoryInterface.php�@��]�6{��Mvendor/composer/composer/src/Composer/Repository/InstalledArrayRepository.phpH@��]H	+;I�Fvendor/composer/composer/src/Composer/Repository/PackageRepository.php\@��]\�兴Fvendor/composer/composer/src/Composer/Repository/RepositoryManager.php@��]^����Cvendor/composer/composer/src/Composer/Repository/PathRepository.php�@��]��3��Qvendor/composer/composer/src/Composer/Repository/Pear/PackageDependencyParser.php�*@��]�*�o}��Mvendor/composer/composer/src/Composer/Repository/Pear/ChannelRest10Reader.php�@��]��>o9�Nvendor/composer/composer/src/Composer/Repository/Pear/DependencyConstraint.php�@��]�s��2�Hvendor/composer/composer/src/Composer/Repository/Pear/DependencyInfo.php�@��]���-m�Evendor/composer/composer/src/Composer/Repository/Pear/ChannelInfo.php�@��]��s�Kvendor/composer/composer/src/Composer/Repository/Pear/BaseChannelReader.php�	@��]�	�H˴Mvendor/composer/composer/src/Composer/Repository/Pear/ChannelRest11Reader.php�@��]�e�δEvendor/composer/composer/src/Composer/Repository/Pear/PackageInfo.phpJ@��]J�r���Evendor/composer/composer/src/Composer/Repository/Pear/ReleaseInfo.php@��]�3&��Gvendor/composer/composer/src/Composer/Repository/Pear/ChannelReader.php�@��]��x��Fvendor/composer/composer/src/Composer/Repository/RepositoryFactory.php$@��]$��ȴLvendor/composer/composer/src/Composer/Repository/WritableArrayRepository.php�@��]���d�Hvendor/composer/composer/src/Composer/Repository/RepositoryInterface.php�@��]�􏉴Ovendor/composer/composer/src/Composer/Repository/InvalidRepositoryException.php�@��]���$�Qvendor/composer/composer/src/Composer/Repository/InstalledRepositoryInterface.phpC@��]C#�*vendor/composer/composer/src/bootstrap.php�@��]�cAl�!vendor/composer/autoload_real.phpn	@��]n	�>m�!vendor/composer/autoload_psr4.php�@��]��&��vendor/composer/ClassLoader.php�4@��]�4�z���2vendor/composer/spdx-licenses/src/SpdxLicenses.php�#@��]�#�����*vendor/composer/ca-bundle/src/CaBundle.php�6@��]�6nC/ �4vendor/composer/xdebug-handler/src/XdebugHandler.php�@@��]�@��K�0vendor/composer/xdebug-handler/src/PhpConfig.php5@��]5j$���-vendor/composer/xdebug-handler/src/Status.php@��]��f�.vendor/composer/xdebug-handler/src/Process.php�@��]�q;���,vendor/composer/semver/src/VersionParser.php�L@��]�L+&h�%vendor/composer/semver/src/Semver.php�@��]�=nQA�4vendor/composer/semver/src/Constraint/Constraint.phpV@��]V�zn��=vendor/composer/semver/src/Constraint/ConstraintInterface.php^@��]^)��X�<vendor/composer/semver/src/Constraint/AbstractConstraint.phpn@��]n��;�9vendor/composer/semver/src/Constraint/EmptyConstraint.php8@��]8)�ʹ9vendor/composer/semver/src/Constraint/MultiConstraint.php&
@��]&
��(�)vendor/composer/semver/src/Comparator.php�	@��]�	�g3	�%vendor/composer/autoload_classmap.php�@��]��GP&�'vendor/composer/autoload_namespaces.php�@��]�d��-�3vendor/symfony/finder/Expression/ValueInterface.php�@��]��yz<�/vendor/symfony/finder/Expression/Expression.php�
@��]�
R�4��*vendor/symfony/finder/Expression/Regex.phpW@��]W����)vendor/symfony/finder/Expression/Glob.php!@��]!NŜ̴3vendor/symfony/finder/Comparator/DateComparator.php�@��]���Gô/vendor/symfony/finder/Comparator/Comparator.php@��]p���5vendor/symfony/finder/Comparator/NumberComparator.php
@��]
���� vendor/symfony/finder/Finder.php�a@��]�ai�^�%vendor/symfony/finder/SplFileInfo.phpW@��]W!tVb�%vendor/symfony/finder/Shell/Shell.phpM@��]M��9��'vendor/symfony/finder/Shell/Command.phpQ@��]Q�����1vendor/symfony/finder/Adapter/AbstractAdapter.php�@��]�8k�2vendor/symfony/finder/Adapter/AdapterInterface.php�@��]�”��0vendor/symfony/finder/Adapter/BsdFindAdapter.php�@��]�J+��5vendor/symfony/finder/Adapter/AbstractFindAdapter.php?*@��]?*����0vendor/symfony/finder/Adapter/GnuFindAdapter.php�@��]�͂���,vendor/symfony/finder/Adapter/PhpAdapter.phpq@��]q�ҔT�9vendor/symfony/finder/Iterator/FilenameFilterIterator.php�@��]��p��<vendor/symfony/finder/Iterator/FilecontentFilterIterator.php�@��]�r�~�;vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php�@��]����4vendor/symfony/finder/Iterator/FilePathsIterator.phpS@��]S�+Q�Avendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php�	@��]�	,��9vendor/symfony/finder/Iterator/FileTypeFilterIterator.php>@��]>p��Y�3vendor/symfony/finder/Iterator/SortableIterator.php
@��]
v0ZN�=vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php�@��]�y�M�5vendor/symfony/finder/Iterator/PathFilterIterator.php�@��]�}���7vendor/symfony/finder/Iterator/CustomFilterIterator.php�@��]���
�1vendor/symfony/finder/Iterator/FilterIterator.php�@��]�8=/��=vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php�@��]�5���:vendor/symfony/finder/Iterator/DateRangeFilterIterator.php�@��]���@��:vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php�@��]��:~д6vendor/symfony/finder/Exception/ExceptionInterface.php�@��]��7�9vendor/symfony/finder/Exception/AccessDeniedException.php�@��]��cW޴Avendor/symfony/finder/Exception/OperationNotPermitedException.phpr@��]r�[&=�;vendor/symfony/finder/Exception/AdapterFailureException.php�@��]�����@vendor/symfony/finder/Exception/ShellCommandFailureException.php[@��][����vendor/symfony/finder/Glob.php�@��]�2���'vendor/symfony/polyfill-ctype/Ctype.php}@��]}\�谴+vendor/symfony/polyfill-ctype/bootstrap.phpS@��]S��7�-vendor/symfony/polyfill-mbstring/Mbstring.php�k@��]�k1D��@vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php�I@��]�I����Fvendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php9@��]9>|zK�@vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.phpI@��]I�(~�.vendor/symfony/polyfill-mbstring/bootstrap.php@��]����.vendor/symfony/process/PhpExecutableFinder.phpk	@��]k	��W��+vendor/symfony/process/ExecutableFinder.php
@��]
s�(�%vendor/symfony/process/PhpProcess.phpp	@��]p	��E��)vendor/symfony/process/ProcessBuilder.phpv@��]v�W��"vendor/symfony/process/Process.php7�@��]7�V)晴-vendor/symfony/process/Pipes/WindowsPipes.php@��]�B��.vendor/symfony/process/Pipes/AbstractPipes.php�@��]��9��*vendor/symfony/process/Pipes/UnixPipes.phpT@��]T]]��/vendor/symfony/process/Pipes/PipesInterface.phpt@��]t?_�'vendor/symfony/process/ProcessUtils.php�@��]�	�x�3vendor/symfony/process/Exception/LogicException.php�@��]���W�=vendor/symfony/process/Exception/ProcessTimedOutException.php{@��]{4��7vendor/symfony/process/Exception/ExceptionInterface.php�@��]��<��=vendor/symfony/process/Exception/InvalidArgumentException.php�@��]�˅��;vendor/symfony/process/Exception/ProcessFailedException.php�@��]�P��5�5vendor/symfony/process/Exception/RuntimeException.php�@��]�>H���9vendor/wp-cli/super-admin-command/super-admin-command.php�@��]�����=vendor/wp-cli/super-admin-command/src/Super_Admin_Command.php8@��]8�*�|�*vendor/wp-cli/export-command/functions.phpp@��]p��/vendor/wp-cli/export-command/export-command.php@��]�2d�<vendor/wp-cli/export-command/src/WP_Export_WXR_Formatter.php�&@��]�&H����Avendor/wp-cli/export-command/src/WP_Export_Split_Files_Writer.php3@��]3�ƻ�=vendor/wp-cli/export-command/src/WP_Export_Term_Exception.phpC@��]C�Ǵ3vendor/wp-cli/export-command/src/Export_Command.php�2@��]�2��Դ4vendor/wp-cli/export-command/src/WP_Export_Query.php�4@��]�4�T��:vendor/wp-cli/export-command/src/WP_Export_File_Writer.php@��]qj˴7vendor/wp-cli/export-command/src/WP_Export_Returner.phpB@��]B���M�9vendor/wp-cli/export-command/src/WP_Post_IDs_Iterator.phpH@��]H����4vendor/wp-cli/export-command/src/WP_Map_Iterator.php@@��]@ѱ��<vendor/wp-cli/export-command/src/WP_Export_XML_Over_HTTP.phpk@��]ke�	J�:vendor/wp-cli/export-command/src/WP_Export_Base_Writer.php�@��]�Ǵb��:vendor/wp-cli/export-command/src/WP_Iterator_Exception.php9@��]9�w�8vendor/wp-cli/export-command/src/WP_Export_Exception.php>@��]>M��5vendor/wp-cli/export-command/src/WP_Export_Oxymel.php�@��]�bfL��+vendor/wp-cli/core-command/core-command.php@��]�:���/vendor/wp-cli/core-command/src/Core_Command.phpD�@��]D���b��Ivendor/wp-cli/core-command/src/WP_CLI/Core/NonDestructiveCoreUpgrader.php�@��]��!z~�;vendor/wp-cli/core-command/src/WP_CLI/Core/CoreUpgrader.php�@��]�n�N�-vendor/wp-cli/shell-command/shell-command.php
@��]
�ٷ�1vendor/wp-cli/shell-command/src/Shell_Command.php=@��]=_-��5vendor/wp-cli/shell-command/src/WP_CLI/Shell/REPL.php�@��]�Db�-vendor/wp-cli/embed-command/embed-command.phpx@��]x#�δ4vendor/wp-cli/embed-command/src/Provider_Command.php�@��]�}t4��*vendor/wp-cli/embed-command/src/oEmbed.php�@��]��p�>�4vendor/wp-cli/embed-command/src/Embeds_Namespace.php�@��]��q��1vendor/wp-cli/embed-command/src/Cache_Command.php�@��]�m�P��3vendor/wp-cli/embed-command/src/Handler_Command.php@��]`�W�1vendor/wp-cli/embed-command/src/Fetch_Command.php�$@��]�$����+vendor/wp-cli/wp-cli/utils/contrib-list.php�@��]�TE:�@vendor/wp-cli/wp-cli/utils/get-package-require-from-composer.php�@��]���3vendor/wp-cli/widget-command/src/Widget_Command.php�F@��]�FqWk�4vendor/wp-cli/widget-command/src/Sidebar_Command.php�@��]��d���/vendor/wp-cli/widget-command/widget-command.phpD@��]D����1vendor/wp-cli/rewrite-command/rewrite-command.php@��]K��5vendor/wp-cli/rewrite-command/src/Rewrite_Command.php0@��]0�;��?vendor/wp-cli/wp-config-transformer/src/WPConfigTransformer.php�)@��]�)��%״'vendor/wp-cli/server-command/router.phpN@��]N7���3vendor/wp-cli/server-command/src/Server_Command.php,@��],��6\�/vendor/wp-cli/server-command/server-command.php�@��]��+��Cvendor/wp-cli/maintenance-mode-command/maintenance-mode-command.phpL@��]L9�v4�Evendor/wp-cli/maintenance-mode-command/src/MaintenanceModeCommand.php7
@��]7
02=@�)vendor/wp-cli/mustangostang-spyc/Spyc.php�@��]����x�.vendor/wp-cli/mustangostang-spyc/php4/5to4.php�@��]�uS])�7vendor/wp-cli/mustangostang-spyc/includes/functions.php@��]��:�-vendor/wp-cli/mustangostang-spyc/src/Spyc.php˅@��]˅�~�-vendor/wp-cli/cache-command/cache-command.phpC@��]C&'e�1vendor/wp-cli/cache-command/src/Cache_Command.php1@��]1�5�Ŵ5vendor/wp-cli/cache-command/src/Transient_Command.php�D@��]�DJo��3vendor/wp-cli/checksum-command/checksum-command.php�@��]�[z�s�<vendor/wp-cli/checksum-command/src/Checksum_Core_Command.php`@��]`Y���>vendor/wp-cli/checksum-command/src/Checksum_Plugin_Command.php>#@��]>#{(qk�<vendor/wp-cli/checksum-command/src/Checksum_Base_Command.phpM@��]M��?s�Gvendor/wp-cli/checksum-command/src/WP_CLI/Fetchers/UnfilteredPlugin.php:@��]:<�g�?vendor/wp-cli/checksum-command/src/Plugin_Command_Namespace.php�@��]��:f�=vendor/wp-cli/checksum-command/src/Core_Command_Namespace.php�@��]�{���+vendor/wp-cli/i18n-command/i18n-command.php�@��]���I�/vendor/wp-cli/i18n-command/src/PotGenerator.phpQ@��]Q��1vendor/wp-cli/i18n-command/src/MakePotCommand.php�q@��]�q0����/vendor/wp-cli/i18n-command/src/JedGenerator.phpm@��]m�ʓ�3vendor/wp-cli/i18n-command/src/CommandNamespace.php�@��]��lh��5vendor/wp-cli/i18n-command/src/JsFunctionsScanner.php�@��]�8"*�8vendor/wp-cli/i18n-command/src/IterableCodeExtractor.phpf @��]f ���)�3vendor/wp-cli/i18n-command/src/MapCodeExtractor.phpe@��]e�v�ƴ2vendor/wp-cli/i18n-command/src/MakeJsonCommand.php@��]����3vendor/wp-cli/i18n-command/src/PhpCodeExtractor.php@��]ya��6vendor/wp-cli/i18n-command/src/PhpFunctionsScanner.php�@��]��I��2vendor/wp-cli/i18n-command/src/JsCodeExtractor.php�@��]����]�+vendor/wp-cli/role-command/role-command.php;@��];a�v�/vendor/wp-cli/role-command/src/Role_Command.phpr)@��]r)��/Դ7vendor/wp-cli/role-command/src/Capabilities_Command.php3@��]3���/vendor/wp-cli/import-command/import-command.php@��]��ʴ3vendor/wp-cli/import-command/src/Import_Command.php�8@��]�8Ңs�3vendor/wp-cli/scaffold-command/scaffold-command.php@��]洃�7vendor/wp-cli/scaffold-command/src/Scaffold_Command.php	�@��]	���)�-vendor/wp-cli/media-command/media-command.php�@��]�6�X��1vendor/wp-cli/media-command/src/Media_Command.php�@��]��</�1vendor/wp-cli/package-command/package-command.php:@��]:`��7�5vendor/wp-cli/package-command/src/Package_Command.phpƬ@��]Ƭ���Ǵ<vendor/wp-cli/package-command/src/WP_CLI/JsonManipulator.phpS@��]SϢ��?vendor/wp-cli/search-replace-command/search-replace-command.php7@��]7����Cvendor/wp-cli/search-replace-command/src/Search_Replace_Command.php��@��]���Ue4�Bvendor/wp-cli/search-replace-command/src/WP_CLI/SearchReplacer.phpL@��]Lt�K@�/vendor/wp-cli/entity-command/entity-command.php�	@��]�	�%��1vendor/wp-cli/entity-command/src/Site_Command.phpR@��]RI�"�3vendor/wp-cli/entity-command/src/Option_Command.php,?@��],?u��1vendor/wp-cli/entity-command/src/User_Command.php��@��]����U�9vendor/wp-cli/entity-command/src/Network_Meta_Command.php]@��]]�kN�6vendor/wp-cli/entity-command/src/Post_Type_Command.php�@��]��z�_�6vendor/wp-cli/entity-command/src/Post_Term_Command.php@��])7���6vendor/wp-cli/entity-command/src/Menu_Item_Command.php�3@��]�3~_�״6vendor/wp-cli/entity-command/src/Site_Meta_Command.php�@��]��v���8vendor/wp-cli/entity-command/src/Site_Option_Command.php�'@��]�'WW���6vendor/wp-cli/entity-command/src/Post_Meta_Command.php�@��]����6vendor/wp-cli/entity-command/src/Network_Namespace.php*@��]*{�d�9vendor/wp-cli/entity-command/src/User_Session_Command.php�@��]��i���1vendor/wp-cli/entity-command/src/Post_Command.phpJm@��]Jm�V��6vendor/wp-cli/entity-command/src/User_Term_Command.php&@��]&�{b�1vendor/wp-cli/entity-command/src/Term_Command.php�K@��]�K�G�<vendor/wp-cli/entity-command/src/WP_CLI/Fetchers/Comment.php#@��]#݆�o�9vendor/wp-cli/entity-command/src/WP_CLI/Fetchers/Post.php�@��]��c�:�9vendor/wp-cli/entity-command/src/WP_CLI/Fetchers/Site.php�@��]�N�O��9vendor/wp-cli/entity-command/src/WP_CLI/Fetchers/User.phpS@��]SbTa�;vendor/wp-cli/entity-command/src/WP_CLI/CommandWithMeta.php;9@��];9y���<vendor/wp-cli/entity-command/src/WP_CLI/CommandWithTerms.phpe@��]e(Q>��?vendor/wp-cli/entity-command/src/WP_CLI/CommandWithDBObject.php�@��]���#�Rvendor/wp-cli/entity-command/src/WP_CLI/Entity/RecursiveDataStructureTraverser.php@��]�Lm�8vendor/wp-cli/entity-command/src/WP_CLI/Entity/Utils.php�@��]��y㦴Jvendor/wp-cli/entity-command/src/WP_CLI/Entity/NonExistentKeyException.php�@��]��̱h�:vendor/wp-cli/entity-command/src/Menu_Location_Command.php�@��]�d��1vendor/wp-cli/entity-command/src/Menu_Command.php	@��]	���ʹ9vendor/wp-cli/entity-command/src/Comment_Meta_Command.php�@��]�Cq��4vendor/wp-cli/entity-command/src/Comment_Command.phpB@��]B�\K�6vendor/wp-cli/entity-command/src/Term_Meta_Command.php�@��]��B�s�5vendor/wp-cli/entity-command/src/Taxonomy_Command.php�@��]��;�f�6vendor/wp-cli/entity-command/src/User_Meta_Command.php�$@��]�$RI|K�3vendor/wp-cli/language-command/language-command.php1@��]1�7�g�=vendor/wp-cli/language-command/src/Theme_Language_Command.php]1@��]]1��k�9vendor/wp-cli/language-command/src/Language_Namespace.php�@��]��)|��>vendor/wp-cli/language-command/src/Plugin_Language_Command.php3@��]3�BM��<vendor/wp-cli/language-command/src/Core_Language_Command.php�&@��]�&)�	ʴDvendor/wp-cli/language-command/src/WP_CLI/CommandWithTranslation.php� @��]� ��i�Bvendor/wp-cli/language-command/src/WP_CLI/LanguagePackUpgrader.php�@��]�rG��Cvendor/wp-cli/language-command/src/Site_Switch_Language_Command.php�@��]��1�$vendor/wp-cli/php-cli-tools/test.phpi@��]i3M��6vendor/wp-cli/php-cli-tools/lib/cli/notify/Spinner.php@��]8�u�3vendor/wp-cli/php-cli-tools/lib/cli/notify/Dots.phpd@��]d�w�z�7vendor/wp-cli/php-cli-tools/lib/cli/arguments/Lexer.php^	@��]^	�G��:vendor/wp-cli/php-cli-tools/lib/cli/arguments/Argument.php @��] =���Bvendor/wp-cli/php-cli-tools/lib/cli/arguments/InvalidArguments.php@��]�R�<vendor/wp-cli/php-cli-tools/lib/cli/arguments/HelpScreen.php�
@��]�
+B%�-vendor/wp-cli/php-cli-tools/lib/cli/Shell.php�@��]��mŻ�,vendor/wp-cli/php-cli-tools/lib/cli/Tree.php�@��]��@�1vendor/wp-cli/php-cli-tools/lib/cli/Arguments.php/@��]/��o��4vendor/wp-cli/php-cli-tools/lib/cli/progress/Bar.php�	@��]�	n�(O�-vendor/wp-cli/php-cli-tools/lib/cli/Table.php�@��]�t�*�/vendor/wp-cli/php-cli-tools/lib/cli/Streams.php�"@��]�"+�f	�+vendor/wp-cli/php-cli-tools/lib/cli/cli.php�@@��]�@�~z״/vendor/wp-cli/php-cli-tools/lib/cli/Memoize.php�@��]����.vendor/wp-cli/php-cli-tools/lib/cli/Colors.phpk!@��]k!y6)�2vendor/wp-cli/php-cli-tools/lib/cli/tree/Ascii.phpx@��]x9���5vendor/wp-cli/php-cli-tools/lib/cli/tree/Renderer.phpO@��]O�c"Ӵ5vendor/wp-cli/php-cli-tools/lib/cli/tree/Markdown.php�@��]��T魴5vendor/wp-cli/php-cli-tools/lib/cli/unicode/regex.phpb@��]bɶ9ʹ.vendor/wp-cli/php-cli-tools/lib/cli/Notify.php�@��]�,���3vendor/wp-cli/php-cli-tools/lib/cli/table/Ascii.php@��]�@%=�5vendor/wp-cli/php-cli-tools/lib/cli/table/Tabular.php�@��]����l�6vendor/wp-cli/php-cli-tools/lib/cli/table/Renderer.phpG@��]GY�ߒ�0vendor/wp-cli/php-cli-tools/lib/cli/Progress.php�
@��]�
�a��,vendor/wp-cli/php-cli-tools/http-console.php@��]��>�5vendor/wp-cli/extension-command/extension-command.php�@��]�@�J8�6vendor/wp-cli/extension-command/src/Plugin_Command.php��@��]����״=vendor/wp-cli/extension-command/src/WP_CLI/Fetchers/Theme.php@��]�3��>vendor/wp-cli/extension-command/src/WP_CLI/Fetchers/Plugin.php"@��]"F� �Hvendor/wp-cli/extension-command/src/WP_CLI/DestructivePluginUpgrader.php�@��]�$�Qr�Gvendor/wp-cli/extension-command/src/WP_CLI/DestructiveThemeUpgrader.php�@��]�hE!��Avendor/wp-cli/extension-command/src/WP_CLI/CommandWithUpgrade.phpY@��]Y��[�5vendor/wp-cli/extension-command/src/Theme_Command.php�k@��]�kL���9vendor/wp-cli/extension-command/src/Theme_Mod_Command.php�@��]�MO�{�+vendor/wp-cli/eval-command/eval-command.php=@��]=��Ak�/vendor/wp-cli/eval-command/src/Eval_Command.phpR@��]R�x_i�3vendor/wp-cli/eval-command/src/EvalFile_Command.php8@��]8:��!�+vendor/wp-cli/cron-command/cron-command.php�@��]���.�5vendor/wp-cli/cron-command/src/Cron_Event_Command.php�3@��]�3{��C�/vendor/wp-cli/cron-command/src/Cron_Command.php@��]��o��8vendor/wp-cli/cron-command/src/Cron_Schedule_Command.phpi@��]i��@�'vendor/wp-cli/db-command/db-command.php�@��]���k�+vendor/wp-cli/db-command/src/DB_Command.php��@��]���6�/vendor/wp-cli/config-command/config-command.php@��]7��Q�3vendor/wp-cli/config-command/src/Config_Command.phpfc@��]fcR�8(�vendor/nb/oxymel/OxymelTest.php�@��]�BL�V�vendor/nb/oxymel/Oxymel.php)@��])�(rմ%vendor/psr/log/Psr/Log/NullLogger.php�@��]����I�*vendor/psr/log/Psr/Log/LoggerInterface.php@��]�d?�)vendor/psr/log/Psr/Log/AbstractLogger.php@��]�Gl�&vendor/psr/log/Psr/Log/LoggerTrait.phpW
@��]W
�W�j�+vendor/psr/log/Psr/Log/LoggerAwareTrait.php�@��]�z%��3vendor/psr/log/Psr/Log/InvalidArgumentException.php`@��]` �X1�/vendor/psr/log/Psr/Log/LoggerAwareInterface.php)@��])�j��#vendor/psr/log/Psr/Log/LogLevel.phpP@��]P���)vendor/seld/phar-utils/src/Timestamps.php�@��]�ׁ�6�0vendor/seld/jsonlint/src/Seld/JsonLint/Lexer.phpe@��]e�)$�4vendor/seld/jsonlint/src/Seld/JsonLint/Undefined.php@��]�g��;vendor/seld/jsonlint/src/Seld/JsonLint/ParsingException.php0@��]0K�粴@vendor/seld/jsonlint/src/Seld/JsonLint/DuplicateKeyException.php8@��]8����5vendor/seld/jsonlint/src/Seld/JsonLint/JsonParser.php�P@��]�PC�ۂ�/vendor/symfony/console/Logger/ConsoleLogger.php�
@��]�
���n�,vendor/symfony/console/Style/OutputStyle.php�@��]��jK�-vendor/symfony/console/Style/SymfonyStyle.php</@��]</J
�l�/vendor/symfony/console/Style/StyleInterface.phpO@��]O��Q�(vendor/symfony/console/ConsoleEvents.php�@��]�G�X��<vendor/symfony/console/Descriptor/ApplicationDescription.php@��]���4vendor/symfony/console/Descriptor/TextDescriptor.php�*@��]�*��޴4vendor/symfony/console/Descriptor/JsonDescriptor.php�@��]�w���9vendor/symfony/console/Descriptor/DescriptorInterface.php�@��]�V�A��8vendor/symfony/console/Descriptor/MarkdownDescriptor.php�@��]�1���3vendor/symfony/console/Descriptor/XmlDescriptor.php-$@��]-$&����0vendor/symfony/console/Descriptor/Descriptor.php�@��]���y� vendor/symfony/console/Shell.php�@��]���)8�,vendor/symfony/console/Question/Question.php�@��]�����8vendor/symfony/console/Question/ConfirmationQuestion.php@��]ͪB�2vendor/symfony/console/Question/ChoiceQuestion.php�@��]�3{vG�/vendor/symfony/console/Tester/CommandTester.php�
@��]�
��3vendor/symfony/console/Tester/ApplicationTester.php�@��]��^r�&vendor/symfony/console/Application.php�@��]�\��-�9vendor/symfony/console/Formatter/OutputFormatterStyle.php�@��]��t�
�>vendor/symfony/console/Formatter/OutputFormatterStyleStack.php�	@��]�	��9�4vendor/symfony/console/Formatter/OutputFormatter.php�@��]���K�Bvendor/symfony/console/Formatter/OutputFormatterStyleInterface.php7@��]7�.@Y�=vendor/symfony/console/Formatter/OutputFormatterInterface.php�@��]�#7�u�6vendor/symfony/console/Helper/DebugFormatterHelper.phpW@��]W���2vendor/symfony/console/Helper/DescriptorHelper.php1
@��]1
^x�1vendor/symfony/console/Helper/HelperInterface.phpp@��]p���n�2vendor/symfony/console/Helper/InputAwareHelper.php�@��]����+vendor/symfony/console/Helper/HelperSet.phpi
@��]i
�%�0vendor/symfony/console/Helper/ProgressHelper.phpP1@��]P1_�;!�-vendor/symfony/console/Helper/TableHelper.php�@��]�su�}�/vendor/symfony/console/Helper/ProcessHelper.php@��]�
д0vendor/symfony/console/Helper/TableSeparator.php@��]�=_�0vendor/symfony/console/Helper/QuestionHelper.php�6@��]�6Q��@�'vendor/symfony/console/Helper/Table.php�I@��]�I�{���1vendor/symfony/console/Helper/FormatterHelper.php	@��]	jߕ�.vendor/symfony/console/Helper/DialogHelper.php�D@��]�D�z�r�7vendor/symfony/console/Helper/SymfonyQuestionHelper.php�@��]���=�-vendor/symfony/console/Helper/ProgressBar.php�E@��]�E�{�z�(vendor/symfony/console/Helper/Helper.php�@��]�� ��3vendor/symfony/console/Helper/ProgressIndicator.php�!@��]�!Q�}�,vendor/symfony/console/Helper/TableStyle.php{@��]{6ϳ]�+vendor/symfony/console/Helper/TableCell.phpz@��]z�q��8vendor/symfony/console/Output/ConsoleOutputInterface.php�@��]�����/vendor/symfony/console/Output/ConsoleOutput.php]@��]]��,�,vendor/symfony/console/Output/NullOutput.phpn@��]nt�D��.vendor/symfony/console/Output/StreamOutput.php(@��](݋�(vendor/symfony/console/Output/Output.php{@��]{�7�0vendor/symfony/console/Output/BufferedOutput.phpH@��]H�9�Դ1vendor/symfony/console/Output/OutputInterface.php
@��]
�7ڴ4vendor/symfony/console/Event/ConsoleCommandEvent.php%@��]%{˾�6vendor/symfony/console/Event/ConsoleTerminateEvent.php@��]{e��-vendor/symfony/console/Event/ConsoleEvent.php�@��]���
��6vendor/symfony/console/Event/ConsoleExceptionEvent.php=@��]=��Ŵ4vendor/symfony/console/Input/InputAwareInterface.php:@��]:� '��0vendor/symfony/console/Input/InputDefinition.php2@��]2��Vq�&vendor/symfony/console/Input/Input.php^@��]^�~w]�.vendor/symfony/console/Input/InputArgument.phpT
@��]T
+��4�+vendor/symfony/console/Input/ArrayInput.php�@��]�pMq�,vendor/symfony/console/Input/StringInput.php�@��]����e�,vendor/symfony/console/Input/InputOption.php�@��]�Y��*vendor/symfony/console/Input/ArgvInput.php�)@��]�)��Yw�/vendor/symfony/console/Input/InputInterface.php�@��]�����.vendor/symfony/console/Command/ListCommand.phpo@��]o�Y�.vendor/symfony/console/Command/HelpCommand.php�
@��]�
�Dž��*vendor/symfony/console/Command/Command.php�N@��]�NӸY��3vendor/symfony/console/Exception/LogicException.php�@��]�SML��;vendor/symfony/console/Exception/InvalidOptionException.php�@��]��;�7vendor/symfony/console/Exception/ExceptionInterface.php�@��]����U�=vendor/symfony/console/Exception/CommandNotFoundException.php�@��]����=vendor/symfony/console/Exception/InvalidArgumentException.php�@��]��u i�5vendor/symfony/console/Exception/RuntimeException.php�@��]��*b�(vendor/symfony/filesystem/Filesystem.php�e@��]�e<F�ʴ)vendor/symfony/filesystem/LockHandler.phpo
@��]o
��޴<vendor/symfony/filesystem/Exception/IOExceptionInterface.php�@��]�k/��:vendor/symfony/filesystem/Exception/ExceptionInterface.php�@��]�h���3vendor/symfony/filesystem/Exception/IOException.php�@��]�ё�A�=vendor/symfony/filesystem/Exception/FileNotFoundException.php�@��]��0z´Ivendor/justinrainbow/json-schema/src/JsonSchema/UriRetrieverInterface.php@��]5�|[�Xvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/UriRetrieverInterface.php�@��]�?�r@�Rvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/FileGetContents.phpj
@��]j
[}x#�Tvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/AbstractRetriever.phpr@��]r���M�Rvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/PredefinedArray.php@��]0�Gvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/Curl.phpo@��]o�n�Dvendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php�$@��]�$X�h��Cvendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriResolver.php;@��];���Jvendor/justinrainbow/json-schema/src/JsonSchema/SchemaStorageInterface.php"@��]"x&7�Avendor/justinrainbow/json-schema/src/JsonSchema/SchemaStorage.php+@��]+��_�Hvendor/justinrainbow/json-schema/src/JsonSchema/UriResolverInterface.php@��]I��Ѵ;vendor/justinrainbow/json-schema/src/JsonSchema/Rfc3339.phpv@��]vx�$��Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/SchemaConstraint.php�@��]��fK�Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/StringConstraint.php�@��]�~��G�Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php�"@��]�"ͥ#K�Svendor/justinrainbow/json-schema/src/JsonSchema/Constraints/UndefinedConstraint.php�>@��]�>o�5�Jvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Constraint.phpX@��]X��\vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/TypeCheckInterface.php�@��]�c>�ϴXvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/LooseTypeCheck.php@��]�
���Yvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/StrictTypeCheck.php+@��]+��QܴNvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/EnumConstraint.phpq@��]q�`� �Svendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstraintInterface.php@��]t�e�Tvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/CollectionConstraint.php�@��]�N���Nvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeConstraint.php&@��]&ٌ]�Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ObjectConstraint.php�@��]�Fgc��Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/NumberConstraint.php�
@��]�
��9|�Gvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.phpF@��]F:0�/�Nvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/BaseConstraint.php%@��]%��2��=vendor/justinrainbow/json-schema/src/JsonSchema/Validator.php�
@��]�
8���Fvendor/justinrainbow/json-schema/src/JsonSchema/Entity/JsonPointer.php>
@��]>
�W�Kvendor/justinrainbow/json-schema/src/JsonSchema/Iterator/ObjectIterator.php�
@��]�
H���Wvendor/justinrainbow/json-schema/src/JsonSchema/Exception/ResourceNotFoundException.phpT@��]T��:�Rvendor/justinrainbow/json-schema/src/JsonSchema/Exception/UriResolverException.phpJ@��]J��-�Pvendor/justinrainbow/json-schema/src/JsonSchema/Exception/ExceptionInterface.phpI@��]I%|��Wvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSourceUriException.php\@��]\�iR�Tvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaException.phpN@��]N��ݠ�^vendor/justinrainbow/json-schema/src/JsonSchema/Exception/UnresolvableJsonPointerException.php�@��]�.�mi�Tvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidConfigException.phpQ@��]Q�J�Qvendor/justinrainbow/json-schema/src/JsonSchema/Exception/ValidationException.php@��]E�B*�Vvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidArgumentException.phpy@��]y�WΖ�]vendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaMediaTypeException.phpW@��]W%*)ִNvendor/justinrainbow/json-schema/src/JsonSchema/Exception/RuntimeException.phpa@��]a`���Svendor/justinrainbow/json-schema/src/JsonSchema/Exception/JsonDecodingException.php�@��]�٣��4vendor/gettext/languages/bin/export-plural-rules.php*$@��]*${	Nb�+vendor/gettext/languages/src/autoloader.phpU@��]UV���)vendor/gettext/languages/src/Category.php@��]�1vendor/gettext/languages/src/FormulaConverter.php@��]��gW�-vendor/gettext/languages/src/Exporter/Php.phpx@��]x7�?X�2vendor/gettext/languages/src/Exporter/Exporter.php0@��]0�p�Ӵ,vendor/gettext/languages/src/Exporter/Po.php4@��]4+���4vendor/gettext/languages/src/Exporter/Prettyjson.php�@��]�����.vendor/gettext/languages/src/Exporter/Html.phpH	@��]H	{�Ÿ�.vendor/gettext/languages/src/Exporter/Json.phpb@��]bW@4��.vendor/gettext/languages/src/Exporter/Docs.php�@��]�S�
\�-vendor/gettext/languages/src/Exporter/Xml.php�@��]���1�)vendor/gettext/languages/src/CldrData.php�5@��]�5�[eA�)vendor/gettext/languages/src/Language.php�=@��]�=n]�e�)vendor/gettext/gettext/src/autoloader.php@��]A�p�$vendor/gettext/gettext/src/Merge.phpD@��]D�m6P�2vendor/gettext/gettext/src/TranslatorInterface.php%
@��]%
����/vendor/gettext/gettext/src/Generators/Xliff.php6@��]6þQ��,vendor/gettext/gettext/src/Generators/Po.php
@��]
��(S�<vendor/gettext/gettext/src/Generators/GeneratorInterface.php�@��]��g�.vendor/gettext/gettext/src/Generators/Json.phpO@��]Of�'N�7vendor/gettext/gettext/src/Generators/CsvDictionary.php�@��]�CT �8vendor/gettext/gettext/src/Generators/YamlDictionary.php�@��]�L5���-vendor/gettext/gettext/src/Generators/Csv.php�@��]���{�.vendor/gettext/gettext/src/Generators/Yaml.php�@��]�����2vendor/gettext/gettext/src/Generators/PhpArray.php�@��]��H�;�8vendor/gettext/gettext/src/Generators/JsonDictionary.php=@��]=��{�-vendor/gettext/gettext/src/Generators/Jed.phpp@��]p�,k.�3vendor/gettext/gettext/src/Generators/Generator.php�@��]��3�,vendor/gettext/gettext/src/Generators/Mo.php�@��]�0�д+vendor/gettext/gettext/src/Translations.php�@@��]�@�;��.vendor/gettext/gettext/src/Extractors/Twig.php<@��]<��K�/vendor/gettext/gettext/src/Extractors/Xliff.php�@��]�W}�,vendor/gettext/gettext/src/Extractors/Po.php�@��]��ԴAvendor/gettext/gettext/src/Extractors/ExtractorMultiInterface.php�@��]���]��.vendor/gettext/gettext/src/Extractors/Json.php.@��].�w=u�7vendor/gettext/gettext/src/Extractors/CsvDictionary.phpR@��]RW�G��8vendor/gettext/gettext/src/Extractors/YamlDictionary.phpQ@��]Q�t�s�/vendor/gettext/gettext/src/Extractors/Blade.phpj@��]j{�7�-vendor/gettext/gettext/src/Extractors/Csv.php@��]�m8´.vendor/gettext/gettext/src/Extractors/Yaml.php]@��]]jя"�2vendor/gettext/gettext/src/Extractors/PhpArray.phpG@��]G�/DѴ<vendor/gettext/gettext/src/Extractors/ExtractorInterface.php
@��]
p}Yд/vendor/gettext/gettext/src/Extractors/VueJs.php�5@��]�5��QD�8vendor/gettext/gettext/src/Extractors/JsonDictionary.php(@��](�-�*�1vendor/gettext/gettext/src/Extractors/PhpCode.php?@��]?…�'�-vendor/gettext/gettext/src/Extractors/Jed.php@��]{c��,vendor/gettext/gettext/src/Extractors/Mo.phpm@��]me�p�3vendor/gettext/gettext/src/Extractors/Extractor.php�@��]�{�n�0vendor/gettext/gettext/src/Extractors/JsCode.php(@��](8�vU�)vendor/gettext/gettext/src/Translator.php�@��]�J�>�*vendor/gettext/gettext/src/Translation.php�+@��]�+��9�0vendor/gettext/gettext/src/GettextTranslator.php�@��]�޼�|�3vendor/gettext/gettext/src/translator_functions.php0@��]0��ȴ-vendor/gettext/gettext/src/BaseTranslator.php�@��]�3v�Ĵ?vendor/gettext/gettext/src/Utils/MultidimensionalArrayTrait.php�@��]��i$a�4vendor/gettext/gettext/src/Utils/DictionaryTrait.php�@��]��'��1vendor/gettext/gettext/src/Utils/StringReader.php�@��]�P����-vendor/gettext/gettext/src/Utils/CsvTrait.php�@��]�S�*�3vendor/gettext/gettext/src/Utils/ParsedFunction.phpq@��]qB�Wc�5vendor/gettext/gettext/src/Utils/FunctionsScanner.phpH@��]HD�|�7vendor/gettext/gettext/src/Utils/JsFunctionsScanner.php�$@��]�$(��d�:vendor/gettext/gettext/src/Utils/HeadersExtractorTrait.php~@��]~���д:vendor/gettext/gettext/src/Utils/HeadersGeneratorTrait.php7@��]76ÉG�8vendor/gettext/gettext/src/Utils/PhpFunctionsScanner.php�@��]�©m,�2vendor/gettext/gettext/src/Utils/ParsedComment.php\@��]\�ܺC�*vendor/mck89/peast/lib/Peast/Traverser.php�@��]���J�8vendor/mck89/peast/lib/Peast/Syntax/CommentsRegistry.php)@��])��|�/vendor/mck89/peast/lib/Peast/Syntax/Scanner.php�@��]��~[N�+vendor/mck89/peast/lib/Peast/Syntax/LSM.php�@��]�7O��-vendor/mck89/peast/lib/Peast/Syntax/Token.php�
@��]�
;/V�6vendor/mck89/peast/lib/Peast/Syntax/SourceLocation.php�@��]��جA�6vendor/mck89/peast/lib/Peast/Syntax/ES2019/Scanner.php�@��]���ϴ5vendor/mck89/peast/lib/Peast/Syntax/ES2019/Parser.php�@��]�� l�5vendor/mck89/peast/lib/Peast/Syntax/EventsEmitter.php@��]�{u�=vendor/mck89/peast/lib/Peast/Syntax/Node/UpdateExpression.php;	@��];	3m��9vendor/mck89/peast/lib/Peast/Syntax/Node/ForStatement.phpC@��]C1���>vendor/mck89/peast/lib/Peast/Syntax/Node/ModuleDeclaration.php�@��]��nt��4vendor/mck89/peast/lib/Peast/Syntax/Node/Literal.php@��]�5ʴ;vendor/mck89/peast/lib/Peast/Syntax/Node/ThisExpression.php�@��]��%a��=vendor/mck89/peast/lib/Peast/Syntax/Node/BinaryExpression.phpW@��]W���[�:vendor/mck89/peast/lib/Peast/Syntax/Node/SpreadElement.php�@��]��%��?vendor/mck89/peast/lib/Peast/Syntax/Node/SequenceExpression.php�@��]�����Cvendor/mck89/peast/lib/Peast/Syntax/Node/ImportDefaultSpecifier.php�@��]���J�;vendor/mck89/peast/lib/Peast/Syntax/Node/NumericLiteral.php�@��]��&O�6vendor/mck89/peast/lib/Peast/Syntax/Node/ClassBody.phpH@��]H�hZ�8vendor/mck89/peast/lib/Peast/Syntax/Node/RestElement.php�@��]�č���<vendor/mck89/peast/lib/Peast/Syntax/Node/ReturnStatement.php�@��]��:C�4vendor/mck89/peast/lib/Peast/Syntax/Node/Pattern.php�@��]�����6vendor/mck89/peast/lib/Peast/Syntax/Node/Statement.php�@��]�����=vendor/mck89/peast/lib/Peast/Syntax/Node/MemberExpression.php�	@��]�	|K
�Evendor/mck89/peast/lib/Peast/Syntax/Node/ImportNamespaceSpecifier.php�@��]�M�c�>vendor/mck89/peast/lib/Peast/Syntax/Node/ContinueStatement.php�@��]���ZY�Evendor/mck89/peast/lib/Peast/Syntax/Node/TaggedTemplateExpression.php\@��]\����:vendor/mck89/peast/lib/Peast/Syntax/Node/WithStatement.phpd@��]d�|J�:vendor/mck89/peast/lib/Peast/Syntax/Node/StringLiteral.php�	@��]�	5�v�;vendor/mck89/peast/lib/Peast/Syntax/Node/ForInStatement.php	@��]	P㛴<vendor/mck89/peast/lib/Peast/Syntax/Node/ModuleSpecifier.phpO@��]O9EM�<vendor/mck89/peast/lib/Peast/Syntax/Node/SwitchStatement.php�@��]����;vendor/mck89/peast/lib/Peast/Syntax/Node/CallExpression.phpe@��]e!��8vendor/mck89/peast/lib/Peast/Syntax/Node/CatchClause.php�@��]�����<vendor/mck89/peast/lib/Peast/Syntax/Node/ArrayExpression.php@��]t8��8vendor/mck89/peast/lib/Peast/Syntax/Node/IfStatement.php�
@��]�
{���Dvendor/mck89/peast/lib/Peast/Syntax/Node/ParenthesizedExpression.php�@��]�(��Ӵ8vendor/mck89/peast/lib/Peast/Syntax/Node/Declaration.php�@��]�8�4�>vendor/mck89/peast/lib/Peast/Syntax/Node/DebuggerStatement.php�@��]�di��=vendor/mck89/peast/lib/Peast/Syntax/Node/MethodDefinition.php�@��]��u�Ѵ;vendor/mck89/peast/lib/Peast/Syntax/Node/BooleanLiteral.phpI@��]Im��|�Avendor/mck89/peast/lib/Peast/Syntax/Node/ExportAllDeclaration.phpa@��]a�~&
�=vendor/mck89/peast/lib/Peast/Syntax/Node/ObjectExpression.php�@��]�'!E�7vendor/mck89/peast/lib/Peast/Syntax/Node/SwitchCase.phpX@��]X�E�<vendor/mck89/peast/lib/Peast/Syntax/Node/UnaryExpression.phpR	@��]R	~uҴ2vendor/mck89/peast/lib/Peast/Syntax/Node/Super.php�@��]�RC�(�;vendor/mck89/peast/lib/Peast/Syntax/Node/EmptyStatement.php�@��]�n���;vendor/mck89/peast/lib/Peast/Syntax/Node/ForOfStatement.php�@��]���,�5vendor/mck89/peast/lib/Peast/Syntax/Node/Property.php@��]��
��<vendor/mck89/peast/lib/Peast/Syntax/Node/ExportSpecifier.php�@��]�L���<vendor/mck89/peast/lib/Peast/Syntax/Node/TemplateElement.php�	@��]�	7��Ѵ;vendor/mck89/peast/lib/Peast/Syntax/Node/BreakStatement.phpy@��]y��2�4vendor/mck89/peast/lib/Peast/Syntax/Node/Program.php�@��]�:�A�>vendor/mck89/peast/lib/Peast/Syntax/Node/ImportDeclaration.php�@��]�X�9�<vendor/mck89/peast/lib/Peast/Syntax/Node/YieldExpression.php3@��]3��5��:vendor/mck89/peast/lib/Peast/Syntax/Node/ObjectPattern.php�@��]����ܴCvendor/mck89/peast/lib/Peast/Syntax/Node/ExportNamedDeclaration.php�	@��]�	�����?vendor/mck89/peast/lib/Peast/Syntax/Node/AssignmentProperty.php�@��]����Y�<vendor/mck89/peast/lib/Peast/Syntax/Node/ImportSpecifier.php�@��]�H�꽴=vendor/mck89/peast/lib/Peast/Syntax/Node/DoWhileStatement.php2@��]2�����4vendor/mck89/peast/lib/Peast/Syntax/Node/Comment.phpX@��]X�X �7vendor/mck89/peast/lib/Peast/Syntax/Node/Identifier.php@��]���:vendor/mck89/peast/lib/Peast/Syntax/Node/RegExpLiteral.phpX
@��]X
�D49�3vendor/mck89/peast/lib/Peast/Syntax/Node/Class_.phpy@��]yn�7vendor/mck89/peast/lib/Peast/Syntax/Node/Expression.php�@��]�zw�!�9vendor/mck89/peast/lib/Peast/Syntax/Node/MetaProperty.php@��]��:�<vendor/mck89/peast/lib/Peast/Syntax/Node/AwaitExpression.phpU@��]U���Bvendor/mck89/peast/lib/Peast/Syntax/Node/ConditionalExpression.php	@��]	�����8vendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXText.php�@��]�?��H�Cvendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXSpreadAttribute.php�@��]�=��W�>vendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXIdentifier.php�@��]��h�v�Dvendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXMemberExpression.php^@��]^1*G��<vendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXFragment.php�
@��]�
�P�״Bvendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXOpeningElement.php@��]�DDd�;vendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXElement.php�
@��]�
��!H�?vendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXSpreadChild.php�@��]��׏ʹCvendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXClosingFragment.php�@��]�|<_�Bvendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXClosingElement.php�@��]��f�=vendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXAttribute.phpx@��]xb.�i�Cvendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXEmptyExpression.php�@��]�[-�S�Cvendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXBoundaryElement.phpz@��]z
KN�Gvendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXExpressionContainer.php�@��]��]O�Cvendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXOpeningFragment.php�@��]��p�Bvendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXNamespacedName.php|@��]|��ʹ=vendor/mck89/peast/lib/Peast/Syntax/Node/ClassDeclaration.php�@��]�%^�j�:vendor/mck89/peast/lib/Peast/Syntax/Node/NewExpression.php�@��]��>��<vendor/mck89/peast/lib/Peast/Syntax/Node/TemplateLiteral.phpN
@��]N
��D�9vendor/mck89/peast/lib/Peast/Syntax/Node/TryStatement.php	@��]	xk���;vendor/mck89/peast/lib/Peast/Syntax/Node/WhileStatement.php*@��]*ꮓ��@vendor/mck89/peast/lib/Peast/Syntax/Node/FunctionDeclaration.php�@��]�D*�Avendor/mck89/peast/lib/Peast/Syntax/Node/AssignmentExpression.php2	@��]2	��T��?vendor/mck89/peast/lib/Peast/Syntax/Node/FunctionExpression.php�@��]����U�Evendor/mck89/peast/lib/Peast/Syntax/Node/ExportDefaultDeclaration.php@��]3"	�Dvendor/mck89/peast/lib/Peast/Syntax/Node/ArrowFunctionExpression.php�@��]�х��6vendor/mck89/peast/lib/Peast/Syntax/Node/Function_.php�
@��]�
w�v�1vendor/mck89/peast/lib/Peast/Syntax/Node/Node.php�!@��]�!���?vendor/mck89/peast/lib/Peast/Syntax/Node/VariableDeclarator.php�@��]��b�ٴ@vendor/mck89/peast/lib/Peast/Syntax/Node/VariableDeclaration.phpc@��]c��I�8vendor/mck89/peast/lib/Peast/Syntax/Node/NullLiteral.php@��]�K��>vendor/mck89/peast/lib/Peast/Syntax/Node/AssignmentPattern.php�@��]��RY�;vendor/mck89/peast/lib/Peast/Syntax/Node/ThrowStatement.phpl@��]l�����;vendor/mck89/peast/lib/Peast/Syntax/Node/BlockStatement.php�@��]�����@vendor/mck89/peast/lib/Peast/Syntax/Node/ExpressionStatement.php�@��]�[�m�>vendor/mck89/peast/lib/Peast/Syntax/Node/LogicalExpression.phpZ@��]Z��S��9vendor/mck89/peast/lib/Peast/Syntax/Node/ArrayPattern.php�@��]��ʑ(�<vendor/mck89/peast/lib/Peast/Syntax/Node/ClassExpression.php�@��]�p~�ô=vendor/mck89/peast/lib/Peast/Syntax/Node/LabeledStatement.php�@��]�(���.vendor/mck89/peast/lib/Peast/Syntax/Parser.phpJ#@��]J#v6ʴ3vendor/mck89/peast/lib/Peast/Syntax/JSX/Scanner.phpe@��]e���Ѵ2vendor/mck89/peast/lib/Peast/Syntax/JSX/Parser.phpr6@��]r6q����6vendor/mck89/peast/lib/Peast/Syntax/ES2016/Scanner.php@��]��ȷ�5vendor/mck89/peast/lib/Peast/Syntax/ES2016/Parser.phpR@��]Ra	�6�6vendor/mck89/peast/lib/Peast/Syntax/ES2018/Scanner.php�@��]��q�5vendor/mck89/peast/lib/Peast/Syntax/ES2018/Parser.php6@��]6P��T�-vendor/mck89/peast/lib/Peast/Syntax/Utils.php?@��]?1�U�9vendor/mck89/peast/lib/Peast/Syntax/EncodingException.php�@��]�L�p�6vendor/mck89/peast/lib/Peast/Syntax/ES2015/Scanner.php	@��]	HiX�5vendor/mck89/peast/lib/Peast/Syntax/ES2015/Parser.phpV�@��]V�=���1vendor/mck89/peast/lib/Peast/Syntax/Exception.phpD@��]DB͌�6vendor/mck89/peast/lib/Peast/Syntax/ES2017/Scanner.php>@��]>D����5vendor/mck89/peast/lib/Peast/Syntax/ES2017/Parser.php}S@��]}Sp��0vendor/mck89/peast/lib/Peast/Syntax/Position.php�@��]��#C.�6vendor/mck89/peast/lib/Peast/Formatter/PrettyPrint.phpr@��]r�j�ϴ2vendor/mck89/peast/lib/Peast/Formatter/Compact.php�@��]���t�/vendor/mck89/peast/lib/Peast/Formatter/Base.php�@��]�|n›�3vendor/mck89/peast/lib/Peast/Formatter/Expanded.php�@��]���x�&vendor/mck89/peast/lib/Peast/Peast.phpC@��]C�무)vendor/mck89/peast/lib/Peast/Renderer.phpٖ@��]ٖ�b4�+vendor/wp-cli/wp-cli/templates/man.mustache�@��]��I<�2vendor/wp-cli/wp-cli/templates/man-params.mustache@��]��C�9vendor/wp-cli/config-command/templates/wp-config.mustache�	@��]�	����6vendor/wp-cli/core-command/templates/versions.mustache�@��]�SU"��?vendor/wp-cli/extension-command/templates/theme-status.mustachex@��]xǁgѴ@vendor/wp-cli/extension-command/templates/plugin-status.mustache�@��]�o[�5�;vendor/wp-cli/scaffold-command/templates/block-php.mustache'@��]'�o��Bvendor/wp-cli/scaffold-command/templates/plugin-gitignore.mustache^@��]^�怴8vendor/wp-cli/scaffold-command/templates/plugin.mustacheq@��]qG�qҴDvendor/wp-cli/scaffold-command/templates/plugin-test-sample.mustache'@��]'\~��?vendor/wp-cli/scaffold-command/templates/plugin-gitlab.mustache\@��]\��?vendor/wp-cli/scaffold-command/templates/plugin-circle.mustacheB
@��]B
/�G2�Bvendor/wp-cli/scaffold-command/templates/plugin-bootstrap.mustaches@��]sԨ�v�Avendor/wp-cli/scaffold-command/templates/block-style-css.mustache@��]��شCvendor/wp-cli/scaffold-command/templates/plugin-distignore.mustache@@��]@pU%~�>vendor/wp-cli/scaffold-command/templates/theme-status.mustachex@��]xǁgѴ<vendor/wp-cli/scaffold-command/templates/install-wp-tests.sh�@��]�a��*�6vendor/wp-cli/scaffold-command/templates/.editorconfig�@��]��hѴ?vendor/wp-cli/scaffold-command/templates/plugin-readme.mustache@��]b��Cvendor/wp-cli/scaffold-command/templates/theme-test-sample.mustache&@��]&���Bvendor/wp-cli/scaffold-command/templates/block-editor-css.mustache�@��]��,V]�Gvendor/wp-cli/scaffold-command/templates/child_theme_functions.mustacheS@��]S �W�Dvendor/wp-cli/scaffold-command/templates/post_type_extended.mustache�@��]��,�@vendor/wp-cli/scaffold-command/templates/block-index-js.mustache�
@��]�
"��0�:vendor/wp-cli/scaffold-command/templates/taxonomy.mustacheU
@��]U
��8�=vendor/wp-cli/scaffold-command/templates/child_theme.mustache�@��]����Cvendor/wp-cli/scaffold-command/templates/taxonomy_extended.mustache�@��]���0��;vendor/wp-cli/scaffold-command/templates/post_type.mustache�
@��]�
��x��8vendor/wp-cli/scaffold-command/templates/.phpcs.xml.distg@��]g��4�Avendor/wp-cli/scaffold-command/templates/plugin-packages.mustache^@��]^�5�+�9vendor/wp-cli/scaffold-command/templates/phpunit.xml.dist�@��]�%�F�?vendor/wp-cli/scaffold-command/templates/plugin-travis.mustacheZ@��]Z#(�Bvendor/wp-cli/scaffold-command/templates/plugin-bitbucket.mustache�@��]�ZBgm�Avendor/wp-cli/scaffold-command/templates/theme-bootstrap.mustache�@��]��D���Bvendor/wp-cli/scaffold-command/templates/plugin-gruntfile.mustache�@��]��T��vendor/autoload.php�@��]��4�� vendor/composer/composer/LICENSE,@��],V�g��1vendor/composer/composer/res/composer-schema.json�@��]�� �<vendor/rmccue/requests/library/Requests/Transport/cacert.pem�~@��]�~a#^�&vendor/wp-cli/wp-cli/COMPOSER_VERSIONS@��]���G�vendor/wp-cli/wp-cli/VERSION@��]:��S�<?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 (http://benramsey.com)
 * @license http://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 = array();

        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

return array(
	'path'              => array(
		'runtime' => '=<path>',
		'file'    => '<path>',
		'desc'    => 'Path to the WordPress files.',
	),

	'url'               => array(
		'runtime' => '=<url>',
		'file'    => '<url>',
		'desc'    => 'Pretend request came from given URL. In multisite, this argument is how the target site is specified.',
	),

	'ssh'               => array(
		'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'              => array(
		'runtime' => '=<http>',
		'file'    => '<http>',
		'desc'    => 'Perform operation against a remote WordPress installation over HTTP.',
	),

	'blog'              => array(
		'deprecated' => 'Use --url instead.',
		'runtime'    => '=<url>',
	),

	'user'              => array(
		'runtime' => '=<id|login|email>',
		'file'    => '<id|login|email>',
		'desc'    => 'Set the WordPress user.',
	),

	'skip-plugins'      => array(
		'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'       => array(
		'runtime' => '[=<themes>]',
		'file'    => '<list>',
		'desc'    => 'Skip loading all themes, or a comma-separated list of themes.',
		'default' => '',
	),

	'skip-packages'     => array(
		'runtime' => '',
		'file'    => '<bool>',
		'desc'    => 'Skip loading all installed packages.',
		'default' => false,
	),

	'require'           => array(
		'runtime'  => '=<path>',
		'file'     => '<path>',
		'desc'     => 'Load PHP file before running the command (may be used more than once).',
		'multiple' => true,
		'default'  => array(),
	),

	'disabled_commands' => array(
		'file'    => '<list>',
		'default' => array(),
		'desc'    => '(Sub)commands to disable.',
	),

	'color'             => array(
		'runtime' => true,
		'file'    => '<bool>',
		'default' => 'auto',
		'desc'    => 'Whether to colorize the output.',
	),

	'debug'             => array(
		'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'            => array(
		'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'             => array(
		'runtime' => '',
		'file'    => '<bool>',
		'default' => false,
		'desc'    => 'Suppress informational messages.',
	),

	'apache_modules'    => array(
		'file'     => '<list>',
		'desc'     => 'List of Apache Modules that are to be reported as loaded.',
		'multiple' => true,
		'default'  => array(),
	),

	# --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'        => array(
		'file'    => false, # Explicit. Just in case the default changes.
		'runtime' => '',
		'hidden'  => true,
	),

);
<?php

// Utilities that do NOT depend on WordPress code.

namespace WP_CLI\Utils;

use ArrayIterator;
use cli;
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\Formatter;
use WP_CLI\Inflector;
use WP_CLI\Iterators\Transform;

const PHAR_STREAM_PREFIX = 'phar://';

function inside_phar() {
	return 0 === strpos( WP_CLI_ROOT, PHAR_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_exists( WP_CLI_ROOT . '/vendor/autoload.php' ) ) {
			require WP_CLI_ROOT . '/vendor/autoload.php';
		} elseif ( file_exists( dirname( dirname( WP_CLI_ROOT ) ) . '/autoload.php' ) ) {
			require dirname( dirname( WP_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 ) {
		fwrite( STDERR, "Internal error: Can't find Composer autoloader.\nTry running: composer install\n" );
		exit( 3 );
	}
}

function get_vendor_paths() {
	$vendor_paths        = array(
		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_decode( file_get_contents( $maybe_composer_json ) );
		if ( ! empty( $composer->config ) && ! empty( $composer->config->{'vendor-dir'} ) ) {
			array_unshift( $vendor_paths, WP_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 Either a plain array or another iterator.
 * @param callback     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_slice( func_get_args(), 1 ) 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 The files (or file) to search for.
 * @param string|null  The directory to start searching from; defaults to CWD.
 * @param callable     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
 * @return string
 */
function args_to_str( $args ) {
	return ' ' . implode( ' ', array_map( 'escapeshellarg', $args ) );
}

/**
 * Composes associative arguments into a command string.
 *
 * @param array
 * @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(
					array(
						$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() < 2 ) {
		trigger_error( 'esc_cmd() requires at least two arguments.', E_USER_WARNING );
	}

	$args = func_get_args();

	$cmd = array_shift( $args );

	return vsprintf( $cmd, array_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_exists( getenv( 'WP_CONFIG_PATH' ) ) ) {
			$path = getenv( 'WP_CONFIG_PATH' );
		} elseif ( file_exists( ABSPATH . 'wp-config.php' ) ) {
			$path = ABSPATH . 'wp-config.php';
		} elseif ( file_exists( dirname( ABSPATH ) . '/wp-config.php' ) && ! file_exists( dirname( ABSPATH ) . '/wp-settings.php' ) ) {
			$path = dirname( ABSPATH ) . '/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.
 * @return null
 */
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 = array() ) {
	if ( ! empty( $headers ) ) {
		fputcsv( $fd, $headers );
	}

	foreach ( $rows as $row ) {
		if ( ! empty( $headers ) ) {
			$row = pick_fields( $row, $headers );
		}

		fputcsv( $fd, array_values( $row ) );
	}
}

/**
 * Pick fields from an associative array or object.
 *
 * @param  array|object Associative array or object to pick fields from.
 * @param  array List of fields to pick.
 * @return array
 */
function pick_fields( $item, $fields ) {
	$values = array();

	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  $content  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 .= '-' . substr( md5( mt_rand() ), 0, 6 ); // 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 = array( STDIN, STDOUT, STDERR );
	$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 MySQL host string, as defined in wp-config.php
 *
 * @return array
 */
function mysql_host_to_cli_args( $raw_host ) {
	$assoc_args = array();

	/**
	 * If the host string begins with 'p:' for a persistent db connection,
	 * replace 'p:' with nothing.
	 */
	if ( substr( $raw_host, 0, 2 ) === 'p:' ) {
		$raw_host = substr_replace( $raw_host, '', 0, 2 );
	}

	$host_parts = explode( ':', $raw_host );
	if ( count( $host_parts ) === 2 ) {
		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;
}

function run_mysql_command( $cmd, $assoc_args, $descriptors = null ) {
	check_proc_available( 'run_mysql_command' );

	if ( ! $descriptors ) {
		$descriptors = array( STDIN, STDOUT, STDERR );
	}

	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_args, mysql_host_to_cli_args( $assoc_args['host'] ) );
	}

	$pass = $assoc_args['pass'];
	unset( $assoc_args['pass'] );

	$old_pass = getenv( 'MYSQL_PWD' );
	putenv( 'MYSQL_PWD=' . $pass );

	$final_cmd = force_env_on_nix_systems( $cmd ) . assoc_args_to_str( $assoc_args );

	$proc = proc_open_compat( $final_cmd, $descriptors, $pipes );
	if ( ! $proc ) {
		exit( 1 );
	}

	$r = proc_close( $proc );

	putenv( 'MYSQL_PWD=' . $old_pass );

	if ( $r ) {
		exit( $r );
	}
}

/**
 * Render PHP or other types of files using Mustache templates.
 *
 * IMPORTANT: Automatic HTML escaping is disabled!
 */
function mustache_render( $template_name, $data = array() ) {
	if ( ! file_exists( $template_name ) ) {
		$template_name = WP_CLI_ROOT . "/templates/$template_name";
	}

	$template = file_get_contents( $template_name );

	$m = new Mustache_Engine(
		array(
			'escape' => function ( $val ) {
				return $val; },
		)
	);

	return $m->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|WP_CLI\NoOp
 */
function make_progress_bar( $message, $count, $interval = 100 ) {
	if ( cli\Shell::isPiped() ) {
		return new WP_CLI\NoOp();
	}

	return new cli\progress\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' )
		&& (
			-1 === $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( $url, PHP_URL_SCHEME, false ) ) {
		// phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url -- Own version based on WP one.
		$url_parts = parse_url( 'http://' . $url, $component, false );
	}

	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 : strtoupper( substr( PHP_OS, 0, 3 ) ) === 'WIN';
}

/**
 * Replace magic constants in some PHP source code.
 *
 * @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 ) {
	$replacements = array(
		'__FILE__' => "'$path'",
		'__DIR__'  => "'" . dirname( $path ) . "'",
	);

	$old = array_keys( $replacements );
	$new = array_values( $replacements );

	return str_replace( $old, $new, $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
 * @return object
 * @throws RuntimeException If the request failed.
 * @throws WP_CLI\ExitException If the request failed and $halt_on_error is true.
 */
function http_request( $method, $url, $data = null, $headers = array(), $options = array() ) {

	$cert_path     = '/rmccue/requests/library/Requests/Transport/cacert.pem';
	$halt_on_error = ! isset( $options['halt_on_error'] ) || (bool) $options['halt_on_error'];
	if ( inside_phar() ) {
		// cURL can't read Phar archives
		$options['verify'] = extract_from_phar(
			WP_CLI_VENDOR_DIR . $cert_path
		);
	} else {
		foreach ( get_vendor_paths() as $vendor_path ) {
			if ( file_exists( $vendor_path . $cert_path ) ) {
				$options['verify'] = $vendor_path . $cert_path;
				break;
			}
		}
		if ( empty( $options['verify'] ) ) {
			$error_msg = 'Cannot find SSL certificate.';
			if ( $halt_on_error ) {
				WP_CLI::error( $error_msg );
			}
			throw new RuntimeException( $error_msg );
		}
	}

	if ( ! class_exists( 'Requests_Hooks' ) ) {
		// Autoloader for the Requests library has not been registered yet.
		Requests::register_autoloader();
	}

	try {
		return Requests::request( $url, $headers, $data, $method, $options );
	} catch ( Requests_Exception $ex ) {
		// CURLE_SSL_CACERT_BADFILE only defined for PHP >= 7.
		if ( 'curlerror' !== $ex->getType() || ! in_array( curl_errno( $ex->getData() ), array( CURLE_SSL_CONNECT_ERROR, CURLE_SSL_CERTPROBLEM, 77 /*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_msg, null, $ex );
		}
		// Handle SSL certificate issues gracefully
		WP_CLI::warning( sprintf( "Re-trying without verify after failing to get verified url '%s' %s.", $url, $ex->getMessage() ) );
		$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_msg, null, $ex );
		}
	}
}

/**
 * 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, 2 );
	$current_version[0] = explode( '.', $current_version[0] );

	switch ( $new_version ) {
		case 'same':
			// do nothing
			break;

		case 'patch':
			$current_version[0][2]++;

			$current_version = array( $current_version[0] ); // Drop possible pre-release info.
			break;

		case 'minor':
			$current_version[0][1]++;
			$current_version[0][2] = 0;

			$current_version = array( $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 = array( $current_version[0] ); // Drop possible pre-release info.
			break;

		default: // not a keyword
			$current_version = array( array( $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 $name '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 ( ! is_null( $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( $path, 1, 1 ) ) {
		$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 = trailingslashit( sys_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 = -1 ) {
	preg_match( '#^((docker|docker\-compose|ssh|vagrant):)?(([^@:]+)@)?([^:/~]+)(:([\d]*))?((/|~)(.+))?$#', $url, $matches );
	$bits = array();
	foreach ( array(
		2 => 'scheme',
		4 => 'user',
		5 => 'host',
		7 => 'port',
		8 => '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 > 1 ? 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] : array();
	$argv = array_map(
		static function ( $arg ) {
			foreach ( array( '"', "'" ) as $char ) {
				if ( substr( $arg, 0, 1 ) === $char && substr( $arg, -1 ) === $char ) {
					$arg = substr( $arg, 1, -1 );
					break;
				}
			}
				return $arg;
		},
		$argv
	);
	return $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( \basename( str_replace( array( '%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_pipe, FILTER_VALIDATE_BOOLEAN );
	}

	return ( function_exists( 'posix_isatty' ) && ! posix_isatty( STDOUT ) );
}

/**
 * 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 = array();

	foreach ( (array) $paths as $path ) {
		$matching = array( $path );

		if ( preg_match( '/[' . preg_quote( '*?[]{}!', '/' ) . ']/', $path ) ) {
			$matching = $glob_func( $path, $flags ) ?: array();
		}
		$expanded = array_merge( $expanded, $matching );
	}

	return array_values( array_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 (http://framework.zend.com/)
 *
 * @link      http://github.com/zendframework/zf2 for the canonical source repository
 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
 * @license   http://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 ] && 0 === $depth-- ) || ( ',' === $pattern[ $current ] && 0 === $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 + 1 );
	if ( null === $next ) {
		return glob( $pattern );
	}

	$rest = $next;

	// Point `$rest` to matching closing brace.
	while ( '}' !== $pattern[ $rest ] ) {
		$rest = $next_brace_sub( $pattern, $rest + 1 );
		if ( null === $rest ) {
			return glob( $pattern );
		}
	}

	$paths = array();
	$p     = $begin + 1;

	// For each comma-separated subpattern.
	do {
		$subpattern = substr( $pattern, 0, $begin )
					. substr( $pattern, $p, $next - $p )
					. substr( $pattern, $rest + 1 );

		$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_values( array_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 = 2 ) {

	$suggestion_map = array(
		'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 ], $options, true ) ) {
		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
	);
}

/**
 * Check whether a given Command object is part of the bundled set of
 * commands.
 *
 * This function accepts both a fully qualified class name as a string as
 * well as an object that extends `WP_CLI\Dispatcher\CompositeCommand`.
 *
 * @param \WP_CLI\Dispatcher\CompositeCommand|string $command
 * @return bool
 */
function is_bundled_command( $command ) {
	static $classes;

	if ( null === $classes ) {
		$classes = array();
		// TODO: This needs to be rebuilt.
		// $class_map = WP_CLI_VENDOR_DIR . '/composer/autoload_commands_classmap.php';
		// if ( file_exists( WP_CLI_VENDOR_DIR . '/composer/' ) ) {
		// 	$classes = include $class_map;
		// }
		$classes = array( 'CLI_Command' => true );
	}

	if ( is_object( $command ) ) {
		$command = get_class( $command );
	}

	return is_string( $command )
		? array_key_exists( $command, $classes )
		: false;
}

/**
 * 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 ( 0 === strncmp( $command, $env_prefix, $env_prefix_len ) ) {
			$command = substr( $command, $env_prefix_len );
		}
	} elseif ( 0 !== 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::error( sprintf( "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 = array(
		'reset' => 'reset',
	);
	if ( isset( $irregular[ $verb ] ) ) {
		return $irregular[ $verb ];
	}
	$last = substr( $verb, -1 );
	if ( 'e' === $last ) {
		$verb = substr( $verb, 0, -1 );
	} elseif ( 'y' === $last && ! preg_match( '/[aeiou]y$/', $verb ) ) {
		$verb = substr( $verb, 0, -1 ) . '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;
	}

	// Available since PHP 5.4.
	if ( defined( 'PHP_BINARY' ) ) {
		return PHP_BINARY;
	}

	if ( @is_executable( PHP_BINDIR . '/php' ) ) {
		return PHP_BINDIR . '/php';
	}

	if ( is_windows() && @is_executable( PHP_BINDIR . '/php.exe' ) ) {
		return PHP_BINDIR . '/php.exe';
	}

	return 'php';
}

/**
 * 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 $command        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 $command 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( $cmd, strlen( $matches[0] ) );
			if ( null === $env ) {
				$env = array();
			}
			$env[ $matches[1] ] = isset( $matches[2][0] ) && '"' === $matches[2][0] ? substr( $matches[2], 1, -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], array( '{', '[' ), 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 (string) sprintf(
					'%s->%s()',
					get_class( $callable[0] ),
					$callable[1]
				);
			}

			return (string) sprintf( '%s::%s()', $callable[0], $callable[1] );
		}

		return (string) gettype( $callable );
	} catch ( Exception $exception ) {
		return 'Callable of unknown type';
	}
}

/**
 * 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 ( 1 === $count ) {
		return $noun;
	}

	return Inflector::pluralize( $noun );
}
<?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 = array();

	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( -1 );
}

if ( version_compare( PHP_VERSION, '5.4.0', '<' ) ) {
	printf( "Error: WP-CLI requires PHP %s or newer. You are running version %s.\n", '5.4.0', PHP_VERSION );
	die( -1 );
}

define( 'WP_CLI_ROOT', dirname( __DIR__ ) );

require_once WP_CLI_ROOT . '/php/wp-cli.php';

<?php


namespace WP_CLI;

use WP_CLI;
use WP_CLI\Utils;

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

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

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

	/**
	 * @param FileCache $cache
	 */
	public function __construct( FileCache $cache ) {
		$this->cache = $cache;

		// hook into wp http api
		add_filter( 'pre_http_request', array( $this, 'filter_pre_http_request' ), 10, 3 );
		add_filter( 'http_response', array( $this, 'filter_http_response' ), 10, 3 );
	}

	/**
	 * 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::log( sprintf( 'Using cached file \'%s\'...', $filename ) );
			if ( copy( $filename, $args['filename'] ) ) {
				// simulate successful download response
				return array(
					'response' => array(
						'code'    => 200,
						'message' => 'OK',
					),
					'filename' => $args['filename'],
				);
			}

			WP_CLI::error( sprintf( '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
	 */
	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 = pathinfo( Utils\parse_url( $url, PHP_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\Utils;
use WP_CLI\Dispatcher;
use WP_CLI\Dispatcher\CompositeCommand;

/**
 * Performs the execution of a command.
 *
 * @package WP_CLI
 */
class Runner {

	private $global_config_path;
	private $project_config_path;

	private $config;
	private $extra_config;

	private $alias;

	private $aliases;

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

	private $colorize = false;

	private $early_invoke = array();

	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;
	}

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

	/**
	 * 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.
	 *
	 * @return string|false
	 */
	public function get_global_config_path() {

		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 ( 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 = array(
			'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\trailingslashit( getenv( '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( array( '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_path( Utils\trailingslashit( $path ) ) );
		} elseif ( ! is_null( $path ) ) {
			WP_CLI::error_multi_line(
				array(
					'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.' );
			}
		}

		if ( isset( $url ) ) {
			return $url;
		}

		return false;
	}

	private function cmd_starts_with( $prefix ) {
		return array_slice( $this->arguments, 0, count( $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 = array();

		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 ) > 1 ) {
					$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 array( $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 = array(), $options = array() ) {
		WP_CLI::do_hook( 'before_run_command' );

		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 = array();

		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 ( WP_CLI\Iterators\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( array( 'help' ) ) ) {
			// Help couldn't find the command so exit with suggestion.
			$suggestion_or_disabled = $this->find_command_to_run( array_slice( $this->arguments, 1 ) );
			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'], 1 );

		if ( $this->alias && ! empty( $wp_args[0] ) && $this->alias === $wp_args[0] ) {
			array_shift( $wp_args );
			$runtime_alias = array();
			foreach ( $this->aliases[ $this->alias ] as $key => $value ) {
				if ( 'ssh' === $key ) {
					continue;
				}
				$runtime_alias[ $key ] = $value;
			}
			if ( ! empty( $runtime_alias ) ) {
				$encoded_alias = json_encode(
					array(
						$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 ( array( '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_isatty( STDOUT );

		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( $cached, true );
			} 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_key, json_encode( $values ) );
				}
			}

			if ( empty( $bits['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_slice( \WP_CLI\Dispatcher\get_path( $command ), 1 ) );
		return in_array( $path, $this->config['disabled_commands'], true );
	}

	/**
	 * Returns wp-config.php code, skipping the loading of wp-settings.php
	 *
	 * @return string
	 */
	public function get_wp_config_code() {
		$wp_config_path = Utils\locate_wp_config();

		$wp_config_code = explode( "\n", file_get_contents( $wp_config_path ) );

		$found_wp_settings = false;

		$lines_to_run = array();

		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 = array(
			'sql'  => 'db',
			'blog' => 'site',
		);
		if ( count( $args ) > 0 ) {
			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 ( array( 'cli', 'aliases' ) === array_slice( $args, 0, 2 ) ) {
			list( $args[0], $args[1], $args[2] ) = array( 'cli', 'alias', 'list' );
		}

		// core (multsite-)install --admin_name=  ->  --admin_user=
		if ( count( $args ) > 0 && '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 ( array( 'core', 'config' ) === array_slice( $args, 0, 2 ) ) {
			list( $args[0], $args[1] ) = array( 'config', 'create' );
		}
		// core language  ->  language core
		if ( array( 'core', 'language' ) === array_slice( $args, 0, 2 ) ) {
			list( $args[0], $args[1] ) = array( 'language', 'core' );
		}

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

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

		// site create --site_id=  ->  site create --network_id=
		if ( count( $args ) >= 2 && '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 ) > 1 && in_array( $args[0], array( 'plugin', 'theme' ), true )
			&& 'update-all' === $args[1]
		) {
			$args[1]           = 'update';
			$assoc_args['all'] = true;
		}

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

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

		// plugin scaffold  ->  scaffold plugin
		if ( array( 'plugin', 'scaffold' ) === array_slice( $args, 0, 2 ) ) {
			list( $args[0], $args[1] ) = array( $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 ) > 1 && in_array( $args[0], array( '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 = array( 'version', 'info' );
			foreach ( $special_flags as $key ) {
				if ( isset( $assoc_args[ $key ] ) ) {
					$args = array( 'cli', $key );
					unset( $assoc_args[ $key ] );
					break;
				}
			}
		}

		// (post|comment|site|term) url  --> (post|comment|site|term) list --*__in --field=url
		if ( count( $args ) >= 2 && in_array( $args[0], array( 'post', 'comment', 'site', 'term' ), true ) && 'url' === $args[1] ) {
			switch ( $args[0] ) {
				case 'post':
					$post_ids                = array_slice( $args, 2 );
					$args                    = array( '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, 2 );
					$args                      = array( '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, 2 );
					$args                   = array( '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, 3 );
					$args                  = array( '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 array( $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 = ( ! \WP_CLI\Utils\isPiped() && ! \WP_CLI\Utils\is_windows() );
		} else {
			$this->colorize = $this->config['color'];
		}
	}

	public function init_logger() {
		if ( $this->config['quiet'] ) {
			$logger = new \WP_CLI\Loggers\Quiet();
		} else {
			$logger = new \WP_CLI\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_exists( ABSPATH . 'wp-includes/version.php' );
	}

	/**
	 * Are WordPress core files readable?
	 *
	 * @return bool
	 */
	private function wp_is_readable() {
		return is_readable( ABSPATH . '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( array( 'help' ) ) ? array_slice( $this->arguments, 1 ) : $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'], 1 );

		$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'] ) {
			return; # they're aware of the risks!
		}
		if ( count( $this->arguments ) >= 2 && 'cli' === $this->arguments[0] && in_array( $this->arguments[1], array( '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() !== 0 ) {
			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 = escapeshellarg( Utils\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}";
			$proc           = Utils\proc_open_compat( $full_command, array( STDIN, STDOUT, STDERR ), $pipes );
			proc_close( $proc );
		}
	}

	private function set_alias( $alias ) {
		$orig_config  = $this->config;
		$alias_config = $this->aliases[ $this->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', $aliases, true );
				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->alias, array_keys( $this->aliases ), $threshold = 2 );
				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( array( '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( array( '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`.'
			);
		}

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

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

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

			// We really need a URL here
			if ( ! isset( $_SERVER['HTTP_HOST'] ) ) {
				$url = 'http://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( array( 'import' ) ) ) {
			define( 'WP_LOAD_IMPORTERS', true );
			define( 'WP_IMPORTING', true );
		}

		if ( $this->cmd_starts_with( array( '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;

		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( array( '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( array( '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 http://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', -1 );

		// 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) array(
			'id'            => 1,
			'blog_id'       => 1,
			'domain'        => $url_parts['host'],
			'path'          => $url_parts['path'],
			'cookie_domain' => $url_parts['host'],
			'site_name'     => 'Fake Site',
		);

		// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Intentional override.
		$current_blog = (object) array(
			'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', array( $this, 'action_setup_theme_wp_cli_skip_themes' ), 999 );
		}

		if ( $this->cmd_starts_with( array( 'help' ) ) ) {
			// Try to trap errors on help.
			$help_handler = array( $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 = array(
				'ms_files_rewriting'             => null,
				'active_sitewide_plugins'        => array(),
				'_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( array( 'cache', 'flush' ) ) ) {
				$run_on_site_not_found = 'cache flush';
			}
			if ( $this->cmd_starts_with( array( '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 \WP_CLI\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 = strtolower( Utils\parse_url( site_url(), PHP_URL_HOST ) );
					if ( substr( $sitename, 0, 4 ) === 'www.' ) {
						$sitename = substr( $sitename, 4 );
					}
					$from_email = 'wordpress@' . $sitename;
				}
				return $from_email;
			}
		);

		// Don't apply set_url_scheme in get_site_url()
		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 array();
			}
			if ( ! is_array( $plugins ) ) {
				return $plugins;
			}
			foreach ( $plugins as $a => $b ) {
				// active_sitewide_plugins stores plugin name as the key.
				if ( false !== strpos( current_filter(), 'active_sitewide_plugins' ) && Utils\is_plugin_skipped( $a ) ) {
					unset( $plugins[ $a ] );
					// active_plugins stores plugin name as the value.
				} elseif ( false !== strpos( current_filter(), 'active_plugins' ) && Utils\is_plugin_skipped( $b ) ) {
					unset( $plugins[ $a ] );
				}
			}
			// Reindex because active_plugins expects a numeric index.
			if ( false !== strpos( current_filter(), 'active_plugins' ) ) {
				$plugins = array_values( $plugins );
			}
			return $plugins;
		};

		$hooks = array(
			'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_plugins, 999 );
		}
		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_plugins, 999 );
				}
			},
			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 !== stripos( current_filter(), 'option_template' ) ) {
				$checked_value = get_option( 'stylesheet' );
			}

			if ( '' === $checked_value || in_array( $checked_value, $skipped_themes, true ) ) {
				return '';
			}
			return $value;
		};
		$hooks                      = array(
			'pre_option_template',
			'option_template',
			'pre_option_stylesheet',
			'option_stylesheet',
		);
		foreach ( $hooks as $hook ) {
			add_filter( $hook, $wp_cli_filter_active_theme, 999 );
		}
		// 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_theme, 999 );
				}
			},
			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 = WP_CLI\Utils\wp_clean_error_message( $message->get_error_message() );
		} elseif ( is_string( $message ) ) {
			$help_exit_warning = WP_CLI\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_writable( dirname( $existing_phar ) ) ) {
			return;
		}

		// Only check for update when a human is operating.
		if ( ! function_exists( 'posix_isatty' ) || ! posix_isatty( STDOUT ) ) {
			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_key, time() );
			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_key, time() );

		// Check whether any updates are available.
		ob_start();
		WP_CLI::run_command(
			array( 'cli', 'check-update' ),
			array(
				'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( array( '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( $entry, CompositeCommand $root_command = null ) {
		$commands = array();
		$this->enumerate_commands( $root_command ?: \WP_CLI::get_root_command(), $commands );

		return Utils\get_suggestion( $entry, $commands, $threshold = 2 );
	}

	/**
	 * 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_commands( CompositeCommand $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_reporting( E_ALL & ~E_DEPRECATED );
		}
		ini_set( 'display_errors', 'stderr' ); // phpcs:ignore WordPress.PHP.IniSet.display_errors_Blacklisted
	}
}
<?php

namespace WP_CLI\Bootstrap;

/**
 * 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 \WP_CLI\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\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 process( BootstrapState $state ) {
		require_once WP_CLI_ROOT . '/php/class-wp-cli-command.php';

		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 process( BootstrapState $state ) {
		$commands        = $this->get_protected_commands();
		$current_command = $this->get_current_command();

		foreach ( $commands as $command ) {
			if ( 0 === strpos( $current_command, $command ) ) {
				$state->setValue( BootstrapState::IS_PROTECTED_COMMAND, true );
			}
		}

		return $state;
	}

	/**
	 * Get the list of protected commands.
	 *
	 * @return array
	 */
	private function get_protected_commands() {
		return array(
			'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 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 = array(
			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;

/**
 * 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 process( BootstrapState $state ) {
		require_once WP_CLI_ROOT . '/php/utils.php';

		return $state;
	}
}
<?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 process( BootstrapState $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;

/**
 * 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 process( BootstrapState $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_decode( file_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;

/**
 * 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 process( BootstrapState $state ) {
		require_once WP_CLI_ROOT . '/php/dispatcher.php';

		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 process( BootstrapState $state ) {
		$runner = new RunnerInstance();
		$runner()->start();

		return $state;
	}
}
<?php

namespace WP_CLI\Bootstrap;

/**
 * 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 process( BootstrapState $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',
			array( $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 = array();
			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 = \WP_CLI\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;

/**
 * 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 process( BootstrapState $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, - 4 ) ) {
				continue;
			}

			include_once "$logger_dir/$filename";
		}
	}
}
<?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 process( BootstrapState $state ) {
		if ( $state->getValue( BootstrapState::IS_PROTECTED_COMMAND, false ) ) {
			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 ( array( '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::error( sprintf( "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 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 process( BootstrapState $state ) {
		require_once WP_CLI_ROOT . '/php/class-wp-cli.php';

		return $state;
	}
}
<?php

namespace WP_CLI\Bootstrap;

/**
 * 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 process( BootstrapState $state ) {
		$cmd_dir = WP_CLI_ROOT . '/php/commands';

		$iterator = new \DirectoryIterator( $cmd_dir );

		foreach ( $iterator as $filename ) {
			if ( '.php' !== substr( $filename, - 4 ) ) {
				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;

/**
 * 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 process( BootstrapState $state ) {
		$runner = new RunnerInstance();
		$runner()->init_colorization();

		return $state;
	}
}
<?php

namespace WP_CLI\Bootstrap;

/**
 * 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->getValue( BootstrapState::IS_PROTECTED_COMMAND, false ) ) {
			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 array(
				$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;

/**
 * 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 = array();

	/**
	 * 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 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 process( BootstrapState $state ) {
		$runner = new RunnerInstance();
		$runner()->init_config();

		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 process( BootstrapState $state );
}
<?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 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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 */

namespace WP_CLI;

use Symfony\Component\Finder\Finder;
use WP_CLI\Utils;

/**
 * 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 > 0 ) {
			$ttl = min( (int) $ttl, $this->ttl );
		} else {
			$ttl = (int) $ttl;
		}

		//
		if ( $ttl > 0 && ( filemtime( $filename ) + $ttl ) < time() ) {
			if ( $this->ttl > 0 && $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_exists( dirname( $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 > 0 ) {
			try {
				$expire = new \DateTime();
			} catch ( \Exception $e ) {
				\WP_CLI::error( $e->getMessage() );
			}
			$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() );
			}
		}

		// unlink older files if max cache size is exceeded
		if ( $max_size > 0 ) {
			$files = array_reverse( iterator_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 = array();

		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( $dir, 0777, true ) ) {
				$error = error_get_last();
				\WP_CLI::warning( sprintf( "Failed to create directory '%s': %s.", $dir, $error['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_exists( dirname( $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, -1, false );
		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'], 1 ) .
				( 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\Fetchers;

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

	/**
	 * @var string $msg The message to display when an item is not found
	 */
	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
	 */
	public function get_check( $arg ) {
		$item = $this->get( $arg );

		if ( ! $item ) {
			\WP_CLI::error( sprintf( $this->msg, $arg ) );
		}

		return $item;
	}

	/**
	 * @param array The raw CLI arguments
	 * @return array The list of found items
	 */
	public function get_many( $args ) {
		$items = array();

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

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

		return $items;
	}
}

<?php

namespace WP_CLI;

use Mustangostang\Spyc;

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

	/**
	 * @var string $docComment PHPdoc command for the command.
	 */
	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_comment, strlen( $shortdesc ) );

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

			$lines[] = $line;
		}
		$longdesc = trim( implode( "\n", $lines ) );

		return $longdesc;
	}

	/**
	 * 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   = array();
		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::YAMLLoadString( implode( "\n", $document ) );
		}
		return null;
	}

}
<?php

namespace WP_CLI;

use Mustangostang\Spyc;

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

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

	/**
	 * @var string $prefix Standard prefix for object fields.
	 */
	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 = array(
			'format' => 'table',
			'fields' => $fields,
			'field'  => null,
		);

		foreach ( array( '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'], array( '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'], array( 'table', 'csv' ), true ) ) {
				if ( $items instanceof \Iterator ) {
					$items = \WP_CLI\Utils\iterator_map( $items, array( $this, 'transform_item_values_to_json' ) );
				} else {
					$items = array_map( array( $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'], array( 'table', 'csv' ), true ) && ( is_object( $value ) || is_array( $value ) ) ) {
				$value = json_encode( $value );
			}
			\WP_CLI::print_value(
				$value,
				array(
					'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':
				\WP_CLI\Utils\write_csv( STDOUT, $items, $fields );
				break;

			case 'json':
			case 'yaml':
				$out = array();
				foreach ( $items as $item ) {
					$out[] = \WP_CLI\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( $out, JSON_PARTIAL_OUTPUT_ON_ERROR );
					} else {
						echo json_encode( $out );
					}
				} elseif ( 'yaml' === $this->args['format'] ) {
					echo Spyc::YAMLDump( $out, 2, 0 );
				}
				break;

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

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

		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,
					array(
						'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 $key
	 */
	private function find_item_key( $item, $field ) {
		foreach ( array( $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 = array();
		foreach ( $this->args['fields'] as $field ) {
			$true_fields[] = $this->find_item_key( $data, $field );
		}

		foreach ( $data as $key => $value ) {
			if ( ! in_array( $key, $true_fields, true ) ) {
				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 = array( 'Field', 'Value' );
				if ( 'table' === $format ) {
					self::show_table( $rows, $fields, $ascii_pre_colorized );
				} elseif ( 'csv' === $format ) {
					\WP_CLI\Utils\write_csv( STDOUT, $rows, $fields );
				}
				break;

			case 'yaml':
			case 'json':
				\WP_CLI::print_value(
					$data,
					array(
						'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 \cli\Table();

		$enabled = \cli\Colors::shouldColorize();
		if ( $enabled ) {
			\cli\Colors::disable( true );
		}

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

		foreach ( $items as $item ) {
			$table->addRow( array_values( \WP_CLI\Utils\pick_fields( $item, $fields ) ) );
		}

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

		if ( $enabled ) {
			\cli\Colors::enable( true );
		}
	}

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

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

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

			$rows[] = (object) array(
				'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;

/**
 * Results of an executed command.
 */
class ProcessRun {
	/**
	 * @var string The full command executed by the system.
	 */
	public $command;

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

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

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

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

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

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

	/**
	 * @var 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;

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 {

	public static function getSubscribedEvents() {

		return array(
			PackageEvents::PRE_PACKAGE_INSTALL  => 'pre_install',
			PackageEvents::POST_PACKAGE_INSTALL => 'post_install',
		);
	}

	public static function pre_install( PackageEvent $event ) {
		$operation_message = $event->getOperation()->__toString();
		WP_CLI::log( ' - ' . $operation_message );
	}

	public static function post_install( PackageEvent $event ) {

		$operation = $event->getOperation();
		$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::log( sprintf( ' - Warning: %s', $composer_error ) );
			}
		}

	}

}
<?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, 1 );

		$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;

/**
 * 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->write( STDOUT, $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 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 = max( array_map( 'strlen', $message_lines ) );

		// write an empty line before the message
		$empty_line = \cli\Colors::colorize( '%w%1 ' . str_repeat( ' ', $longest ) . ' %n' );
		$this->write( STDERR, "\n\t$empty_line\n" );

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

		// write an empty line after the message
		$this->write( STDERR, "\t$empty_line\n\n" );
	}
}
<?php

namespace WP_CLI\Loggers;

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

	/**
	 * 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->write( STDERR, \WP_CLI::colorize( "%RError:%n $message\n" ) );
	}

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

		$this->write( STDERR, \WP_CLI::colorize( "%RError:%n\n$message\n" ) );
		$this->write( STDERR, \WP_CLI::colorize( "%R---------%n\n\n" ) );
	}
}
<?php

namespace WP_CLI\Loggers;

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 = microtime( true );
		}
		$debug = $this->get_runner()->config['debug'];
		if ( ! $debug ) {
			return;
		}
		if ( true !== $debug && $group !== $debug ) {
			return;
		}
		$time   = round( microtime( true ) - ( defined( 'WP_CLI_START_MICROTIME' ) ? WP_CLI_START_MICROTIME : $start_time ), 3 );
		$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 = \cli\Colors::colorize( "$color$label:%n", $this->in_color );
		} else {
			$label = "$label:";
		}
		$this->write( $handle, "$label $message\n" );
	}

}
<?php

namespace WP_CLI\Loggers;

/**
 * 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 Message to write.
	 */
	public function error_multi_line( $message_lines ) {
		$message = implode( "\n", $message_lines );

		$this->write( STDERR, \WP_CLI::colorize( "%RError:%n\n$message\n" ) );
		$this->write( STDERR, \WP_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() {
		// To ensure sequential output, give a chunk size of 1 (or 2 if PHP < 5.4 as 1 was a special value meaning a 4KB chunk) to `ob_start()`, so that each write gets flushed immediately.
		ob_start( array( $this, 'ob_start_callback' ), version_compare( PHP_VERSION, '5.4.0', '<' ) ? 2 : 1 );
	}

	/**
	 * 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->write( STDOUT, $str );
		return '';
	}

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

namespace WP_CLI;

use WP_CLI;
use WP_CLI\Utils;
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() {}

	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( $string, ENT_QUOTES, get_bloginfo( 'charset' ) );

		WP_CLI::log( $string );
	}
}

<?php

namespace WP_CLI;

class Completions {

	private $words;
	private $opts = array();

	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'], array( '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 );
			}
		}

	}

	private function get_command( $words ) {
		$positional_args = array();
		$assoc_args      = array();

		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 array( $command, $args, $assoc_args );
	}

	private function get_global_parameters() {
		$params = array();
		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;
	}

	private function add( $opt ) {
		if ( '' !== $this->cur_word ) {
			if ( 0 !== strpos( $opt, $this->cur_word ) ) {
				return;
			}
		}

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

	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 = array();

	/**
	 * 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( array( $this, 'autoload' ) );
	}

	/**
	 * Unregisters the autoload callback with the SPL autoload system.
	 */
	public function unregister() {
		spl_autoload_unregister( array( $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[] = array(
			'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 ( 0 !== 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(
				array( $namespace['root'], '\\' ),
				array( '', 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\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\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 $name => $subcommand ) {
			$prefix = ( 0 === $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_slice( get_path( $this ), 1 ) );
		$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\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 = array();

	/**
	 * Instantiate a new CompositeCommand
	 *
	 * @param mixed $parent Parent command (either Root or Composite)
	 * @param string $name Represents how command should be invoked
	 * @param \WP_CLI\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
	 */
	public function add_subcommand( $name, $command ) {
		$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
	 */
	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
	 */
	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 $name => $subcommand ) {
			$prefix = ( 0 === $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_slice( get_path( $this ), 1 ) );

		\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 \WP_CLI\Dispatcher\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 = array();

		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                 = array();
		$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'];
			}

			$binding['parameters'][] = array(
				'synopsis' => $synopsis,
				'desc'     => $details['desc'],
			);
		}

		if ( $this->get_subcommands() ) {
			$binding['has_subcommands'] = true;
		}

		return Utils\mustache_render( 'man-params.mustache', $binding );
	}
}
<?php

namespace WP_CLI\Dispatcher;

use ReflectionClass;
use ReflectionFunction;
use ReflectionMethod;

/**
 * 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 = array();

	/**
	 * 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 ) ) {
			$reflection = new ReflectionClass( $callable[0] );
			$command    = self::create_subcommand(
				$parent,
				$name,
				array( $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,
					array( $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 = array();
	}

	/**
	 * 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
	 * @param string $class A subclass of WP_CLI_Command
	 * @param string $method Class method to be called upon invocation.
	 */
	private static function create_subcommand( $parent, $name, $callable, $reflection ) {
		$doc_comment = self::get_doc_comment( $reflection );
		$docparser   = new \WP_CLI\DocParser( $doc_comment );

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

			if ( ! $name ) {
				$name = $reflection->name;
			}
		}
		if ( ! $doc_comment ) {
			\WP_CLI::debug( null === $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( array( $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::debug( null === $doc_comment ? "Failed to get doc comment for {$name}." : "No doc comment for {$name}.", 'commandfactory' );
		}
		$docparser = new \WP_CLI\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( $container, false, array( $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::debug( null === $doc_comment ? "Failed to get doc comment for {$name}." : "No doc comment for {$name}.", 'commandfactory' );
		}
		$docparser = new \WP_CLI\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() && 0 !== 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 ) {
		$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_comment( implode( "\n", array_slice( $contents, 0, $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 + 2 ), $dummy /*needed for PHP 5.3*/ ) > 1 ) {
			return false;
		}
		$content           = substr( $content, 0, $comment_end_pos + 2 );
		$comment_start_pos = strrpos( $content, '/**' );
		if ( false === $comment_start_pos || ( $comment_start_pos + 2 ) === $comment_end_pos ) {
			return false;
		}
		// Make sure comment start belongs to this comment end.
		$comment_end2_pos = strpos( substr( $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( $content, 0, $comment_start_pos );
		while ( false !== ( $comment_start2_pos = strrpos( $subcontent, '/**' ) ) && false === strpos( $subcontent, '*/', $comment_start2_pos ) ) {
			$comment_start_pos = $comment_start2_pos;
			$subcontent        = substr( $subcontent, 0, $comment_start_pos );
		}
		return substr( $content, $comment_start_pos, $comment_end_pos + 2 );
	}
}
<?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_params( true );
	}

	/**
	 * Find a subcommand registered on the root
	 * command.
	 *
	 * @param array $args
	 * @return \WP_CLI\Dispatcher\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\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
	 */
	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_line( STDIN, 1024, "\n" );
		if ( Utils\is_windows() && "\r" === substr( $ret, -1 ) ) {
			$ret = substr( $ret, 0, -1 );
		}
		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 array( $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(
			\WP_CLI\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 = array();

		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_args, true ) ) {
					continue;
				}
			}

			$current_prompt = ( $key + 1 ) . '/' . 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 array( $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 array( $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 array( $args, $assoc_args );
				}

				if ( $response ) {
					switch ( $spec_arg['type'] ) {
						case 'positional':
							if ( $spec_arg['repeating'] ) {
								$response = explode( ' ', $response );
							} else {
								$response = array( $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 array( $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 array( array(), $args, $assoc_args, $extra_args );
		}

		$validator = new \WP_CLI\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( 1 );
		}

		$unknown_positionals = $validator->unknown_positionals( $args );
		if ( ! empty( $unknown_positionals ) ) {
			\WP_CLI::error(
				'Too many positional arguments: ' .
				implode( ' ', $unknown_positionals )
			);
		}

		$synopsis_spec = \WP_CLI\SynopsisParser::parse( $synopsis );
		$i             = 0;
		$errors        = array(
			'fatal'   => array(),
			'warning' => array(),
		);
		$mock_doc      = array( $this->get_shortdesc(), '' );
		$mock_doc      = array_merge( $mock_doc, explode( "\n", $this->get_longdesc() ) );
		$mock_doc      = '/**' . PHP_EOL . '* ' . implode( PHP_EOL . '* ', $mock_doc ) . PHP_EOL . '*/';
		$docparser     = new \WP_CLI\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'] ) ) {
					// phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict -- This is a loose comparison by design.
					if ( ! in_array( $assoc_args[ $spec['name'] ], $spec_args['options'] ) ) {
						$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 ( array( '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 array( $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 ) {

		if ( 'help' !== $this->name ) {
			static $prompted_once = false;

			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 = array();
		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, 1 ) );
		$cmd    = $this->name;
		if ( $parent ) {
			WP_CLI::do_hook( "before_invoke:{$parent}" );
			$cmd = $parent . ' ' . $cmd;
		}
		WP_CLI::do_hook( "before_invoke:{$cmd}" );

		call_user_func( $this->when_invoked, $args, array_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 = array() ) {
		$local_parameters  = array_column( $spec, 'name' );
		$global_parameters = array_column(
			WP_CLI\SynopsisParser::parse( $this->get_global_params() ),
			'name'
		);

		return array_unique( array_merge( $local_parameters, $global_parameters ) );
	}
}
<?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 A synopsis
	 * @return array List of parameters
	 */
	public static function parse( $synopsis ) {
		$tokens = array_filter( preg_split( '/[\s\t]+/', $synopsis ) );

		$params = array();
		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 $param
	 */
	private static function classify_token( $token ) {
		$param = array();

		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( $token, strlen( $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 = array(
						'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( $token, 0, 1 ) && ']' === substr( $token, -1 ) ) {
			return array( true, substr( $token, 1, -1 ) );
		}

		return array( false, $token );
	}

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

		return array( false, $token );
	}
}
<?php

namespace WP_CLI;

class ExitException extends \Exception {}
<?php

namespace WP_CLI;

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

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

	/**
	 * @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(
				array(
					'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(
			array(
				'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(
			array(
				'type'      => 'positional',
				'repeating' => true,
			)
		);

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

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

		return array_slice( $args, count( $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(
			array(
				'type' => 'assoc',
			)
		);

		$errors = array(
			'fatal'   => array(),
			'warning' => array(),
		);

		$to_unset = array();

		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 array( $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(
			array(
				'type' => 'generic',
			)
		);

		if ( count( $generic ) ) {
			return array();
		}

		$known_assoc = array();

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

		return array_diff( array_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 = array();

		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 > 0 )
				|| ( 'NOT' === $operator && 0 === $matched ) ) {
					$filtered[ $key ] = $to_match;
			}
		}

		return $filtered;
	}

}
<?php

namespace WP_CLI;

use Mustangostang\Spyc;

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

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

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

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

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

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

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

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

		$defaults = array(
			'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 array( $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 = array();
			foreach ( json_decode( $runtime_alias, true ) as $key => $value ) {
				if ( preg_match( '#' . self::ALIAS_REGEX . '#', $key ) ) {
					$returned_aliases[ $key ] = array();
					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)
	 * @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 array( $positional_args, $assoc_args, $runtime_config );
	}

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

		foreach ( $arguments as $arg ) {
			$positional_arg = null;
			$assoc_arg      = null;

			if ( preg_match( '|^--no-([^=]+)$|', $arg, $matches ) ) {
				$assoc_arg = array( $matches[1], false );
			} elseif ( preg_match( '|^--([^=]+)$|', $arg, $matches ) ) {
				$assoc_arg = array( $matches[1], true );
			} elseif ( preg_match( '|^--([^=]+)=(.*)|s', $arg, $matches ) ) {
				$assoc_arg = array( $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 array( $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 = array(), $local_assoc = array() ) {
		$assoc_args     = array();
		$runtime_config = array();

		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 array( $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'] ) ) {
			fwrite( STDERR, "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 ] = array();
				$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 = array();
					foreach ( $value as $i => $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( $key, self::$alias_spec, true ) ) {
					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 = \WP_CLI\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 $config Declared configuration values
	 */
	private static function load_yml( $yml_file ) {
		if ( ! $yml_file ) {
			return array();
		}

		$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'] = \WP_CLI\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 ) && ! \WP_CLI\Utils\is_path_absolute( $path ) ) {
			$path = $base . DIRECTORY_SEPARATOR . $path;
		}
	}

}
<?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( $message ) {
		$message = preg_replace( '#<(https?)([^>]+)>#', '$1$2', $message );
		// phpcs:ignore WordPress.WP.AlternativeFunctions.strip_tags_strip_tags
		WP_CLI::log( strip_tags( trim( $message ) ) );
	}

}
<?php

namespace WP_CLI;

use WP_CLI\Utils;

/**
 * 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 = array(
		0 => STDIN,
		1 => array( 'pipe', 'w' ),
		2 => array( '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 = array();

	/**
	 * @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 = array() ) {
		$proc = new self();

		$proc->command = $command;
		$proc->cwd     = $cwd;
		$proc->env     = $env;

		return $proc;
	}

	private function __construct() {}

	/**
	 * Run the command.
	 *
	 * @return \WP_CLI\ProcessRun
	 */
	public function run() {
		$start_time = microtime( true );

		$proc = Utils\proc_open_compat( $this->command, self::$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 = microtime( true ) - $start_time;

		if ( self::$log_run_times ) {
			if ( ! isset( self::$run_times[ $this->command ] ) ) {
				self::$run_times[ $this->command ] = array( 0, 0 );
			}
			self::$run_times[ $this->command ][0] += $run_time;
			self::$run_times[ $this->command ][1]++;
		}

		return new ProcessRun(
			array(
				'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 \WP_CLI\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 \WP_CLI\ProcessRun
	 */
	public function run_check_stderr() {
		$r = $this->run();

		if ( $r->return_code || ! empty( $r->stderr ) ) {
			throw new \RuntimeException( $r );
		}

		return $r;
	}
}
<?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
 * <http://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 <kvesteri@cc.hut.fi>
 * @author Jonathan H. Wage <jonwage@gmail.com>
 */
class Inflector {

	/**
	 * Plural inflector rules.
	 *
	 * @var array
	 */
	private static $plural = array(
		'rules'       => array(
			'/(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' => array(
			'.*[nrlm]ese',
			'.*deer',
			'.*fish',
			'.*measles',
			'.*ois',
			'.*pox',
			'.*sheep',
			'people',
			'cookie',
		),
		'irregular'   => array(
			'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 = array(
		'rules'       => array(
			'/(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' => array(
			'.*[nrlm]ese',
			'.*deer',
			'.*fish',
			'.*measles',
			'.*ois',
			'.*pox',
			'.*sheep',
			'.*ss',
		),
		'irregular'   => array(
			'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 = array(
		'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 = array();

	/**
	 * The initial state of Inflector so reset() works.
	 *
	 * @var array
	 */
	private static $initial_state = array();

	/**
	 * 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 strtolower( preg_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( ' ', '', ucwords( strtr( $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 lcfirst( self::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( $pattern, self::${$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'] = array();
				self::$cache['tableize']  = array();
			} elseif ( 'singular' === $type ) {
				self::$cache['singularize'] = array();
			}
		}

		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_merge( self::$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_keys( self::$plural['merged']['irregular'] ) ) . ')';
		}

		if ( preg_match( '/(.*)\\b(' . self::$plural['cacheIrregular'] . ')$/i', $word, $regs ) ) {
			self::$cache['pluralize'][ $word ] = $regs[1] . substr( $word, 0, 1 ) . substr( self::$plural['merged']['irregular'][ strtolower( $regs[2] ) ], 1 );

			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_flip( self::$plural['irregular'] )
			);
		}

		if ( ! isset( self::$singular['cacheUninflected'] ) || ! isset( self::$singular['cacheIrregular'] ) ) {
			self::$singular['cacheUninflected'] = '(?:' . join( '|', self::$singular['merged']['uninflected'] ) . ')';
			self::$singular['cacheIrregular']   = '(?:' . join( '|', array_keys( self::$singular['merged']['irregular'] ) ) . ')';
		}

		if ( preg_match( '/(.*)\\b(' . self::$singular['cacheIrregular'] . ')$/i', $word, $regs ) ) {
			self::$cache['singularize'][ $word ] = $regs[1] . substr( $word, 0, 1 ) . substr( self::$singular['merged']['irregular'][ strtolower( $regs[2] ) ], 1 );

			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 Exception;
use PharData;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use DirectoryIterator;
use WP_CLI;
use WP_CLI\Utils;
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 ) <= 0 ) {
			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 ) <= 0 ) {
			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 ( 0 !== $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( $dest, 0777, true );
		}

		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 ( 0 !== 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 = array(
			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 = trim( substr( $stderr, 0, $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( $dir, 0777, true ) ) {
				$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 + 3 );
		}

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

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

			// Special case: only drive letter, like "C:".
			if ( 2 === 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\Iterators;

/**
 * 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          = array();
	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 ', $query, 1, $replacements );
		if ( 1 !== $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          = array();
		$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;

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

	private $transformers = array();

	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 = array() ) {
		$defaults = array(
			'fields'     => '*',
			'where'      => array(),
			'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 = array();
			foreach ( $where as $key => $value ) {
				if ( is_array( $value ) ) {
					$conditions[] = $key . ' IN (' . esc_sql( implode( ',', $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;

class Exception extends \RuntimeException {}

<?php

namespace WP_CLI\Iterators;

/**
 * Allows incrementally reading and parsing lines from a CSV file.
 */
class CSV implements \Countable, \Iterator {

	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::error( sprintf( 'Could not open file: %s', $filename ) );
		}

		$this->delimiter = $delimiter;
	}

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

		$this->columns = fgetcsv( $this->file_pointer, self::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 = array();
			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->seek( PHP_INT_MAX );
		return $file->key() + 1;
	}

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

<?php

namespace WP_CLI;

use WP_CLI\Bootstrap\BootstrapState;

/**
 * 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 array(
		'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\LoadRequiredCommand',
		'WP_CLI\Bootstrap\IncludePackageAutoloader',
		'WP_CLI\Bootstrap\IncludeFallbackAutoloader',
		'WP_CLI\Bootstrap\RegisterFrameworkCommands',
		'WP_CLI\Bootstrap\RegisterDeferredCommands',
		'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 \WP_CLI\Bootstrap\BootstrapStep $step_instance */
		$step_instance = new $step();
		$state         = $step_instance->process( $state );
	}
}
<?php

// Can be used by plugins/themes to check if WP-CLI is running or not.
define( 'WP_CLI', true );
define( 'WP_CLI_VERSION', trim( file_get_contents( WP_CLI_ROOT . '/VERSION' ) ) );
define( 'WP_CLI_START_MICROTIME', microtime( true ) );

if ( file_exists( WP_CLI_ROOT . '/vendor/autoload.php' ) ) {
	define( 'WP_CLI_VENDOR_DIR', WP_CLI_ROOT . '/vendor' );
} elseif ( file_exists( dirname( dirname( WP_CLI_ROOT ) ) . '/autoload.php' ) ) {
	define( 'WP_CLI_VENDOR_DIR', dirname( dirname( WP_CLI_ROOT ) ) );
} elseif ( file_exists( dirname( WP_CLI_ROOT ) . '/vendor/autoload.php' ) ) {
	define( 'WP_CLI_VENDOR_DIR', dirname( WP_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

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

use WP_CLI\Utils;
use WP_CLI\Dispatcher;

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, $matches, PREG_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', array( __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( ' ', 2 );

		// Need to de-tab for wordwrapping to work properly.
		$out = str_replace( "\t", $tab, $out );

		$wordwrap_width = \cli\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_available( null /*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 http://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 = array(
			0 => $fd,
			1 => STDOUT,
			2 => STDERR,
		);

		return proc_close( Utils\proc_open_compat( $pager, $descriptorspec, $pipes ) );
	}

	private static function get_initial_markdown( $command ) {
		$name = implode( ' ', Dispatcher\get_path( $command ) );

		$binding = array(
			'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 = array();
		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_len( array_keys( $subcommands ) );

		$lines = array();
		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 ( 0 === strpos( $line, '#' ) ) {
				break;
			}
			$description .= $line . "\n";
		}

		// Fires if it has description text at the head of `$longdesc`.
		if ( $description ) {
			$links   = array(); // 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_replace( trim( $description ), trim( $newdesc ), $longdesc );
			}
		}

		return $longdesc;
	}
}
<?php

/**
 * Retrieves, sets and updates aliases for WordPress Installations.
 */

use Mustangostang\Spyc;

/**
 * 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: dev@somedeve.env: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: runcommand@runcommand.io~/webapps/production
	 *     @dev:
	 *       ssh: vagrant@192.168.50.10/srv/www/runcommand.dev
	 *     @both:
	 *       - @prod
	 *       - @dev
	 *
	 * @subcommand list
	 */
	public function list_( $args, $assoc_args ) {
		WP_CLI::print_value( WP_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: dev@somedeve.env: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, '' );

		$this->validate_config_file( $config_path );

		$alias    = $args[0];
		$grouping = WP_CLI\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_args, false );
		} else {
			$aliases = $this->build_aliases( $aliases, $alias, $assoc_args, true, $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 = WP_CLI\Utils\get_flag_value( $assoc_args, 'grouping' );

		list( $config_path, $aliases ) = $this->get_aliases_data( $config, $alias );

		$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_args, false, '', true );
		} else {
			$aliases = $this->build_aliases( $aliases, $alias, $assoc_args, true, $grouping, true );
		}

		$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.
	 *
	 * @return array Config Path and Aliases in it.
	 */
	private function get_aliases_data( $config, $alias ) {

		$global_config_path = WP_CLI::get_runner()->get_global_config_path();
		$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 array( $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  $key_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 = array( 'config', 'grouping' );
			$invalid_args     = array_diff( array_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 WP_CLI\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_filter( array_intersect_key( $assoc_args, array_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 WP_CLI\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

/**
 * 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

use Composer\Semver\Comparator;
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 = array(
			'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_bin = Utils\get_php_binary();

		// 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' );
		}

		$runner = WP_CLI::get_runner();

		$packages_dir = $runner->get_packages_dir_path();
		if ( ! is_dir( $packages_dir ) ) {
			$packages_dir = null;
		}
		if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'format' ) === 'json' ) {
			$info = array(
				'php_binary_path'          => $php_bin,
				'global_config_path'       => $runner->global_config_path,
				'project_config_path'      => $runner->project_config_path,
				'wp_cli_dir_path'          => WP_CLI_ROOT,
				'wp_cli_packages_dir_path' => $packages_dir,
				'wp_cli_version'           => WP_CLI_VERSION,
				'system_os'                => $system_os,
				'shell'                    => $shell,
			);

			WP_CLI::line( json_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( "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 \WP_CLI\Formatter(
				$assoc_args,
				array( '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.
	 *
	 * ## 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::error( sprintf( '%s is not writable by current user.', $old_phar ) );
		} elseif ( ! is_writable( dirname( $old_phar ) ) ) {
			WP_CLI::error( sprintf( '%s is not writable by current user.', dirname( $old_phar ) ) );
		}

		if ( Utils\get_flag_value( $assoc_args, 'nightly' ) ) {
			WP_CLI::confirm( sprintf( '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::confirm( sprintf( '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::confirm( sprintf( '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::log( sprintf( 'Downloading from %s...', $download_url ) );

		$temp = \WP_CLI\Utils\get_temp_dir() . uniqid( 'wp_', true ) . '.phar';

		$headers = array();
		$options = array(
			'timeout'  => 600,  // 10 minutes ought to be enough for everybody.
			'filename' => $temp,
		);

		Utils\http_request( 'GET', $download_url, null, $headers, $options );

		$md5_response = Utils\http_request( 'GET', $md5_url );
		if ( '20' !== substr( $md5_response->status_code, 0, 2 ) ) {
			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    = WP_CLI\Process::create( "{$php_binary} $temp --info {$allow_root}" );
		$result     = $process->run();
		if ( 0 !== $result->return_code || false === stripos( $result->stdout, 'WP-CLI version' ) ) {
			$multi_line = explode( PHP_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::error( sprintf( '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::error( sprintf( 'Cannot move %s to %s', $temp, $old_phar ) );
		}

		if ( Utils\get_flag_value( $assoc_args, 'nightly' ) ) {
			$updated_version = 'the latest nightly release';
		} elseif ( Utils\get_flag_value( $assoc_args, 'stable' ) ) {
			$updated_version = 'the latest stable release';
		} else {
			$updated_version = $newest['version'];
		}
		WP_CLI::success( sprintf( '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 = array(
			'timeout' => 30,
		);

		$headers = array(
			'Accept' => 'application/json',
		);

		$github_token = getenv( 'GITHUB_TOKEN' );
		if ( false !== $github_token ) {
			$headers['Authorization'] = 'token ' . $github_token;
		}

		$response = Utils\http_request( 'GET', $url, null, $headers, $options );

		if ( ! $response->success || 200 !== $response->status_code ) {
			WP_CLI::error( sprintf( 'Failed to get latest version (HTTP code %d).', $response->status_code ) );
		}

		$release_data = json_decode( $response->body );

		$updates = array(
			'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_version, 0, 1 ) ) {
				$release_version = ltrim( $release_version, 'v' );
			}

			$update_type = Utils\get_named_sem_ver( $release_version, WP_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 ] = array(
				'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 ( array( 'major', 'minor', 'patch' ) as $type ) {
			if ( true === \WP_CLI\Utils\get_flag_value( $assoc_args, $type ) ) {
				return ! empty( $updates[ $type ] ) ? array( $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_url );
			if ( ! $response->success || 200 !== $response->status_code ) {
				WP_CLI::error( sprintf( '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'] = array(
					'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 ( \WP_CLI\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' === \WP_CLI\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_array( WP_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 \WP_CLI\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 ( array( 'major', 'minor', 'patch' ) as $type ) {
			if ( true === \WP_CLI\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::halt( is_array( WP_CLI::get_runner()->find_command_to_run( $command ) ) ? 0 : 1 );
	}
}
<?php

/**
 * Base class for WP-CLI commands
 *
 * @package wp-cli
 */
abstract class WP_CLI_Command {

	public function __construct() {}
}

<?php
/**
 * A modified version of wp-settings.php, tailored for CLI use.
 *
 * @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', 0 );
ini_set( 'magic_quotes_sybase', 0 );
// phpc:enable PHPCompatibility.IniDirectives.RemovedIniDirectives,WordPress.PHP.IniSet

// WordPress calculates offsets from UTC.
// phpcs:ignore WordPress.WP.TimezoneChange.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_exists( WP_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', array() );

// 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'] = array();
}

// 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_directory( get_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();

// 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 ( ( 0 === 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_exists( STYLESHEETPATH . '/functions.php' ) ) {
		include STYLESHEETPATH . '/functions.php';
	}
	if ( file_exists( TEMPLATEPATH . '/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 http://codex.wordpress.org/AJAX_in_Plugins
 *
 * @since 3.0.0
 */
do_action( 'wp_loaded' );
<?php

use WP_CLI\ExitException;
use WP_CLI\Dispatcher;
use WP_CLI\FileCache;
use WP_CLI\Process;
use WP_CLI\ProcessRun;
use WP_CLI\WpHttpCacheManager;
use WP_CLI\Utils;
use Mustangostang\Spyc;

/**
 * Various utilities for WP-CLI commands.
 */
class WP_CLI {

	private static $configurator;

	private static $logger;

	private static $hooks = array();

	private static $hooks_passed = array();

	private static $capture_exit = false;

	private static $deferred_additions = array();

	/**
	 * Set the logger instance.
	 *
	 * @param object $logger
	 */
	public static function set_logger( $logger ) {
		self::$logger = $logger;
	}

	/**
	 * Get the Configurator instance
	 *
	 * @return \WP_CLI\Configurator
	 */
	public static function get_configurator() {
		static $configurator;

		if ( ! $configurator ) {
			$configurator = new WP_CLI\Configurator( WP_CLI_ROOT . '/php/config-spec.php' );
		}

		return $configurator;
	}

	public static function get_root_command() {
		static $root;

		if ( ! $root ) {
			$root = new Dispatcher\RootCommand();
		}

		return $root;
	}

	public static function get_runner() {
		static $runner;

		if ( ! $runner ) {
			$runner = new WP_CLI\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";

			// 6 months, 300mb
			$cache = new FileCache( $dir, 15552000, 314572800 );

			// Clean older files on shutdown with 1/50 probability.
			// phpcs:ignore WordPress.WP.AlternativeFunctions.rand_mt_rand -- no crypto and WP not loaded.
			if ( 0 === mt_rand( 0, 50 ) ) {
				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 \WP_CLI\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']  = \WP_CLI\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 WpHttpCacheManager( self::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 \cli\Colors::colorize( $string, self::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_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` - Just before the command is executed.
	 *
	 * 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( $when, self::$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 ... Optional. Arguments that will be passed onto the
	 *                  callback provided by `WP_CLI::add_hook()`.
	 * @return null
	 */
	public static function do_hook( $when ) {

		$args = func_num_args() > 1
			? array_slice( func_get_args(), 1 )
			: array();

		self::$hooks_passed[ $when ] = $args;

		if ( ! isset( self::$hooks[ $when ] ) ) {
			return;
		}

		self::debug(
			sprintf(
				'Processing hook "%s" with %d callbacks',
				$when,
				count( self::$hooks[ $when ] )
			),
			'hooks'
		);

		foreach ( self::$hooks[ $when ] as $callback ) {
			self::debug(
				sprintf(
					'On hook "%s": %s',
					$when,
					Utils\describe_callable( $callback )
				),
				'hooks'
			);
			call_user_func_array( $callback, $args );
		}
	}

	/**
	 * 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 = 1 ) {
		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 ] = array(
				'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 = 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];
			}

			$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 = array() ) {
		// 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;
		}
		if ( ! $valid ) {
			if ( is_array( $callable ) ) {
				$callable[0] = is_object( $callable[0] ) ? get_class( $callable[0] ) : $callable[0];
				$callable    = array( $callable[0], $callable[1] );
			}
			self::error( sprintf( 'Callable %s does not exist, and cannot be registered as `wp %s`.', json_encode( $callable ), $name ) );
		}

		$addition = new Dispatcher\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 ( array( '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 );
		$full_path = $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 Dispatcher\CompositeCommand(
						$command,
						$subcommand_name,
						new \WP_CLI\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 = Dispatcher\CommandFactory::create( $leaf_name, $callable, $command );

		// Only add a command namespace if the command itself does not exist yet.
		if ( $leaf_command instanceof Dispatcher\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 ( false !== $existing_command ) {
			$subcommands = $existing_command->get_subcommands();
			if ( ! empty( $subcommands )
				&& ( $leaf_command instanceof Dispatcher\CompositeCommand
					|| $leaf_command->can_have_subcommands ) ) {
				foreach ( $subcommands as $subname => $subcommand ) {
					$leaf_command->add_subcommand( $subname, $subcommand );
				}
			}
		}

		/** @var $leaf_command Dispatcher\Subcommand|Dispatcher\CompositeCommand|Dispatcher\CommandNamespace */

		if ( ! $command->can_have_subcommands() ) {
			throw new Exception(
				sprintf(
					"'%s' can't have subcommands.",
					implode( ' ', Dispatcher\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 = \WP_CLI\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 = array();
					foreach ( array( '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 = trim( str_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;
	}

	/**
	 * 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 = array() ) {
		$args['is_deferred']               = true;
		self::$deferred_additions[ $name ] = array(
			'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( $name, self::$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 ) {
		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 ) {
		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 $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 = array();

		if ( ! self::$logger ) {
			$storage[] = array( $message, $group );
			return;
		}

		if ( ! empty( $storage ) && self::$logger ) {
			foreach ( $storage as $entry ) {
				list( $stored_message, $stored_group ) = $entry;
				self::$logger->debug( self::error_to_string( $stored_message ), $stored_group );
			}
			$storage = array();
		}

		self::$logger->debug( self::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 $message Message to write to STDERR.
	 * @return null
	 */
	public static function warning( $message ) {
		self::$logger->warning( self::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  $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 ( ! isset( self::get_runner()->assoc_args['completions'] ) ) {
			self::$logger->error( self::error_to_string( $message ) );
		}

		$return_code = false;
		if ( true === $exit ) {
			$return_code = 1;
		} elseif ( is_int( $exit ) && $exit >= 1 ) {
			$return_code = $exit;
		}

		if ( $return_code ) {
			if ( self::$capture_exit ) {
				throw new ExitException( null, $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 ExitException( null, $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 Multi-line error message to be displayed.
	 */
	public static function error_multi_line( $message_lines ) {
		if ( ! isset( self::get_runner()->assoc_args['completions'] ) && is_array( $message_lines ) ) {
			self::$logger->error_multi_line( array_map( array( __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 = array() ) {
		if ( ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'yes' ) ) {
			fwrite( STDOUT, $question . ' [y/n] ' );

			$answer = strtolower( trim( fgets( STDIN ) ) );

			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 = fgets( STDIN ) ) ) {
				$raw_value .= $line;
			}
		}

		return $raw_value;
	}

	/**
	 * Read a value, from various formats.
	 *
	 * @access public
	 * @category Input
	 *
	 * @param mixed $value
	 * @param array $assoc_args
	 */
	public static function read_value( $raw_value, $assoc_args = array() ) {
		if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'format' ) === 'json' ) {
			$value = json_decode( $raw_value, true );
			if ( null === $value ) {
				self::error( sprintf( '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 = array() ) {
		if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'format' ) === 'json' ) {
			$value = json_encode( $value );
		} elseif ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'format' ) === 'yaml' ) {
			$value = Spyc::YAMLDump( $value, 2, 0 );
		} elseif ( is_array( $value ) || is_object( $value ) ) {
			$value = var_export( $value, true );
		}

		echo $value . "\n";
	}

	/**
	 * Convert a wp_error into a string
	 *
	 * @param mixed $errors
	 * @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;
			}
		}
	}

	/**
	 * 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|\WP_CLI\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 ( -1 === $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|\WP_CLI\ProcessRun The command exit status, or a ProcessRun instance
	 */
	public static function launch_self( $command, $args = array(), $assoc_args = array(), $exit_on_error = true, $return_detailed = false, $runtime_args = array() ) {
		$reused_runtime_args = array(
			'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 = escapeshellarg( Utils\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 = \WP_CLI\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( $key, self::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 = array() ) {
		$defaults   = array(
			'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 = array(
				0 => STDIN,
				1 => STDOUT,
				2 => STDERR,
			);

			if ( $return ) {
				$descriptors = array(
					0 => STDIN,
					1 => array( 'pipe', 'w' ),
					2 => array( 'pipe', 'w' ),
				);
			}

			$php_bin     = escapeshellarg( Utils\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'], 1 );

			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}";

			$proc = Utils\proc_open_compat( $runcommand, $descriptors, $pipes, getcwd() );

			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 ( -1 === $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) array(
					'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 WP_CLI\Loggers\Execution();
				self::$logger->ob_start();
			}
			if ( ! $exit_error ) {
				self::$capture_exit = true;
			}
			try {
				self::get_runner()->run_command(
					$args,
					$assoc_args,
					array(
						'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) array(
						'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( $retval, true );
		}
		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 = array() ) {
		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 ) {
		fwrite( STDOUT, $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 depend on WordPress code.

namespace WP_CLI\Utils;

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 = array();
		if ( count( $tables ) ) {
			foreach ( $tables as $table ) {
				$maybe_prefix = substr( $table, 0, - 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 ) > 1 ? '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_reporting( E_ALL & ~E_DEPRECATED & ~E_STRICT );
	} else {
		if ( WP_DEBUG ) {
			error_reporting( E_ALL );

			if ( WP_DEBUG_DISPLAY ) {
				ini_set( 'display_errors', 1 );
			} elseif ( null !== WP_DEBUG_DISPLAY ) {
				ini_set( 'display_errors', 0 );
			}

			if ( WP_DEBUG_LOG ) {
				ini_set( 'log_errors', 1 );
				ini_set( 'error_log', WP_CONTENT_DIR . '/debug.log' );
			}
		} else {
			error_reporting( E_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', 0 );
		}
	}

	// 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 = array(
		'<code>'  => '`',
		'</code>' => '`',
	);
	$message        = str_replace( array_keys( $search_replace ), array_values( $search_replace ), $message );
	$message        = namespace\strip_tags( $message );
	$message        = html_entity_decode( $message, ENT_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();
	fwrite( STDERR, ob_get_clean() );

	return $url;
}

function maybe_require( $since, $path ) {
	if ( wp_version_compare( $since, '>=' ) ) {
		require $path;
	}
}

function get_upgrader( $class ) {
	if ( ! class_exists( '\WP_Upgrader' ) ) {
		require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
	}

	return new $class( new \WP_CLI\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_name( str_replace( WP_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( $name, array_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( $name, array_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(
		array(
			'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->m ) && $wp_object_cache->m instanceof \Memcached ) {
			$message = 'Memcached';

			// Test for Memcache PECL extension memcached object cache (http://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 (http://plugins.svn.wordpress.org/xcache/trunk/object-cache.php)
		} elseif ( $wp_object_cache instanceof \XCache_Object_Cache ) {
			$message = 'Xcache';

			// Test for WinCache object cache (http://wordpress.org/extend/plugins/wincache-object-cache-backend/)
		} elseif ( class_exists( 'WinCache_Object_Cache' ) ) {
			$message = 'WinCache';

			// Test for APC object cache (http://wordpress.org/extend/plugins/apc/)
		} elseif ( class_exists( 'APC_Object_Cache' ) ) {
			$message = 'APC';

			// Test for Redis Object Cache (https://github.com/alleyinteractive/wp-redis)
		} elseif ( isset( $wp_object_cache->redis ) && $wp_object_cache->redis instanceof \Redis ) {
			$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 = array();

	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 = array();
	}
	if ( isset( $wp_object_cache->stats ) ) {
		$wp_object_cache->stats = array();
	}
	if ( isset( $wp_object_cache->memcache_debug ) ) {
		$wp_object_cache->memcache_debug = array();
	}
	// Used by `WP_Object_Cache` also.
	if ( isset( $wp_object_cache->cache ) ) {
		$wp_object_cache->cache = array();
	}
}

/**
 * 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 $tables
 */
function wp_get_table_names( $args, $assoc_args = array() ) {
	global $wpdb;

	$tables = array();

	// 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, 0 );

	} 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, 0 );

	} 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, array( 'all', 'global', 'ms_global' ), true ) ? ( 'all' === $scope ? 'global' : $scope ) : '';
			$wp_tables            = array_values( $wpdb->tables( $network_global_scope ) );
			if ( in_array( $scope, array( '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_tables, array_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_values( array_diff( $wp_tables, array( $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_col( sprintf( "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, 0 );
			$tables             = array_intersect( $tables, $views_query_tables );
		}
	}

	// Filter by `$args`.
	if ( $args ) {
		$args_tables = array();
		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_values( array_unique( $args_tables ) );
		$tables      = array_values( array_intersect( $tables, $args_tables ) );
		if ( empty( $tables ) ) {
			\WP_CLI::error( sprintf( "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

// 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( 1 );
}
#!/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 <fabien@symfony.com>
 */
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::HEADER, strftime('%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($file, 0666 & ~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_COMMENT, T_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 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 $ctag;
    private $otagLen;
    private $ctagLen;

    /**
     * Scan and tokenize template source.
     *
     * @throws Mustache_Exception_SyntaxException when mismatched section tags are encountered
     *
     * @param string $text       Mustache template source to tokenize
     * @param string $delimiters Optionally, pass initial opening and closing delimiters (default: null)
     *
     * @return array Set of Mustache tokens
     */
    public function scan($text, $delimiters = null)
    {
        // Setting mbstring.func_overload makes things *really* slow.
        // Let's do everyone a favor and scan this string as ASCII instead.
        //
        // @codeCoverageIgnoreStart
        $encoding = null;
        if (function_exists('mb_internal_encoding') && ini_get('mbstring.func_overload') & 2) {
            $encoding = mb_internal_encoding();
            mb_internal_encoding('ASCII');
        }
        // @codeCoverageIgnoreEnd

        $this->reset();

        if ($delimiters = trim($delimiters)) {
            $this->setDelimiters($delimiters);
        }

        $len = strlen($text);
        for ($i = 0; $i < $len; $i++) {
            switch ($this->state) {
                case self::IN_TEXT:
                    if ($this->tagChange($this->otag, $this->otagLen, $text, $i)) {
                        $i--;
                        $this->flushBuffer();
                        $this->state = self::IN_TAG_TYPE;
                    } else {
                        $char = $text[$i];
                        $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:
                    if ($this->tagChange($this->ctag, $this->ctagLen, $text, $i)) {
                        $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 + 2 < $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($lastName, 0, -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 .= $text[$i];
                    }
                    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->ctag    = '}}';
        $this->otagLen = 2;
        $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.
     *
     * @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);

        $this->setDelimiters(trim(substr($text, $startIndex, $closeIndex - $startIndex)));

        $this->tokens[] = array(
            self::TYPE => self::T_DELIM_CHANGE,
            self::LINE => $this->line,
        );

        return $closeIndex + strlen($close) - 1;
    }

    /**
     * Set the current Mustache `otag` and `ctag` delimiters.
     *
     * @param string $delimiters
     */
    private function setDelimiters($delimiters)
    {
        list($otag, $ctag) = explode(' ', $delimiters);
        $this->otag = $otag;
        $this->ctag = $ctag;
        $this->otagLen = strlen($otag);
        $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;
    }

    /**
     * Test whether it's time to change tags.
     *
     * @param string $tag    Current tag name
     * @param int    $tagLen Current tag name length
     * @param string $text   Mustache template source
     * @param int    $index  Current tokenizer index
     *
     * @return bool True if this is a closing section tag
     */
    private function tagChange($tag, $tagLen, $text, $index)
    {
        return substr($text, $index, $tagLen) === $tag;
    }
}
<?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($level, self::$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($level, self::$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->stream, self::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.
 */

/**
 * 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 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 $mustache, Mustache_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.
 */

/**
 * 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 ($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 (empty($parent[Mustache_Tokenizer::TYPE])) {
            return;
        }
        if ($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.
 */

/**
 * 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($class, 1);
        }

        if (strpos($class, 'Mustache') !== 0) {
            return;
        }

        $file = sprintf('%s/%s.php', $this->baseDir, str_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.
 */

/**
 * 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.
 */

/**
 * 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.
 */

/**
 * 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->fileName, false, null, $this->offset);
            foreach (preg_split("/^@@(?= [\w\d\.]+$)/m", $data, -1) as $chunk) {
                if (trim($chunk)) {
                    list($name, $content)         = explode("\n", $chunk, 2);
                    $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 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 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($fileName, 0 - strlen($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.
 */

/**
 * 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_Loader, Mustache_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 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($dirName, 0777, true);
            // @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($dirName, basename($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 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.
 */

/**
 * 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 int    $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 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.
 */

/**
 * 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($value, Mustache_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.
 */

/**
 * 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.12.0';
    const SPEC_VERSION   = '1.1.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($source, Mustache_Cache $cache = null)
    {
        $className = $this->getTemplateClassName($source);

        if (!isset($this->templates[$className])) {
            if ($cache === null) {
                $cache = $this->getCache();
            }

            if (!class_exists($className, false)) {
                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 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($klass, 0, false, true), $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($id, true);

        $else = $this->walk($nodes, $level);
        if ($else !== '') {
            $else = sprintf($this->prepare(self::BLOCK_VAR_ELSE, $level + 1, false, true), $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);
        $keystr = var_export($key, true);
        $id = var_export($id, true);

        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($nodes, 0);
        $key = ucfirst(md5($code));

        if (!isset($this->blocks[$key])) {
            $this->blocks[$key] = sprintf($this->prepare(self::BLOCK_FUNCTION, 0), $key, $code);
        }

        return $key;
    }

    const SECTION_CALL = '
        // %s section
        $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 = call_user_func($value, $source, %s);
                if (strpos($result, \'{{\') === false) {
                    $buffer .= $result;
                } else {
                    $buffer .= $this->mustache
                        ->loadLambda((string) $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($nodes, 2));
        }

        $method  = $this->getFindMethod($id);
        $id      = var_export($id, true);
        $filters = $this->getFilters($filters, $level);

        return sprintf($this->prepare(self::SECTION_CALL, $level), $id, $method, $id, $filters, $key);
    }

    const INVERTED_SECTION = '
        // %s 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($id, true);
        $filters = $this->getFilters($filters, $level);

        return sprintf($this->prepare(self::INVERTED_SECTION, $level), $id, $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_INDENT, var_export($indent, true));
        } else {
            $indentParam = '';
        }

        return sprintf(
            $this->prepare(self::PARTIAL, $level),
            var_export($id, true),
            $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($id, true));
        }

        return sprintf(
            $this->prepare(self::PARENT, $level),
            var_export($id, true),
            $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%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($id, true) : '';
        $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($name, true) : '';
        $callable = $this->getCallable('$filter');
        $msg      = var_export($name, true);

        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($text, true));
        $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, $value, var_export($this->entityFlags, true), var_export($this->charset, true));
    }

    /**
     * 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($id, 0, 1) === '.') {
                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.
 */

/**
 * 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.
 */

/**
 * 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);
}
<?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 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.
 */

/**
 * 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.
 */

/**
 * 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 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 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.
 */

/**
 * 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.
 */

/**
 * Unknown helper exception.
 */
class Mustache_Exception_UnknownHelperException extends InvalidArgumentException implements Mustache_Exception
{
    protected $helperName;

    /**
     * @param string    $helperName
     * @param Exception $previous
     */
    public function __construct($helperName, Exception $previous = null)
    {
        $this->helperName = $helperName;
        $message = sprintf('Unknown helper: %s', $helperName);
        if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
            parent::__construct($message, 0, $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.
 */

/**
 * 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 $token, Exception $previous = null)
    {
        $this->token = $token;
        if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
            parent::__construct($msg, 0, $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 filter exception.
 */
class Mustache_Exception_UnknownFilterException extends UnexpectedValueException implements Mustache_Exception
{
    protected $filterName;

    /**
     * @param string    $filterName
     * @param Exception $previous
     */
    public function __construct($filterName, Exception $previous = null)
    {
        $this->filterName = $filterName;
        $message = sprintf('Unknown filter: %s', $filterName);
        if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
            parent::__construct($message, 0, $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.
 */

/**
 * 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.
 */

/**
 * 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.
 */

/**
 * Unknown template exception.
 */
class Mustache_Exception_UnknownTemplateException extends InvalidArgumentException implements Mustache_Exception
{
    protected $templateName;

    /**
     * @param string    $templateName
     * @param Exception $previous
     */
    public function __construct($templateName, Exception $previous = null)
    {
        $this->templateName = $templateName;
        $message = sprintf('Unknown template: %s', $templateName);
        if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
            parent::__construct($message, 0, $previous);
        } else {
            parent::__construct($message); // @codeCoverageIgnore
        }
    }

    public function getTemplateName()
    {
        return $this->templateName;
    }
}
<?php
/**
 * PEAR package builder
 *
 * Inspired by Twig's create_pear_package.php.
 * @link https://raw.github.com/fabpot/Twig/master/bin/create_pear_package.php
 * @author Twig Team
 * @license BSD license
 */

if (!isset($argv[1]) || $argv[1] === '-h' || $argv[1] === '--help') {
	echo 'usage: php ' . $argv[0] . ' <version> <stability>' . PHP_EOL;
	echo PHP_EOL;
	echo '    version:' . PHP_EOL;
	echo '        Version of the package, in the form of major.minor.bug' . PHP_EOL;
	echo PHP_EOL;
	echo '    stability:' . PHP_EOL;
	echo '        One of alpha, beta, stable' . PHP_EOL;
	die();
}

if (!isset($argv[2])) {
	die('You must provide the stability (alpha, beta, or stable)');
}

$context = array(
	'date'          => gmdate('Y-m-d'),
	'time'          => gmdate('H:m:00'),
	'version'       => $argv[1],
	'api_version'   => $argv[1],
	'stability'     => $argv[2],
	'api_stability' => $argv[2],
);

$context['files'] = '';
$path = realpath(dirname(__FILE__).'/../library/Requests');
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path), RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
	if (preg_match('/\.php$/', $file)) 	{
		$name = str_replace($path . DIRECTORY_SEPARATOR, '', $file);
		$name = str_replace(DIRECTORY_SEPARATOR, '/', $name);
		$context['files'][] = "\t\t\t\t\t" . '<file install-as="Requests/' . $name . '" name="' . $name . '" role="php" />';
	}
}

$context['files'] = implode("\n", $context['files']);

$template = file_get_contents(dirname(__FILE__).'/../package.xml.tpl');
$content = preg_replace_callback('/\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/', 'replace_parameters', $template);
file_put_contents(dirname(__FILE__).'/../package.xml', $content);

function replace_parameters($matches) {
	global $context;

	return isset($context[$matches[1]]) ? $context[$matches[1]] : null;
}
<?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($string, self::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);

		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);
					}

					$character |= ($value & 0x3F) << (--$remaining * 6);
				}
				$position--;
			}

			if (
				// Non-shortest form sequences are invalid
				   $length > 1 && $character <= 0x7F
				|| $length > 2 && $character <= 0x7FF
				|| $length > 3 && $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 = $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
		while ($h < count($codepoints)) {
#			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 < count($codepoints); $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 < 0 || $digit > 35) {
			throw new Requests_Exception(sprintf('Invalid digit %d', $digit), 'idna.invalid_digit', $digit);
		}
		// @codeCoverageIgnoreEnd
		$digits = 'abcdefghijklmnopqrstuvwxyz0123456789';
		return substr($digits, $digit, 1);
	}

	/**
	 * 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
	 */
	protected static function adapt($delta, $numpoints, $firsttime) {
#	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
/**
 * 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(300, 301, 302, 303, 307)) || $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
/**
 * 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 <alexander.merz@web.de>
	 * @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 === '') ? -1 : substr_count($ip1, ':');
		$c2 = ($ip2 === '') ? -1 : substr_count($ip2, ':');

		if (strpos($ip2, '.') !== false) {
			$c2++;
		}
		// ::
		if ($c1 === -1 && $c2 === -1) {
			$ip = '0:0:0:0:0:0:0:0';
		}
		// ::xxx
		else if ($c1 === -1) {
			$fill = str_repeat('0:', 7 - $c2);
			$ip = str_replace('::', $fill, $ip);
		}
		// xxx::
		else if ($c2 === -1) {
			$fill = str_repeat(':0', 7 - $c1);
			$ip = str_replace('::', $fill, $ip);
		}
		// xxx::xxx
		else {
			$fill = ':' . str_repeat('0:', 6 - $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], $matches, PREG_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($ip, 0, $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) === 8 && count($ipv4) === 1 || count($ipv6) === 6 && 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 < 0 || $value > 0xFFFF) {
					return false;
				}
			}
			if (count($ipv4) === 4) {
				foreach ($ipv4 as $ipv4_part) {
					$value = (int) $ipv4_part;
					if ((string) $value !== $ipv4_part || $value < 0 || $value > 0xFF) {
						return false;
					}
				}
			}
			return true;
		}
		else {
			return false;
		}
	}
}
<?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($handle, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
		curl_setopt($handle, CURLOPT_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
/**
 * Cookie holder object
 *
 * @package Requests
 * @subpackage Cookies
 */

/**
 * Cookie holder object
 *
 * @package Requests
 * @subpackage Cookies
 */
class Requests_Cookie_Jar implements ArrayAccess, IteratorAggregate {
	/**
	 * 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 Item value
	 */
	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
/**
 * 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,
				// 'CN_match' => $host,
				'capture_peer_cert' => true
			);
			$verifyname = true;

			// SNI, if enabled (OpenSSL >=0.9.8j)
			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;
				}
				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'];

		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, $errstr, ceil($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, null, '&');
			}
			else {
				$request_body = $data;
			}

			if (!empty($data)) {
				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 (( 'http' === strtolower($url_parts['scheme']) && $url_parts['port'] !== 80 ) || ( 'https' === strtolower($url_parts['scheme']) && $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($headers, "\r\n") . "\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($socket, Requests::BUFFER_SIZE);
			if (!$doingbody) {
				$response .= $block;
				if (strpos($response, "\r\n\r\n")) {
					list($headers, $block) = explode("\r\n\r\n", $response, 2);
					$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($block, 0, $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, null, '&');
			$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) === 0 && ($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
/**
 * 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;

	/**
	 * Version string
	 *
	 * @var long
	 */
	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->handle, CURLOPT_HEADER, false);
		curl_setopt($this->handle, CURLOPT_RETURNTRANSFER, 1);
		if ($this->version >= self::CURL_7_10_5) {
			curl_setopt($this->handle, CURLOPT_ENCODING, '');
		}
		if (defined('CURLOPT_PROTOCOLS')) {
			curl_setopt($this->handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
		}
		if (defined('CURLOPT_REDIR_PROTOCOLS')) {
			curl_setopt($this->handle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_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->handle, CURLOPT_SSL_VERIFYHOST, 0);
				curl_setopt($this->handle, CURLOPT_SSL_VERIFYPEER, 0);
			}
			elseif (is_string($options['verify'])) {
				curl_setopt($this->handle, CURLOPT_CAINFO, $options['verify']);
			}
		}

		if (isset($options['verifyname']) && $options['verifyname'] === false) {
			curl_setopt($this->handle, CURLOPT_SSL_VERIFYHOST, 0);
		}

		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->handle, CURLOPT_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->handle, CURLOPT_HEADERFUNCTION, null);
		curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, null);

		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();

		$request['options']['hooks']->dispatch('curl.before_multi_exec', array(&$multihandle));

		do {
			$active = false;

			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'], $subhandles, true);
				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 (CURLE_OK !== $done['result']) {
					//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 < count($subrequests));

		$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';
		}

		$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($data, null, '&');
			}
		}

		switch ($options['type']) {
			case Requests::POST:
				curl_setopt($this->handle, CURLOPT_POST, true);
				curl_setopt($this->handle, CURLOPT_POSTFIELDS, $data);
				break;
			case Requests::HEAD:
				curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']);
				curl_setopt($this->handle, CURLOPT_NOBODY, true);
				break;
			case Requests::TRACE:
				curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']);
				break;
			case Requests::PATCH:
			case Requests::PUT:
			case Requests::DELETE:
			case Requests::OPTIONS:
			default:
				curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, $options['type']);
				if (!empty($data)) {
					curl_setopt($this->handle, CURLOPT_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->handle, CURLOPT_TIMEOUT, ceil($timeout));
		}
		else {
			curl_setopt($this->handle, CURLOPT_TIMEOUT_MS, round($timeout * 1000));
		}

		if (is_int($options['connect_timeout']) || $this->version < self::CURL_7_16_2) {
			curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT, ceil($options['connect_timeout']));
		}
		else {
			curl_setopt($this->handle, CURLOPT_CONNECTTIMEOUT_MS, round($options['connect_timeout'] * 1000));
		}
		curl_setopt($this->handle, CURLOPT_URL, $url);
		curl_setopt($this->handle, CURLOPT_REFERER, $url);
		curl_setopt($this->handle, CURLOPT_USERAGENT, $options['useragent']);
		if (!empty($headers)) {
			curl_setopt($this->handle, CURLOPT_HTTPHEADER, $headers);
		}
		if ($options['protocol_version'] === 1.1) {
			curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
		}
		else {
			curl_setopt($this->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
		}

		if (true === $options['blocking']) {
			curl_setopt($this->handle, CURLOPT_HEADERFUNCTION, array(&$this, 'stream_headers'));
			curl_setopt($this->handle, CURLOPT_WRITEFUNCTION, array(&$this, 'stream_body'));
			curl_setopt($this->handle, CURLOPT_BUFFERSIZE, Requests::BUFFER_SIZE);
		}
	}

	/**
	 * Process a response
	 *
	 * @param string $response Response data from the body
	 * @param array $options Request options
	 * @return string HTTP response data including headers
	 */
	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) {
			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($data, 0, $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)) {
			$url_parts = parse_url($url);
			if (empty($url_parts['query'])) {
				$query = $url_parts['query'] = '';
			}
			else {
				$query = $url_parts['query'];
			}

			$query .= '&' . http_build_query($data, null, '&');
			$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;
	}
}
<?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
/**
 * 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
	 */
	protected $scheme = null;

	/**
	 * User Information
	 *
	 * @var string
	 */
	protected $iuserinfo = null;

	/**
	 * ihost
	 *
	 * @var string
	 */
	protected $ihost = null;

	/**
	 * Port
	 *
	 * @var string
	 */
	protected $port = null;

	/**
	 * ipath
	 *
	 * @var string
	 */
	protected $ipath = '';

	/**
	 * iquery
	 *
	 * @var string
	 */
	protected $iquery = null;

	/**
	 * ifragment
	 *
	 * @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.
	 */
	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($name, 1)), $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($name, 1)) && array_key_exists($prop, $props)) {
			$name = $prop;
			$return = $this->$prop;
		}
		else {
			trigger_error('Undefined property: ' . get_class($this) . '::' . $name, E_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 IRI|string $base (Absolute) Base IRI
	 * @param IRI|string $relative Relative IRI
	 * @return 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->ipath, 0, $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($input, 3);
			}
			elseif (strpos($input, './') === 0) {
				$input = substr($input, 2);
			}
			// 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($input, 2);
			}
			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($input, 3);
				$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($input, 0, $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 > 1 && $character <= 0x7F
				|| $length > 2 && $character <= 0x7FF
				|| $length > 3 && $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($string, sprintf('%%%02X', ord($string[$j])), $j, 1);
					$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 > 1 && $character <= 0x7F
					|| $length > 2 && $character <= 0x7FF
					|| $length > 3 && $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($remaining, 0, $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($remaining, 0, $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($ihost, 0, 1) === '[' && substr($ihost, -1) === ']') {
			if (Requests_IPv6::check_ipv6(substr($ihost, 1, -1))) {
				$this->ihost = '[' . Requests_IPv6::compress(substr($ihost, 1, -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($string, sprintf('%%%02X', ord($string[$position])), $position, 1);
			$position += 3;
			$strlen += 2;
		}

		return $string;
	}

	/**
	 * Get the complete IRI
	 *
	 * @return string
	 */
	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
	 */
	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
/**
 * 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
/**
 * 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 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 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
/**
 * 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($handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
		curl_setopt($handle, CURLOPT_PROXY, $this->proxy);

		if ($this->use_authentication) {
			curl_setopt($handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
			curl_setopt($handle, CURLOPT_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
/**
 * 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, $headers, null, Requests::GET, $options);
	}

	/**
	 * Send a HEAD request
	 */
	public function head($url, $headers = array(), $options = array()) {
		return $this->request($url, $headers, null, Requests::HEAD, $options);
	}

	/**
	 * Send a DELETE request
	 */
	public function delete($url, $headers = array(), $options = array()) {
		return $this->request($url, $headers, null, Requests::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, $data, Requests::POST, $options);
	}

	/**
	 * Send a PUT request
	 */
	public function put($url, $headers = array(), $data = array(), $options = array()) {
		return $this->request($url, $headers, $data, Requests::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, $data, Requests::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($request, false);
		}

		$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
/**
 * 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.
	 * Instead
	 *
	 * @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) {
		// Calculate the valid wildcard match if the host is not an IP address
		$parts = explode('.', $host);
		if (ip2long($host) === false) {
			$parts[0] = '*';
		}
		$wildcard = implode('.', $parts);

		$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($altname, 4));

				// 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 blacklisted 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
/**
 * 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
/**
 * 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
/**
 * 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
/**
 * 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, -1 * strlen($domain_string)) !== $domain_string) {
			// The domain string should be a suffix of the string.
			return false;
		}

		$prefix = substr($string, 0, strlen($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_path, 0, strlen($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_path, strlen($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':
				// Domain normalization, as per RFC 6265 section 5.2.3
				if ($value[0] === '.') {
					$value = substr($value, 1);
				}

				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('=', $kvparts, 2);
		}
		$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('=', $part, 2);
					$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 $headers, Requests_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($path, 0, 1) !== '/') {
					// 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($path, 0, strrpos($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 string
	 */
	public static function parseFromHeaders(Requests_Response_Headers $headers) {
		return self::parse_from_headers($headers);
	}
}
<?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 ArrayAccess, IteratorAggregate {
	/**
	 * 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 Item value
	 */
	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();
		$value = call_user_func($this->callback, $value);
		return $value;
	}
}
<?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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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
/**
 * 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 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 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 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 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 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 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 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 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 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 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 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

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

class Requests_Exception_Transport extends Requests_Exception {

}
<?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.7';

	/**
	 * 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) {
			return new self::$transport[$cap_string]();
		}
		// @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);
		}

		return new self::$transport[$cap_string]();
	}

	/**#@+
	 * @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, $headers, null, self::GET, $options);
	}

	/**
	 * Send a HEAD request
	 */
	public static function head($url, $headers = array(), $options = array()) {
		return self::request($url, $headers, null, self::HEAD, $options);
	}

	/**
	 * Send a DELETE request
	 */
	public static function delete($url, $headers = array(), $options = array()) {
		return self::request($url, $headers, null, self::DELETE, $options);
	}

	/**
	 * Send a TRACE request
	 */
	public static function trace($url, $headers = array(), $options = array()) {
		return self::request($url, $headers, null, self::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, $data, self::POST, $options);
	}
	/**
	 * Send a PUT request
	 */
	public static function put($url, $headers = array(), $data = array(), $options = array()) {
		return self::request($url, $headers, $data, self::PUT, $options);
	}

	/**
	 * Send an OPTIONS request
	 */
	public static function options($url, $headers = array(), $data = array(), $options = array()) {
		return self::request($url, $headers, $data, self::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, $data, self::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 = (0 === stripos($url, 'https://'));
			$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' => Requests::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( Requests::$certificate_path ) ) {
			return Requests::$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 ) {
		Requests::$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::HEAD, self::GET, self::DELETE))) {
				$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 = $url;

		if (!$options['filename']) {
			if (($pos = strpos($headers, "\r\n\r\n")) === false) {
				// Crap!
				throw new Requests_Exception('Missing header/body separator', 'requests.no_crlf_separator');
			}

			$headers = substr($return->raw, 0, $pos);
			$return->body = substr($return->raw, $pos + strlen("\n\r\n\r"));
		}
		else {
			$return->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(':', $header, 2);
			$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://') !== 0 && 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($data, 0, 2) !== "\x1f\x8b" && substr($data, 0, 2) !== "\x78\x9c") {
			// Not actually compressed. Probably cURL ruining this for us.
			return $data;
		}

		if (function_exists('gzdecode') && ($decoded = @gzdecode($data)) !== false) {
			return $decoded;
		}
		elseif (function_exists('gzinflate') && ($decoded = @gzinflate($data)) !== false) {
			return $decoded;
		}
		elseif (($decoded = self::compatible_gzinflate($data)) !== false) {
			return $decoded;
		}
		elseif (function_exists('gzuncompress') && ($decoded = @gzuncompress($data)) !== 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 $gzData String to decompress.
	 * @return string|bool False on failure.
	 */
	public static function compatible_gzinflate($gzData) {
		// Compressed data might contain a full zlib header, if so strip it for
		// gzinflate()
		if (substr($gzData, 0, 3) == "\x1f\x8b\x08") {
			$i = 10;
			$flg = ord(substr($gzData, 3, 1));
			if ($flg > 0) {
				if ($flg & 4) {
					list($xlen) = unpack('v', substr($gzData, $i, 2));
					$i = $i + 2 + $xlen;
				}
				if ($flg & 8) {
					$i = strpos($gzData, "\0", $i) + 1;
				}
				if ($flg & 16) {
					$i = strpos($gzData, "\0", $i) + 1;
				}
				if ($flg & 2) {
					$i = $i + 2;
				}
			}
			$decompressed = self::compatible_gzinflate(substr($gzData, $i));
			if (false !== $decompressed) {
				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', $gzData);

		// First 2 bytes should be divisible by 0x1F
		list(, $first_two_bytes) = unpack('n', $gzData);

		if (0x08 == $first_nibble && 0 == ($first_two_bytes % 0x1F)) {
			$huffman_encoded = true;
		}

		if ($huffman_encoded) {
			if (false !== ($decompressed = @gzinflate(substr($gzData, 2)))) {
				return $decompressed;
			}
		}

		if ("\x50\x4b\x03\x04" == substr($gzData, 0, 4)) {
			// 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($gzData, 6, 2));

			// 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 == (0x08 & $general_purpose_flag));

			if (!$zip_compressed_on_the_fly) {
				// Don't attempt to decode a compressed zip file
				return $gzData;
			}

			// Determine the first byte of data, based on the above ZIP header
			// offsets:
			$first_file_start = array_sum(unpack('v2', substr($gzData, 26, 4)));
			if (false !== ($decompressed = @gzinflate(substr($gzData, 30 + $first_file_start)))) {
				return $decompressed;
			}
			return false;
		}

		// Finally fall back to straight gzinflate
		if (false !== ($decompressed = @gzinflate($gzData))) {
			return $decompressed;
		}

		// Fallback for all above failing, not expected, but included for
		// debugging and preventing regressions and to track stats
		if (false !== ($decompressed = @gzinflate(substr($gzData, 2)))) {
			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

// 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',
    '8a0ad02df6a5087f2c380f8fd52db273' => $vendorDir . '/wp-cli/cache-command/cache-command.php',
    'f3f0199a3ecd9f501d0a3b361bd2f61c' => $vendorDir . '/wp-cli/entity-command/entity-command.php',
    'c65f753375faee349b7adc48c2ee7cc2' => $vendorDir . '/wp-cli/db-command/db-command.php',
    '3937806105cc8e221b8fa8db5b70d2f2' => $vendorDir . '/wp-cli/mustangostang-spyc/includes/functions.php',
    'be01b9b16925dcb22165c40b46681ac6' => $vendorDir . '/wp-cli/php-cli-tools/lib/cli/cli.php',
    '3f201033d5aceb2293314273be88f7c6' => $vendorDir . '/wp-cli/extension-command/extension-command.php',
    'd979c11fe80ba96ae3037b43429fe546' => $vendorDir . '/wp-cli/scaffold-command/scaffold-command.php',
    'b66d29757fcb2fb7a9608d068e3716b0' => $vendorDir . '/wp-cli/checksum-command/checksum-command.php',
    '5c6ec5cff8f9d625772c8ed147f6b894' => $vendorDir . '/wp-cli/export-command/export-command.php',
    'ac949ce40a981819ba132473518a9a31' => $vendorDir . '/wp-cli/wp-config-transformer/src/WPConfigTransformer.php',
    '5deaf6ce9c8bbdfb65104c7e938d5875' => $vendorDir . '/wp-cli/config-command/config-command.php',
    '68c39b88215b6cf7a0da164166670ef9' => $vendorDir . '/wp-cli/core-command/core-command.php',
    'f958dca3f412fd7975da1700912a9321' => $vendorDir . '/wp-cli/eval-command/eval-command.php',
    '30cbb6e4122dc988e494c6b9c0438233' => $vendorDir . '/wp-cli/import-command/import-command.php',
    '5e099d3cac677dd2bec1003ea7707745' => $vendorDir . '/wp-cli/media-command/media-command.php',
    'ba366f96f4fddbdef61ad7a862b44f61' => $vendorDir . '/wp-cli/package-command/package-command.php',
    '7654e00bf0e632580764400bd8293a9c' => $vendorDir . '/wp-cli/cron-command/cron-command.php',
    '021d3a13471556f0b57038d679f7f8ea' => $vendorDir . '/wp-cli/embed-command/embed-command.php',
    'ffb465a494c3101218c4417180c2c9a2' => $vendorDir . '/wp-cli/i18n-command/i18n-command.php',
    'ace0d205db7f4135ec32132a0076d555' => $vendorDir . '/wp-cli/language-command/language-command.php',
    '1c88c1eff05217a8cac80c64c9ac2080' => $vendorDir . '/wp-cli/maintenance-mode-command/maintenance-mode-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',
    '9f04dd0aa5d67ec75a75c88c345a079e' => $vendorDir . '/wp-cli/server-command/server-command.php',
    '129d58fa8151374aceb8571bcaa97504' => $vendorDir . '/wp-cli/shell-command/shell-command.php',
    '8519779bbb65eeb842af2f629ce7b6f8' => $vendorDir . '/wp-cli/super-admin-command/super-admin-command.php',
    '1f05372afcc7d0c51a305cef1d56dd01' => $vendorDir . '/wp-cli/widget-command/widget-command.php',
);
<?php

// autoload_static.php @generated by Composer

namespace Composer\Autoload;

class ComposerStaticInit5925cbfbda4231e100bd9070d8ec30d5
{
    public static $files = array (
        '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
        '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
        '8a0ad02df6a5087f2c380f8fd52db273' => __DIR__ . '/..' . '/wp-cli/cache-command/cache-command.php',
        'f3f0199a3ecd9f501d0a3b361bd2f61c' => __DIR__ . '/..' . '/wp-cli/entity-command/entity-command.php',
        'c65f753375faee349b7adc48c2ee7cc2' => __DIR__ . '/..' . '/wp-cli/db-command/db-command.php',
        '3937806105cc8e221b8fa8db5b70d2f2' => __DIR__ . '/..' . '/wp-cli/mustangostang-spyc/includes/functions.php',
        'be01b9b16925dcb22165c40b46681ac6' => __DIR__ . '/..' . '/wp-cli/php-cli-tools/lib/cli/cli.php',
        '3f201033d5aceb2293314273be88f7c6' => __DIR__ . '/..' . '/wp-cli/extension-command/extension-command.php',
        'd979c11fe80ba96ae3037b43429fe546' => __DIR__ . '/..' . '/wp-cli/scaffold-command/scaffold-command.php',
        'b66d29757fcb2fb7a9608d068e3716b0' => __DIR__ . '/..' . '/wp-cli/checksum-command/checksum-command.php',
        '5c6ec5cff8f9d625772c8ed147f6b894' => __DIR__ . '/..' . '/wp-cli/export-command/export-command.php',
        'ac949ce40a981819ba132473518a9a31' => __DIR__ . '/..' . '/wp-cli/wp-config-transformer/src/WPConfigTransformer.php',
        '5deaf6ce9c8bbdfb65104c7e938d5875' => __DIR__ . '/..' . '/wp-cli/config-command/config-command.php',
        '68c39b88215b6cf7a0da164166670ef9' => __DIR__ . '/..' . '/wp-cli/core-command/core-command.php',
        'f958dca3f412fd7975da1700912a9321' => __DIR__ . '/..' . '/wp-cli/eval-command/eval-command.php',
        '30cbb6e4122dc988e494c6b9c0438233' => __DIR__ . '/..' . '/wp-cli/import-command/import-command.php',
        '5e099d3cac677dd2bec1003ea7707745' => __DIR__ . '/..' . '/wp-cli/media-command/media-command.php',
        'ba366f96f4fddbdef61ad7a862b44f61' => __DIR__ . '/..' . '/wp-cli/package-command/package-command.php',
        '7654e00bf0e632580764400bd8293a9c' => __DIR__ . '/..' . '/wp-cli/cron-command/cron-command.php',
        '021d3a13471556f0b57038d679f7f8ea' => __DIR__ . '/..' . '/wp-cli/embed-command/embed-command.php',
        'ffb465a494c3101218c4417180c2c9a2' => __DIR__ . '/..' . '/wp-cli/i18n-command/i18n-command.php',
        'ace0d205db7f4135ec32132a0076d555' => __DIR__ . '/..' . '/wp-cli/language-command/language-command.php',
        '1c88c1eff05217a8cac80c64c9ac2080' => __DIR__ . '/..' . '/wp-cli/maintenance-mode-command/maintenance-mode-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',
        '9f04dd0aa5d67ec75a75c88c345a079e' => __DIR__ . '/..' . '/wp-cli/server-command/server-command.php',
        '129d58fa8151374aceb8571bcaa97504' => __DIR__ . '/..' . '/wp-cli/shell-command/shell-command.php',
        '8519779bbb65eeb842af2f629ce7b6f8' => __DIR__ . '/..' . '/wp-cli/super-admin-command/super-admin-command.php',
        '1f05372afcc7d0c51a305cef1d56dd01' => __DIR__ . '/..' . '/wp-cli/widget-command/widget-command.php',
    );

    public static $prefixLengthsPsr4 = array (
        'c' => 
        array (
            'cweagans\\Composer\\' => 18,
        ),
        'W' => 
        array (
            'WP_CLI\\Maintenance\\' => 19,
            '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\\Yaml\\' => 23,
            'Symfony\\Component\\Translation\\' => 30,
            'Symfony\\Component\\Process\\' => 26,
            'Symfony\\Component\\Finder\\' => 25,
            'Symfony\\Component\\Filesystem\\' => 29,
            'Symfony\\Component\\EventDispatcher\\' => 34,
            'Symfony\\Component\\DependencyInjection\\' => 38,
            'Symfony\\Component\\Debug\\' => 24,
            'Symfony\\Component\\Console\\' => 26,
            'Symfony\\Component\\Config\\' => 25,
            'Seld\\PharUtils\\' => 15,
            'Seld\\JsonLint\\' => 14,
        ),
        'P' => 
        array (
            'Psr\\Log\\' => 8,
            'Prophecy\\' => 9,
            'Peast\\test\\' => 11,
            'Peast\\' => 6,
        ),
        'M' => 
        array (
            'Mustangostang\\' => 14,
        ),
        'J' => 
        array (
            'JsonSchema\\' => 11,
            'JakubOnderka\\PhpConsoleHighlighter\\' => 35,
            'JakubOnderka\\PhpConsoleColor\\' => 29,
        ),
        'G' => 
        array (
            'Gettext\\Languages\\' => 18,
            'Gettext\\' => 8,
        ),
        'D' => 
        array (
            'Doctrine\\Instantiator\\' => 22,
            'Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\' => 55,
        ),
        'C' => 
        array (
            'Composer\\XdebugHandler\\' => 23,
            'Composer\\Spdx\\' => 14,
            'Composer\\Semver\\' => 16,
            'Composer\\CaBundle\\' => 18,
            'Composer\\' => 9,
        ),
    );

    public static $prefixDirsPsr4 = array (
        'cweagans\\Composer\\' => 
        array (
            0 => __DIR__ . '/..' . '/cweagans/composer-patches/src',
        ),
        'WP_CLI\\Maintenance\\' => 
        array (
            0 => __DIR__ . '/../..' . '/utils/maintenance',
        ),
        'WP_CLI\\MaintenanceMode\\' => 
        array (
            0 => __DIR__ . '/..' . '/wp-cli/maintenance-mode-command/src',
        ),
        'WP_CLI\\I18n\\' => 
        array (
            0 => __DIR__ . '/..' . '/wp-cli/i18n-command/src',
        ),
        'WP_CLI\\Embeds\\' => 
        array (
            0 => __DIR__ . '/..' . '/wp-cli/embed-command/src',
        ),
        'WP_CLI\\' => 
        array (
            0 => __DIR__ . '/..' . '/wp-cli/entity-command/src/WP_CLI',
            1 => __DIR__ . '/..' . '/wp-cli/shell-command/src/WP_CLI',
        ),
        'Symfony\\Polyfill\\Mbstring\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
        ),
        'Symfony\\Polyfill\\Ctype\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/polyfill-ctype',
        ),
        'Symfony\\Component\\Yaml\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/yaml',
        ),
        'Symfony\\Component\\Translation\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/translation',
        ),
        'Symfony\\Component\\Process\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/process',
        ),
        'Symfony\\Component\\Finder\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/finder',
        ),
        'Symfony\\Component\\Filesystem\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/filesystem',
        ),
        'Symfony\\Component\\EventDispatcher\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/event-dispatcher',
        ),
        'Symfony\\Component\\DependencyInjection\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/dependency-injection',
        ),
        'Symfony\\Component\\Debug\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/debug',
        ),
        'Symfony\\Component\\Console\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/console',
        ),
        'Symfony\\Component\\Config\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/config',
        ),
        'Seld\\PharUtils\\' => 
        array (
            0 => __DIR__ . '/..' . '/seld/phar-utils/src',
        ),
        'Seld\\JsonLint\\' => 
        array (
            0 => __DIR__ . '/..' . '/seld/jsonlint/src/Seld/JsonLint',
        ),
        'Psr\\Log\\' => 
        array (
            0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
        ),
        'Prophecy\\' => 
        array (
            0 => __DIR__ . '/..' . '/phpspec/prophecy/src/Prophecy',
        ),
        'Peast\\test\\' => 
        array (
            0 => __DIR__ . '/..' . '/mck89/peast/test/Peast',
        ),
        'Peast\\' => 
        array (
            0 => __DIR__ . '/..' . '/mck89/peast/lib/Peast',
        ),
        'Mustangostang\\' => 
        array (
            0 => __DIR__ . '/..' . '/wp-cli/mustangostang-spyc/src',
        ),
        'JsonSchema\\' => 
        array (
            0 => __DIR__ . '/..' . '/justinrainbow/json-schema/src/JsonSchema',
        ),
        'JakubOnderka\\PhpConsoleHighlighter\\' => 
        array (
            0 => __DIR__ . '/..' . '/jakub-onderka/php-console-highlighter/src',
        ),
        'JakubOnderka\\PhpConsoleColor\\' => 
        array (
            0 => __DIR__ . '/..' . '/jakub-onderka/php-console-color/src',
        ),
        'Gettext\\Languages\\' => 
        array (
            0 => __DIR__ . '/..' . '/gettext/languages/src',
        ),
        'Gettext\\' => 
        array (
            0 => __DIR__ . '/..' . '/gettext/gettext/src',
        ),
        'Doctrine\\Instantiator\\' => 
        array (
            0 => __DIR__ . '/..' . '/doctrine/instantiator/src/Doctrine/Instantiator',
        ),
        'Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\' => 
        array (
        ),
        'Composer\\XdebugHandler\\' => 
        array (
            0 => __DIR__ . '/..' . '/composer/xdebug-handler/src',
        ),
        'Composer\\Spdx\\' => 
        array (
        ),
        'Composer\\Semver\\' => 
        array (
            0 => __DIR__ . '/..' . '/composer/semver/src',
        ),
        'Composer\\CaBundle\\' => 
        array (
            0 => __DIR__ . '/..' . '/composer/ca-bundle/src',
        ),
        'Composer\\' => 
        array (
            0 => __DIR__ . '/..' . '/composer/composer/src/Composer',
        ),
    );

    public static $fallbackDirsPsr4 = array (
        0 => __DIR__ . '/..' . '/wp-cli/cache-command/src',
        1 => __DIR__ . '/..' . '/wp-cli/checksum-command/src',
        2 => __DIR__ . '/..' . '/wp-cli/config-command/src',
        3 => __DIR__ . '/..' . '/wp-cli/core-command/src',
        4 => __DIR__ . '/..' . '/wp-cli/cron-command/src',
        5 => __DIR__ . '/..' . '/wp-cli/db-command/src',
        6 => __DIR__ . '/..' . '/wp-cli/entity-command/src',
        7 => __DIR__ . '/..' . '/wp-cli/eval-command/src',
        8 => __DIR__ . '/..' . '/wp-cli/export-command/src',
        9 => __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/scaffold-command/src',
        17 => __DIR__ . '/..' . '/wp-cli/search-replace-command/src',
        18 => __DIR__ . '/..' . '/wp-cli/server-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 (
        'p' => 
        array (
            'phpDocumentor' => 
            array (
                0 => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src',
            ),
        ),
        'c' => 
        array (
            'cli' => 
            array (
                0 => __DIR__ . '/..' . '/wp-cli/php-cli-tools/lib',
            ),
        ),
        'W' => 
        array (
            'WP_CLI' => 
            array (
                0 => __DIR__ . '/..' . '/wp-cli/wp-cli/php',
            ),
        ),
        'R' => 
        array (
            'Requests' => 
            array (
                0 => __DIR__ . '/..' . '/rmccue/requests/library',
            ),
        ),
        'O' => 
        array (
            'Oxymel' => 
            array (
                0 => __DIR__ . '/..' . '/nb/oxymel',
            ),
        ),
        'M' => 
        array (
            'Mustache' => 
            array (
                0 => __DIR__ . '/..' . '/mustache/mustache/src',
            ),
        ),
        'B' => 
        array (
            'Behat\\Gherkin' => 
            array (
                0 => __DIR__ . '/..' . '/behat/gherkin/src',
            ),
            'Behat\\Behat' => 
            array (
                0 => __DIR__ . '/..' . '/behat/behat/src',
            ),
        ),
    );

    public static $classMap = array (
        'ExampleClass' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/tests/skip-on-5.3/class.php',
        'ExampleTrait' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/tests/skip-on-5.3/trait.php',
        'JakubOnderka\\PhpParallelLint\\ArrayIterator' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/src/Settings.php',
        'JakubOnderka\\PhpParallelLint\\Blame' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/src/Error.php',
        'JakubOnderka\\PhpParallelLint\\ConsoleWriter' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/src/Output.php',
        'JakubOnderka\\PhpParallelLint\\Error' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/src/Error.php',
        'JakubOnderka\\PhpParallelLint\\ErrorFormatter' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/src/ErrorFormatter.php',
        'JakubOnderka\\PhpParallelLint\\Exception' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/src/exceptions.php',
        'JakubOnderka\\PhpParallelLint\\FileWriter' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/src/Output.php',
        'JakubOnderka\\PhpParallelLint\\IWriter' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/src/Output.php',
        'JakubOnderka\\PhpParallelLint\\InvalidArgumentException' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/src/exceptions.php',
        'JakubOnderka\\PhpParallelLint\\JsonOutput' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/src/Output.php',
        'JakubOnderka\\PhpParallelLint\\Manager' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/src/Manager.php',
        'JakubOnderka\\PhpParallelLint\\MultipleWriter' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/src/Output.php',
        'JakubOnderka\\PhpParallelLint\\NotExistsPathException' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/src/exceptions.php',
        'JakubOnderka\\PhpParallelLint\\NullWriter' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/src/Output.php',
        'JakubOnderka\\PhpParallelLint\\Output' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/src/Output.php',
        'JakubOnderka\\PhpParallelLint\\ParallelLint' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/src/ParallelLint.php',
        'JakubOnderka\\PhpParallelLint\\Process\\GitBlameProcess' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/src/Process/GitBlameProcess.php',
        'JakubOnderka\\PhpParallelLint\\Process\\LintProcess' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/src/Process/LintProcess.php',
        'JakubOnderka\\PhpParallelLint\\Process\\PhpExecutable' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/src/Process/PhpExecutable.php',
        'JakubOnderka\\PhpParallelLint\\Process\\PhpProcess' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/src/Process/PhpProcess.php',
        'JakubOnderka\\PhpParallelLint\\Process\\Process' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/src/Process/Process.php',
        'JakubOnderka\\PhpParallelLint\\Process\\SkipLintProcess' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/src/Process/SkipLintProcess.php',
        'JakubOnderka\\PhpParallelLint\\RecursiveDirectoryFilterIterator' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/src/Manager.php',
        'JakubOnderka\\PhpParallelLint\\Result' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/src/Result.php',
        'JakubOnderka\\PhpParallelLint\\RunTimeException' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/src/exceptions.php',
        'JakubOnderka\\PhpParallelLint\\Settings' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/src/Settings.php',
        'JakubOnderka\\PhpParallelLint\\SyntaxError' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/src/Error.php',
        'JakubOnderka\\PhpParallelLint\\TextOutput' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/src/Output.php',
        'JakubOnderka\\PhpParallelLint\\TextOutputColored' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/src/Output.php',
        'JsonSerializable' => __DIR__ . '/..' . '/jakub-onderka/php-parallel-lint/src/JsonSerializable.php',
        'SebastianBergmann\\Comparator\\ArrayComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ArrayComparator.php',
        'SebastianBergmann\\Comparator\\Comparator' => __DIR__ . '/..' . '/sebastian/comparator/src/Comparator.php',
        'SebastianBergmann\\Comparator\\ComparisonFailure' => __DIR__ . '/..' . '/sebastian/comparator/src/ComparisonFailure.php',
        'SebastianBergmann\\Comparator\\DOMNodeComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/DOMNodeComparator.php',
        'SebastianBergmann\\Comparator\\DateTimeComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/DateTimeComparator.php',
        'SebastianBergmann\\Comparator\\DoubleComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/DoubleComparator.php',
        'SebastianBergmann\\Comparator\\ExceptionComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ExceptionComparator.php',
        'SebastianBergmann\\Comparator\\Factory' => __DIR__ . '/..' . '/sebastian/comparator/src/Factory.php',
        'SebastianBergmann\\Comparator\\MockObjectComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/MockObjectComparator.php',
        'SebastianBergmann\\Comparator\\NumericComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/NumericComparator.php',
        'SebastianBergmann\\Comparator\\ObjectComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ObjectComparator.php',
        'SebastianBergmann\\Comparator\\ResourceComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ResourceComparator.php',
        'SebastianBergmann\\Comparator\\ScalarComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ScalarComparator.php',
        'SebastianBergmann\\Comparator\\SplObjectStorageComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/SplObjectStorageComparator.php',
        'SebastianBergmann\\Comparator\\TypeComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/TypeComparator.php',
        'SebastianBergmann\\Diff\\Chunk' => __DIR__ . '/..' . '/sebastian/diff/src/Chunk.php',
        'SebastianBergmann\\Diff\\Diff' => __DIR__ . '/..' . '/sebastian/diff/src/Diff.php',
        'SebastianBergmann\\Diff\\Differ' => __DIR__ . '/..' . '/sebastian/diff/src/Differ.php',
        'SebastianBergmann\\Diff\\LCS\\LongestCommonSubsequence' => __DIR__ . '/..' . '/sebastian/diff/src/LCS/LongestCommonSubsequence.php',
        'SebastianBergmann\\Diff\\LCS\\MemoryEfficientImplementation' => __DIR__ . '/..' . '/sebastian/diff/src/LCS/MemoryEfficientLongestCommonSubsequenceImplementation.php',
        'SebastianBergmann\\Diff\\LCS\\TimeEfficientImplementation' => __DIR__ . '/..' . '/sebastian/diff/src/LCS/TimeEfficientLongestCommonSubsequenceImplementation.php',
        'SebastianBergmann\\Diff\\Line' => __DIR__ . '/..' . '/sebastian/diff/src/Line.php',
        'SebastianBergmann\\Diff\\Parser' => __DIR__ . '/..' . '/sebastian/diff/src/Parser.php',
        'SebastianBergmann\\Environment\\Console' => __DIR__ . '/..' . '/sebastian/environment/src/Console.php',
        'SebastianBergmann\\Environment\\Runtime' => __DIR__ . '/..' . '/sebastian/environment/src/Runtime.php',
        'SebastianBergmann\\Exporter\\Exporter' => __DIR__ . '/..' . '/sebastian/exporter/src/Exporter.php',
        'SebastianBergmann\\GlobalState\\Blacklist' => __DIR__ . '/..' . '/sebastian/global-state/src/Blacklist.php',
        'SebastianBergmann\\GlobalState\\CodeExporter' => __DIR__ . '/..' . '/sebastian/global-state/src/CodeExporter.php',
        'SebastianBergmann\\GlobalState\\Exception' => __DIR__ . '/..' . '/sebastian/global-state/src/Exception.php',
        'SebastianBergmann\\GlobalState\\Restorer' => __DIR__ . '/..' . '/sebastian/global-state/src/Restorer.php',
        'SebastianBergmann\\GlobalState\\RuntimeException' => __DIR__ . '/..' . '/sebastian/global-state/src/RuntimeException.php',
        'SebastianBergmann\\GlobalState\\Snapshot' => __DIR__ . '/..' . '/sebastian/global-state/src/Snapshot.php',
        'SebastianBergmann\\RecursionContext\\Context' => __DIR__ . '/..' . '/sebastian/recursion-context/src/Context.php',
        'SebastianBergmann\\RecursionContext\\Exception' => __DIR__ . '/..' . '/sebastian/recursion-context/src/Exception.php',
        'SebastianBergmann\\RecursionContext\\InvalidArgumentException' => __DIR__ . '/..' . '/sebastian/recursion-context/src/InvalidArgumentException.php',
        'SebastianBergmann\\Version' => __DIR__ . '/..' . '/sebastian/version/src/Version.php',
    );

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

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

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

namespace Composer\IO;

use Composer\Config;
use Composer\Util\ProcessExecutor;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;

abstract class BaseIO implements IOInterface, LoggerInterface
{
    protected $authentications = array();

    /**
     * {@inheritDoc}
     */
    public function getAuthentications()
    {
        return $this->authentications;
    }

    /**
     * {@inheritDoc}
     */
    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);
    }

    /**
     * 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
     */
    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();

        // reload oauth tokens from config if available

        foreach ($bitbucketOauth as $domain => $cred) {
            $this->checkAndSetAuthentication($domain, $cred['consumer-key'], $cred['consumer-secret']);
        }

        foreach ($githubOauth as $domain => $token) {
            if (!preg_match('{^[.a-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) {
            $this->checkAndSetAuthentication($domain, $token, 'private-token');
        }

        // reload http basic credentials from config if available
        foreach ($httpBasic as $domain => $cred) {
            $this->checkAndSetAuthentication($domain, $cred['username'], $cred['password']);
        }

        // setup process timeout
        ProcessExecutor::setTimeout((int) $config->get('process-timeout'));
    }

    /**
     * System is unusable.
     *
     * @param  string $message
     * @param  array  $context
     * @return null
     */
    public function emergency($message, array $context = array())
    {
        return $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 null
     */
    public function alert($message, array $context = array())
    {
        return $this->log(LogLevel::ALERT, $message, $context);
    }

    /**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     *
     * @param  string $message
     * @param  array  $context
     * @return null
     */
    public function critical($message, array $context = array())
    {
        return $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 null
     */
    public function error($message, array $context = array())
    {
        return $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 null
     */
    public function warning($message, array $context = array())
    {
        return $this->log(LogLevel::WARNING, $message, $context);
    }

    /**
     * Normal but significant events.
     *
     * @param  string $message
     * @param  array  $context
     * @return null
     */
    public function notice($message, array $context = array())
    {
        return $this->log(LogLevel::NOTICE, $message, $context);
    }

    /**
     * Interesting events.
     *
     * Example: User logs in, SQL logs.
     *
     * @param  string $message
     * @param  array  $context
     * @return null
     */
    public function info($message, array $context = array())
    {
        return $this->log(LogLevel::INFO, $message, $context);
    }

    /**
     * Detailed debug information.
     *
     * @param  string $message
     * @param  array  $context
     * @return null
     */
    public function debug($message, array $context = array())
    {
        return $this->log(LogLevel::DEBUG, $message, $context);
    }

    /**
     * Logs with an arbitrary level.
     *
     * @param  mixed  $level
     * @param  string $message
     * @param  array  $context
     * @return null
     */
    public function log($level, $message, array $context = array())
    {
        if (in_array($level, array(LogLevel::EMERGENCY, LogLevel::ALERT, LogLevel::CRITICAL, LogLevel::ERROR))) {
            $this->writeError('<error>'.$message.'</error>', true, self::NORMAL);
        } elseif ($level === LogLevel::WARNING) {
            $this->writeError('<warning>'.$message.'</warning>', true, self::NORMAL);
        } elseif ($level === LogLevel::NOTICE) {
            $this->writeError('<info>'.$message.'</info>', true, self::VERBOSE);
        } elseif ($level === LogLevel::INFO) {
            $this->writeError('<info>'.$message.'</info>', true, self::VERY_VERBOSE);
        } else {
            $this->writeError($message, true, self::DEBUG);
        }
    }
}
<?php

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

namespace Composer\IO;

use Composer\Question\StrictConfirmationQuestion;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\Question;

/**
 * The Input/Output helper.
 *
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ConsoleIO extends BaseIO
{
    /** @var InputInterface */
    protected $input;
    /** @var OutputInterface */
    protected $output;
    /** @var HelperSet */
    protected $helperSet;
    /** @var string */
    protected $lastMessage;
    /** @var string */
    protected $lastMessageErr;

    /** @var float */
    private $startTime;
    /** @var array<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 $input, OutputInterface $output, HelperSet $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
     */
    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, $newline, false, $verbosity);
    }

    /**
     * {@inheritDoc}
     */
    public function writeError($messages, $newline = true, $verbosity = self::NORMAL)
    {
        $this->doWrite($messages, $newline, true, $verbosity);
    }

    /**
     * @param array|string $messages
     * @param bool         $newline
     * @param bool         $stderr
     * @param int          $verbosity
     */
    private function doWrite($messages, $newline, $stderr, $verbosity)
    {
        $sfVerbosity = $this->verbosityMap[$verbosity];
        if ($sfVerbosity > $this->output->getVerbosity()) {
            return;
        }

        // hack to keep our usage BC with symfony<2.8 versions
        // this removes the quiet output but there is no way around it
        // see https://github.com/composer/composer/pull/4913
        if (OutputInterface::VERBOSITY_QUIET === 0) {
            $sfVerbosity = OutputInterface::OUTPUT_NORMAL;
        }

        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, $size, false, $verbosity);
    }

    /**
     * {@inheritDoc}
     */
    public function overwriteError($messages, $newline = true, $size = null, $verbosity = self::NORMAL)
    {
        $this->doOverwrite($messages, $newline, $size, true, $verbosity);
    }

    /**
     * @param array|string $messages
     * @param bool         $newline
     * @param int|null     $size
     * @param bool         $stderr
     * @param int          $verbosity
     */
    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($messages, false, $stderr, $verbosity);

        // In cmd.exe on Win8.1 (possibly 10?), the line can not be cleared, so we need to
        // track the length of previous output and fill it with spaces to make sure the line is cleared.
        // See https://github.com/composer/composer/pull/5836 for more details
        $fill = $size - strlen(strip_tags($messages));
        if ($fill > 0) {
            // whitespace whatever has left
            $this->doWrite(str_repeat(' ', $fill), false, $stderr, $verbosity);
            // move the cursor back
            $this->doWrite(str_repeat("\x08", $fill), false, $stderr, $verbosity);
        }

        if ($newline) {
            $this->doWrite('', true, $stderr, $verbosity);
        }

        if ($stderr) {
            $this->lastMessageErr = $messages;
        } else {
            $this->lastMessage = $messages;
        }
    }

    /**
     * {@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, $choices, true);
        }

        $results = array();
        foreach ($choices as $index => $choice) {
            if (in_array($choice, $result, true)) {
                $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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\IO;

use Composer\Config;

/**
 * The Input/Output helper interface.
 *
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 */
interface IOInterface
{
    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|array $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
     */
    public function write($messages, $newline = true, $verbosity = self::NORMAL);

    /**
     * Writes a message to the error output.
     *
     * @param string|array $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
     */
    public function writeError($messages, $newline = true, $verbosity = self::NORMAL);

    /**
     * Overwrites a previous message to the output.
     *
     * @param string|array $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
     */
    public function overwrite($messages, $newline = true, $size = null, $verbosity = self::NORMAL);

    /**
     * Overwrites a previous message to the error output.
     *
     * @param string|array $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
     */
    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            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 The answer
     */
    public function askAndHideAnswer($question);

    /**
     * Asks the user to select a value.
     *
     * @param string      $question     The question to ask
     * @param array       $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|array          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 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 The 'username' and 'password'
     */
    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
     */
    public function setAuthentication($repositoryName, $username, $password = null);

    /**
     * Loads authentications from a config instance
     *
     * @param Config $config
     */
    public function loadConfiguration(Config $config);
}
<?php

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

namespace Composer\IO;

use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Output\StreamOutput;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Input\StreamableInputInterface;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Helper\HelperSet;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class BufferIO extends ConsoleIO
{
    /**
     * @param string                        $input
     * @param int                           $verbosity
     * @param OutputFormatterInterface|null $formatter
     */
    public function __construct($input = '', $verbosity = StreamOutput::VERBOSITY_NORMAL, OutputFormatterInterface $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(),
        )));
    }

    public function getOutput()
    {
        fseek($this->output->getStream(), 0);

        $output = stream_get_contents($this->output->getStream());

        $output = preg_replace_callback("{(?<=^|\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;
    }

    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);
    }

    private 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 Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\IO;

/**
 * IOInterface that is not interactive and never writes the output
 *
 * @author Christophe Coevoet <stof@notk.org>
 */
class NullIO extends BaseIO
{
    /**
     * {@inheritDoc}
     */
    public function isInteractive()
    {
        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 = false, $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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\SelfUpdate;

use Composer\Util\RemoteFilesystem;
use Composer\Config;
use Composer\Json\JsonFile;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Versions
{
    private $rfs;
    private $config;
    private $channel;

    public function __construct(Config $config, RemoteFilesystem $rfs)
    {
        $this->rfs = $rfs;
        $this->config = $config;
    }

    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';
    }

    public function setChannel($channel)
    {
        if (!in_array($channel, array('stable', 'preview', 'snapshot'), true)) {
            throw new \InvalidArgumentException('Invalid channel '.$channel.', must be one of: stable, preview, snapshot');
        }

        $channelFile = $this->config->get('home').'/update-channel';
        $this->channel = $channel;
        file_put_contents($channelFile, $channel.PHP_EOL);
    }

    public function getLatest()
    {
        $protocol = extension_loaded('openssl') ? 'https' : 'http';
        $versions = JsonFile::parseJson($this->rfs->getContents('getcomposer.org', $protocol . '://getcomposer.org/versions', false));

        foreach ($versions[$this->getChannel()] as $version) {
            if ($version['min-php'] <= PHP_VERSION_ID) {
                return $version;
            }
        }

        throw new \LogicException('There is no version of Composer available for your PHP version ('.PHP_VERSION.')');
    }
}
<?php

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

namespace Composer\SelfUpdate;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Keys
{
    public static function fingerprint($path)
    {
        $hash = strtoupper(hash('sha256', preg_replace('{\s}', '', file_get_contents($path))));

        return implode(' ', array(
            substr($hash, 0, 8),
            substr($hash, 8, 8),
            substr($hash, 16, 8),
            substr($hash, 24, 8),
            '', // Extra space
            substr($hash, 32, 8),
            substr($hash, 40, 8),
            substr($hash, 48, 8),
            substr($hash, 56, 8),
        ));
    }
}
<?php

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

namespace Composer\Console;

use Symfony\Component\Console\Formatter\OutputFormatter;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class HtmlOutputFormatter extends OutputFormatter
{
    private static $availableForegroundColors = array(
        30 => 'black',
        31 => 'red',
        32 => 'green',
        33 => 'yellow',
        34 => 'blue',
        35 => 'magenta',
        36 => 'cyan',
        37 => 'white',
    );
    private static $availableBackgroundColors = array(
        40 => 'black',
        41 => 'red',
        42 => 'green',
        43 => 'yellow',
        44 => 'blue',
        45 => 'magenta',
        46 => 'cyan',
        47 => 'white',
    );
    private static $availableOptions = array(
        1 => 'bold',
        4 => 'underscore',
        //5 => 'blink',
        //7 => 'reverse',
        //8 => 'conceal'
    );

    /**
     * @param array $styles Array of "name => FormatterStyle" instances
     */
    public function __construct(array $styles = array())
    {
        parent::__construct(true, $styles);
    }

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

        $clearEscapeCodes = '(?:39|49|0|22|24|25|27|28)';

        return preg_replace_callback("{\033\[([0-9;]+)m(.*?)\033\[(?:".$clearEscapeCodes.";)*?".$clearEscapeCodes."m}s", array($this, 'formatHtml'), $formatted);
    }

    private function formatHtml($matches)
    {
        $out = '<span style="';
        foreach (explode(';', $matches[1]) as $code) {
            if (isset(self::$availableForegroundColors[$code])) {
                $out .= 'color:'.self::$availableForegroundColors[$code].';';
            } elseif (isset(self::$availableBackgroundColors[$code])) {
                $out .= 'background-color:'.self::$availableBackgroundColors[$code].';';
            } elseif (isset(self::$availableOptions[$code])) {
                switch (self::$availableOptions[$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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Console;

use Composer\IO\NullIO;
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\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
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\EventDispatcher\ScriptExecutionException;
use Composer\Exception\NoSslException;

/**
 * The console application that handles the commands
 *
 * @author Ryan Weaver <ryan@knplabs.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 */
class Application extends BaseApplication
{
    /**
     * @var Composer
     */
    protected $composer;

    /**
     * @var IOInterface
     */
    protected $io;

    private static $logo = '   ______
  / ____/___  ____ ___  ____  ____  ________  _____
 / /   / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__  )  __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
                    /_/
';

    private $hasPluginCommands = false;
    private $disablePluginsByDefault = false;

    public function __construct()
    {
        static $shutdownRegistered = false;

        if (function_exists('ini_set') && extension_loaded('xdebug')) {
            ini_set('xdebug.show_exception_trace', false);
            ini_set('xdebug.scream', false);
        }

        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) {
            $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();

        parent::__construct('Composer', Composer::getVersion());
    }

    /**
     * {@inheritDoc}
     */
    public function run(InputInterface $input = null, OutputInterface $output = null)
    {
        if (null === $output) {
            $output = Factory::createOutput();
        }

        return parent::run($input, $output);
    }

    /**
     * {@inheritDoc}
     */
    public function doRun(InputInterface $input, OutputInterface $output)
    {
        $this->disablePluginsByDefault = $input->hasParameterOption('--no-plugins');

        if (getenv('COMPOSER_NO_INTERACTION')) {
            $input->setInteractive(false);
        }

        $io = $this->io = new ConsoleIO($input, $output, new HelperSet(array(
            new QuestionHelper(),
        )));
        ErrorHandler::register($io);

        if ($input->hasParameterOption('--no-cache')) {
            $io->writeError('Disabling cache usage', true, IOInterface::DEBUG);
            putenv('COMPOSER_CACHE_DIR='.(Platform::isWindows() ? 'nul' : '/dev/null'));
        }

        // switch working dir
        if ($newWorkDir = $this->getNewWorkingDir($input)) {
            $oldWorkingDir = getcwd();
            chdir($newWorkDir);
            $io->writeError('Changed CWD to ' . getcwd(), true, IOInterface::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'), true) && !file_exists(Factory::getComposerFile())) {
            $dir = dirname(getcwd());
            $home = realpath(getenv('HOME') ?: 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 ($io->askConfirmation('<info>No composer.json in current directory, do you want to use the one at '.$dir.'?</info> [<comment>Y,n</comment>]? ', true)) {
                        $oldWorkingDir = getcwd();
                        chdir($dir);
                    }
                    break;
                }
                $dir = dirname($dir);
            }
        }

        if (!$this->disablePluginsByDefault && !$this->hasPluginCommands && 'global' !== $commandName) {
            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
            }

            $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'
            ), true, IOInterface::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 (extension_loaded('xdebug') && !getenv('COMPOSER_DISABLE_XDEBUG_WARN')) {
                $io->writeError('<warning>You are running composer with xdebug enabled. This has a major impact on runtime performance. 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') && !getenv('COMPOSER_ALLOW_SUPERUSER') && !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 ($uid = (int) 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) && is_readable($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);

            if (isset($oldWorkingDir)) {
                chdir($oldWorkingDir);
            }

            if (isset($startTime)) {
                $io->writeError('<info>Memory usage: '.round(memory_get_usage() / 1024 / 1024, 2).'MiB (peak: '.round(memory_get_peak_usage() / 1024 / 1024, 2).'MiB), time: '.round(microtime(true) - $startTime, 2).'s');
            }

            restore_error_handler();

            return $result;
        } catch (ScriptExecutionException $e) {
            return $e->getCode();
        } catch (\Exception $e) {
            $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;
    }

    /**
     * {@inheritDoc}
     */
    private function hintCommonErrors($exception)
    {
        $io = $this->getIO();

        Silencer::suppress();
        try {
            $composer = $this->getComposer(false, true);
            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>', true, IOInterface::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>', true, IOInterface::QUIET);
            $io->writeError('<error>Check https://getcomposer.org/doc/articles/troubleshooting.md#-the-system-cannot-find-the-path-specified-windows- for details</error>', true, IOInterface::QUIET);
        }

        if (false !== strpos($exception->getMessage(), 'fork failed - Cannot allocate memory')) {
            $io->writeError('<error>The following exception is caused by a lack of memory or swap, or not having swap configured</error>', true, IOInterface::QUIET);
            $io->writeError('<error>Check https://getcomposer.org/doc/articles/troubleshooting.md#proc-open-fork-failed-errors for details</error>', true, IOInterface::QUIET);
        }
    }

    /**
     * @param  bool                    $required
     * @param  bool|null               $disablePlugins
     * @throws JsonValidationException
     * @return \Composer\Composer
     */
    public function getComposer($required = true, $disablePlugins = null)
    {
        if (null === $disablePlugins) {
            $disablePlugins = $this->disablePluginsByDefault;
        }

        if (null === $this->composer) {
            try {
                $this->composer = Factory::create($this->io, null, $disablePlugins);
            } catch (\InvalidArgumentException $e) {
                if ($required) {
                    $this->io->writeError($e->getMessage());
                    exit(1);
                }
            } catch (JsonValidationException $e) {
                $errors = ' - ' . implode(PHP_EOL . ' - ', $e->getErrors());
                $message = $e->getMessage() . ':' . PHP_EOL . $errors;
                throw new JsonValidationException($message);
            }
        }

        return $this->composer;
    }

    /**
     * Removes the cached composer instance
     */
    public function resetComposer()
    {
        $this->composer = null;
        if ($this->getIO() && method_exists($this->getIO(), 'resetAuthentications')) {
            $this->getIO()->resetAuthentications();
        }
    }

    /**
     * @return IOInterface
     */
    public function getIO()
    {
        return $this->io;
    }

    public function getHelp()
    {
        return self::$logo . parent::getHelp();
    }

    /**
     * Initializes all the composer commands.
     */
    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(),
        ));

        if ('phar:' === substr(__FILE__, 0, 5)) {
            $commands[] = new Command\SelfUpdateCommand();
        }

        return $commands;
    }

    /**
     * {@inheritDoc}
     */
    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;
    }

    /**
     * {@inheritDoc}
     */
    protected function getDefaultInputDefinition()
    {
        $definition = parent::getDefaultInputDefinition();
        $definition->addOption(new InputOption('--profile', null, InputOption::VALUE_NONE, 'Display timing and memory usage information'));
        $definition->addOption(new InputOption('--no-plugins', null, InputOption::VALUE_NONE, 'Whether to disable plugins.'));
        $definition->addOption(new InputOption('--working-dir', '-d', InputOption::VALUE_REQUIRED, 'If specified, use the given directory as working directory.'));
        $definition->addOption(new InputOption('--no-cache', null, InputOption::VALUE_NONE, 'Prevent use of the cache'));

        return $definition;
    }

    private function getPluginCommands()
    {
        $commands = array();

        $composer = $this->getComposer(false, false);
        if (null === $composer) {
            $composer = Factory::createGlobal($this->io, false);
        }

        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;
    }
}
<?php

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

namespace Composer\EventDispatcher;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ScriptExecutionException extends \RuntimeException
{
}
<?php

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

namespace Composer\EventDispatcher;

/**
 * An EventSubscriber knows which events it is interested in.
 *
 * If an EventSubscriber is added to an EventDispatcher, the manager invokes
 * {@link getSubscribedEvents} and registers the subscriber as a listener for all
 * returned events.
 *
 * @author Guilherme Blanco <guilhermeblanco@hotmail.com>
 * @author Jonathan Wage <jonwage@gmail.com>
 * @author Roman Borschel <roman@code-factory.org>
 * @author Bernhard Schussek <bschussek@gmail.com>
 */
interface EventSubscriberInterface
{
    /**
     * Returns an array of event names this subscriber wants to listen to.
     *
     * The array keys are event names and the value can be:
     *
     * * The method name to call (priority defaults to 0)
     * * An array composed of the method name to call and the priority
     * * An array of arrays composed of the method names to call and respective
     *   priorities, or 0 if unset
     *
     * For instance:
     *
     * * array('eventName' => 'methodName')
     * * array('eventName' => array('methodName', $priority))
     * * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
     *
     * @return array The event names to listen to
     */
    public static function getSubscribedEvents();
}
<?php

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

namespace Composer\EventDispatcher;

use Composer\DependencyResolver\PolicyInterface;
use Composer\DependencyResolver\Pool;
use Composer\DependencyResolver\Request;
use Composer\Installer\InstallerEvent;
use Composer\IO\IOInterface;
use Composer\Composer;
use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\Repository\CompositeRepository;
use Composer\Script;
use Composer\Installer\PackageEvent;
use Composer\Installer\BinaryInstaller;
use Composer\Util\ProcessExecutor;
use Composer\Script\Event as ScriptEvent;
use Symfony\Component\Process\PhpExecutableFinder;

/**
 * The Event Dispatcher.
 *
 * Example in command:
 *     $dispatcher = new EventDispatcher($this->getComposer(), $this->getApplication()->getIO());
 *     // ...
 *     $dispatcher->dispatch(ScriptEvents::POST_INSTALL_CMD);
 *     // ...
 *
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Nils Adermann <naderman@naderman.de>
 */
class EventDispatcher
{
    protected $composer;
    protected $io;
    protected $loader;
    protected $process;
    protected $listeners;
    private $eventStack;

    /**
     * Constructor.
     *
     * @param Composer        $composer The composer instance
     * @param IOInterface     $io       The IOInterface instance
     * @param ProcessExecutor $process
     */
    public function __construct(Composer $composer, IOInterface $io, ProcessExecutor $process = null)
    {
        $this->composer = $composer;
        $this->io = $io;
        $this->process = $process ?: new ProcessExecutor($io);
        $this->eventStack = array();
    }

    /**
     * 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($eventName, Event $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  $additionalArgs Arguments passed by the user
     * @param  array  $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 PolicyInterface     $policy        The policy
     * @param Pool                $pool          The pool
     * @param CompositeRepository $installedRepo The installed repository
     * @param Request             $request       The request
     * @param array               $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, $devMode, PolicyInterface $policy, Pool $pool, CompositeRepository $installedRepo, Request $request, array $operations, OperationInterface $operation)
    {
        return $this->doDispatch(new PackageEvent($eventName, $this->composer, $this->io, $devMode, $policy, $pool, $installedRepo, $request, $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 PolicyInterface     $policy        The policy
     * @param Pool                $pool          The pool
     * @param CompositeRepository $installedRepo The installed repository
     * @param Request             $request       The request
     * @param array               $operations    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, PolicyInterface $policy, Pool $pool, CompositeRepository $installedRepo, Request $request, array $operations = array())
    {
        return $this->doDispatch(new InstallerEvent($eventName, $this->composer, $this->io, $devMode, $policy, $pool, $installedRepo, $request, $operations));
    }

    /**
     * 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)
    {
        $pathStr = 'PATH';
        if (!isset($_SERVER[$pathStr]) && isset($_SERVER['Path'])) {
            $pathStr = '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);
            if (isset($_SERVER[$pathStr]) && !preg_match('{(^|'.PATH_SEPARATOR.')'.preg_quote($binDir).'($|'.PATH_SEPARATOR.')}', $_SERVER[$pathStr])) {
                $_SERVER[$pathStr] = $binDir.PATH_SEPARATOR.getenv($pathStr);
                putenv($pathStr.'='.$_SERVER[$pathStr]);
            }
        }

        $listeners = $this->getListeners($event);

        $this->pushEvent($event);

        $return = 0;
        foreach ($listeners as $callable) {
            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');
                }
                $event = $this->checkListenerExpectedEvent($callable, $event);
                $return = false === call_user_func($callable, $event) ? 1 : 0;
            } elseif ($this->isComposerScript($callable)) {
                $this->io->writeError(sprintf('> %s: %s', $event->getName(), $callable), true, IOInterface::VERBOSE);

                $script = explode(' ', substr($callable, 1));
                $scriptName = $script[0];
                unset($script[0]);

                $args = array_merge($script, $event->getArguments());
                $flags = $event->getFlags();
                if (substr($callable, 0, 10) === '@composer ') {
                    $exec = $this->getPhpExecCommand() . ' ' . ProcessExecutor::escape(getenv('COMPOSER_BINARY')) . substr($callable, 9);
                    if (0 !== ($exitCode = $this->process->execute($exec))) {
                        $this->io->writeError(sprintf('<error>Script %s handling the %s event returned with error code '.$exitCode.'</error>', $callable, $event->getName()), true, IOInterface::QUIET);

                        throw new ScriptExecutionException('Error Output: '.$this->process->getErrorOutput(), $exitCode);
                    }
                } else {
                    if (!$this->getListeners(new Event($scriptName))) {
                        $this->io->writeError(sprintf('<warning>You made a reference to a non-existent script %s</warning>', $callable), true, IOInterface::QUIET);
                    }

                    try {
                        $scriptEvent = new Script\Event($scriptName, $event->getComposer(), $event->getIO(), $event->isDevMode(), $args, $flags);
                        $scriptEvent->setOriginatingEvent($event);
                        $return = $this->dispatch($scriptName, $scriptEvent);
                    } catch (ScriptExecutionException $e) {
                        $this->io->writeError(sprintf('<error>Script %s was called via %s</error>', $callable, $event->getName()), true, IOInterface::QUIET);
                        throw $e;
                    }
                }
            } elseif ($this->isPhpScript($callable)) {
                $className = substr($callable, 0, strpos($callable, '::'));
                $methodName = substr($callable, strpos($callable, '::') + 2);

                if (!class_exists($className)) {
                    $this->io->writeError('<warning>Class '.$className.' is not autoloadable, can not call '.$event->getName().' script</warning>', true, IOInterface::QUIET);
                    continue;
                }
                if (!is_callable($callable)) {
                    $this->io->writeError('<warning>Method '.$callable.' is not callable, can not call '.$event->getName().' script</warning>', true, IOInterface::QUIET);
                    continue;
                }

                try {
                    $return = false === $this->executeEventPhpScript($className, $methodName, $event) ? 1 : 0;
                } catch (\Exception $e) {
                    $message = "Script %s handling the %s event terminated with an exception";
                    $this->io->writeError('<error>'.sprintf($message, $callable, $event->getName()).'</error>', true, IOInterface::QUIET);
                    throw $e;
                }
            } 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));
                } else {
                    $this->io->writeError(sprintf('> %s', $exec));
                }

                $possibleLocalBinaries = $this->composer->getPackage()->getBinaries();
                if ($possibleLocalBinaries) {
                    foreach ($possibleLocalBinaries as $localExec) {
                        if (preg_match('{\b'.preg_quote($callable).'$}', $localExec)) {
                            $caller = BinaryInstaller::determineBinaryCaller($localExec);
                            $exec = preg_replace('{^'.preg_quote($callable).'}', $caller . ' ' . $localExec, $exec);
                            break;
                        }
                    }
                }

                if (substr($exec, 0, 5) === '@php ') {
                    $exec = $this->getPhpExecCommand() . ' ' . substr($exec, 5);
                } else {
                    $finder = new PhpExecutableFinder();
                    $phpPath = $finder->find(false);
                    if ($phpPath) {
                        putenv('PHP_BINARY=' . $phpPath);
                    }
                }

                if (0 !== ($exitCode = $this->process->execute($exec))) {
                    $this->io->writeError(sprintf('<error>Script %s handling the %s event returned with error code '.$exitCode.'</error>', $callable, $event->getName()), true, IOInterface::QUIET);

                    throw new ScriptExecutionException('Error Output: '.$this->process->getErrorOutput(), $exitCode);
                }
            }

            if ($event->isPropagationStopped()) {
                break;
            }
        }

        $this->popEvent();

        return $return;
    }

    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
     */
    protected function executeEventPhpScript($className, $methodName, Event $event)
    {
        $event = $this->checkListenerExpectedEvent(array($className, $methodName), $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);
    }

    /**
     * @param  mixed $target
     * @param  Event $event
     * @return Event
     */
    protected function checkListenerExpectedEvent($target, Event $event)
    {
        if (in_array($event->getName(), array(
            'init',
            'command',
            'pre-file-download',
        ), true)) {
            return $event;
        }

        try {
            $reflected = new \ReflectionParameter($target, 0);
        } catch (\Exception $e) {
            return $event;
        }

        $typehint = $reflected->getClass();

        if (!$typehint instanceof \ReflectionClass) {
            return $event;
        }

        $expected = $typehint->getName();

        // BC support
        if (!$event instanceof $expected && $expected === 'Composer\Script\CommandEvent') {
            trigger_error('The callback '.$this->serializeCallback($target).' declared at '.$reflected->getDeclaringFunction()->getFileName().' accepts a '.$expected.' but '.$event->getName().' events use a '.get_class($event).' instance. Please adjust your type hint accordingly, see https://getcomposer.org/doc/articles/scripts.md#event-classes', E_USER_DEPRECATED);
            $event = new \Composer\Script\CommandEvent(
                $event->getName(),
                $event->getComposer(),
                $event->getIO(),
                $event->isDevMode(),
                $event->getArguments()
            );
        }
        if (!$event instanceof $expected && $expected === 'Composer\Script\PackageEvent') {
            trigger_error('The callback '.$this->serializeCallback($target).' declared at '.$reflected->getDeclaringFunction()->getFileName().' accepts a '.$expected.' but '.$event->getName().' events use a '.get_class($event).' instance. Please adjust your type hint accordingly, see https://getcomposer.org/doc/articles/scripts.md#event-classes', E_USER_DEPRECATED);
            $event = new \Composer\Script\PackageEvent(
                $event->getName(),
                $event->getComposer(),
                $event->getIO(),
                $event->isDevMode(),
                $event->getPolicy(),
                $event->getPool(),
                $event->getInstalledRepo(),
                $event->getRequest(),
                $event->getOperations(),
                $event->getOperation()
            );
        }
        if (!$event instanceof $expected && $expected === 'Composer\Script\Event') {
            trigger_error('The callback '.$this->serializeCallback($target).' declared at '.$reflected->getDeclaringFunction()->getFileName().' accepts a '.$expected.' but '.$event->getName().' events use a '.get_class($event).' instance. Please adjust your type hint accordingly, see https://getcomposer.org/doc/articles/scripts.md#event-classes', E_USER_DEPRECATED);
            $event = new \Composer\Script\Event(
                $event->getName(),
                $event->getComposer(),
                $event->getIO(),
                $event->isDevMode(),
                $event->getArguments(),
                $event->getFlags()
            );
        }

        return $event;
    }

    private function serializeCallback($cb)
    {
        if (is_array($cb) && count($cb) === 2) {
            if (is_object($cb[0])) {
                $cb[0] = get_class($cb[0]);
            }
            if (is_string($cb[0]) && is_string($cb[1])) {
                $cb = implode('::', $cb);
            }
        }
        if (is_string($cb)) {
            return $cb;
        }

        return var_export($cb, true);
    }

    /**
     * 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
     */
    public function addListener($eventName, $listener, $priority = 0)
    {
        $this->listeners[$eventName][$priority][] = $listener;
    }

    /**
     * Adds object methods as listeners for the events in getSubscribedEvents
     *
     * @see EventSubscriberInterface
     *
     * @param EventSubscriberInterface $subscriber
     */
    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 All listeners: callables and scripts
     */
    protected function getListeners(Event $event)
    {
        $scriptListeners = $this->getScriptListeners($event);

        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 array 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->loader->register();

        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 '@' === substr($callable, 0, 1) && '@php ' !== substr($callable, 0, 5);
    }

    /**
     * Push an event to the stack of active event
     *
     * @param  Event             $event
     * @throws \RuntimeException
     * @return number
     */
    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 mixed
     */
    protected function popEvent()
    {
        return array_pop($this->eventStack);
    }
}
<?php

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

namespace Composer\EventDispatcher;

/**
 * The base event class
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class Event
{
    /**
     * @var string This event's name
     */
    protected $name;

    /**
     * @var array Arguments passed by the user, these will be forwarded to CLI script handlers
     */
    protected $args;

    /**
     * @var array 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 array  $args  Arguments passed by the user
     * @param array  $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 array The event arguments
     */
    public function getArguments()
    {
        return $this->args;
    }

    /**
     * Returns the event's flags.
     *
     * @return array 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
     */
    public function stopPropagation()
    {
        $this->propagationStopped = true;
    }
}
<?php

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

namespace Composer\Config;

/**
 * Configuration Source Interface
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Beau Simensen <beau@dflydev.com>
 */
interface ConfigSourceInterface
{
    /**
     * Add a repository
     *
     * @param string $name   Name
     * @param array  $config Configuration
     */
    public function addRepository($name, $config);

    /**
     * Remove a repository
     *
     * @param string $name
     */
    public function removeRepository($name);

    /**
     * Add a config setting
     *
     * @param string $name  Name
     * @param string $value Value
     */
    public function addConfigSetting($name, $value);

    /**
     * Remove a config setting
     *
     * @param string $name
     */
    public function removeConfigSetting($name);

    /**
     * Add a property
     *
     * @param string $name  Name
     * @param string $value Value
     */
    public function addProperty($name, $value);

    /**
     * Remove a property
     *
     * @param string $name
     */
    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
     */
    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
     */
    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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Config;

use Composer\Json\JsonFile;
use Composer\Json\JsonManipulator;
use Composer\Util\Silencer;

/**
 * JSON Configuration Source
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Beau Simensen <beau@dflydev.com>
 */
class JsonConfigSource implements ConfigSourceInterface
{
    /**
     * @var JsonFile
     */
    private $file;

    /**
     * @var bool
     */
    private $authConfig;

    /**
     * Constructor
     *
     * @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)
    {
        $this->manipulateJson('addRepository', $name, $config, function (&$config, $repo, $repoConfig) {
            // 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;
                    }
                }
            }

            $config['repositories'][$repo] = $repoConfig;
        });
    }

    /**
     * {@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_match('{^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|http-basic|platform)\.}', $key)) {
                list($key, $host) = explode('.', $key, 2);
                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_match('{^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|http-basic|platform)\.}', $key)) {
                list($key, $host) = explode('.', $key, 2);
                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 (substr($key, 0, 6) === 'extra.' || substr($key, 0, 8) === 'scripts.') {
                $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)
    {
        $authConfig = $this->authConfig;
        $this->manipulateJson('removeProperty', $name, function (&$config, $key) {
            if (substr($key, 0, 6) === 'extra.' || substr($key, 0, 8) === 'scripts.') {
                $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]);

            if (0 === count($config[$type])) {
                unset($config[$type]);
            }
        });
    }

    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 (!is_readable($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);
            $this->file->write($config);
        }

        if ($newFile) {
            Silencer::call('chmod', $this->file->getPath(), 0600);
        }
    }

    /**
     * Prepend a reference to an element to the beginning of an array.
     *
     * @param  array $array
     * @param  mixed $value
     * @return array
     */
    private function arrayUnshiftRef(&$array, &$value)
    {
        $return = array_unshift($array, '');
        $array[0] = &$value;

        return $return;
    }
}
<?php

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

namespace Composer\Question;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Question\Question;

/**
 * Represents a yes/no question
 * Enforces strict responses rather than non-standard answers counting as default
 * Based on Symfony\Component\Console\Question\ConfirmationQuestion
 *
 * @author Theo Tonge <theo@theotonge.co.uk>
 */
class StrictConfirmationQuestion extends Question
{
    private $trueAnswerRegex;
    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_match($trueRegex, $answer)) {
                return true;
            }

            if (preg_match($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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Autoload;

use Composer\Config;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Installer\InstallationManager;
use Composer\IO\IOInterface;
use Composer\Package\AliasPackage;
use Composer\Package\PackageInterface;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Util\Filesystem;
use Composer\Script\ScriptEvents;
use Composer\Util\PackageSorter;

/**
 * @author Igor Wiedler <igor@wiedler.ch>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class AutoloadGenerator
{
    /**
     * @var EventDispatcher
     */
    private $eventDispatcher;

    /**
     * @var IOInterface
     */
    private $io;

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

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

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

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

    public function __construct(EventDispatcher $eventDispatcher, IOInterface $io = null)
    {
        $this->eventDispatcher = $eventDispatcher;
        $this->io = $io;
    }

    public function setDevMode($devMode = true)
    {
        $this->devMode = (bool) $devMode;
    }

    /**
     * Whether or not generated autoloader considers the class map
     * authoritative.
     *
     * @param bool $classMapAuthoritative
     */
    public function setClassMapAuthoritative($classMapAuthoritative)
    {
        $this->classMapAuthoritative = (bool) $classMapAuthoritative;
    }

    /**
     * Whether or not generated autoloader considers APCu caching.
     *
     * @param bool $apcu
     */
    public function setApcu($apcu)
    {
        $this->apcu = (bool) $apcu;
    }

    /**
     * Set whether to run scripts or not
     *
     * @param bool $runScripts
     */
    public function setRunScripts($runScripts = true)
    {
        $this->runScripts = (bool) $runScripts;
    }

    public function dump(Config $config, InstalledRepositoryInterface $localRepo, PackageInterface $mainPackage, InstallationManager $installationManager, $targetDir, $scanPsr0Packages = false, $suffix = '')
    {
        if ($this->classMapAuthoritative) {
            // Force scanPsr0Packages when classmap is authoritative
            $scanPsr0Packages = true;
        }
        if ($this->runScripts) {
            $this->eventDispatcher->dispatchScript(ScriptEvents::PRE_AUTOLOAD_DUMP, $this->devMode, array(), array(
                'optimize' => (bool) $scanPsr0Packages,
            ));
        }

        $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), $vendorPath, true);
        $vendorPathCode52 = str_replace('__DIR__', 'dirname(__FILE__)', $vendorPathCode);
        $vendorPathToTargetDirCode = $filesystem->findShortestPathCode($vendorPath, realpath($targetDir), true);

        $appBaseDirCode = $filesystem->findShortestPathCode($vendorPath, $basePath, true);
        $appBaseDirCode = str_replace('__DIR__', '$vendorDir', $appBaseDirCode);

        $namespacesFile = <<<EOF
<?php

// autoload_namespaces.php @generated by Composer

\$vendorDir = $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.
        $packageMap = $this->buildPackageMap($installationManager, $mainPackage, $localRepo->getCanonicalPackages());
        $autoloads = $this->parseAutoloads($packageMap, $mainPackage, $this->devMode === false);

        // 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($namespace, true);
            $namespacesFile .= "    $exportedPrefix => ";
            $namespacesFile .= "array(".implode(', ', $exportedPaths)."),\n";
        }
        $namespacesFile .= ");\n";

        // Process the 'psr-4' base directories.
        foreach ($autoloads['psr-4'] as $namespace => $paths) {
            $exportedPaths = array();
            foreach ($paths as $path) {
                $exportedPaths[] = $this->getPathCode($filesystem, $basePath, $vendorPath, $path);
            }
            $exportedPrefix = var_export($namespace, true);
            $psr4File .= "    $exportedPrefix => ";
            $psr4File .= "array(".implode(', ', $exportedPaths)."),\n";
        }
        $psr4File .= ");\n";

        $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 = $mainPackage->getAutoload();
        if ($mainPackage->getTargetDir() && !empty($mainAutoload['psr-0'])) {
            $levels = substr_count($filesystem->normalizePath($mainPackage->getTargetDir()), '/') + 1;
            $prefixes = implode(', ', array_map(function ($prefix) {
                return var_export($prefix, true);
            }, array_keys($mainAutoload['psr-0'])));
            $baseDirFromTargetDirCode = $filesystem->findShortestPathCode($targetDir, $basePath, true);

            $targetDirLoader = <<<EOF

    public static function autoload(\$class)
    {
        \$dir = $baseDirFromTargetDirCode . '/';
        \$prefixes = array($prefixes);
        foreach (\$prefixes as \$prefix) {
            if (0 !== strpos(\$class, \$prefix)) {
                continue;
            }
            \$path = \$dir . implode('/', array_slice(explode('\\\\', \$class), $levels)).'.php';
            if (!\$path = stream_resolve_include_path(\$path)) {
                return false;
            }
            require \$path;

            return true;
        }
    }

EOF;
        }

        $blacklist = null;
        if (!empty($autoloads['exclude-from-classmap'])) {
            $blacklist = '{(' . implode('|', $autoloads['exclude-from-classmap']) . ')}';
        }

        // flatten array
        $classMap = array();
        if ($scanPsr0Packages) {
            $namespacesToScan = array();

            // Scan the PSR-0/4 directories for class files, and add them to the class map
            foreach (array('psr-0', 'psr-4') 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;
                        }

                        $namespaceFilter = $namespace === '' ? null : $namespace;
                        $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, $namespaceFilter, $classMap);
                    }
                }
            }
        }

        foreach ($autoloads['classmap'] as $dir) {
            $classMap = $this->addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist, null, $classMap);
        }

        ksort($classMap);
        foreach ($classMap as $class => $code) {
            $classmapFile .= '    '.var_export($class, true).' => '.$code;
        }
        $classmapFile .= ");\n";

        if (!$suffix) {
            if (!$config->get('autoloader-suffix') && is_readable($vendorPath.'/autoload.php')) {
                $content = file_get_contents($vendorPath.'/autoload.php');
                if (preg_match('{ComposerAutoloaderInit([^:\s]+)::}', $content, $match)) {
                    $suffix = $match[1];
                }
            }

            if (!$suffix) {
                $suffix = $config->get('autoloader-suffix') ?: md5(uniqid('', true));
            }
        }

        file_put_contents($targetDir.'/autoload_namespaces.php', $namespacesFile);
        file_put_contents($targetDir.'/autoload_psr4.php', $psr4File);
        file_put_contents($targetDir.'/autoload_classmap.php', $classmapFile);
        $includePathFilePath = $targetDir.'/include_paths.php';
        if ($includePathFileContents = $this->getIncludePathsFile($packageMap, $filesystem, $basePath, $vendorPath, $vendorPathCode52, $appBaseDirCode)) {
            file_put_contents($includePathFilePath, $includePathFileContents);
        } elseif (file_exists($includePathFilePath)) {
            unlink($includePathFilePath);
        }
        $includeFilesFilePath = $targetDir.'/autoload_files.php';
        if ($includeFilesFileContents = $this->getIncludeFilesFile($autoloads['files'], $filesystem, $basePath, $vendorPath, $vendorPathCode52, $appBaseDirCode)) {
            file_put_contents($includeFilesFilePath, $includeFilesFileContents);
        } elseif (file_exists($includeFilesFilePath)) {
            unlink($includeFilesFilePath);
        }
        file_put_contents($targetDir.'/autoload_static.php', $this->getStaticFile($suffix, $targetDir, $vendorPath, $basePath, $staticPhpVersion));
        file_put_contents($vendorPath.'/autoload.php', $this->getAutoloadFile($vendorPathToTargetDirCode, $suffix));
        file_put_contents($targetDir.'/autoload_real.php', $this->getAutoloadRealFile(true, (bool) $includePathFileContents, $targetDirLoader, (bool) $includeFilesFileContents, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader, $staticPhpVersion));

        $this->safeCopy(__DIR__.'/ClassLoader.php', $targetDir.'/ClassLoader.php');
        $this->safeCopy(__DIR__.'/../../../LICENSE', $targetDir.'/LICENSE');

        if ($this->runScripts) {
            $this->eventDispatcher->dispatchScript(ScriptEvents::POST_AUTOLOAD_DUMP, $this->devMode, array(), array(
                'optimize' => (bool) $scanPsr0Packages,
            ));
        }

        return count($classMap);
    }

    private function addClassMapCode($filesystem, $basePath, $vendorPath, $dir, $blacklist = null, $namespaceFilter = null, array $classMap = array())
    {
        foreach ($this->generateClassMap($dir, $blacklist, $namespaceFilter) 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_match('{/(test|fixture|example|stub)s?/}i', strtr($classMap[$class].' '.$path, '\\', '/'))) {
                $this->io->writeError(
                    '<warning>Warning: Ambiguous class resolution, "'.$class.'"'.
                    ' was found in both "'.str_replace(array('$vendorDir . \'', "',\n"), array($vendorPath, ''), $classMap[$class]).'" and "'.$path.'", the first will be used.</warning>'
                );
            }
        }

        return $classMap;
    }

    private function generateClassMap($dir, $blacklist = null, $namespaceFilter = null, $showAmbiguousWarning = true)
    {
        return ClassMapGenerator::createMap($dir, $blacklist, $showAmbiguousWarning ? $this->io : null, $namespaceFilter);
    }

    public function buildPackageMap(InstallationManager $installationManager, PackageInterface $mainPackage, array $packages)
    {
        // build package => install path map
        $packageMap = array(array($mainPackage, ''));

        foreach ($packages as $package) {
            if ($package instanceof AliasPackage) {
                continue;
            }
            $this->validatePackage($package);

            $packageMap[] = array(
                $package,
                $installationManager->getInstallPath($package),
            );
        }

        return $packageMap;
    }

    /**
     * @param PackageInterface $package
     *
     * @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            $packageMap  array of array(package, installDir-relative-to-composer.json)
     * @param  PackageInterface $mainPackage root package instance
     * @param  bool             $filterOutRequireDevPackages whether to filter out require-dev packages
     * @return array            array('psr-0' => array('Ns\\Foo' => array('installDir')))
     */
    public function parseAutoloads(array $packageMap, PackageInterface $mainPackage, $filterOutRequireDevPackages = false)
    {
        $mainPackageMap = array_shift($packageMap);
        if ($filterOutRequireDevPackages) {
            $packageMap = $this->filterPackageMap($packageMap, $mainPackage);
        }
        $sortedPackageMap = $this->sortPackageMap($packageMap);
        $sortedPackageMap[] = $mainPackageMap;
        array_unshift($packageMap, $mainPackageMap);

        $psr0 = $this->parseAutoloadsType($packageMap, 'psr-0', $mainPackage);
        $psr4 = $this->parseAutoloadsType($packageMap, 'psr-4', $mainPackage);
        $classmap = $this->parseAutoloadsType(array_reverse($sortedPackageMap), 'classmap', $mainPackage);
        $files = $this->parseAutoloadsType($sortedPackageMap, 'files', $mainPackage);
        $exclude = $this->parseAutoloadsType($sortedPackageMap, 'exclude-from-classmap', $mainPackage);

        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       $autoloads see parseAutoloads return value
     * @return ClassLoader
     */
    public function createLoader(array $autoloads)
    {
        $loader = new ClassLoader();

        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'])) {
            $blacklist = null;
            if (!empty($autoloads['exclude-from-classmap'])) {
                $blacklist = '{(' . implode('|', $autoloads['exclude-from-classmap']) . ')}';
            }

            foreach ($autoloads['classmap'] as $dir) {
                try {
                    $loader->addClassMap($this->generateClassMap($dir, $blacklist, null, false));
                } catch (\RuntimeException $e) {
                    $this->io->writeError('<warning>'.$e->getMessage().'</warning>');
                }
            }
        }

        return $loader;
    }

    protected function getIncludePathsFile(array $packageMap, Filesystem $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($installPath, 0, -strlen('/'.$package->getTargetDir()));
            }

            foreach ($package->getIncludePaths() as $includePath) {
                $includePath = trim($includePath, '/');
                $includePaths[] = empty($installPath) ? $includePath : $installPath.'/'.$includePath;
            }
        }

        if (!$includePaths) {
            return;
        }

        $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;
    }

    protected function getIncludeFilesFile(array $files, Filesystem $filesystem, $basePath, $vendorPath, $vendorPathCode, $appBaseDirCode)
    {
        $filesCode = '';
        foreach ($files as $fileIdentifier => $functionFile) {
            $filesCode .= '    ' . var_export($fileIdentifier, true) . ' => '
                . $this->getPathCode($filesystem, $basePath, $vendorPath, $functionFile) . ",\n";
        }

        if (!$filesCode) {
            return false;
        }

        return <<<EOF
<?php

// autoload_files.php @generated by Composer

\$vendorDir = $vendorPathCode;
\$baseDir = $appBaseDirCode;

return array(
$filesCode);

EOF;
    }

    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($path, strlen($vendorPath));
            $baseDir = '$vendorDir';

            if ($path !== false) {
                $baseDir .= " . ";
            }
        } else {
            $path = $filesystem->normalizePath($filesystem->findShortestPath($basePath, $path, true));
            if (!$filesystem->isAbsolutePath($path)) {
                $baseDir = '$baseDir . ';
                $path = '/' . $path;
            }
        }

        if (strpos($path, '.phar') !== false) {
            $baseDir = "'phar://' . " . $baseDir;
        }

        return $baseDir . (($path !== false) ? var_export($path, true) : "");
    }

    protected function getAutoloadFile($vendorPathToTargetDirCode, $suffix)
    {
        $lastChar = $vendorPathToTargetDirCode[strlen($vendorPathToTargetDirCode) - 1];
        if ("'" === $lastChar || '"' === $lastChar) {
            $vendorPathToTargetDirCode = substr($vendorPathToTargetDirCode, 0, -1).'/autoload_real.php'.$lastChar;
        } else {
            $vendorPathToTargetDirCode .= " . '/autoload_real.php'";
        }

        return <<<AUTOLOAD
<?php

// autoload.php @generated by Composer

require_once $vendorPathToTargetDirCode;

return ComposerAutoloaderInit$suffix::getLoader();

AUTOLOAD;
    }

    protected function getAutoloadRealFile($useClassMap, $useIncludePath, $targetDirLoader, $useIncludeFiles, $vendorPathCode, $appBaseDirCode, $suffix, $useGlobalIncludePath, $prependAutoloader, $staticPhpVersion = 70000)
    {
        $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';
        }
    }

    public static function getLoader()
    {
        if (null !== self::\$loader) {
            return self::\$loader;
        }

        spl_autoload_register(array('ComposerAutoloaderInit$suffix', 'loadClassLoader'), true, $prependAutoloader);
        self::\$loader = \$loader = new \\Composer\\Autoload\\ClassLoader();
        spl_autoload_unregister(array('ComposerAutoloaderInit$suffix', 'loadClassLoader'));


HEADER;

        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_once __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 = substr(base64_encode(md5(uniqid('', true), true)), 0, -3);
            $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
}

function composerRequire$suffix(\$fileIdentifier, \$file)
{
    if (empty(\$GLOBALS['__composer_autoload_files'][\$fileIdentifier])) {
        require \$file;

        \$GLOBALS['__composer_autoload_files'][\$fileIdentifier] = true;
    }
}

FOOTER;
        }

        return $file . <<<FOOTER
}

FOOTER;
    }

    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), $vendorPath, true, true) . " . '/";
        $vendorPharPathCode = ' => \'phar://\' . ' . $filesystem->findShortestPathCode(realpath($targetDir), $vendorPath, true, true) . " . '/";
        $appBaseDirCode = ' => ' . $filesystem->findShortestPathCode(realpath($targetDir), $basePath, true, true) . " . '/";
        $appBaseDirPharCode = ' => \'phar://\' . ' . $filesystem->findShortestPathCode(realpath($targetDir), $basePath, true, true) . " . '/";

        $absoluteVendorPathCode = ' => ' . substr(var_export(rtrim($vendorDir, '\\/') . '/', true), 0, -1);
        $absoluteVendorPharPathCode = ' => ' . substr(var_export(rtrim('phar://' . $vendorDir, '\\/') . '/', true), 0, -1);
        $absoluteAppBaseDirCode = ' => ' . substr(var_export(rtrim($baseDir, '\\/') . '/', true), 0, -1);
        $absoluteAppBaseDirPharCode = ' => ' . substr(var_export(rtrim('phar://' . $baseDir, '\\/') . '/', true), 0, -1);

        $initializer = '';
        $prefix = "\0Composer\Autoload\ClassLoader\0";
        $prefixLen = strlen($prefix);
        if (file_exists($targetDir . '/autoload_files.php')) {
            $maps = array('files' => require $targetDir . '/autoload_files.php');
        } else {
            $maps = array();
        }

        foreach ((array) $loader as $prop => $value) {
            if ($value && 0 === 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($value, true),
                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;
    }

    protected function parseAutoloadsType(array $packageMap, $type, PackageInterface $mainPackage)
    {
        $autoloads = array();

        foreach ($packageMap as $item) {
            list($package, $installPath) = $item;

            $autoload = $package->getAutoload();
            if ($this->devMode && $package === $mainPackage) {
                $autoload = array_merge_recursive($autoload, $package->getDevAutoload());
            }

            // skip misconfigured packages
            if (!isset($autoload[$type]) || !is_array($autoload[$type])) {
                continue;
            }
            if (null !== $package->getTargetDir() && $package !== $mainPackage) {
                $installPath = substr($installPath, 0, -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() && !is_readable($installPath.'/'.$path)) {
                        // remove target-dir from file paths of the root package
                        if ($package === $mainPackage) {
                            $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 = str_replace('\\*\\*', '.+?', $path);
                        $path = str_replace('\\*', '[^/]+?', $path);

                        // add support for up-level relative paths
                        $updir = null;
                        $path = preg_replace_callback(
                            '{^((?:(?:\\\\\\.){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;
                    } elseif ($type === 'classmap') {
                        $autoloads[] = $relativePath;
                        continue;
                    }

                    $autoloads[$namespace][] = $relativePath;
                }
            }
        }

        return $autoloads;
    }

    protected function getFileIdentifier(PackageInterface $package, $path)
    {
        return md5($package->getName() . ':' . $path);
    }

    /**
     * Filters out dev-dependencies
     *
     * @param  array            $packageMap
     * @param  PackageInterface $mainPackage
     * @return array
     */
    protected function filterPackageMap(array $packageMap, PackageInterface $mainPackage)
    {
        $packages = array();
        $include = array();

        foreach ($packageMap as $item) {
            $package = $item[0];
            $name = $package->getName();
            $packages[$name] = $package;
        }

        $add = function (PackageInterface $package) use (&$add, $packages, &$include) {
            foreach ($package->getRequires() as $link) {
                $target = $link->getTarget();
                if (!isset($include[$target])) {
                    $include[$target] = true;
                    if (isset($packages[$target])) {
                        $add($packages[$target]);
                    }
                }
            }
        };
        $add($mainPackage);

        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 $packageMap
     * @return array
     */
    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;
    }

    /**
     * Copy file using stream_copy_to_stream to work around https://bugs.php.net/bug.php?id=6463
     *
     * @param string $source
     * @param string $target
     */
    protected function safeCopy($source, $target)
    {
        $source = fopen($source, 'r');
        $target = fopen($target, 'w+');

        stream_copy_to_stream($source, $target);
        fclose($source);
        fclose($target);
    }
}
<?php

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

/*
 * This file is copied from the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 */

namespace Composer\Autoload;

use Symfony\Component\Finder\Finder;
use Composer\IO\IOInterface;
use Composer\Util\Filesystem;

/**
 * ClassMapGenerator
 *
 * @author Gyula Sallai <salla016@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ClassMapGenerator
{
    /**
     * Generate a class map file
     *
     * @param \Traversable $dirs Directories or a single path to search in
     * @param string       $file The name of the class map file
     */
    public static function dump($dirs, $file)
    {
        $maps = array();

        foreach ($dirs as $dir) {
            $maps = array_merge($maps, static::createMap($dir));
        }

        file_put_contents($file, sprintf('<?php return %s;', var_export($maps, true)));
    }

    /**
     * Iterate over all files in the given directory searching for classes
     *
     * @param \Iterator|string $path      The path to search in or an iterator
     * @param string           $blacklist Regex that matches against the file path that exclude from the classmap.
     * @param IOInterface      $io        IO object
     * @param string           $namespace Optional namespace prefix to filter by
     *
     * @throws \RuntimeException When the path is neither an existing file nor directory
     * @return array             A class map array
     */
    public static function createMap($path, $blacklist = null, IOInterface $io = null, $namespace = null)
    {
        if (is_string($path)) {
            if (is_file($path)) {
                $path = array(new \SplFileInfo($path));
            } elseif (is_dir($path)) {
                $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'
                );
            }
        }

        $map = array();
        $filesystem = new Filesystem();
        $cwd = realpath(getcwd());

        foreach ($path as $file) {
            $filePath = $file->getPathname();
            if (!in_array(pathinfo($filePath, PATHINFO_EXTENSION), array('php', 'inc', 'hh'))) {
                continue;
            }

            if (!$filesystem->isAbsolutePath($filePath)) {
                $filePath = $cwd . '/' . $filePath;
                $filePath = $filesystem->normalizePath($filePath);
            } else {
                $filePath = preg_replace('{[\\\\/]{2,}}', '/', $filePath);
            }

            // check the realpath of the file against the blacklist as the path might be a symlink and the blacklist is realpath'd so symlink are resolved
            if ($blacklist && preg_match($blacklist, strtr(realpath($filePath), '\\', '/'))) {
                continue;
            }
            // check non-realpath of file for directories symlink in project dir
            if ($blacklist && preg_match($blacklist, strtr($filePath, '\\', '/'))) {
                continue;
            }

            $classes = self::findClasses($filePath);

            foreach ($classes as $class) {
                // skip classes not within the given namespace prefix
                if (null !== $namespace && 0 !== strpos($class, $namespace)) {
                    continue;
                }

                if (!isset($map[$class])) {
                    $map[$class] = $filePath;
                } elseif ($io && $map[$class] !== $filePath && !preg_match('{/(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;
    }

    /**
     * Extract the classes in the given file
     *
     * @param  string            $path The file to check
     * @throws \RuntimeException
     * @return array             The found classes
     */
    private static function findClasses($path)
    {
        $extraTypes = PHP_VERSION_ID < 50400 ? '' : '|trait';
        if (defined('HHVM_VERSION') && version_compare(HHVM_VERSION, '3.3', '>=')) {
            $extraTypes .= '|enum';
        }

        // 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 (!is_readable($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
        if (!preg_match('{\b(?:class|interface'.$extraTypes.')\s}i', $contents)) {
            return array();
        }

        // strip heredocs/nowdocs
        $contents = preg_replace('{<<<[ \t]*([\'"]?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)(?:\s*)\\2(?=\s+|[;,.)])}s', 'null', $contents);
        // strip strings
        $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents);
        // strip leading non-php code if needed
        if (substr($contents, 0, 2) !== '<?') {
            $contents = preg_replace('{^.+?<\?}s', '<?', $contents, 1, $replacements);
            if ($replacements === 0) {
                return array();
            }
        }
        // strip non-php blocks in the file
        $contents = preg_replace('{\?>(?:[^<]++|<(?!\?))*+<\?}s', '?><?', $contents);
        // strip trailing non-php code if needed
        $pos = strrpos($contents, '?>');
        if (false !== $pos && false === strpos(substr($contents, $pos), '<?')) {
            $contents = substr($contents, 0, $pos);
        }
        // strip comments if short open tags are in the file
        if (preg_match('{(<\?)(?!(php|hh))}i', $contents)) {
            $contents = preg_replace('{//.* | /\*(?:[^*]++|\*(?!/))*\*/}x', '', $contents);
        }

        preg_match_all('{
            (?:
                 \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;
    }
}
<?php

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

namespace Composer\Autoload;

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

    // PSR-0
    private $prefixesPsr0 = array();
    private $fallbackDirsPsr0 = array();

    private $useIncludePath = false;
    private $classMap = array();
    private $classMapAuthoritative = false;
    private $missingClasses = array();
    private $apcuPrefix;

    public function getPrefixes()
    {
        if (!empty($this->prefixesPsr0)) {
            return call_user_func_array('array_merge', $this->prefixesPsr0);
        }

        return array();
    }

    public function getPrefixesPsr4()
    {
        return $this->prefixDirsPsr4;
    }

    public function getFallbackDirs()
    {
        return $this->fallbackDirsPsr0;
    }

    public function getFallbackDirsPsr4()
    {
        return $this->fallbackDirsPsr4;
    }

    public function getClassMap()
    {
        return $this->classMap;
    }

    /**
     * @param array $classMap Class to filename map
     */
    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 array|string $paths   The PSR-0 root directories
     * @param bool         $prepend Whether to prepend the directories
     */
    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 array|string $paths   The PSR-4 base directories
     * @param bool         $prepend Whether to prepend the directories
     *
     * @throws \InvalidArgumentException
     */
    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 array|string $paths  The PSR-0 base directories
     */
    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 array|string $paths  The PSR-4 base directories
     *
     * @throws \InvalidArgumentException
     */
    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
     */
    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
     */
    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
     */
    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
     */
    public function register($prepend = false)
    {
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
    }

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

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

            return true;
        }
    }

    /**
     * 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;
    }

    private function findFileWithExtension($class, $ext)
    {
        // PSR-4 lookup
        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

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

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

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

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

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

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

        return false;
    }
}

/**
 * Scope isolated include.
 *
 * Prevents access to $this/self from included files.
 */
function includeFile($file)
{
    include $file;
}
<?php

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

namespace Composer;

use Symfony\Component\Console\Output\OutputInterface;

trigger_error('The ' . __NAMESPACE__ . '\XdebugHandler class is deprecated, use Composer\XdebugHandler\XdebugHandler instead,', E_USER_DEPRECATED);

/**
 * @deprecated use Composer\XdebugHandler\XdebugHandler instead
 */
class XdebugHandler extends XdebugHandler\XdebugHandler
{
    const ENV_ALLOW = 'COMPOSER_ALLOW_XDEBUG';
    const ENV_VERSION = 'COMPOSER_XDEBUG_VERSION';

    public function __construct(OutputInterface $output)
    {
        parent::__construct('composer', '--ansi');
    }
}
<?php

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

namespace Composer;

use Composer\Autoload\AutoloadGenerator;
use Composer\DependencyResolver\DefaultPolicy;
use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\DependencyResolver\Operation\UninstallOperation;
use Composer\DependencyResolver\Operation\MarkAliasUninstalledOperation;
use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\DependencyResolver\PolicyInterface;
use Composer\DependencyResolver\Pool;
use Composer\DependencyResolver\Request;
use Composer\DependencyResolver\Rule;
use Composer\DependencyResolver\Solver;
use Composer\DependencyResolver\SolverProblemsException;
use Composer\Downloader\DownloadManager;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Installer\InstallationManager;
use Composer\Installer\InstallerEvents;
use Composer\Installer\NoopInstaller;
use Composer\Installer\SuggestedPackagesReporter;
use Composer\IO\IOInterface;
use Composer\Package\AliasPackage;
use Composer\Package\BasePackage;
use Composer\Package\CompletePackage;
use Composer\Package\Link;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Dumper\ArrayDumper;
use Composer\Semver\Constraint\Constraint;
use Composer\Package\Locker;
use Composer\Package\PackageInterface;
use Composer\Package\RootPackageInterface;
use Composer\Repository\CompositeRepository;
use Composer\Repository\InstalledArrayRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\RepositoryManager;
use Composer\Repository\WritableRepositoryInterface;
use Composer\Script\ScriptEvents;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Beau Simensen <beau@dflydev.com>
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Nils Adermann <naderman@naderman.de>
 */
class Installer
{
    /**
     * @var IOInterface
     */
    protected $io;

    /**
     * @var Config
     */
    protected $config;

    /**
     * @var RootPackageInterface
     */
    protected $package;

    /**
     * @var DownloadManager
     */
    protected $downloadManager;

    /**
     * @var RepositoryManager
     */
    protected $repositoryManager;

    /**
     * @var Locker
     */
    protected $locker;

    /**
     * @var InstallationManager
     */
    protected $installationManager;

    /**
     * @var EventDispatcher
     */
    protected $eventDispatcher;

    /**
     * @var AutoloadGenerator
     */
    protected $autoloadGenerator;

    protected $preferSource = false;
    protected $preferDist = false;
    protected $optimizeAutoloader = false;
    protected $classMapAuthoritative = false;
    protected $apcuAutoloader = false;
    protected $devMode = false;
    protected $dryRun = false;
    protected $verbose = false;
    protected $update = false;
    protected $dumpAutoloader = true;
    protected $runScripts = true;
    protected $ignorePlatformReqs = false;
    protected $preferStable = false;
    protected $preferLowest = false;
    protected $skipSuggest = false;
    protected $writeLock = true;
    protected $executeOperations = true;

    /**
     * Array of package names/globs flagged for update
     *
     * @var array|null
     */
    protected $updateWhitelist = null;
    protected $whitelistDependencies = false; // TODO 2.0 rename to whitelistTransitiveDependencies
    protected $whitelistAllDependencies = false;

    /**
     * @var SuggestedPackagesReporter
     */
    protected $suggestedPackagesReporter;

    /**
     * @var RepositoryInterface
     */
    protected $additionalInstalledRepository;

    /**
     * Constructor
     *
     * @param IOInterface          $io
     * @param Config               $config
     * @param RootPackageInterface $package
     * @param DownloadManager      $downloadManager
     * @param RepositoryManager    $repositoryManager
     * @param Locker               $locker
     * @param InstallationManager  $installationManager
     * @param EventDispatcher      $eventDispatcher
     * @param AutoloadGenerator    $autoloadGenerator
     */
    public function __construct(IOInterface $io, Config $config, RootPackageInterface $package, DownloadManager $downloadManager, RepositoryManager $repositoryManager, Locker $locker, InstallationManager $installationManager, EventDispatcher $eventDispatcher, AutoloadGenerator $autoloadGenerator)
    {
        $this->io = $io;
        $this->config = $config;
        $this->package = $package;
        $this->downloadManager = $downloadManager;
        $this->repositoryManager = $repositoryManager;
        $this->locker = $locker;
        $this->installationManager = $installationManager;
        $this->eventDispatcher = $eventDispatcher;
        $this->autoloadGenerator = $autoloadGenerator;
    }

    /**
     * Run installation (or update)
     *
     * @throws \Exception
     * @return int        0 on success or a positive error code on failure
     */
    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();

        // Force update if there is no lock file present
        if (!$this->update && !$this->locker->isLocked()) {
            $this->update = true;
        }

        if ($this->dryRun) {
            $this->verbose = true;
            $this->runScripts = false;
            $this->executeOperations = false;
            $this->writeLock = false;
            $this->dumpAutoloader = false;
            $this->installationManager->addInstaller(new NoopInstaller);
            $this->mockLocalRepositories($this->repositoryManager);
        }

        if ($this->runScripts) {
            $devMode = (int) $this->devMode;
            putenv("COMPOSER_DEV_MODE=$devMode");

            // dispatch pre event
            $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);

        // create installed repo, this contains all local packages + platform packages (php & extensions)
        $localRepo = $this->repositoryManager->getLocalRepository();
        if ($this->update) {
            $platformOverrides = $this->config->get('platform') ?: array();
        } else {
            $platformOverrides = $this->locker->getPlatformOverrides();
        }
        $platformRepo = new PlatformRepository(array(), $platformOverrides);
        $installedRepo = $this->createInstalledRepo($localRepo, $platformRepo);

        $aliases = $this->getRootAliases();
        $this->aliasPlatformPackages($platformRepo, $aliases);

        if (!$this->suggestedPackagesReporter) {
            $this->suggestedPackagesReporter = new SuggestedPackagesReporter($this->io);
        }

        try {
            list($res, $devPackages) = $this->doInstall($localRepo, $installedRepo, $platformRepo, $aliases);
            if ($res !== 0) {
                return $res;
            }
        } catch (\Exception $e) {
            if ($this->executeOperations) {
                $this->installationManager->notifyInstalls($this->io);
            }

            throw $e;
        }
        if ($this->executeOperations) {
            $this->installationManager->notifyInstalls($this->io);
        }

        // output suggestions if we're in dev mode
        if ($this->devMode && !$this->skipSuggest) {
            $this->suggestedPackagesReporter->output($installedRepo);
        }

        # Find abandoned packages and warn user
        foreach ($localRepo->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
                )
            );
        }

        // write lock
        if ($this->update && $this->writeLock) {
            $localRepo->reload();

            $platformReqs = $this->extractPlatformRequirements($this->package->getRequires());
            $platformDevReqs = $this->extractPlatformRequirements($this->package->getDevRequires());

            $updatedLock = $this->locker->setLockData(
                array_diff($localRepo->getCanonicalPackages(), $devPackages),
                $devPackages,
                $platformReqs,
                $platformDevReqs,
                $aliases,
                $this->package->getMinimumStability(),
                $this->package->getStabilityFlags(),
                $this->preferStable || $this->package->getPreferStable(),
                $this->preferLowest,
                $this->config->get('platform') ?: array()
            );
            if ($updatedLock) {
                $this->io->writeError('<info>Writing lock file</info>');
            }
        }

        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->setDevMode($this->devMode);
            $this->autoloadGenerator->setClassMapAuthoritative($this->classMapAuthoritative);
            $this->autoloadGenerator->setApcu($this->apcuAutoloader);
            $this->autoloadGenerator->setRunScripts($this->runScripts);
            $this->autoloadGenerator->dump($this->config, $localRepo, $this->package, $this->installationManager, 'composer', $this->optimizeAutoloader);
        }

        if ($this->executeOperations) {
            // force binaries re-generation in case they are missing
            foreach ($localRepo->getPackages() as $package) {
                $this->installationManager->ensureBinariesPresence($package);
            }

            $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 ($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  RepositoryInterface $localRepo
     * @param  RepositoryInterface $installedRepo
     * @param  PlatformRepository  $platformRepo
     * @param  array               $aliases
     * @return array               [int, PackageInterfaces[]|null] with the exit code and an array of dev packages on update, or null on install
     */
    protected function doInstall($localRepo, $installedRepo, $platformRepo, $aliases)
    {
        // init vars
        $lockedRepository = null;
        $repositories = null;

        // initialize locked repo if we are installing from lock or in a partial update
        // and a lock file is present as we need to force install non-whitelisted lock file
        // packages in that case
        if (!$this->update || (!empty($this->updateWhitelist) && $this->locker->isLocked())) {
            try {
                $lockedRepository = $this->locker->getLockedRepository($this->devMode);
            } catch (\RuntimeException $e) {
                // if there are dev requires, then we really can not install
                if ($this->package->getDevRequires()) {
                    throw $e;
                }
                // no require-dev in composer.json and the lock file was created with no dev info, so skip them
                $lockedRepository = $this->locker->getLockedRepository();
            }
        }

        $this->whitelistUpdateDependencies(
            $lockedRepository ?: $localRepo,
            $this->package->getRequires(),
            $this->package->getDevRequires()
        );

        $this->io->writeError('<info>Loading composer repositories with package information</info>');

        // creating repository pool
        $policy = $this->createPolicy();
        $pool = $this->createPool($this->update ? null : $lockedRepository);
        $pool->addRepository($installedRepo, $aliases);
        if ($this->update) {
            $repositories = $this->repositoryManager->getRepositories();
            foreach ($repositories as $repository) {
                $pool->addRepository($repository, $aliases);
            }
        }
        // Add the locked repository after the others in case we are doing a
        // partial update so missing packages can be found there still.
        // For installs from lock it's the only one added so it is first
        if ($lockedRepository) {
            $pool->addRepository($lockedRepository, $aliases);
        }

        // creating requirements request
        $request = $this->createRequest($this->package, $platformRepo);

        if ($this->update) {
            // remove unstable packages from the localRepo if they don't match the current stability settings
            $removedUnstablePackages = array();
            foreach ($localRepo->getPackages() as $package) {
                if (
                    !$pool->isPackageAcceptable($package->getNames(), $package->getStability())
                    && $this->installationManager->isPackageInstalled($localRepo, $package)
                ) {
                    $removedUnstablePackages[$package->getName()] = true;
                    $request->remove($package->getName(), new Constraint('=', $package->getVersion()));
                }
            }

            $this->io->writeError('<info>Updating dependencies'.($this->devMode ? ' (including require-dev)' : '').'</info>');

            $request->updateAll();

            $links = array_merge($this->package->getRequires(), $this->package->getDevRequires());

            foreach ($links as $link) {
                $request->install($link->getTarget(), $link->getConstraint());
            }

            // if the updateWhitelist is enabled, packages not in it are also fixed
            // to the version specified in the lock, or their currently installed version
            if ($this->updateWhitelist) {
                $currentPackages = $this->getCurrentPackages($installedRepo);

                // collect packages to fixate from root requirements as well as installed packages
                $candidates = array();
                foreach ($links as $link) {
                    $candidates[$link->getTarget()] = true;
                    $rootRequires[$link->getTarget()] = $link;
                }
                foreach ($currentPackages as $package) {
                    $candidates[$package->getName()] = true;
                }

                // fix them to the version in lock (or currently installed) if they are not updateable
                foreach ($candidates as $candidate => $dummy) {
                    foreach ($currentPackages as $curPackage) {
                        if ($curPackage->getName() === $candidate) {
                            if (!$this->isUpdateable($curPackage) && !isset($removedUnstablePackages[$curPackage->getName()])) {
                                $constraint = new Constraint('=', $curPackage->getVersion());
                                $description = $this->locker->isLocked() ? '(locked at' : '(installed at';
                                $requiredAt = isset($rootRequires[$candidate]) ? ', required as ' . $rootRequires[$candidate]->getPrettyConstraint() : '';
                                $constraint->setPrettyString($description . ' ' . $curPackage->getPrettyVersion() . $requiredAt . ')');
                                $request->install($curPackage->getName(), $constraint);
                            }
                            break;
                        }
                    }
                }
            }
        } else {
            $this->io->writeError('<info>Installing dependencies'.($this->devMode ? ' (including require-dev)' : '').' from lock file</info>');

            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. Run update to update them.</warning>', true, IOInterface::QUIET);
            }

            foreach ($lockedRepository->getPackages() as $package) {
                $version = $package->getVersion();
                if (isset($aliases[$package->getName()][$version])) {
                    $version = $aliases[$package->getName()][$version]['alias_normalized'];
                }
                $constraint = new Constraint('=', $version);
                $constraint->setPrettyString($package->getPrettyVersion());
                $request->install($package->getName(), $constraint);
            }

            foreach ($this->locker->getPlatformRequirements($this->devMode) as $link) {
                $request->install($link->getTarget(), $link->getConstraint());
            }
        }

        // force dev packages to have the latest links if we update or install from a (potentially new) lock
        $this->processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, 'force-links');

        // solve dependencies
        $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, $this->devMode, $policy, $pool, $installedRepo, $request);
        $solver = new Solver($policy, $pool, $installedRepo, $this->io);
        try {
            $operations = $solver->solve($request, $this->ignorePlatformReqs);
            $ruleSetSize = $solver->getRuleSetSize();
            $solver = null;
        } catch (SolverProblemsException $e) {
            $this->io->writeError('<error>Your requirements could not be resolved to an installable set of packages.</error>', true, IOInterface::QUIET);
            $this->io->writeError($e->getMessage());
            if ($this->update && !$this->devMode) {
                $this->io->writeError('<warning>Running update with --no-dev does not mean require-dev is ignored, it just means the packages will not be installed. If dev requirements are blocking the update you have to resolve those problems.</warning>', true, IOInterface::QUIET);
            }

            return array(max(1, $e->getCode()), array());
        }

        // force dev packages to be updated if we update or install from a (potentially new) lock
        $operations = $this->processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, 'force-updates', $operations);

        $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, $this->devMode, $policy, $pool, $installedRepo, $request, $operations);

        $this->io->writeError("Analyzed ".count($pool)." packages to resolve dependencies", true, IOInterface::VERBOSE);
        $this->io->writeError("Analyzed ".$ruleSetSize." rules to resolve dependencies", true, IOInterface::VERBOSE);

        // execute operations
        if (!$operations) {
            $this->io->writeError('Nothing to install or update');
        }

        $operations = $this->movePluginsToFront($operations);
        $operations = $this->moveUninstallsToFront($operations);

        // extract dev packages and mark them to be skipped if it's a --no-dev install or update
        // we also force them to be uninstalled if they are present in the local repo
        if ($this->update) {
            $devPackages = $this->extractDevPackages($operations, $localRepo, $platformRepo, $aliases);
            if (!$this->devMode) {
                $operations = $this->filterDevPackageOperations($devPackages, $operations, $localRepo);
            }
        } else {
            $devPackages = null;
        }

        if ($operations) {
            $installs = $updates = $uninstalls = array();
            foreach ($operations 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),
                1 === count($installs) ? '' : 's',
                count($updates),
                1 === count($updates) ? '' : 's',
                count($uninstalls),
                1 === count($uninstalls) ? '' : 's'
            ));
            if ($installs) {
                $this->io->writeError("Installs: ".implode(', ', $installs), true, IOInterface::VERBOSE);
            }
            if ($updates) {
                $this->io->writeError("Updates: ".implode(', ', $updates), true, IOInterface::VERBOSE);
            }
            if ($uninstalls) {
                $this->io->writeError("Removals: ".implode(', ', $uninstalls), true, IOInterface::VERBOSE);
            }
        }

        foreach ($operations as $operation) {
            // collect suggestions
            $jobType = $operation->getJobType();
            if ('install' === $jobType) {
                $this->suggestedPackagesReporter->addSuggestionsFromPackage($operation->getPackage());
            }

            // updating, force dev packages' references if they're in root package refs
            if ($this->update) {
                $package = null;
                if ('update' === $jobType) {
                    $package = $operation->getTargetPackage();
                } elseif ('install' === $jobType) {
                    $package = $operation->getPackage();
                }
                if ($package && $package->isDev()) {
                    $references = $this->package->getReferences();
                    if (isset($references[$package->getName()])) {
                        $this->updateInstallReferences($package, $references[$package->getName()]);
                    }
                }
                if ('update' === $jobType) {
                    $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;
                        }
                    }
                }
            }

            $event = 'Composer\Installer\PackageEvents::PRE_PACKAGE_'.strtoupper($jobType);
            if (defined($event) && $this->runScripts) {
                $this->eventDispatcher->dispatchPackageEvent(constant($event), $this->devMode, $policy, $pool, $installedRepo, $request, $operations, $operation);
            }

            // output non-alias ops when not executing operations (i.e. dry run), output alias ops in debug verbosity
            if (!$this->executeOperations && false === strpos($operation->getJobType(), 'Alias')) {
                $this->io->writeError('  - ' . $operation);
            } elseif ($this->io->isDebug() && false !== strpos($operation->getJobType(), 'Alias')) {
                $this->io->writeError('  - ' . $operation);
            }

            $this->installationManager->execute($localRepo, $operation);

            // output reasons why the operation was ran, only for install/update operations
            if ($this->verbose && $this->io->isVeryVerbose() && in_array($jobType, array('install', 'update'))) {
                $reason = $operation->getReason();
                if ($reason instanceof Rule) {
                    switch ($reason->getReason()) {
                        case Rule::RULE_JOB_INSTALL:
                            $this->io->writeError('    REASON: Required by the root package: '.$reason->getPrettyString($pool));
                            $this->io->writeError('');
                            break;
                        case Rule::RULE_PACKAGE_REQUIRES:
                            $this->io->writeError('    REASON: '.$reason->getPrettyString($pool));
                            $this->io->writeError('');
                            break;
                    }
                }
            }

            if ($this->executeOperations || $this->writeLock) {
                $localRepo->write();
            }

            $event = 'Composer\Installer\PackageEvents::POST_PACKAGE_'.strtoupper($jobType);
            if (defined($event) && $this->runScripts) {
                $this->eventDispatcher->dispatchPackageEvent(constant($event), $this->devMode, $policy, $pool, $installedRepo, $request, $operations, $operation);
            }
        }

        if ($this->executeOperations) {
            // force source/dist urls to be updated for all packages
            $this->processPackageUrls($pool, $policy, $localRepo, $repositories);
            $localRepo->write();
        }

        return array(0, $devPackages);
    }

    /**
     * Extracts the dev packages out of the localRepo
     *
     * This works by faking the operations so we can see what the dev packages
     * would be at the end of the operation execution. This lets us then remove
     * the dev packages from the list of operations accordingly if we are in a
     * --no-dev install or update.
     *
     * @return array
     */
    private function extractDevPackages(array $operations, RepositoryInterface $localRepo, PlatformRepository $platformRepo, array $aliases)
    {
        if (!$this->package->getDevRequires()) {
            return array();
        }

        // fake-apply all operations to this clone of the local repo so we see the complete set of package we would end up with
        $tempLocalRepo = clone $localRepo;
        foreach ($operations as $operation) {
            switch ($operation->getJobType()) {
                case 'install':
                case 'markAliasInstalled':
                    if (!$tempLocalRepo->hasPackage($operation->getPackage())) {
                        $tempLocalRepo->addPackage(clone $operation->getPackage());
                    }
                    break;

                case 'uninstall':
                case 'markAliasUninstalled':
                    $tempLocalRepo->removePackage($operation->getPackage());
                    break;

                case 'update':
                    $tempLocalRepo->removePackage($operation->getInitialPackage());
                    if (!$tempLocalRepo->hasPackage($operation->getTargetPackage())) {
                        $tempLocalRepo->addPackage(clone $operation->getTargetPackage());
                    }
                    break;

                default:
                    throw new \LogicException('Unknown type: '.$operation->getJobType());
            }
        }

        // we have to reload the local repo to handle aliases properly
        // but as it is not persisted on disk we use a loader/dumper
        // to reload it in memory
        $localRepo = new InstalledArrayRepository(array());
        $loader = new ArrayLoader(null, true);
        $dumper = new ArrayDumper();
        foreach ($tempLocalRepo->getCanonicalPackages() as $pkg) {
            $localRepo->addPackage($loader->load($dumper->dump($pkg)));
        }
        unset($tempLocalRepo, $loader, $dumper);

        $policy = $this->createPolicy();
        $pool = $this->createPool();
        $installedRepo = $this->createInstalledRepo($localRepo, $platformRepo);
        $pool->addRepository($installedRepo, $aliases);

        // creating requirements request without dev requirements
        $request = $this->createRequest($this->package, $platformRepo);
        $request->updateAll();
        foreach ($this->package->getRequires() as $link) {
            $request->install($link->getTarget(), $link->getConstraint());
        }

        // solve deps to see which get removed
        $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::PRE_DEPENDENCIES_SOLVING, false, $policy, $pool, $installedRepo, $request);
        $solver = new Solver($policy, $pool, $installedRepo, $this->io);
        $ops = $solver->solve($request, $this->ignorePlatformReqs);
        $this->eventDispatcher->dispatchInstallerEvent(InstallerEvents::POST_DEPENDENCIES_SOLVING, false, $policy, $pool, $installedRepo, $request, $ops);

        $devPackages = array();
        foreach ($ops as $op) {
            if ($op->getJobType() === 'uninstall') {
                $devPackages[] = $op->getPackage();
            }
        }

        return $devPackages;
    }

    /**
     * @return OperationInterface[] filtered operations, dev packages are uninstalled and all operations on them ignored
     */
    private function filterDevPackageOperations(array $devPackages, array $operations, RepositoryInterface $localRepo)
    {
        $finalOps = array();
        $packagesToSkip = array();
        foreach ($devPackages as $pkg) {
            $packagesToSkip[$pkg->getName()] = true;
            if ($installedDevPkg = $localRepo->findPackage($pkg->getName(), '*')) {
                if ($installedDevPkg instanceof AliasPackage) {
                    $finalOps[] = new MarkAliasUninstalledOperation($installedDevPkg, 'non-dev install removing it');
                    $installedDevPkg = $installedDevPkg->getAliasOf();
                }
                $finalOps[] = new UninstallOperation($installedDevPkg, 'non-dev install removing it');
            }
        }

        // skip operations applied on dev packages
        foreach ($operations as $op) {
            $package = $op->getJobType() === 'update' ? $op->getTargetPackage() : $op->getPackage();
            if (isset($packagesToSkip[$package->getName()])) {
                continue;
            }

            $finalOps[] = $op;
        }

        return $finalOps;
    }

    /**
     * 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)
    {
        $pluginsNoDeps = array();
        $pluginsWithDeps = array();
        $pluginRequires = array();

        foreach (array_reverse($operations, true) as $idx => $op) {
            if ($op instanceof InstallOperation) {
                $package = $op->getPackage();
            } elseif ($op instanceof UpdateOperation) {
                $package = $op->getTargetPackage();
            } else {
                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 or 'composer-plugin-api'
                $requires = array_filter(array_keys($package->getRequires()), function ($req) {
                    return $req !== 'composer-plugin-api' && !preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $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($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 UninstallOperation) {
                $uninstOps[] = $op;
                unset($operations[$idx]);
            }
        }

        return array_merge($uninstOps, $operations);
    }

    /**
     * @return RepositoryInterface
     */
    private function createInstalledRepo(RepositoryInterface $localRepo, PlatformRepository $platformRepo)
    {
        // clone root package to have one in the installed repo that does not require anything
        // we don't want it to be uninstallable, but its requirements should not conflict
        // with the lock file for example
        $installedRootPackage = clone $this->package;
        $installedRootPackage->setRequires(array());
        $installedRootPackage->setDevRequires(array());

        $repos = array(
            $localRepo,
            new InstalledArrayRepository(array($installedRootPackage)),
            $platformRepo,
        );
        $installedRepo = new CompositeRepository($repos);
        if ($this->additionalInstalledRepository) {
            $installedRepo->addRepository($this->additionalInstalledRepository);
        }

        return $installedRepo;
    }

    /**
     * @param  RepositoryInterface|null $lockedRepository
     * @return Pool
     */
    private function createPool(RepositoryInterface $lockedRepository = null)
    {
        if ($this->update) {
            $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;
            }
        }

        $rootConstraints = array();
        foreach ($requires as $req => $constraint) {
            // skip platform requirements from the root package to avoid filtering out existing platform packages
            if ($this->ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $req)) {
                continue;
            }
            if ($constraint instanceof Link) {
                $rootConstraints[$req] = $constraint->getConstraint();
            } else {
                $rootConstraints[$req] = $constraint;
            }
        }

        return new Pool($minimumStability, $stabilityFlags, $rootConstraints);
    }

    /**
     * @return DefaultPolicy
     */
    private function createPolicy()
    {
        $preferStable = null;
        $preferLowest = null;
        if (!$this->update) {
            $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 $rootPackage
     * @param  PlatformRepository   $platformRepo
     * @return Request
     */
    private function createRequest(RootPackageInterface $rootPackage, PlatformRepository $platformRepo)
    {
        $request = new Request();

        $constraint = new Constraint('=', $rootPackage->getVersion());
        $constraint->setPrettyString($rootPackage->getPrettyVersion());
        $request->install($rootPackage->getName(), $constraint);

        $fixedPackages = $platformRepo->getPackages();
        if ($this->additionalInstalledRepository) {
            $additionalFixedPackages = $this->additionalInstalledRepository->getPackages();
            $fixedPackages = array_merge($fixedPackages, $additionalFixedPackages);
        }

        // fix the version of all platform packages + additionally installed packages
        // to prevent the solver trying to remove or update those
        $provided = $rootPackage->getProvides();
        foreach ($fixedPackages as $package) {
            $constraint = new Constraint('=', $package->getVersion());
            $constraint->setPrettyString($package->getPrettyVersion());

            // skip platform packages that are provided by the root package
            if ($package->getRepository() !== $platformRepo
                || !isset($provided[$package->getName()])
                || !$provided[$package->getName()]->getConstraint()->matches($constraint)
            ) {
                $request->fix($package->getName(), $constraint);
            }
        }

        return $request;
    }

    /**
     * @param  WritableRepositoryInterface $localRepo
     * @param  Pool                        $pool
     * @param  PolicyInterface             $policy
     * @param  array                       $repositories
     * @param  RepositoryInterface         $installedRepo
     * @param  RepositoryInterface         $lockedRepository
     * @param  string                      $task
     * @param  array|null                  $operations
     * @return array
     */
    private function processDevPackages($localRepo, $pool, $policy, $repositories, $installedRepo, $lockedRepository, $task, array $operations = null)
    {
        if ($task === 'force-updates' && null === $operations) {
            throw new \InvalidArgumentException('Missing operations argument');
        }
        if ($task === 'force-links') {
            $operations = array();
        }

        if ($this->update && $this->updateWhitelist) {
            $currentPackages = $this->getCurrentPackages($installedRepo);
        }

        foreach ($localRepo->getCanonicalPackages() as $package) {
            // skip non-dev packages
            if (!$package->isDev()) {
                continue;
            }

            // skip packages that will be updated/uninstalled
            foreach ($operations as $operation) {
                if (('update' === $operation->getJobType() && $operation->getInitialPackage()->equals($package))
                    || ('uninstall' === $operation->getJobType() && $operation->getPackage()->equals($package))
                ) {
                    continue 2;
                }
            }

            if ($this->update) {
                // skip package if the whitelist is enabled and it is not in it
                if ($this->updateWhitelist && !$this->isUpdateable($package)) {
                    // check if non-updateable packages are out of date compared to the lock file to ensure we don't corrupt it
                    foreach ($currentPackages as $curPackage) {
                        if ($curPackage->isDev() && $curPackage->getName() === $package->getName() && $curPackage->getVersion() === $package->getVersion()) {
                            if ($task === 'force-links') {
                                $package->setRequires($curPackage->getRequires());
                                $package->setConflicts($curPackage->getConflicts());
                                $package->setProvides($curPackage->getProvides());
                                $package->setReplaces($curPackage->getReplaces());
                            } elseif ($task === 'force-updates') {
                                if (($curPackage->getSourceReference() && $curPackage->getSourceReference() !== $package->getSourceReference())
                                    || ($curPackage->getDistReference() && $curPackage->getDistReference() !== $package->getDistReference())
                                ) {
                                    $operations[] = new UpdateOperation($package, $curPackage);
                                }
                            }

                            break;
                        }
                    }

                    continue;
                }

                // find similar packages (name/version) in all repositories
                $matches = $pool->whatProvides($package->getName(), new Constraint('=', $package->getVersion()));
                foreach ($matches as $index => $match) {
                    // skip local packages
                    if (!in_array($match->getRepository(), $repositories, true)) {
                        unset($matches[$index]);
                        continue;
                    }

                    // skip providers/replacers
                    if ($match->getName() !== $package->getName()) {
                        unset($matches[$index]);
                        continue;
                    }

                    $matches[$index] = $match->getId();
                }

                // select preferred package according to policy rules
                if ($matches && $matches = $policy->selectPreferredPackages($pool, array(), $matches)) {
                    $newPackage = $pool->literalToPackage($matches[0]);

                    if ($task === 'force-links' && $newPackage) {
                        $package->setRequires($newPackage->getRequires());
                        $package->setConflicts($newPackage->getConflicts());
                        $package->setProvides($newPackage->getProvides());
                        $package->setReplaces($newPackage->getReplaces());
                    }

                    if (
                        $task === 'force-updates'
                        && $newPackage
                        && (
                            ($newPackage->getSourceReference() && $newPackage->getSourceReference() !== $package->getSourceReference())
                            || ($newPackage->getDistReference() && $newPackage->getDistReference() !== $package->getDistReference())
                        )
                    ) {
                        $operations[] = new UpdateOperation($package, $newPackage);

                        continue;
                    }
                }

                if ($task === 'force-updates') {
                    // force installed package to update to referenced version in root package if it does not match the installed version
                    $references = $this->package->getReferences();

                    if (isset($references[$package->getName()]) && $references[$package->getName()] !== $package->getSourceReference()) {
                        // changing the source ref to update to will be handled in the operations loop
                        $operations[] = new UpdateOperation($package, clone $package);
                    }
                }
            } else {
                // force update to locked version if it does not match the installed version
                foreach ($lockedRepository->findPackages($package->getName()) as $lockedPackage) {
                    if ($lockedPackage->isDev() && $lockedPackage->getVersion() === $package->getVersion()) {
                        if ($task === 'force-links') {
                            $package->setRequires($lockedPackage->getRequires());
                            $package->setConflicts($lockedPackage->getConflicts());
                            $package->setProvides($lockedPackage->getProvides());
                            $package->setReplaces($lockedPackage->getReplaces());
                        } elseif ($task === 'force-updates') {
                            if (($lockedPackage->getSourceReference() && $lockedPackage->getSourceReference() !== $package->getSourceReference())
                                || ($lockedPackage->getDistReference() && $lockedPackage->getDistReference() !== $package->getDistReference())
                            ) {
                                $operations[] = new UpdateOperation($package, $lockedPackage);
                            }
                        }

                        break;
                    }
                }
            }
        }

        return $operations;
    }

    /**
     * Loads the most "current" list of packages that are installed meaning from lock ideally or from installed repo as fallback
     * @param  RepositoryInterface $installedRepo
     * @return array
     */
    private function getCurrentPackages($installedRepo)
    {
        if ($this->locker->isLocked()) {
            try {
                return $this->locker->getLockedRepository(true)->getPackages();
            } catch (\RuntimeException $e) {
                // fetch only non-dev packages from lock if doing a dev update fails due to a previously incomplete lock file
                return $this->locker->getLockedRepository()->getPackages();
            }
        }

        return $installedRepo->getPackages();
    }

    /**
     * @return array
     */
    private function getRootAliases()
    {
        if ($this->update) {
            $aliases = $this->package->getAliases();
        } else {
            $aliases = $this->locker->getAliases();
        }

        $normalizedAliases = array();

        foreach ($aliases as $alias) {
            $normalizedAliases[$alias['package']][$alias['version']] = array(
                'alias' => $alias['alias'],
                'alias_normalized' => $alias['alias_normalized'],
            );
        }

        return $normalizedAliases;
    }

    /**
     * @param Pool                        $pool
     * @param PolicyInterface             $policy
     * @param WritableRepositoryInterface $localRepo
     * @param array                       $repositories
     */
    private function processPackageUrls($pool, $policy, $localRepo, $repositories)
    {
        if (!$this->update) {
            return;
        }

        $rootRefs = $this->package->getReferences();

        foreach ($localRepo->getCanonicalPackages() as $package) {
            // find similar packages (name/version) in all repositories
            $matches = $pool->whatProvides($package->getName(), new Constraint('=', $package->getVersion()));
            foreach ($matches as $index => $match) {
                // skip local packages
                if (!in_array($match->getRepository(), $repositories, true)) {
                    unset($matches[$index]);
                    continue;
                }

                // skip providers/replacers
                if ($match->getName() !== $package->getName()) {
                    unset($matches[$index]);
                    continue;
                }

                $matches[$index] = $match->getId();
            }

            // select preferred package according to policy rules
            if ($matches && $matches = $policy->selectPreferredPackages($pool, array(), $matches)) {
                $newPackage = $pool->literalToPackage($matches[0]);

                // update the dist and source URLs
                $sourceUrl = $package->getSourceUrl();
                $newSourceUrl = $newPackage->getSourceUrl();
                $newReference = $newPackage->getSourceReference();

                if ($package->isDev() && isset($rootRefs[$package->getName()]) && $package->getSourceReference() === $rootRefs[$package->getName()]) {
                    $newReference = $rootRefs[$package->getName()];
                }

                $this->updatePackageUrl($package, $newSourceUrl, $newPackage->getSourceType(), $newReference, $newPackage->getDistUrl(), $newPackage->getDistType(), $newPackage->getDistSha1Checksum());

                if ($package instanceof CompletePackage && $newPackage instanceof CompletePackage) {
                    $package->setAbandoned($newPackage->getReplacementPackage() ?: $newPackage->isAbandoned());
                }

                $package->setDistMirrors($newPackage->getDistMirrors());
                $package->setSourceMirrors($newPackage->getSourceMirrors());
                $package->setTransportOptions($newPackage->getTransportOptions());
            }
        }
    }

    private function updatePackageUrl(PackageInterface $package, $sourceUrl, $sourceType, $sourceReference, $distUrl, $distType, $distShaSum)
    {
        $oldSourceRef = $package->getSourceReference();

        if ($package->getSourceUrl() !== $sourceUrl) {
            $package->setSourceType($sourceType);
            $package->setSourceUrl($sourceUrl);
            $package->setSourceReference($sourceReference);
        }

        // only update dist url for github/bitbucket/gitlab dists as they use a combination of dist url + dist reference to install
        // but for other urls this is ambiguous and could result in bad outcomes
        if (preg_match('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com|(?:www\.)?gitlab\.com)/}i', $distUrl)) {
            $package->setDistUrl($distUrl);
            $package->setDistType($distType);
            $package->setDistSha1Checksum($distShaSum);
            $this->updateInstallReferences($package, $sourceReference);
        }

        if ($this->updateWhitelist && !$this->isUpdateable($package)) {
            $this->updateInstallReferences($package, $oldSourceRef);
        }
    }

    private function updateInstallReferences(PackageInterface $package, $reference)
    {
        if (!$reference) {
            return;
        }

        $package->setSourceReference($reference);

        if (preg_match('{^https?://(?:(?:www\.)?bitbucket\.org|(api\.)?github\.com|(?:www\.)?gitlab\.com)/}i', $package->getDistUrl())) {
            $package->setDistReference($reference);
            $package->setDistUrl(preg_replace('{(?<=/|sha=)[a-f0-9]{40}(?=/|$)}i', $reference, $package->getDistUrl()));
        } elseif ($package->getDistReference()) { // update the dist reference if there was one, but if none was provided ignore it
            $package->setDistReference($reference);
        }
    }

    /**
     * @param PlatformRepository $platformRepo
     * @param array              $aliases
     */
    private function aliasPlatformPackages(PlatformRepository $platformRepo, $aliases)
    {
        foreach ($aliases as $package => $versions) {
            foreach ($versions as $version => $alias) {
                $packages = $platformRepo->findPackages($package, $version);
                foreach ($packages as $package) {
                    $aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias']);
                    $aliasPackage->setRootPackageAlias(true);
                    $platformRepo->addPackage($aliasPackage);
                }
            }
        }
    }

    /**
     * @param  PackageInterface $package
     * @return bool
     */
    private function isUpdateable(PackageInterface $package)
    {
        if (!$this->updateWhitelist) {
            throw new \LogicException('isUpdateable should only be called when a whitelist is present');
        }

        foreach ($this->updateWhitelist as $whiteListedPattern => $void) {
            $patternRegexp = BasePackage::packageNameToRegexp($whiteListedPattern);
            if (preg_match($patternRegexp, $package->getName())) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param  array $links
     * @return array
     */
    private function extractPlatformRequirements($links)
    {
        $platformReqs = array();
        foreach ($links as $link) {
            if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $link->getTarget())) {
                $platformReqs[$link->getTarget()] = $link->getPrettyConstraint();
            }
        }

        return $platformReqs;
    }

    /**
     * Adds all dependencies of the update whitelist to the whitelist, too.
     *
     * Packages which are listed as requirements in the root package will be
     * skipped including their dependencies, unless they are listed in the
     * update whitelist themselves or $whitelistAllDependencies is true.
     *
     * @param RepositoryInterface $localOrLockRepo Use the locked repo if available, otherwise installed repo will do
     *                                             As we want the most accurate package list to work with, and installed
     *                                             repo might be empty but locked repo will always be current.
     * @param array               $rootRequires    An array of links to packages in require of the root package
     * @param array               $rootDevRequires An array of links to packages in require-dev of the root package
     */
    private function whitelistUpdateDependencies($localOrLockRepo, array $rootRequires, array $rootDevRequires)
    {
        if (!$this->updateWhitelist) {
            return;
        }

        $rootRequires = array_merge($rootRequires, $rootDevRequires);

        $skipPackages = array();
        if (!$this->whitelistAllDependencies) {
            foreach ($rootRequires as $require) {
                $skipPackages[$require->getTarget()] = true;
            }
        }

        $pool = new Pool('dev');
        $pool->addRepository($localOrLockRepo);

        $seen = array();

        $rootRequiredPackageNames = array_keys($rootRequires);

        foreach ($this->updateWhitelist as $packageName => $void) {
            $packageQueue = new \SplQueue;
            $nameMatchesRequiredPackage = false;

            $depPackages = $pool->whatProvides($packageName);
            $matchesByPattern = array();
            // check if the name is a glob pattern that did not match directly
            if (empty($depPackages)) {
                // add any installed package matching the whitelisted name/pattern
                $whitelistPatternSearchRegexp = BasePackage::packageNameToRegexp($packageName, '^%s$');
                foreach ($localOrLockRepo->search($whitelistPatternSearchRegexp) as $installedPackage) {
                    $matchesByPattern[] = $pool->whatProvides($installedPackage['name']);
                }

                // add root requirements which match the whitelisted name/pattern
                $whitelistPatternRegexp = BasePackage::packageNameToRegexp($packageName);
                foreach ($rootRequiredPackageNames as $rootRequiredPackageName) {
                    if (preg_match($whitelistPatternRegexp, $rootRequiredPackageName)) {
                        $nameMatchesRequiredPackage = true;
                        break;
                    }
                }
            }

            if (!empty($matchesByPattern)) {
                $depPackages = array_merge($depPackages, call_user_func_array('array_merge', $matchesByPattern));
            }

            if (count($depPackages) == 0 && !$nameMatchesRequiredPackage && !in_array($packageName, array('nothing', 'lock', 'mirrors'))) {
                $this->io->writeError('<warning>Package "' . $packageName . '" listed for update is not installed. Ignoring.</warning>');
            }

            foreach ($depPackages as $depPackage) {
                $packageQueue->enqueue($depPackage);
            }

            while (!$packageQueue->isEmpty()) {
                $package = $packageQueue->dequeue();
                if (isset($seen[$package->getId()])) {
                    continue;
                }

                $seen[$package->getId()] = true;
                $this->updateWhitelist[$package->getName()] = true;

                if (!$this->whitelistDependencies && !$this->whitelistAllDependencies) {
                    continue;
                }

                $requires = $package->getRequires();

                foreach ($requires as $require) {
                    $requirePackages = $pool->whatProvides($require->getTarget());

                    foreach ($requirePackages as $requirePackage) {
                        if (isset($this->updateWhitelist[$requirePackage->getName()])) {
                            continue;
                        }

                        if (isset($skipPackages[$requirePackage->getName()]) && !preg_match(BasePackage::packageNameToRegexp($packageName), $requirePackage->getName())) {
                            $this->io->writeError('<warning>Dependency "' . $requirePackage->getName() . '" is also a root requirement, but is not explicitly whitelisted. Ignoring.</warning>');
                            continue;
                        }

                        $packageQueue->enqueue($requirePackage);
                    }
                }
            }
        }
    }

    /**
     * Replace local repositories with InstalledArrayRepository instances
     *
     * This is to prevent any accidental modification of the existing repos on disk
     *
     * @param RepositoryManager $rm
     */
    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();
                $packages[$key] = new AliasPackage($packages[$alias], $package->getVersion(), $package->getPrettyVersion());
            }
        }
        $rm->setLocalRepository(
            new InstalledArrayRepository($packages)
        );
    }

    /**
     * Create Installer
     *
     * @param  IOInterface $io
     * @param  Composer    $composer
     * @return Installer
     */
    public static function create(IOInterface $io, Composer $composer)
    {
        return new static(
            $io,
            $composer->getConfig(),
            $composer->getPackage(),
            $composer->getDownloadManager(),
            $composer->getRepositoryManager(),
            $composer->getLocker(),
            $composer->getInstallationManager(),
            $composer->getEventDispatcher(),
            $composer->getAutoloadGenerator()
        );
    }

    /**
     * @param  RepositoryInterface $additionalInstalledRepository
     * @return $this
     */
    public function setAdditionalInstalledRepository(RepositoryInterface $additionalInstalledRepository)
    {
        $this->additionalInstalledRepository = $additionalInstalledRepository;

        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 = false)
    {
        $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 = false)
    {
        $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
     * @return Installer
     */
    public function setApcuAutoloader($apcuAutoloader = false)
    {
        $this->apcuAutoloader = (bool) $apcuAutoloader;

        return $this;
    }

    /**
     * update packages
     *
     * @param  bool      $update
     * @return Installer
     */
    public function setUpdate($update = true)
    {
        $this->update = (bool) $update;

        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
     */
    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
     *
     * @param  bool      $ignorePlatformReqs
     * @return Installer
     */
    public function setIgnorePlatformRequirements($ignorePlatformReqs = false)
    {
        $this->ignorePlatformReqs = (bool) $ignorePlatformReqs;

        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  array     $packages
     * @return Installer
     */
    public function setUpdateWhitelist(array $packages)
    {
        $this->updateWhitelist = array_flip(array_map('strtolower', $packages));

        return $this;
    }

    /**
     * @deprecated use setWhitelistTransitiveDependencies instead
     */
    public function setWhitelistDependencies($updateDependencies = true)
    {
        return $this->setWhitelistTransitiveDependencies($updateDependencies);
    }

    /**
     * Should dependencies of whitelisted packages (but not direct dependencies) be updated?
     *
     * This will NOT whitelist any dependencies that are also directly defined
     * in the root package.
     *
     * @param  bool      $updateTransitiveDependencies
     * @return Installer
     */
    public function setWhitelistTransitiveDependencies($updateTransitiveDependencies = true)
    {
        $this->whitelistDependencies = (bool) $updateTransitiveDependencies;

        return $this;
    }

    /**
     * Should all dependencies of whitelisted packages be updated recursively?
     *
     * This will whitelist any dependencies of the whitelisted packages, including
     * those defined in the root package.
     *
     * @param  bool      $updateAllDependencies
     * @return Installer
     */
    public function setWhitelistAllDependencies($updateAllDependencies = true)
    {
        $this->whitelistAllDependencies = (bool) $updateAllDependencies;

        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;
    }

    /**
     * Should suggestions be skipped?
     *
     * @param  bool      $skipSuggest
     * @return Installer
     */
    public function setSkipSuggest($skipSuggest = true)
    {
        $this->skipSuggest = (bool) $skipSuggest;

        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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Script;

use Composer\Installer\PackageEvent as BasePackageEvent;

/**
 * The Package Event.
 *
 * @deprecated Use Composer\Installer\PackageEvent instead
 */
class PackageEvent extends BasePackageEvent
{
}
<?php

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

namespace Composer\Script;

use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\EventDispatcher\Event as BaseEvent;

/**
 * The script event class
 *
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 * @author Nils Adermann <naderman@naderman.de>
 */
class Event extends BaseEvent
{
    /**
     * @var Composer The composer instance
     */
    private $composer;

    /**
     * @var IOInterface The IO instance
     */
    private $io;

    /**
     * @var bool Dev mode flag
     */
    private $devMode;

    /**
     * @var BaseEvent
     */
    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       $args     Arguments passed by the user
     * @param array       $flags    Optional flags to pass data not as argument
     */
    public function __construct($name, Composer $composer, IOInterface $io, $devMode = false, array $args = array(), array $flags = array())
    {
        parent::__construct($name, $args, $flags);
        $this->composer = $composer;
        $this->io = $io;
        $this->devMode = $devMode;
        $this->originatingEvent = null;
    }

    /**
     * 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 \Composer\EventDispatcher\Event|null
     */
    public function getOriginatingEvent()
    {
        return $this->originatingEvent;
    }

    /**
     * Set the originating event.
     *
     * @param \Composer\EventDispatcher\Event $event
     * @return $this
     */
    public function setOriginatingEvent(BaseEvent $event)
    {
        $this->originatingEvent = $this->calculateOriginatingEvent($event);

        return $this;
    }

    /**
     * Returns the upper-most event in chain.
     *
     * @param \Composer\EventDispatcher\Event $event
     * @return \Composer\EventDispatcher\Event
     */
    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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Script;

/**
 * The Command Event.
 *
 * @deprecated use Composer\Script\Event instead
 */
class CommandEvent extends Event
{
}
<?php

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

namespace Composer\Script;

/**
 * The Script Events.
 *
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ScriptEvents
{
    /**
     * The PRE_INSTALL_CMD event occurs before the install command is executed.
     *
     * The event listener method receives a Composer\Script\Event instance.
     *
     * @var string
     */
    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';

    /** Deprecated constants below */

    /**
     * The PRE_PACKAGE_INSTALL event occurs before a package is installed.
     *
     * The event listener method receives a Composer\Installer\PackageEvent instance.
     *
     * @deprecated Use Composer\Installer\PackageEvents::PRE_PACKAGE_INSTALL instead.
     * @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.
     *
     * @deprecated Use Composer\Installer\PackageEvents::POST_PACKAGE_INSTALL instead.
     * @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.
     *
     * @deprecated Use Composer\Installer\PackageEvents::PRE_PACKAGE_UPDATE instead.
     * @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.
     *
     * @deprecated Use Composer\Installer\PackageEvents::POST_PACKAGE_UPDATE instead.
     * @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.
     *
     * @deprecated Use Composer\Installer\PackageEvents::PRE_PACKAGE_UNINSTALL instead.
     * @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.
     *
     * @deprecated Use Composer\Installer\PackageEvents::POST_PACKAGE_UNINSTALL instead.
     * @var string
     */
    const POST_PACKAGE_UNINSTALL = 'post-package-uninstall';
}
<?php

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

namespace Composer\Json;

use Exception;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class JsonValidationException extends Exception
{
    protected $errors;

    public function __construct($message, $errors = array(), Exception $previous = null)
    {
        $this->errors = $errors;
        parent::__construct($message, 0, $previous);
    }

    public function getErrors()
    {
        return $this->errors;
    }
}
<?php

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

namespace Composer\Json;

use Composer\Repository\PlatformRepository;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class JsonManipulator
{
    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) ) )
    )';

    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)
    {
        $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;
        }

        $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($links, true);
            $this->sortPackages($requirements);
            $links = $this->format($requirements);
        }

        $this->contents = $matches['start'] . $matches['property'] . $links . $matches['end'];

        return true;
    }

    /**
     * Sorts packages by importance (platform packages first, then PHP dependencies) and alphabetically.
     *
     * @link https://getcomposer.org/doc/02-libraries.md#platform-packages
     *
     * @param array $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($name, 0, 6) === 'extra.') {
            return $this->addSubNode('extra', substr($name, 6), $value);
        }

        if (substr($name, 0, 8) === 'scripts.') {
            return $this->addSubNode('scripts', substr($name, 8), $value);
        }

        return $this->addMainKey($name, $value);
    }

    public function removeProperty($name)
    {
        if (substr($name, 0, 6) === 'extra.') {
            return $this->removeSubNode('extra', substr($name, 6));
        }

        if (substr($name, 0, 8) === 'scripts.') {
            return $this->removeSubNode('scripts', substr($name, 8));
        }

        return $this->removeMainKey($name);
    }

    public function addSubNode($mainNode, $name, $value)
    {
        $decoded = JsonFile::parseJson($this->contents);

        $subName = null;
        if (in_array($mainNode, array('config', 'extra', 'scripts')) && false !== strpos($name, '.')) {
            list($name, $subName) = explode('.', $name, 2);
        }

        // 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;
        }

        $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 ($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($value, 1) . $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($value, 1) . $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($value, 1) . $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)
    {
        $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 (!$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($children, true)) {
            return false;
        }

        $subName = null;
        if (in_array($mainNode, array('config', 'extra', 'scripts')) && false !== strpos($name, '.')) {
            list($name, $subName) = explode('.', $name, 2);
        }

        // no node to remove
        if (!isset($decoded[$mainNode][$name]) || ($subName && !isset($decoded[$mainNode][$name][$subName]))) {
            return true;
        }

        // try and find a match for the subkey
        $keyRegex = str_replace('/', '\\\\?/', preg_quote($name));
        if ($this->pregMatch('{"'.$keyRegex.'"\s*:}i', $children)) {
            // find best match for the value of "name"
            if (preg_match_all('{'.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 (1 !== $count) {
                    $childrenClean = preg_replace('{'.preg_quote($bestMatch).'\s*,?\s*}i', '', $childrenClean, -1, $count);
                    if (1 !== $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($children, true);
                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($curVal, 0);
            }

            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 (!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 ($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);
                    }
                    // no break
                default:
                    throw new \RuntimeException('Failed to execute regex: Unknown error');
            }
        }

        return $count;
    }
}
<?php

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

namespace Composer\Json;

use JsonSchema\Validator;
use Seld\JsonLint\JsonParser;
use Seld\JsonLint\ParsingException;
use Composer\Util\RemoteFilesystem;
use Composer\IO\IOInterface;
use Composer\Downloader\TransportException;

/**
 * Reads/writes json files.
 *
 * @author Konstantin Kudryashiv <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class JsonFile
{
    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';

    private $path;
    private $rfs;
    private $io;

    /**
     * Initializes json file reader/parser.
     *
     * @param  string                    $path path to a lockfile
     * @param  RemoteFilesystem          $rfs  required for loading http/https json files
     * @param  IOInterface               $io
     * @throws \InvalidArgumentException
     */
    public function __construct($path, RemoteFilesystem $rfs = null, IOInterface $io = null)
    {
        $this->path = $path;

        if (null === $rfs && preg_match('{^https?://}i', $path)) {
            throw new \InvalidArgumentException('http urls require a RemoteFilesystem instance to be passed');
        }
        $this->rfs = $rfs;
        $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 \RuntimeException
     * @return mixed
     */
    public function read()
    {
        try {
            if ($this->rfs) {
                $json = $this->rfs->getContents($this->path, $this->path, false);
            } else {
                if ($this->io && $this->io->isDebug()) {
                    $this->io->writeError('Reading ' . $this->path);
                }
                $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  array                                $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
     */
    public function write(array $hash, $options = 448)
    {
        $dir = dirname($this->path);
        if (!is_dir($dir)) {
            if (file_exists($dir)) {
                throw new \UnexpectedValueException(
                    $dir.' exists and is not a directory.'
                );
            }
            if (!@mkdir($dir, 0777, true)) {
                throw new \UnexpectedValueException(
                    $dir.' does not exist and could not be created.'
                );
            }
        }

        $retries = 3;
        while ($retries--) {
            try {
                file_put_contents($this->path, static::encode($hash, $options). ($options & self::JSON_PRETTY_PRINT ? "\n" : ''));
                break;
            } catch (\Exception $e) {
                if ($retries) {
                    usleep(500000);
                    continue;
                }

                throw $e;
            }
        }
    }

    /**
     * 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
     * @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);
        }

        if (null === $schemaFile) {
            $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();
        }

        $validator = new Validator();
        $validator->check($data, $schemaData);

        // TODO add more validation like check version constraints and such, perhaps build that into the arrayloader?

        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
     */
    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
     *
     * @return mixed
     */
    public static function parseJson($json, $file = null)
    {
        if (null === $json) {
            return;
        }
        $data = json_decode($json, true);
        if (null === $data && JSON_ERROR_NONE !== json_last_error()) {
            self::validateSyntax($json, $file);
        }

        return $data;
    }

    /**
     * Validates the syntax of a JSON string
     *
     * @param  string                    $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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Json;

/**
 * Formats json strings used for php < 5.4 because the json_encode doesn't
 * supports the flags JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE
 * in these versions
 *
 * @author Konstantin Kudryashiv <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class JsonFormatter
{
    /**
     * This code is based on the function found at:
     *  http://recursive-design.com/blog/2008/03/11/format-json-with-php/
     *
     * Originally licensed under MIT by Dave Perrett <mail@recursive-design.com>
     *
     *
     * @param  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, $i, 1);

            // Are we inside a quoted string?
            if ('"' === $char && $noescape) {
                $outOfQuotes = !$outOfQuotes;
            }

            if (!$outOfQuotes) {
                $buffer .= $char;
                $noescape = '\\' === $char ? !$noescape : true;
                continue;
            } elseif ('' !== $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_replace_callback('/(\\\\+)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 - 1, 1);

                if ('{' !== $prevChar && '[' !== $prevChar) {
                    // If this character is the end of an element,
                    // output a new line and indent the next line
                    $result .= $newLine;
                    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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Spdx\SpdxLicenses;

trigger_error('The ' . __NAMESPACE__ . '\SpdxLicense class is deprecated, use Composer\Spdx\SpdxLicenses instead.', E_USER_DEPRECATED);

/**
 * @deprecated use Composer\Spdx\SpdxLicenses instead
 */
class SpdxLicense extends SpdxLicenses
{
}
<?php


namespace Composer\Util;

use Composer\Package\Link;
use Composer\Package\PackageInterface;

class PackageSorter
{
    /**
     * Sorts packages by dependency weight
     *
     * Packages of equal weight retain the original order
     *
     * @param  array $packages
     * @return array
     */
    public static function sortPackages(array $packages) {
        $usageList = array();

        foreach ($packages as $package) { /** @var PackageInterface $package */
            foreach (array_merge($package->getRequires(), $package->getDevRequires()) as $link) { /** @var Link $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 -= 1 - $computeImportance($user);
                }
            }

            unset($computing[$name]);
            $computed[$name] = $weight;

            return $weight;
        };

        $weightList = array();

        foreach ($packages as $name => $package) {
            $weight = $computeImportance($name);
            $weightList[$name] = $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 $name) {
            $sortedPackages[] = $packages[$name];
        }
        return $sortedPackages;
    }
}
<?php

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

namespace Composer\Util;

use Composer\IO\IOInterface;
use Composer\Config;
use Composer\Factory;
use Composer\Downloader\TransportException;
use Composer\Json\JsonFile;

/**
 * @author Roshan Gautam <roshan.gautam@hotmail.com>
 */
class GitLab
{
    protected $io;
    protected $config;
    protected $process;
    protected $remoteFilesystem;

    /**
     * Constructor.
     *
     * @param IOInterface      $io               The IO instance
     * @param Config           $config           The composer configuration
     * @param ProcessExecutor  $process          Process instance, injectable for mocking
     * @param RemoteFilesystem $remoteFilesystem Remote Filesystem, injectable for mocking
     */
    public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, RemoteFilesystem $remoteFilesystem = null)
    {
        $this->io = $io;
        $this->config = $config;
        $this->process = $process ?: new ProcessExecutor($io);
        $this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($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 (0 === $this->process->execute('git config gitlab.accesstoken', $output)) {
            $this->io->setAuthentication($originUrl, trim($output), 'oauth2');

            return true;
        }

        // if available use token from composer config
        $authTokens = $this->config->get('gitlab-token');

        if (isset($authTokens[$originUrl])) {
            $this->io->setAuthentication($originUrl, $authTokens[$originUrl], 'private-token');

            return true;
        }

        if (isset($authTokens[$bcOriginUrl])) {
            $this->io->setAuthentication($originUrl, $authTokens[$bcOriginUrl], 'private-token');

            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 '.$originUrl.'/profile/applications');

        $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(403, 401))) {
                    if (401 === $e->getCode()) {
                        $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 token 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.');
    }

    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',
        ), null, '&');
        $options = array(
            'retry-auth-failure' => false,
            'http' => array(
                'method' => 'POST',
                'header' => $headers,
                'content' => $data,
            ),
        );

        $json = $this->remoteFilesystem->getContents($originUrl, $scheme.'://'.$apiUrl.'/oauth/token', false, $options);

        $this->io->writeError('Token successfully created');

        return JsonFile::parseJson($json);
    }
}
<?php

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

namespace Composer\Util;

use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Finder\Finder;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class Filesystem
{
    private $processExecutor;

    public function __construct(ProcessExecutor $executor = null)
    {
        $this->processExecutor = $executor ?: new ProcessExecutor();
    }

    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;
    }

    public function emptyDirectory($dir, $ensureDirectoryExists = true)
    {
        if (file_exists($dir) && is_link($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)
    {
        if ($this->isSymlinkedDirectory($directory)) {
            return $this->unlinkSymlinkedDirectory($directory);
        }

        if ($this->isJunction($directory)) {
            return $this->removeJunction($directory);
        }

        if (is_link($directory)) {
            return unlink($directory);
        }

        if (!file_exists($directory) || !is_dir($directory)) {
            return true;
        }

        if (preg_match('{^(?:[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')) {
            return $this->removeDirectoryPhp($directory);
        }

        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 && !file_exists($directory)) {
            return true;
        }

        return $this->removeDirectoryPhp($directory);
    }

    /**
     * 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)
    {
        try {
            $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS);
        } catch (\UnexpectedValueException $e) {
            // re-try once after clearing the stat cache if it failed as it
            // sometimes fails without apparent reason, see https://github.com/composer/composer/issues/4009
            clearstatcache();
            usleep(100000);
            if (!is_dir($directory)) {
                return true;
            }
            $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS);
        }
        $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);

        foreach ($ri as $file) {
            if ($file->isDir()) {
                $this->rmdir($file->getPathname());
            } else {
                $this->unlink($file->getPathname());
            }
        }

        return $this->rmdir($directory);
    }

    public function ensureDirectoryExists($directory)
    {
        if (!is_dir($directory)) {
            if (file_exists($directory)) {
                throw new \RuntimeException(
                    $directory.' exists and is not a directory.'
                );
            }
            if (!@mkdir($directory, 0777, true)) {
                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
     */
    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($source, RecursiveDirectoryIterator::SKIP_DOTS);
        $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::SELF_FIRST);
        $this->ensureDirectoryExists($target);

        $result = true;
        foreach ($ri as $file) {
            $targetPath = $target . DIRECTORY_SEPARATOR . $ri->getSubPathName();
            if ($file->isDir()) {
                $this->ensureDirectoryExists($targetPath);
            } else {
                $result = $result && copy($file->getPathname(), $targetPath);
            }
        }

        return $result;
    }

    public function rename($source, $target)
    {
        if (true === @rename($source, $target)) {
            return;
        }

        if (!function_exists('proc_open')) {
            return $this->copyThenRemove($source, $target);
        }

        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->processExecutor->execute($command, $output);

            // clear stat cache because external processes aren't tracked by the php stat cache
            clearstatcache();

            if (0 === $result) {
                $this->remove($source);

                return;
            }
        } else {
            // We do not use PHP's "rename" function here since it does not support
            // the case where $source, and $target are located on different partitions.
            $command = sprintf('mv %s %s', ProcessExecutor::escape($source), ProcessExecutor::escape($target));
            $result = $this->processExecutor->execute($command, $output);

            // clear stat cache because external processes aren't tracked by the php stat cache
            clearstatcache();

            if (0 === $result) {
                return;
            }
        }

        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.'/') !== 0 && '/' !== $commonPath && !preg_match('{^[a-z]:/?$}i', $commonPath)) {
            $commonPath = strtr(dirname($commonPath), '\\', '/');
        }

        if (0 !== 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.'/') !== 0 && '/' !== $commonPath && !preg_match('{^[a-z]:/?$}i', $commonPath) && '.' !== $commonPath) {
            $commonPath = strtr(dirname($commonPath), '\\', '/');
        }

        if (0 !== strpos($from, $commonPath) || '/' === $commonPath || '.' === $commonPath) {
            return var_export($to, true);
        }

        $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('/' . $relTarget, true) : '');
    }

    /**
     * Checks if the given path is absolute
     *
     * @param  string $path
     * @return bool
     */
    public function isAbsolutePath($path)
    {
        return substr($path, 0, 1) === '/' || substr($path, 1, 1) === ':' || substr($path, 0, 2) === '\\\\';
    }

    /**
     * Returns size of a file or directory specified by path. If a directory is
     * given, it's 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 = false;

        // extract a prefix being a protocol://, protocol:, protocol://drive: or simply drive:
        if (preg_match('{^( [0-9a-z]{2,}+: (?: // (?: [a-z]: )? )? | [a-z]: )}ix', $path, $match)) {
            $prefix = $match[1];
            $path = substr($path, strlen($prefix));
        }

        if (substr($path, 0, 1) === '/') {
            $absolute = true;
            $path = substr($path, 1);
        }

        $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.($absolute ? '/' : '').implode('/', $parts);
    }

    /**
     * Return if the given path is local
     *
     * @param  string $path
     * @return bool
     */
    public static function isLocalPath($path)
    {
        return (bool) preg_match('{^(file://(?!//)|/(?!/)|/?[a-z]:[\\\\/]|\.\.[\\\\/]|[a-z0-9_.-]+[\\\\/])}i', $path);
    }

    public static function getPlatformPath($path)
    {
        if (Platform::isWindows()) {
            $path = preg_replace('{^(?:file:///([a-z]):?/)}i', 'file://$1:/', $path);
        }

        return preg_replace('{^file://}i', '', $path);
    }

    protected function directorySize($directory)
    {
        $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS);
        $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);

        $size = 0;
        foreach ($ri as $file) {
            if ($file->isFile()) {
                $size += $file->getSize();
            }
        }

        return $size;
    }

    protected function getProcess()
    {
        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)
    {
        $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
     */
    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), 0, null, $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), 0, null, $target);
        }
        clearstatcache(true, $junction);
    }

    /**
     * Returns whether the target directory is a Windows NTFS Junction.
     *
     * We test if the path is a directory and not an ordinary link, then check
     * that the mode value returned from lstat (which gives the status of the
     * link itself) is not a directory, by replicating the POSIX S_ISDIR test.
     *
     * This logic works because PHP does not set the mode value for a junction,
     * since there is no universal file type flag for it. Unfortunately an
     * uninitialized variable in PHP prior to 7.2.16 and 7.3.3 may cause a
     * random value to be returned. See https://bugs.php.net/bug.php?id=77552
     *
     * If this random value passes the S_ISDIR test, then a junction will not be
     * detected and a recursive delete operation could lead to loss of data in
     * the target directory. Note that Windows rmdir can handle this situation
     * and will only delete the junction (from Windows 7 onwards).
     *
     * @param  string $junction Path to check.
     * @return bool
     */
    public function isJunction($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);
    }
}
<?php

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

namespace Composer\Util;

/**
 * Temporarily suppress PHP error reporting, usually warnings and below.
 *
 * @author Niels Keurentjes <niels.keurentjes@omines.com>
 */
class Silencer
{
    /**
     * @var int[] Unpop stack
     */
    private static $stack = 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.
     */
    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($callable, array_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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\IO\IOInterface;

/**
 * Convert PHP errors into exceptions
 *
 * @author Artem Lopata <biozshock@gmail.com>
 */
class ErrorHandler
{
    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;
        }

        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($message, 0, $level, $file, $line);
        }

        if (self::$io) {
            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
     */
    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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\IO\IOInterface;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\ProcessUtils;

/**
 * @author Robert Schönthal <seroscho@googlemail.com>
 */
class ProcessExecutor
{
    protected static $timeout = 300;

    protected $captureOutput;
    protected $errorOutput;
    protected $io;

    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 ($this->io && $this->io->isDebug()) {
            $safeCommand = preg_replace_callback('{://(?P<user>[^:/\s]+):(?P<password>[^@\s/]+)@}i', function ($m) {
                if (preg_match('{^[a-f0-9]{12,}$}', $m['user'])) {
                    return '://***:***@';
                }

                return '://'.$m['user'].':***@';
            }, $command);
            $safeCommand = preg_replace("{--password (.*[^\\\\]\') }", '--password \'***\' ', $safeCommand);
            $this->io->writeError('Executing command ('.($cwd ?: 'CWD').'): '.$safeCommand);
        }

        // 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());
        }

        $this->captureOutput = func_num_args() > 1;
        $this->errorOutput = null;

        // 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, $cwd, null, null, static::getTimeout());
        } else {
            $process = new Process($command, $cwd, null, null, static::getTimeout());
        }

        $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();
    }

    public function splitLines($output)
    {
        $output = trim($output);

        return ((string) $output === '') ? array() : preg_split('{\r?\n}', $output);
    }

    /**
     * Get any error output from the last command
     *
     * @return string
     */
    public function getErrorOutput()
    {
        return $this->errorOutput;
    }

    public function outputHandler($type, $buffer)
    {
        if ($this->captureOutput) {
            return;
        }

        if (null === $this->io) {
            echo $buffer;

            return;
        }

        if (Process::ERR === $type) {
            $this->io->writeError($buffer, false);
        } else {
            $this->io->write($buffer, false);
        }
    }

    public static function getTimeout()
    {
        return static::$timeout;
    }

    public static function setTimeout($timeout)
    {
        static::$timeout = $timeout;
    }

    /**
     * Escapes a string to be used as a shell argument.
     *
     * @param string $argument The argument that will be escaped
     *
     * @return string The escaped argument
     */
    public static function escape($argument)
    {
        return self::escapeArgument($argument);
    }

    /**
     * Copy of ProcessUtils::escapeArgument() that is deprecated in Symfony 3.3 and removed in Symfony 4.
     *
     * @param string $argument
     *
     * @return string
     */
    private static function escapeArgument($argument)
    {
        //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/bug.php?id=43784
        //@see https://bugs.php.net/bug.php?id=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($part, 1, -1).'"^%';
                } else {
                    // escape trailing backslash
                    if ('\\' === substr($part, -1)) {
                        $part .= '\\';
                    }
                    $quote = true;
                    $escapedArgument .= $part;
                }
            }
            if ($quote) {
                $escapedArgument = '"'.$escapedArgument.'"';
            }

            return $escapedArgument;
        }

        return "'".str_replace("'", "'\\''", $argument)."'";
    }

    private static function isSurroundedBy($arg, $char)
    {
        return 2 < strlen($arg) && $char === $arg[0] && $char === $arg[strlen($arg) - 1];
    }
}
<?php

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

namespace Composer\Util;

/**
 * Composer mirror utilities
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ComposerMirror
{
    public static function processUrl($mirrorUrl, $packageName, $version, $reference, $type)
    {
        if ($reference) {
            $reference = preg_match('{^([a-f0-9]*|%reference%)$}', $reference) ? $reference : md5($reference);
        }
        $version = strpos($version, '/') === false ? $version : md5($version);

        return str_replace(
            array('%package%', '%version%', '%reference%', '%type%'),
            array($packageName, $version, $reference, $type),
            $mirrorUrl
        );
    }

    public static function processGitUrl($mirrorUrl, $packageName, $url, $type)
    {
        if (preg_match('#^(?:(?:https?|git)://github\.com/|git@github\.com:)([^/]+)/(.+?)(?:\.git)?$#', $url, $match)) {
            $url = 'gh-'.$match[1].'/'.$match[2];
        } elseif (preg_match('#^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
        );
    }

    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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

/**
 * Platform helper for uniform platform-specific tests.
 *
 * @author Niels Keurentjes <niels.keurentjes@omines.com>
 */
class Platform
{
    /**
     * Parses tildes and environment variables in paths.
     *
     * @param  string $path
     * @return string
     */
    public static function expandPath($path)
    {
        if (preg_match('#^~[\\/]#', $path)) {
            return self::getUserDirectory() . substr($path, 1);
        }

        return preg_replace_callback('#^(\$|(?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 (getenv('HOME') ?: getenv('USERPROFILE')) . $matches['path'];
            }

            return 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 = getenv('HOME'))) {
            return $home;
        }

        if (self::isWindows() && false !== ($home = 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 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);
    }
}
<?php

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

namespace Composer\Util;

use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\Config;
use Composer\Downloader\TransportException;

/**
 * @author Paul Wenke <wenke.paul@gmail.com>
 */
class Bitbucket
{
    private $io;
    private $config;
    private $process;
    private $remoteFilesystem;
    private $token = array();
    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 RemoteFilesystem $remoteFilesystem Remote Filesystem, injectable for mocking
     * @param int              $time             Timestamp, injectable for mocking
     */
    public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, RemoteFilesystem $remoteFilesystem = null, $time = null)
    {
        $this->io = $io;
        $this->config = $config;
        $this->process = $process ?: new ProcessExecutor($io);
        $this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($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 (0 === $this->process->execute('git config bitbucket.accesstoken', $output)) {
            $this->io->setAuthentication($originUrl, 'x-token-auth', trim($output));

            return true;
        }

        return false;
    }

    /**
     * @param  string $originUrl
     * @return bool
     */
    private function requestAccessToken($originUrl)
    {
        try {
            $json = $this->remoteFilesystem->getContents($originUrl, self::OAUTH2_ACCESS_TOKEN_URL, false, array(
                'retry-auth-failure' => false,
                'http' => array(
                    'method' => 'POST',
                    'content' => 'grant_type=client_credentials',
                ),
            ));

            $this->token = json_decode($json, true);
        } 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;
            } elseif (in_array($e->getCode(), array(403, 401))) {
                $this->io->writeError('<error>Invalid OAuth consumer provided.</error>');
                $this->io->writeError('You can also add it manually later by using "composer config --global --auth bitbucket-oauth.bitbucket.org <consumer-key> <consumer-secret>"');

                return false;
            }

            throw $e;
        }

        return true;
    }

    /**
     * Authorizes a Bitbucket domain interactively via OAuth
     *
     * @param  string                        $originUrl The host this Bitbucket instance is located at
     * @param  string                        $message   The reason this authorization is required
     * @throws \RuntimeException
     * @throws TransportException|\Exception
     * @return bool                          true on success
     */
    public function authorizeOAuthInteractively($originUrl, $message = null)
    {
        if ($message) {
            $this->io->writeError($message);
        }

        $url = 'https://confluence.atlassian.com/bitbucket/oauth-on-bitbucket-cloud-238027431.html';
        $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($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($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($originUrl)) {
            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 (!empty($this->token) || $this->getTokenFromConfig($originUrl)) {
            return $this->token['access_token'];
        }

        $this->io->setAuthentication($originUrl, $consumerKey, $consumerSecret);
        if (!$this->requestAccessToken($originUrl)) {
            return '';
        }

        $this->storeInAuthConfig($originUrl, $consumerKey, $consumerSecret);

        return $this->token['access_token'];
    }

    /**
     * Store the new/updated credentials to the configuration
     * @param string $originUrl
     * @param string $consumerKey
     * @param string $consumerSecret
     */
    private function storeInAuthConfig($originUrl, $consumerKey, $consumerSecret)
    {
        $this->config->getConfigSource()->removeConfigSetting('bitbucket-oauth.'.$originUrl);

        $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'])
            || !isset($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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Composer;

/**
 * Allows the creation of a basic context supporting http proxy
 *
 * @author Jordan Alliot <jordan.alliot@gmail.com>
 * @author Markus Tacker <m@coderbyheart.de>
 */
final class StreamContextFactory
{
    /**
     * Creates a context supporting HTTP proxies
     *
     * @param  string            $url            URL the context is to be used for
     * @param  array             $defaultOptions Options to merge with the default
     * @param  array             $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,
        ));

        // Handle HTTP_PROXY/http_proxy on CLI only for security reasons
        if ((PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') && (!empty($_SERVER['HTTP_PROXY']) || !empty($_SERVER['http_proxy']))) {
            $proxy = parse_url(!empty($_SERVER['http_proxy']) ? $_SERVER['http_proxy'] : $_SERVER['HTTP_PROXY']);
        }

        // Prefer CGI_HTTP_PROXY if available
        if (!empty($_SERVER['CGI_HTTP_PROXY'])) {
            $proxy = parse_url($_SERVER['CGI_HTTP_PROXY']);
        }

        // Override with HTTPS proxy if present and URL is https
        if (preg_match('{^https://}i', $url) && (!empty($_SERVER['HTTPS_PROXY']) || !empty($_SERVER['https_proxy']))) {
            $proxy = parse_url(!empty($_SERVER['https_proxy']) ? $_SERVER['https_proxy'] : $_SERVER['HTTPS_PROXY']);
        }

        // Remove proxy if URL matches no_proxy directive
        if (!empty($_SERVER['NO_PROXY']) || !empty($_SERVER['no_proxy']) && parse_url($url, PHP_URL_HOST)) {
            $pattern = new NoProxyPattern(!empty($_SERVER['no_proxy']) ? $_SERVER['no_proxy'] : $_SERVER['NO_PROXY']);
            if ($pattern->test($url)) {
                unset($proxy);
            }
        }

        if (!empty($proxy)) {
            $proxyURL = isset($proxy['scheme']) ? $proxy['scheme'] . '://' : '';
            $proxyURL .= isset($proxy['host']) ? $proxy['host'] : '';

            if (isset($proxy['port'])) {
                $proxyURL .= ":" . $proxy['port'];
            } elseif ('http://' == substr($proxyURL, 0, 7)) {
                $proxyURL .= ":80";
            } elseif ('https://' == substr($proxyURL, 0, 8)) {
                $proxyURL .= ":443";
            }

            // http(s):// is not supported in proxy
            $proxyURL = str_replace(array('http://', 'https://'), array('tcp://', 'ssl://'), $proxyURL);

            if (0 === strpos($proxyURL, 'ssl:') && !extension_loaded('openssl')) {
                throw new \RuntimeException('You must enable the openssl extension to use a proxy over https');
            }

            $options['http']['proxy'] = $proxyURL;

            // enabled request_fulluri unless it is explicitly disabled
            switch (parse_url($url, PHP_URL_SCHEME)) {
                case 'http': // default request_fulluri to true
                    $reqFullUriEnv = getenv('HTTP_PROXY_REQUEST_FULLURI');
                    if ($reqFullUriEnv === false || $reqFullUriEnv === '' || (strtolower($reqFullUriEnv) !== 'false' && (bool) $reqFullUriEnv)) {
                        $options['http']['request_fulluri'] = true;
                    }
                    break;
                case 'https': // default request_fulluri to true
                    $reqFullUriEnv = getenv('HTTPS_PROXY_REQUEST_FULLURI');
                    if ($reqFullUriEnv === false || $reqFullUriEnv === '' || (strtolower($reqFullUriEnv) !== 'false' && (bool) $reqFullUriEnv)) {
                        $options['http']['request_fulluri'] = true;
                    }
                    break;
            }

            // add SNI opts for https URLs
            if ('https' === parse_url($url, PHP_URL_SCHEME)) {
                $options['ssl']['SNI_enabled'] = true;
                if (PHP_VERSION_ID < 50600) {
                    $options['ssl']['SNI_server_name'] = parse_url($url, PHP_URL_HOST);
                }
            }

            // handle proxy auth if present
            if (isset($proxy['user'])) {
                $auth = rawurldecode($proxy['user']);
                if (isset($proxy['pass'])) {
                    $auth .= ':' . rawurldecode($proxy['pass']);
                }
                $auth = base64_encode($auth);

                // Preserve headers if already set in default options
                if (isset($defaultOptions['http']['header'])) {
                    if (is_string($defaultOptions['http']['header'])) {
                        $defaultOptions['http']['header'] = array($defaultOptions['http']['header']);
                    }
                    $defaultOptions['http']['header'][] = "Proxy-Authorization: Basic {$auth}";
                } else {
                    $options['http']['header'] = array("Proxy-Authorization: Basic {$auth}");
                }
            }
        }

        $options = array_replace_recursive($options, $defaultOptions);

        if (isset($options['http']['header'])) {
            $options['http']['header'] = self::fixHttpHeaderField($options['http']['header']);
        }

        if (defined('HHVM_VERSION')) {
            $phpVersion = 'HHVM ' . HHVM_VERSION;
        } else {
            $phpVersion = 'PHP ' . PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '.' . PHP_RELEASE_VERSION;
        }

        if (!isset($options['http']['header']) || false === stripos(implode('', $options['http']['header']), 'user-agent')) {
            $options['http']['header'][] = sprintf(
                'User-Agent: Composer/%s (%s; %s; %s%s)',
                Composer::getVersion(),
                function_exists('php_uname') ? php_uname('s') : 'Unknown',
                function_exists('php_uname') ? php_uname('r') : 'Unknown',
                $phpVersion,
                getenv('CI') ? '; CI' : ''
            );
        }

        return stream_context_create($options, $defaultParams);
    }

    /**
     * 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|array $header
     * @return array
     */
    private static function fixHttpHeaderField($header)
    {
        if (!is_array($header)) {
            $header = explode("\r\n", $header);
        }
        uasort($header, function ($el) {
            return stripos($el, 'content-type') === 0 ? 1 : -1;
        });

        return $header;
    }
}
<?php

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

namespace Composer\Util;

/**
 * @author Andreas Schempp <andreas.schempp@terminal42.ch>
 */
class Zip
{
    /**
     * Gets content of the root composer.json inside a ZIP archive.
     *
     * @param string $pathToZip
     * @param string $filename
     *
     * @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 (0 == $zip->numFiles) {
            $zip->close();

            return null;
        }

        $foundFileIndex = self::locateFile($zip, 'composer.json');
        if (false === $foundFileIndex) {
            $zip->close();

            return null;
        }

        $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
     *
     * @return bool|int
     */
    private static function locateFile(\ZipArchive $zip, $filename)
    {
        $indexOfShortestMatch = false;
        $lengthOfShortestMatch = -1;

        for ($i = 0; $i < $zip->numFiles; $i++) {
            $stat = $zip->statIndex($i);
            if (strcmp(basename($stat['name']), $filename) === 0) {
                $directoryName = dirname($stat['name']);
                if ($directoryName === '.') {
                    //if composer.json is in root directory
                    //it has to be the one to use.
                    return $i;
                }

                if (strpos($directoryName, '\\') !== false ||
                    strpos($directoryName, '/') !== false) {
                    //composer.json files below first directory are rejected
                    continue;
                }

                $length = strlen($stat['name']);
                if ($indexOfShortestMatch === false || $length < $lengthOfShortestMatch) {
                    //Check it's not a directory.
                    $contents = $zip->getFromIndex($i);
                    if ($contents !== false) {
                        $indexOfShortestMatch = $i;
                        $lengthOfShortestMatch = $length;
                    }
                }
            }
        }

        return $indexOfShortestMatch;
    }
}
<?php

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

namespace Composer\Util;

use Composer\IO\IOInterface;
use Symfony\Component\Process\Process;

/**
 * @author Matt Whittom <Matt.Whittom@veteransunited.com>
 */
class Perforce
{
    protected $path;
    protected $p4Depot;
    protected $p4Client;
    protected $p4User;
    protected $p4Password;
    protected $p4Port;
    protected $p4Stream;
    protected $p4ClientSpec;
    protected $p4DepotType;
    protected $p4Branch;
    protected $process;
    protected $uniquePerforceClientName;
    protected $windowsFlag;
    protected $commandResult;

    protected $io;

    protected $filesystem;

    public function __construct($repoConfig, $port, $path, ProcessExecutor $process, $isWindows, IOInterface $io)
    {
        $this->windowsFlag = $isWindows;
        $this->p4Port = $port;
        $this->initializePath($path);
        $this->process = $process;
        $this->initialize($repoConfig);
        $this->io = $io;
    }

    public static function create($repoConfig, $port, $path, ProcessExecutor $process, IOInterface $io)
    {
        return new Perforce($repoConfig, $port, $path, $process, Platform::isWindows(), $io);
    }

    public static function checkServerExists($url, ProcessExecutor $processExecutor)
    {
        $output = null;

        return  0 === $processExecutor->execute('p4 -p ' . ProcessExecutor::escape($url) . ' info -s', $output);
    }

    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'];
        }
    }

    public function initializeDepotAndBranch($depot, $branch)
    {
        if (isset($depot)) {
            $this->p4Depot = $depot;
        }
        if (isset($branch)) {
            $this->p4Branch = $branch;
        }
    }

    public function generateUniquePerforceClientName()
    {
        return gethostname() . "_" . time();
    }

    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);
    }

    protected function executeCommand($command)
    {
        $this->commandResult = '';

        return $this->process->execute($command, $this->commandResult);
    }

    public function getClient()
    {
        if (!isset($this->p4Client)) {
            $cleanStreamName = str_replace(array('//', '/', '@'), array('', '_', ''), $this->getStream());
            $this->p4Client = 'composer_perforce_' . $this->uniquePerforceClientName . '_' . $cleanStreamName;
        }

        return $this->p4Client;
    }

    protected function getPath()
    {
        return $this->path;
    }

    public function initializePath($path)
    {
        $this->path = $path;
        $fs = $this->getFilesystem();
        $fs->ensureDirectoryExists($path);
    }

    protected function getPort()
    {
        return $this->p4Port;
    }

    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';
        }
    }

    public function isStream()
    {
        return (strcmp($this->p4DepotType, 'stream') === 0);
    }

    public function getStream()
    {
        if (!isset($this->p4Stream)) {
            if ($this->isStream()) {
                $this->p4Stream = '//' . $this->p4Depot . '/' . $this->p4Branch;
            } else {
                $this->p4Stream = '//' . $this->p4Depot;
            }
        }

        return $this->p4Stream;
    }

    public function getStreamWithoutLabel($stream)
    {
        $index = strpos($stream, '@');
        if ($index === false) {
            return $stream;
        }

        return substr($stream, 0, $index);
    }

    public function getP4ClientSpec()
    {
        return $this->path . '/' . $this->getClient() . '.p4.spec';
    }

    public function getUser()
    {
        return $this->p4User;
    }

    public function setUser($user)
    {
        $this->p4User = $user;
    }

    public function queryP4User()
    {
        $this->getUser();
        if (strlen($this->p4User) > 0) {
            return;
        }
        $this->p4User = $this->getP4variable('P4USER');
        if (strlen($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);
    }

    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;
    }

    public function queryP4Password()
    {
        if (isset($this->p4Password)) {
            return $this->p4Password;
        }
        $password = $this->getP4variable('P4PASSWD');
        if (strlen($password) <= 0) {
            $password = $this->io->askAndHideAnswer('Enter password for Perforce user ' . $this->getUser() . ': ');
        }
        $this->p4Password = $password;

        return $password;
    }

    public function generateP4Command($command, $useClient = true)
    {
        $p4Command = 'p4 ';
        $p4Command .= '-u ' . $this->getUser() . ' ';
        if ($useClient) {
            $p4Command .= '-c ' . $this->getClient() . ' ';
        }
        $p4Command = $p4Command . '-p ' . $this->getPort() . ' ' . $command;

        return $p4Command;
    }

    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;
    }

    public function connectClient()
    {
        $p4CreateClientCommand = $this->generateP4Command(
            'client -i < ' . str_replace(" ", "\\ ", $this->getP4ClientSpec())
        );
        $this->executeCommand($p4CreateClientCommand);
    }

    public function syncCodeBase($sourceReference)
    {
        $prevDir = getcwd();
        chdir($this->path);
        $p4SyncCommand = $this->generateP4Command('sync -f ');
        if (null !== $sourceReference) {
            $p4SyncCommand .= '@' . $sourceReference;
        }
        $this->executeCommand($p4SyncCommand);
        chdir($prevDir);
    }

    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
            );
        }
    }

    public function writeP4ClientSpec()
    {
        $clientSpec = $this->getP4ClientSpec();
        $spec = fopen($clientSpec, 'w');
        try {
            $this->writeClientSpecToFile($spec);
        } catch (\Exception $e) {
            fclose($spec);
            throw $e;
        }
        fclose($spec);
    }

    protected function read($pipe, $name)
    {
        if (feof($pipe)) {
            return;
        }
        $line = fgets($pipe);
        while ($line !== false) {
            $line = fgets($pipe);
        }
    }

    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($command, null, null, $password);
        } else {
            $process = new Process($command, null, null, $password);
        }

        return $process->run();
    }

    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);
                $result = trim($this->commandResult);
                if ($exitCode) {
                    throw new \Exception("Error logging in:" . $this->process->getErrorOutput());
                }
            }
        }
    }

    public function getComposerInformation($identifier)
    {
        $composerFileContent = $this->getFileContent('composer.json', $identifier);

        if (!$composerFileContent) {
            return;
        }

        return json_decode($composerFileContent, true);
    }

    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;
    }

    public function getFilePath($file, $identifier)
    {
        $index = strpos($identifier, '@');
        if ($index === false) {
            $path = $identifier. '/' . $file;

            return $path;
        }

        $path = substr($identifier, 0, $index) . '/' . $file . substr($identifier, $index);
        $command = $this->generateP4Command(' files ' . ProcessExecutor::escape($path), false);
        $this->executeCommand($command);
        $result = $this->commandResult;
        $index2 = strpos($result, 'no such file(s).');
        if ($index2 === false) {
            $index3 = strpos($result, 'change');
            if ($index3 !== false) {
                $phrase = trim(substr($result, $index3));
                $fields = explode(' ', $phrase);

                return substr($identifier, 0, $index) . '/' . $file . '@' . $fields[1];
            }
        }

        return null;
    }

    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];

        $branches = array('master' => $possibleBranches[$this->p4Branch] . '@'. $lastCommitNum);

        return $branches;
    }

    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;
    }

    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($fromReference, 0, $index) . '/...';
        $command = $this->generateP4Command('filelog ' . ProcessExecutor::escape($main . '@' . $fromChangeList. ',' . $toChangeList));
        $this->executeCommand($command);

        return $this->commandResult;
    }

    public function getFilesystem()
    {
        if (empty($this->filesystem)) {
            $this->filesystem = new Filesystem($this->process);
        }

        return $this->filesystem;
    }

    public function setFilesystem(Filesystem $fs)
    {
        $this->filesystem = $fs;
    }
}
<?php

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

namespace Composer\Util;

use Composer\Config;
use Composer\IO\IOInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Git
{
    private static $version;

    /** @var IOInterface */
    protected $io;
    /** @var Config */
    protected $config;
    /** @var ProcessExecutor */
    protected $process;
    /** @var Filesystem */
    protected $filesystem;

    public function __construct(IOInterface $io, Config $config, ProcessExecutor $process, Filesystem $fs)
    {
        $this->io = $io;
        $this->config = $config;
        $this->process = $process;
        $this->filesystem = $fs;
    }

    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_match('{^ssh://[^@]+@[^:]+:[^0-9]+}', $url)) {
            throw new \InvalidArgumentException('The source URL ' . $url . ' is invalid, ssh URLs should have a port number after ":".' . "\n" . 'Use ssh://git@example.com:22/path or just git@example.com:path if you do not want to provide a password or custom port.');
        }

        if (!$initialClone) {
            // capture username/password from URL if there is one and we have no auth configured yet
            $this->process->execute('git remote -v', $output, $cwd);
            if (preg_match('{^(?: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_match('{^(?: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 (0 === $this->process->execute(call_user_func($commandCallable, $protoUrl), $ignoredOutput, $cwd)) {
                    return;
                }
                $messages[] = '- ' . $protoUrl . "\n" . preg_replace('#^#m', '  ', $this->process->getErrorOutput());
                if ($initialClone) {
                    $this->filesystem->removeDirectory($origCwd);
                }
            }

            // failed to checkout, first check git accessibility
            $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_match('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url) && !in_array('ssh', $protocols, true);

        $command = call_user_func($commandCallable, $url);

        $auth = null;
        if ($bypassSshForGitHub || 0 !== $this->process->execute($command, $ignoredOutput, $cwd)) {
            // private github repository without ssh key access, try https with auth
            if (preg_match('{^git@' . self::getGitHubDomainsRegex($this->config) . ':(.+?)\.git$}i', $url, $match)
                || preg_match('{^(https?)://' . self::getGitHubDomainsRegex($this->config) . '/(.*)}', $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 (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
                        return;
                    }
                }
            } elseif (preg_match('{^https://(bitbucket\.org)/(.*)(\.git)?$}U', $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 (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
                        return;
                    }
                } else { // Falling back to ssh
                    $sshUrl = 'git@bitbucket.org:' . $match[2] . '.git';
                    $this->io->writeError('    No bitbucket authentication configured. Falling back to ssh.');
                    $command = call_user_func($commandCallable, $sshUrl);
                    if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
                        return;
                    }
                }
            } elseif (
                preg_match('{^(git)@' . self::getGitLabDomainsRegex($this->config) . ':(.+?)\.git$}i', $url, $match)
                || preg_match('{^(https?)://' . self::getGitLabDomainsRegex($this->config) . '/(.*)}', $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 (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
                        return;
                    }
                }
            } 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(':', $authParts, 2);
                        } 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 (0 === $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;
                    }
                }
            }

            $errorMsg = $this->process->getErrorOutput();
            if ($initialClone) {
                $this->filesystem->removeDirectory($origCwd);
            }

            $this->throwException('Failed to execute ' . $command . "\n\n" . $errorMsg, $url);
        }
    }

    public function syncMirror($url, $dir)
    {
        // update the repo if it is a valid git repository
        if (is_dir($dir) && 0 === $this->process->execute('git rev-parse --git-dir', $output, $dir) && trim($output) === '.') {
            try {
                $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', ProcessExecutor::escape($url), ProcessExecutor::escape($sanitizedUrl));
                };
                $this->runCommand($commandCallable, $url, $dir);
            } catch (\Exception $e) {
                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, $dir, true);

        return true;
    }

    public function fetchRefOrSyncMirror($url, $dir, $ref)
    {
        if ($this->checkRefIsInMirror($url, $dir, $ref)) {
            return true;
        }

        if ($this->syncMirror($url, $dir)) {
            return $this->checkRefIsInMirror($url, $dir, $ref);
        }

        return false;
    }

    private function checkRefIsInMirror($url, $dir, $ref)
    {
        if (is_dir($dir) && 0 === $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;
    }

    private function isAuthenticationFailure($url, &$match)
    {
        if (!preg_match('{^(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;
    }

    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 (getenv('GIT_ASKPASS') !== 'echo') {
            putenv('GIT_ASKPASS=echo');
            unset($_SERVER['GIT_ASKPASS']);
        }

        // clean up rogue git env vars in case this is running in a git hook
        if (getenv('GIT_DIR')) {
            putenv('GIT_DIR');
            unset($_SERVER['GIT_DIR']);
        }
        if (getenv('GIT_WORK_TREE')) {
            putenv('GIT_WORK_TREE');
            unset($_SERVER['GIT_WORK_TREE']);
        }

        // Run processes with predictable LANGUAGE
        if (getenv('LANGUAGE') !== 'C') {
            putenv('LANGUAGE=C');
        }

        // clean up env for OSX, see https://github.com/composer/composer/issues/2146#issuecomment-35478940
        putenv("DYLD_LIBRARY_PATH");
        unset($_SERVER['DYLD_LIBRARY_PATH']);
    }

    public static function getGitHubDomainsRegex(Config $config)
    {
        return '(' . implode('|', array_map('preg_quote', $config->get('github-domains'))) . ')';
    }

    public static function getGitLabDomainsRegex(Config $config)
    {
        return '(' . implode('|', array_map('preg_quote', $config->get('gitlab-domains'))) . ')';
    }

    public static function sanitizeUrl($message)
    {
        return preg_replace_callback('{://(?P<user>[^@]+?):(?P<password>.+?)@}', function ($m) {
            if (preg_match('{^[a-f0-9]{12,}$}', $m[1])) {
                return '://***:***@';
            }

            return '://' . $m[1] . ':***@';
        }, $message);
    }

    private function throwException($message, $url)
    {
        // git might delete a directory when it fails and php will not know
        clearstatcache();

        if (0 !== $this->process->execute('git --version', $ignoredOutput)) {
            throw new \RuntimeException(self::sanitizeUrl('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(self::sanitizeUrl($message));
    }

    /**
     * Retrieves the current git version.
     *
     * @return string|null The git version number.
     */
    public function getVersion()
    {
        if (isset(self::$version)) {
            return self::$version;
        }
        if (0 !== $this->process->execute('git --version', $output)) {
            return;
        }
        if (preg_match('/^git version (\d+(?:\.\d+)+)/m', $output, $matches)) {
            return self::$version = $matches[1];
        }
    }
}
<?php

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

namespace Composer\Util;

use Composer\XdebugHandler\XdebugHandler;

/**
 * Provides ini file location functions that work with and without a restart.
 * When the process has restarted it uses a tmp ini and stores the original
 * ini locations in an environment variable.
 *
 * @author John Stevenson <john-stevenson@blueyonder.co.uk>
 */
class IniHelper
{
    /**
     * Returns an array of php.ini locations with at least one entry
     *
     * The equivalent of calling php_ini_loaded_file then php_ini_scanned_files.
     * The loaded ini location is the first entry and may be empty.
     *
     * @return array
     */
    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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Config;
use Composer\IO\IOInterface;

/**
 * @author Till Klampaeckel <till@php.net>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Svn
{
    const MAX_QTY_AUTH_TRIES = 5;

    /**
     * @var array
     */
    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($url, IOInterface $io, Config $config, ProcessExecutor $process = null)
    {
        $this->url = $url;
        $this->io = $io;
        $this->config = $config;
        $this->process = $process ?: new ProcessExecutor($io);
    }

    public static function cleanEnv()
    {
        // clean up env for OSX, see https://github.com/composer/composer/issues/2146#issuecomment-35478940
        putenv("DYLD_LIBRARY_PATH");
        unset($_SERVER['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);
    }

    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;
            }
            if ('Redirecting to URL ' === substr($buffer, 0, 19)) {
                return;
            }
            $output .= $buffer;
            if ($verbose) {
                $io->writeError($buffer, false);
            }
        };
        $status = $this->process->execute($command, $handler, $cwd);
        if (0 === $status) {
            return $output;
        }

        $errorOutput = $this->process->getErrorOutput();
        $fullOutput = 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
     */
    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['username'] = $this->io->ask("Username: ");
        $this->credentials['password'] = $this->io->askAndHideAnswer("Password: ");

        $this->cacheCredentials = $this->io->askConfirmation("Should Subversion cache these credentials? (yes/no) ", true);

        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 isset($this->credentials['password']) ? $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 $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->url, PHP_URL_HOST);
        if (isset($authConfig[$host])) {
            $this->credentials['username'] = $authConfig[$host]['username'];
            $this->credentials['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['username'] = $uri['user'];
        if (!empty($uri['pass'])) {
            $this->credentials['password'] = $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 (0 === $this->process->execute('svn --version', $output)) {
                if (preg_match('{(\d+(?:\.\d+)+)}', $output, $match)) {
                    self::$version = $match[1];
                }
            }
        }

        return self::$version;
    }
}
<?php

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

namespace Composer\Util;

use Composer\Config;
use Composer\IO\IOInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class AuthHelper
{
    protected $io;
    protected $config;

    public function __construct(IOInterface $io, Config $config)
    {
        $this->io = $io;
        $this->config = $config;
    }

    public function storeAuth($originUrl, $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 '.$originUrl.' in '.$configSource->getName().' ? [Yn] ',
                function ($value) {
                    $input = strtolower(substr(trim($value), 0, 1));
                    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.'.$originUrl,
                $this->io->getAuthentication($originUrl)
            );
        }
    }
}
<?php

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

namespace Composer\Util;

use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\Config;
use Composer\Downloader\TransportException;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class GitHub
{
    protected $io;
    protected $config;
    protected $process;
    protected $remoteFilesystem;

    /**
     * Constructor.
     *
     * @param IOInterface      $io               The IO instance
     * @param Config           $config           The composer configuration
     * @param ProcessExecutor  $process          Process instance, injectable for mocking
     * @param RemoteFilesystem $remoteFilesystem Remote Filesystem, injectable for mocking
     */
    public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, RemoteFilesystem $remoteFilesystem = null)
    {
        $this->io = $io;
        $this->config = $config;
        $this->process = $process ?: new ProcessExecutor($io);
        $this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($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 (0 === $this->process->execute('git config github.accesstoken', $output)) {
            $this->io->setAuthentication($originUrl, trim($output), 'x-oauth-basic');

            return true;
        }

        return false;
    }

    /**
     * Authorizes a GitHub domain interactively via OAuth
     *
     * @param  string                        $originUrl The host this GitHub instance is located at
     * @param  string                        $message   The reason this authorization is required
     * @throws \RuntimeException
     * @throws TransportException|\Exception
     * @return bool                          true on success
     */
    public function authorizeOAuthInteractively($originUrl, $message = null)
    {
        if ($message) {
            $this->io->writeError($message);
        }

        $note = 'Composer';
        if ($this->config->get('github-expose-hostname') === true && 0 === $this->process->execute('hostname', $output)) {
            $note .= ' on ' . trim($output);
        }
        $note .= ' ' . date('Y-m-d Hi');

        $url = 'https://'.$originUrl.'/settings/tokens/new?scopes=repo&description=' . str_replace('%20', '+', rawurlencode($note));
        $this->io->writeError(sprintf('Head to %s', $url));
        $this->io->writeError(sprintf('to retrieve a token. It will be stored in "%s" for future use by Composer.', $this->config->getAuthConfigSource()->getName()));

        $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->remoteFilesystem->getContents($originUrl, 'https://'. $apiUrl, false, array(
                'retry-auth-failure' => false,
            ));
        } catch (TransportException $e) {
            if (in_array($e->getCode(), array(403, 401))) {
                $this->io->writeError('<error>Invalid token provided.</error>');
                $this->io->writeError('You can also add it manually later by using "composer config --global --auth github-oauth.github.com <token>"');

                return false;
            }

            throw $e;
        }

        // store value in 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 ratelimit from response.
     *
     * @param array $headers Headers from Composer\Downloader\TransportException.
     *
     * @return array Associative array with the keys limit and reset.
     */
    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(':', $header, 2);
            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;
    }

    /**
     * Finds whether a request failed due to rate limiting
     *
     * @param array $headers Headers from Composer\Downloader\TransportException.
     *
     * @return bool
     */
    public function isRateLimited(array $headers)
    {
        foreach ($headers as $header) {
            if (preg_match('{^X-RateLimit-Remaining: *0$}i', trim($header))) {
                return true;
            }
        }

        return false;
    }
}
<?php

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

namespace Composer\Util;

use Composer\Config;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Url
{
    public static function updateDistReference(Config $config, $url, $ref)
    {
        $host = parse_url($url, PHP_URL_HOST);

        if ($host === 'api.github.com' || $host === 'github.com' || $host === 'www.github.com') {
            if (preg_match('{^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_match('{^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_match('{^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_match('{^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_match('{^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;
    }
}
<?php

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

namespace Composer\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\Spdx\SpdxLicenses;

/**
 * Validates a composer configuration.
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ConfigValidator
{
    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
     *
     * @return array a triple containing the errors, publishable errors, and warnings
     */
    public function validate($file, $arrayLoaderValidationFlags = ValidatingArrayLoader::CHECK_ALL)
    {
        $errors = array();
        $publishErrors = array();
        $warnings = array();

        // validate json schema
        $laxValid = false;
        try {
            $json = new JsonFile($file, null, $this->io);
            $manifest = $json->read();

            $json->validateSchema(JsonFile::LAX_SCHEMA);
            $laxValid = true;
            $json->validateSchema();
        } catch (JsonValidationException $e) {
            foreach ($e->getErrors() as $message) {
                if ($laxValid) {
                    $publishErrors[] = $message;
                } else {
                    $errors[] = $message;
                }
            }
        } catch (\Exception $e) {
            $errors[] = $e->getMessage();

            return 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_match('{^[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_match('{^[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 (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_match('{[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']) && isset($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 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_match('/#/', $version) === 1) {
                $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";
        }

        try {
            $loader = new ValidatingArrayLoader(new ArrayLoader(), true, null, $arrayLoaderValidationFlags);
            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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Util;

use Composer\Config;
use Composer\Composer;
use Composer\Semver\Constraint\Constraint;
use Composer\Package\Version\VersionParser;
use Composer\IO\IOInterface;
use Composer\Downloader\TransportException;
use Composer\CaBundle\CaBundle;
use Psr\Log\LoggerInterface;

/**
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Nils Adermann <naderman@naderman.de>
 */
class RemoteFilesystem
{
    private $io;
    private $config;
    private $scheme;
    private $bytesMax;
    private $originUrl;
    private $fileUrl;
    private $fileName;
    private $retry;
    private $progress;
    private $lastProgress;
    private $options = array();
    private $peerCertificateMap = array();
    private $disableTls = false;
    private $retryAuthFailure;
    private $lastHeaders;
    private $storeAuth;
    private $degradedMode = false;
    private $redirects;
    private $maxRedirects = 20;

    /**
     * Constructor.
     *
     * @param IOInterface $io         The IO instance
     * @param Config      $config     The config
     * @param array       $options    The options
     * @param bool        $disableTls
     */
    public function __construct(IOInterface $io, Config $config = null, array $options = array(), $disableTls = false)
    {
        $this->io = $io;

        // Setup TLS options
        // The cafile option can be set via config.json
        if ($disableTls === false) {
            $this->options = $this->getTlsDefaults($options);
        } else {
            $this->disableTls = true;
        }

        // handle the other externally set options normally.
        $this->options = array_replace_recursive($this->options, $options);
        $this->config = $config;
    }

    /**
     * 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 array  $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 array  $options   Additional context options
     *
     * @return bool|string The content
     */
    public function getContents($originUrl, $fileUrl, $progress = true, $options = array())
    {
        return $this->get($originUrl, $fileUrl, $options, null, $progress);
    }

    /**
     * Retrieve the options set in the constructor
     *
     * @return array Options
     */
    public function getOptions()
    {
        return $this->options;
    }

    /**
     * Merges new options
     *
     * @param array $options
     */
    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 array
     */
    public function getLastHeaders()
    {
        return $this->lastHeaders;
    }

    /**
     * @param  array       $headers array of returned headers like from getLastHeaders()
     * @param  string      $name    header name (case insensitive)
     * @return string|null
     */
    public function findHeaderValue(array $headers, $name)
    {
        $value = null;
        foreach ($headers as $header) {
            if (preg_match('{^'.$name.':\s*(.+?)\s*$}i', $header, $match)) {
                $value = $match[1];
            } elseif (preg_match('{^HTTP/}i', $header)) {
                // In case of redirects, http_response_headers contains the headers of all responses
                // so we reset the flag when a new response is being parsed as we are only interested in the last response
                $value = null;
            }
        }

        return $value;
    }

    /**
     * @param  array    $headers array of returned headers like from getLastHeaders()
     * @return int|null
     */
    public function findStatusCode(array $headers)
    {
        $value = null;
        foreach ($headers as $header) {
            if (preg_match('{^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  array       $headers array of returned headers like from getLastHeaders()
     * @return string|null
     */
    public function findStatusMessage(array $headers)
    {
        $value = null;
        foreach ($headers as $header) {
            if (preg_match('{^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 array  $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)
    {
        if (strpos($originUrl, '.github.com') === (strlen($originUrl) - 11)) {
            $originUrl = 'github.com';
        }

        // 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 (
            $this->config
            && is_array($this->config->get('gitlab-domains'))
            && false === strpos($originUrl, '/')
            && !in_array($originUrl, $this->config->get('gitlab-domains'))
        ) {
            foreach ($this->config->get('gitlab-domains') as $gitlabDomain) {
                if (0 === strpos($gitlabDomain, $originUrl)) {
                    $originUrl = $gitlabDomain;
                    break;
                }
            }
            unset($gitlabDomain);
        }

        $this->scheme = parse_url($fileUrl, PHP_URL_SCHEME);
        $this->bytesMax = 0;
        $this->originUrl = $originUrl;
        $this->fileUrl = $fileUrl;
        $this->fileName = $fileName;
        $this->progress = $progress;
        $this->lastProgress = null;
        $this->retryAuthFailure = true;
        $this->lastHeaders = array();
        $this->redirects = 1; // The first request counts.

        // capture username/password from URL if there is one
        if (preg_match('{^https?://([^:/]+):([^@/]+)@([^/]+)}i', $fileUrl, $match)) {
            $this->io->setAuthentication($originUrl, rawurldecode($match[1]), rawurldecode($match[2]));
        }

        $tempAdditionalOptions = $additionalOptions;
        if (isset($tempAdditionalOptions['retry-auth-failure'])) {
            $this->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['github-token'])) {
            // only add the access_token if it is actually a github URL (in case we were redirected to S3)
            if (preg_match('{^https?://([a-z0-9-]+\.)*github\.com/}', $fileUrl)) {
                $fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token='.$options['github-token'];
            }
            unset($options['github-token']);
        }

        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 && substr($fileUrl, 0, 26) === 'http://repo.packagist.org/') {
            // access packagist using the resolved IPv4 instead of the hostname to force IPv4 protocol
            $fileUrl = 'http://' . gethostbyname('repo.packagist.org') . substr($fileUrl, 20);
            $degradedPackagist = true;
        }

        $ctx = StreamContextFactory::getContext($fileUrl, $options, array('notification' => array($this, 'callbackGet')));

        $actualContextOptions = stream_context_get_options($ctx);
        $usingProxy = !empty($actualContextOptions['http']['proxy']) ? ' using proxy ' . $actualContextOptions['http']['proxy'] : '';
        $this->io->writeError((substr($origFileUrl, 0, 4) === 'http' ? 'Downloading ' : 'Reading ') . $this->stripCredentialsFromUrl($origFileUrl) . $usingProxy, true, IOInterface::DEBUG);
        unset($origFileUrl, $actualContextOptions);

        // Check for secure HTTP, but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256
        if ((!preg_match('{^http://(repo\.)?packagist\.org/p/}', $fileUrl) || (false === strpos($fileUrl, '$') && false === strpos($fileUrl, '%24'))) && empty($degradedPackagist) && $this->config) {
            $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;
        });
        try {
            $result = $this->getRemoteContents($originUrl, $fileUrl, $ctx, $http_response_header);

            if (!empty($http_response_header[0])) {
                $statusCode = $this->findStatusCode($http_response_header);
                if ($statusCode >= 400 && $this->findHeaderValue($http_response_header, 'content-type') === 'application/json') {
                    self::outputWarnings($this->io, $originUrl, json_decode($result, true));
                }

                if (in_array($statusCode, array(401, 403)) && $this->retryAuthFailure) {
                    $this->promptAuthAndRetry($statusCode, $this->findStatusMessage($http_response_header), null, $http_response_header);
                }
            }

            $contentLength = !empty($http_response_header[0]) ? $this->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($this->findStatusCode($http_response_header));
                $e->setResponse($result);
                $this->io->writeError('Content-Length mismatch, received '.Platform::strlen($result).' out of '.$contentLength.' bytes: (' . base64_encode($result).')', true, IOInterface::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($this->findStatusCode($http_response_header));
            }
            if ($e instanceof TransportException && $result !== false) {
                $e->setResponse($result);
            }
            $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 = $this->findStatusCode($http_response_header);
            $contentType = $this->findHeaderValue($http_response_header, 'content-type');
            $locationHeader = $this->findHeaderValue($http_response_header, 'location');
        }

        // check for bitbucket login page asking to authenticate
        if ($originUrl === 'bitbucket.org'
            && !$this->isPublicBitBucketDownload($fileUrl)
            && substr($fileUrl, -4) === '.zip'
            && (!$locationHeader || substr($locationHeader, -4) !== '.zip')
            && $contentType && preg_match('{^text/html\b}i', $contentType)
        ) {
            $result = false;
            if ($this->retryAuthFailure) {
                $this->promptAuthAndRetry(401);
            }
        }

        // check for gitlab 404 when downloading archives
        if ($statusCode === 404
            && $this->config && in_array($originUrl, $this->config->get('gitlab-domains'), true)
            && false !== strpos($fileUrl, 'archive.zip')
        ) {
            $result = false;
            if ($this->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 && !$this->retry && !$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($result);
                $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') && substr($fileUrl, 0, 4) === 'http' && !$hasFollowedRedirect) {
            $contentEncoding = $this->findHeaderValue($http_response_header, 'content-encoding');
            $decode = $contentEncoding && 'gzip' === strtolower($contentEncoding);

            if ($decode) {
                try {
                    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) {
                        throw new TransportException('Failed to decode zlib stream');
                    }
                } 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->config) {
                $authHelper = new AuthHelper($this->io, $this->config);
                $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
     *
     * @return string|false The response contents or false on failure
     */
    protected function getRemoteContents($originUrl, $fileUrl, $context, array &$responseHeaders = null)
    {
        try {
            $e = null;
            $result = file_get_contents($fileUrl, false, $context);
        } catch (\Throwable $e) {
        } catch (\Exception $e) {
        }

        $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
     * @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 > 0 && $this->progress) {
                    $progression = min(100, round($bytesTransferred / $this->bytesMax * 100));

                    if ((0 === $progression % 5) && 100 !== $progression && $progression !== $this->lastProgress) {
                        $this->lastProgress = $progression;
                        $this->io->overwriteError("Downloading (<comment>$progression%</comment>)", false);
                    }
                }
                break;

            default:
                break;
        }
    }

    protected function promptAuthAndRetry($httpStatus, $reason = null, $warning = null, $headers = array())
    {
        if ($this->config && in_array($this->originUrl, $this->config->get('github-domains'), true)) {
            $gitHubUtil = new GitHub($this->io, $this->config, null);
            $message = "\n";

            $rateLimited = $gitHubUtil->isRateLimited($headers);
            if ($rateLimited) {
                $rateLimit = $gitHubUtil->getRateLimit($headers);
                if ($this->io->hasAuthentication($this->originUrl)) {
                    $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 '.$this->fileUrl.'. '.$message.' You can also wait until %s for the rate limit to reset.',
                    $rateLimit['limit'],
                    $rateLimit['reset']
                )."\n";
            } else {
                $message .= 'Could not fetch '.$this->fileUrl.', please ';
                if ($this->io->hasAuthentication($this->originUrl)) {
                    $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($this->originUrl)
                && (!$this->io->isInteractive() || !$gitHubUtil->authorizeOAuthInteractively($this->originUrl, $message))
            ) {
                throw new TransportException('Could not authenticate against '.$this->originUrl, 401);
            }
        } elseif ($this->config && in_array($this->originUrl, $this->config->get('gitlab-domains'), true)) {
            $message = "\n".'Could not fetch '.$this->fileUrl.', enter your ' . $this->originUrl . ' credentials ' .($httpStatus === 401 ? 'to access private repos' : 'to go over the API rate limit');
            $gitLabUtil = new GitLab($this->io, $this->config, null);

            if ($this->io->hasAuthentication($this->originUrl) && ($auth = $this->io->getAuthentication($this->originUrl)) && in_array($auth['password'], array('gitlab-ci-token', 'private-token'), true)) {
                throw new TransportException("Invalid credentials for '" . $this->fileUrl . "', aborting.", $httpStatus);
            }

            if (!$gitLabUtil->authorizeOAuth($this->originUrl)
                && (!$this->io->isInteractive() || !$gitLabUtil->authorizeOAuthInteractively($this->scheme, $this->originUrl, $message))
            ) {
                throw new TransportException('Could not authenticate against '.$this->originUrl, 401);
            }
        } elseif ($this->config && $this->originUrl === 'bitbucket.org') {
            $askForOAuthToken = true;
            if ($this->io->hasAuthentication($this->originUrl)) {
                $auth = $this->io->getAuthentication($this->originUrl);
                if ($auth['username'] !== 'x-token-auth') {
                    $bitbucketUtil = new Bitbucket($this->io, $this->config);
                    $accessToken = $bitbucketUtil->requestToken($this->originUrl, $auth['username'], $auth['password']);
                    if (!empty($accessToken)) {
                        $this->io->setAuthentication($this->originUrl, 'x-token-auth', $accessToken);
                        $askForOAuthToken = false;
                    }
                } else {
                    throw new TransportException('Could not authenticate against ' . $this->originUrl, 401);
                }
            }

            if ($askForOAuthToken) {
                $message = "\n".'Could not fetch ' . $this->fileUrl . ', please create a bitbucket OAuth token to ' . (($httpStatus === 401 || $httpStatus === 403) ? 'access private repos' : 'go over the API rate limit');
                $bitBucketUtil = new Bitbucket($this->io, $this->config);
                if (! $bitBucketUtil->authorizeOAuth($this->originUrl)
                    && (! $this->io->isInteractive() || !$bitBucketUtil->authorizeOAuthInteractively($this->originUrl, $message))
                ) {
                    throw new TransportException('Could not authenticate against ' . $this->originUrl, 401);
                }
            }
        } else {
            // 404s are only handled for github
            if ($httpStatus === 404) {
                return;
            }

            // fail if the console is not interactive
            if (!$this->io->isInteractive()) {
                if ($httpStatus === 401) {
                    $message = "The '" . $this->fileUrl . "' URL required authentication.\nYou must be using the interactive console to authenticate";
                }
                if ($httpStatus === 403) {
                    $message = "The '" . $this->fileUrl . "' URL could not be accessed: " . $reason;
                }

                throw new TransportException($message, $httpStatus);
            }
            // fail if we already have auth
            if ($this->io->hasAuthentication($this->originUrl)) {
                throw new TransportException("Invalid credentials for '" . $this->fileUrl . "', aborting.", $httpStatus);
            }

            $this->io->writeError('    Authentication required (<info>'.$this->originUrl.'</info>):');
            $username = $this->io->ask('      Username: ');
            $password = $this->io->askAndHideAnswer('      Password: ');
            $this->io->setAuthentication($this->originUrl, $username, $password);
            $this->storeAuth = $this->config->get('store-auths');
        }

        $this->retry = true;
        throw new TransportException('RETRY');
    }

    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->fileUrl, PHP_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('', true, IOInterface::DEBUG);
                $this->io->writeError(sprintf(
                    'Using <info>%s</info> as CN for subjectAltName enabled host <info>%s</info>',
                    $certMap['cn'],
                    $urlAuthority
                ), true, IOInterface::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';
        }

        if ($this->io->hasAuthentication($originUrl)) {
            $auth = $this->io->getAuthentication($originUrl);
            if ('github.com' === $originUrl && 'x-oauth-basic' === $auth['password']) {
                $options['github-token'] = $auth['username'];
            } elseif ($this->config && in_array($originUrl, $this->config->get('gitlab-domains'), true)) {
                if ($auth['password'] === 'oauth2') {
                    $headers[] = 'Authorization: Bearer '.$auth['username'];
                } elseif ($auth['password'] === 'private-token' || $auth['password'] === 'gitlab-ci-token') {
                    $headers[] = 'PRIVATE-TOKEN: '.$auth['username'];
                }
            } elseif ('bitbucket.org' === $originUrl
                && $this->fileUrl !== Bitbucket::OAUTH2_ACCESS_TOKEN_URL && 'x-token-auth' === $auth['username']
            ) {
                if (!$this->isPublicBitBucketDownload($this->fileUrl)) {
                    $headers[] = 'Authorization: Bearer ' . $auth['password'];
                }
            } else {
                $authStr = base64_encode($auth['username'] . ':' . $auth['password']);
                $headers[] = 'Authorization: Basic '.$authStr;
            }
        }

        $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;
    }

    private function handleRedirect(array $http_response_header, array $additionalOptions, $result)
    {
        if ($locationHeader = $this->findHeaderValue($http_response_header, 'location')) {
            if (parse_url($locationHeader, PHP_URL_SCHEME)) {
                // Absolute URL; e.g. https://example.com/composer
                $targetUrl = $locationHeader;
            } elseif (parse_url($locationHeader, PHP_URL_HOST)) {
                // Scheme relative; e.g. //example.com/foo
                $targetUrl = $this->scheme.':'.$locationHeader;
            } elseif ('/' === $locationHeader[0]) {
                // Absolute path; e.g. /foo
                $urlHost = parse_url($this->fileUrl, PHP_URL_HOST);

                // Replace path using hostname as an anchor.
                $targetUrl = preg_replace('{^(.+(?://|@)'.preg_quote($urlHost).'(?::\d+)?)(?:[/\?].*)?$}', '\1'.$locationHeader, $this->fileUrl);
            } else {
                // Relative path; e.g. foo
                // This actually differs from PHP which seems to add duplicate slashes.
                $targetUrl = preg_replace('{^(.+/)[^/?]*(?:\?.*)?$}', '\1'.$locationHeader, $this->fileUrl);
            }
        }

        if (!empty($targetUrl)) {
            $this->redirects++;

            $this->io->writeError('', true, IOInterface::DEBUG);
            $this->io->writeError(sprintf('Following redirect (%u) %s', $this->redirects, $this->stripCredentialsFromUrl($targetUrl)), true, IOInterface::DEBUG);

            $additionalOptions['redirects'] = $this->redirects;

            return $this->get(parse_url($targetUrl, PHP_URL_HOST), $targetUrl, $additionalOptions, $this->fileName, $this->progress);
        }

        if (!$this->retry) {
            $e = new TransportException('The "'.$this->fileUrl.'" file could not be downloaded, got redirect without Location ('.$http_response_header[0].')');
            $e->setHeaders($http_response_header);
            $e->setResponse($result);

            throw $e;
        }

        return false;
    }

    /**
     * @param array $options
     *
     * @return array
     */
    private function getTlsDefaults(array $options)
    {
        $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']);
        }

        $caBundleLogger = $this->io instanceof LoggerInterface ? $this->io : null;

        /**
         * 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($caBundleLogger);

            if (is_dir($result)) {
                $defaults['ssl']['capath'] = $result;
            } else {
                $defaults['ssl']['cafile'] = $result;
            }
        }

        if (isset($defaults['ssl']['cafile']) && (!is_readable($defaults['ssl']['cafile']) || !CaBundle::validateCaFile($defaults['ssl']['cafile'], $caBundleLogger))) {
            throw new TransportException('The configured cafile was not valid or could not be read.');
        }

        if (isset($defaults['ssl']['capath']) && (!is_dir($defaults['ssl']['capath']) || !is_readable($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;
    }

    /**
     * Fetch certificate common name and fingerprint for validation of SAN.
     *
     * @todo Remove when PHP 5.6 is minimum supported version.
     */
    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;
        }

        // 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($peerCertificate, parse_url($url, PHP_URL_HOST), $commonName)) {
                return array(
                    'cn' => $commonName,
                    'fp' => TlsHelper::getCertificateFingerprint($peerCertificate),
                );
            }
        }
    }

    private function getUrlAuthority($url)
    {
        $defaultPorts = array(
            'ftp' => 21,
            'http' => 80,
            'https' => 443,
            'ssh2.sftp' => 22,
            'ssh2.scp' => 22,
        );

        $scheme = parse_url($url, PHP_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($url, PHP_URL_PORT) ?: $defaultPort;

        return parse_url($url, PHP_URL_HOST).':'.$port;
    }

    /**
     * @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.
     */
    private function isPublicBitBucketDownload($urlToBitBucketFile)
    {
        $domain = parse_url($urlToBitBucketFile, PHP_URL_HOST);
        if (strpos($domain, 'bitbucket.org') === false) {
            // Bitbucket downloads are hosted on amazonaws.
            // We do not need to authenticate there at all
            return true;
        }

        $path = parse_url($urlToBitBucketFile, PHP_URL_PATH);

        // Path for a public download follows this pattern /{user}/{repo}/downloads/{whatever}
        // {@link https://blog.bitbucket.org/2009/04/12/new-feature-downloads/}
        $pathParts = explode('/', $path);

        return count($pathParts) >= 4 && $pathParts[3] == 'downloads';
    }

    public static function outputWarnings(IOInterface $io, $url, $data)
    {
        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.': '.$data[$type].'</'.$type.'>');
        }
    }

    public static function getOrigin($urlOrPath)
    {
        $hostPort = parse_url($urlOrPath, PHP_URL_HOST);
        if (!$hostPort) {
            return $urlOrPath;
        }
        if (parse_url($urlOrPath, PHP_URL_PORT)) {
            $hostPort .= ':'.parse_url($urlOrPath, PHP_URL_PORT);
        }

        return $hostPort;
    }

    private function stripCredentialsFromUrl($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
        return preg_replace('{([&?]access_token=)[^&]+}', '$1***', $url);
    }
}
<?php

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

namespace Composer\Util;

use Composer\Config;
use Composer\IO\IOInterface;

/**
 * @author Jonas Renaudot <jonas.renaudot@gmail.com>
 */
class Hg
{
    /**
     * @var \Composer\IO\IOInterface
     */
    private $io;

    /**
     * @var \Composer\Config
     */
    private $config;

    /**
     * @var \Composer\Util\ProcessExecutor
     */
    private $process;

    public function __construct(IOInterface $io, Config $config, ProcessExecutor $process)
    {
        $this->io = $io;
        $this->config = $config;
        $this->process = $process;
    }

    public function runCommand($commandCallable, $url, $cwd)
    {
        $this->config->prohibitUrlByConfig($url, $this->io);

        // Try as is
        $command = call_user_func($commandCallable, $url);

        if (0 === $this->process->execute($command, $ignoredOutput, $cwd)) {
            return;
        }

        // Try with the authentication informations available
        if (preg_match('{^(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 (0 === $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);
    }

    public static function sanitizeUrl($message)
    {
        return preg_replace_callback('{://(?P<user>[^@]+?):(?P<password>.+?)@}', function ($m) {
            if (preg_match('{^[a-f0-9]{12,}$}', $m[1])) {
                return '://***:***@';
            }

            return '://' . $m[1] . ':***@';
        }, $message);
    }

    private function throwException($message, $url)
    {
        if (0 !== $this->process->execute('hg --version', $ignoredOutput)) {
            throw new \RuntimeException(self::sanitizeUrl('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(self::sanitizeUrl($message));
    }
}
<?php

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

namespace Composer\Util;

use Composer\CaBundle\CaBundle;

/**
 * @author Chris Smith <chris@cs278.org>
 */
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|null
     */
    public static function getCertificateNames($certificate)
    {
        if (is_array($certificate)) {
            $info = $certificate;
        } elseif (CaBundle::isOpensslParseSafe()) {
            $info = openssl_x509_parse($certificate, false);
        }

        if (!isset($info['subject']['commonName'])) {
            return null;
        }

        $commonName = strtolower($info['subject']['commonName']);
        $subjectAltNames = array();

        if (isset($info['extensions']['subjectAltName'])) {
            $subjectAltNames = preg_split('{\s*,\s*}', $info['extensions']['subjectAltName']);
            $subjectAltNames = array_filter(array_map(function ($name) {
                if (0 === strpos($name, 'DNS:')) {
                    return strtolower(ltrim(substr($name, 4)));
                }

                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 http://tools.ietf.org/html/draft-ietf-websec-key-pinning-02
     *
     * This method was adapted from Sslurp.
     * https://github.com/EvanDotPro/Sslurp
     *
     * (c) Evan Coury <me@evancoury.com>
     *
     * For the full copyright and license information, please see below:
     *
     * Copyright (c) 2013, Evan Coury
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without modification,
     * are permitted provided that the following conditions are met:
     *
     *     * Redistributions of source code must retain the above copyright notice,
     *       this list of conditions and the following disclaimer.
     *
     *     * Redistributions in binary form must reproduce the above copyright notice,
     *       this list of conditions and the following disclaimer in the documentation
     *       and/or other materials provided with the distribution.
     *
     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
     * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
     * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
     * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     */
    public static function getCertificateFingerprint($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($pubkeypem, strpos($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|void
     */
    private static function certNameMatcher($certName)
    {
        $wildcards = substr_count($certName, '*');

        if (0 === $wildcards) {
            // Literal match.
            return function ($hostname) use ($certName) {
                return $hostname === $certName;
            };
        }

        if (1 === $wildcards) {
            $components = explode('.', $certName);

            if (3 > count($components)) {
                // Must have 3+ components
                return;
            }

            $firstComponent = $components[0];

            // Wildcard must be the last character.
            if ('*' !== $firstComponent[strlen($firstComponent) - 1]) {
                return;
            }

            $wildcardRegex = preg_quote($certName);
            $wildcardRegex = str_replace('\\*', '[a-z0-9-]+', $wildcardRegex);
            $wildcardRegex = "{^{$wildcardRegex}$}";

            return function ($hostname) use ($wildcardRegex) {
                return 1 === preg_match($wildcardRegex, $hostname);
            };
        }
    }
}
<?php

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

namespace Composer\Util;

/**
 * Tests URLs against no_proxy patterns.
 */
class NoProxyPattern
{
    /**
     * @var string[]
     */
    protected $rules = array();

    /**
     * @param string $pattern no_proxy pattern
     */
    public function __construct($pattern)
    {
        $this->rules = preg_split("/[\s,]+/", $pattern);
    }

    /**
     * Test a URL against the stored pattern.
     *
     * @param string $url
     *
     * @return bool true if the URL matches one of the rules.
     */
    public function test($url)
    {
        $host = parse_url($url, PHP_URL_HOST);
        $port = parse_url($url, PHP_URL_PORT);

        if (empty($port)) {
            switch (parse_url($url, PHP_URL_SCHEME)) {
                case 'http':
                    $port = 80;
                    break;
                case 'https':
                    $port = 443;
                    break;
            }
        }

        foreach ($this->rules as $rule) {
            if ($rule == '*') {
                return true;
            }

            $match = false;

            list($ruleHost) = explode(':', $rule);
            list($base) = explode('/', $ruleHost);

            if (filter_var($base, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
                // ip or cidr match

                if (!isset($ip)) {
                    $ip = gethostbyname($host);
                }

                if (strpos($ruleHost, '/') === false) {
                    $match = $ip === $ruleHost;
                } else {
                    // gethostbyname() failed to resolve $host to an ip, so we assume
                    // it must be proxied to let the proxy's DNS resolve it
                    if ($ip === $host) {
                        $match = false;
                    } else {
                        // match resolved IP against the rule
                        $match = self::inCIDRBlock($ruleHost, $ip);
                    }
                }
            } else {
                // match end of domain

                $haystack = '.' . trim($host, '.') . '.';
                $needle = '.'. trim($ruleHost, '.') .'.';
                $match = stripos(strrev($haystack), strrev($needle)) === 0;
            }

            // final port check
            if ($match && strpos($rule, ':') !== false) {
                list(, $rulePort) = explode(':', $rule);
                if (!empty($rulePort) && $port != $rulePort) {
                    $match = false;
                }
            }

            if ($match) {
                return true;
            }
        }

        return false;
    }

    /**
     * Check an IP address against a CIDR
     *
     * http://framework.zend.com/svn/framework/extras/incubator/library/ZendX/Whois/Adapter/Cidr.php
     *
     * @param string $cidr IPv4 block in CIDR notation
     * @param string $ip   IPv4 address
     *
     * @return bool
     */
    private static function inCIDRBlock($cidr, $ip)
    {
        // Get the base and the bits from the CIDR
        list($base, $bits) = explode('/', $cidr);

        // Now split it up into it's classes
        list($a, $b, $c, $d) = explode('.', $base);

        // Now do some bit shifting/switching to convert to ints
        $i = ($a << 24) + ($b << 16) + ($c << 8) + $d;
        $mask = $bits == 0 ? 0 : (~0 << (32 - $bits));

        // Here's our lowest int
        $low = $i & $mask;

        // Here's our highest int
        $high = $i | (~$mask & 0xFFFFFFFF);

        // Now split the ip we're checking against up into classes
        list($a, $b, $c, $d) = explode('.', $ip);

        // Now convert the ip we're checking against to an int
        $check = ($a << 24) + ($b << 16) + ($c << 8) + $d;

        // If the ip is within the range, including highest/lowest values,
        // then it's within the CIDR range
        return $check >= $low && $check <= $high;
    }
}
<?php

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

namespace Composer\DependencyResolver;

use Composer\Package\PackageInterface;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
interface PolicyInterface
{
    public function versionCompare(PackageInterface $a, PackageInterface $b, $operator);

    public function findUpdatePackages(Pool $pool, array $installedMap, PackageInterface $package);

    public function selectPreferredPackages(Pool $pool, array $installedMap, array $literals, $requiredPackage = null);
}
<?php

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

namespace Composer\DependencyResolver;

use Composer\Package\AliasPackage;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
class Transaction
{
    protected $policy;
    protected $pool;
    protected $installedMap;
    protected $decisions;
    protected $transaction;

    public function __construct($policy, $pool, $installedMap, $decisions)
    {
        $this->policy = $policy;
        $this->pool = $pool;
        $this->installedMap = $installedMap;
        $this->decisions = $decisions;
        $this->transaction = array();
    }

    public function getOperations()
    {
        $installMeansUpdateMap = $this->findUpdates();

        $updateMap = array();
        $installMap = array();
        $uninstallMap = array();

        foreach ($this->decisions as $i => $decision) {
            $literal = $decision[Decisions::DECISION_LITERAL];
            $reason = $decision[Decisions::DECISION_REASON];

            $package = $this->pool->literalToPackage($literal);

            // wanted & installed || !wanted & !installed
            if (($literal > 0) == isset($this->installedMap[$package->id])) {
                continue;
            }

            if ($literal > 0) {
                if (isset($installMeansUpdateMap[abs($literal)]) && !$package instanceof AliasPackage) {
                    $source = $installMeansUpdateMap[abs($literal)];

                    $updateMap[$package->id] = array(
                        'package' => $package,
                        'source' => $source,
                        'reason' => $reason,
                    );

                    // avoid updates to one package from multiple origins
                    unset($installMeansUpdateMap[abs($literal)]);
                    $ignoreRemove[$source->id] = true;
                } else {
                    $installMap[$package->id] = array(
                        'package' => $package,
                        'reason' => $reason,
                    );
                }
            }
        }

        foreach ($this->decisions as $i => $decision) {
            $literal = $decision[Decisions::DECISION_LITERAL];
            $reason = $decision[Decisions::DECISION_REASON];
            $package = $this->pool->literalToPackage($literal);

            if ($literal <= 0 &&
                isset($this->installedMap[$package->id]) &&
                !isset($ignoreRemove[$package->id])) {
                $uninstallMap[$package->id] = array(
                    'package' => $package,
                    'reason' => $reason,
                );
            }
        }

        $this->transactionFromMaps($installMap, $updateMap, $uninstallMap);

        return $this->transaction;
    }

    protected function transactionFromMaps($installMap, $updateMap, $uninstallMap)
    {
        $queue = array_map(
            function ($operation) {
                return $operation['package'];
            },
            $this->findRootPackages($installMap, $updateMap)
        );

        $visited = array();

        while (!empty($queue)) {
            $package = array_pop($queue);
            $packageId = $package->id;

            if (!isset($visited[$packageId])) {
                $queue[] = $package;

                if ($package instanceof AliasPackage) {
                    $queue[] = $package->getAliasOf();
                } else {
                    foreach ($package->getRequires() as $link) {
                        $possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());

                        foreach ($possibleRequires as $require) {
                            $queue[] = $require;
                        }
                    }
                }

                $visited[$package->id] = true;
            } else {
                if (isset($installMap[$packageId])) {
                    $this->install(
                        $installMap[$packageId]['package'],
                        $installMap[$packageId]['reason']
                    );
                    unset($installMap[$packageId]);
                }
                if (isset($updateMap[$packageId])) {
                    $this->update(
                        $updateMap[$packageId]['source'],
                        $updateMap[$packageId]['package'],
                        $updateMap[$packageId]['reason']
                    );
                    unset($updateMap[$packageId]);
                }
            }
        }

        foreach ($uninstallMap as $uninstall) {
            $this->uninstall($uninstall['package'], $uninstall['reason']);
        }
    }

    protected function findRootPackages($installMap, $updateMap)
    {
        $packages = $installMap + $updateMap;
        $roots = $packages;

        foreach ($packages as $packageId => $operation) {
            $package = $operation['package'];

            if (!isset($roots[$packageId])) {
                continue;
            }

            foreach ($package->getRequires() as $link) {
                $possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());

                foreach ($possibleRequires as $require) {
                    if ($require !== $package) {
                        unset($roots[$require->id]);
                    }
                }
            }
        }

        return $roots;
    }

    protected function findUpdates()
    {
        $installMeansUpdateMap = array();

        foreach ($this->decisions as $i => $decision) {
            $literal = $decision[Decisions::DECISION_LITERAL];
            $package = $this->pool->literalToPackage($literal);

            if ($package instanceof AliasPackage) {
                continue;
            }

            // !wanted & installed
            if ($literal <= 0 && isset($this->installedMap[$package->id])) {
                $updates = $this->policy->findUpdatePackages($this->pool, $this->installedMap, $package);

                $literals = array($package->id);

                foreach ($updates as $update) {
                    $literals[] = $update->id;
                }

                foreach ($literals as $updateLiteral) {
                    if ($updateLiteral !== $literal) {
                        $installMeansUpdateMap[abs($updateLiteral)] = $package;
                    }
                }
            }
        }

        return $installMeansUpdateMap;
    }

    protected function install($package, $reason)
    {
        if ($package instanceof AliasPackage) {
            return $this->markAliasInstalled($package, $reason);
        }

        $this->transaction[] = new Operation\InstallOperation($package, $reason);
    }

    protected function update($from, $to, $reason)
    {
        $this->transaction[] = new Operation\UpdateOperation($from, $to, $reason);
    }

    protected function uninstall($package, $reason)
    {
        if ($package instanceof AliasPackage) {
            return $this->markAliasUninstalled($package, $reason);
        }

        $this->transaction[] = new Operation\UninstallOperation($package, $reason);
    }

    protected function markAliasInstalled($package, $reason)
    {
        $this->transaction[] = new Operation\MarkAliasInstalledOperation($package, $reason);
    }

    protected function markAliasUninstalled($package, $reason)
    {
        $this->transaction[] = new Operation\MarkAliasUninstalledOperation($package, $reason);
    }
}
<?php

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

namespace Composer\DependencyResolver;

use Composer\Package\PackageInterface;
use Composer\Package\AliasPackage;
use Composer\Repository\PlatformRepository;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
class RuleSetGenerator
{
    protected $policy;
    protected $pool;
    protected $rules;
    protected $jobs;
    protected $installedMap;
    protected $whitelistedMap;
    protected $addedMap;
    protected $conflictAddedMap;
    protected $addedPackages;
    protected $addedPackagesByNames;

    public function __construct(PolicyInterface $policy, Pool $pool)
    {
        $this->policy = $policy;
        $this->pool = $pool;
    }

    /**
     * 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  PackageInterface $package    The package with a requirement
     * @param  array            $providers  The providers of the requirement
     * @param  int              $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
     */
    protected function createRequireRule(PackageInterface $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  array $packages The set of packages to choose from
     * @param  int   $reason   A RULE_* constant describing the reason for
     *                         generating this rule
     * @param  array $job      The job this rule was created from
     * @return Rule  The generated rule
     */
    protected function createInstallOneOfRule(array $packages, $reason, $job)
    {
        $literals = array();
        foreach ($packages as $package) {
            $literals[] = $package->id;
        }

        return new GenericRule($literals, $reason, $job['packageName'], $job);
    }

    /**
     * Creates a rule to remove a package
     *
     * The rule for a package A is (-A).
     *
     * @param  PackageInterface $package The package to be removed
     * @param  int              $reason  A RULE_* constant describing the
     *                                   reason for generating this rule
     * @param  array            $job     The job this rule was created from
     * @return Rule             The generated rule
     */
    protected function createRemoveRule(PackageInterface $package, $reason, $job)
    {
        return new GenericRule(array(-$package->id), $reason, $job['packageName'], $job);
    }

    /**
     * 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  PackageInterface $issuer     The package declaring the conflict
     * @param  PackageInterface $provider   The package causing the conflict
     * @param  int              $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|null             The generated rule
     */
    protected function createRule2Literals(PackageInterface $issuer, PackageInterface $provider, $reason, $reasonData = null)
    {
        // ignore self conflict
        if ($issuer === $provider) {
            return null;
        }

        return new Rule2Literals(-$issuer->id, -$provider->id, $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 int  $type    A TYPE_* constant defining the rule type
     * @param Rule $newRule The rule about to be added
     */
    private function addRule($type, Rule $newRule = null)
    {
        if (!$newRule) {
            return;
        }

        $this->rules->add($newRule, $type);
    }

    protected function whitelistFromPackage(PackageInterface $package)
    {
        $workQueue = new \SplQueue;
        $workQueue->enqueue($package);

        while (!$workQueue->isEmpty()) {
            $package = $workQueue->dequeue();
            if (isset($this->whitelistedMap[$package->id])) {
                continue;
            }

            $this->whitelistedMap[$package->id] = true;

            foreach ($package->getRequires() as $link) {
                $possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint(), true);

                foreach ($possibleRequires as $require) {
                    $workQueue->enqueue($require);
                }
            }

            $obsoleteProviders = $this->pool->whatProvides($package->getName(), null, true);

            foreach ($obsoleteProviders as $provider) {
                if ($provider === $package) {
                    continue;
                }

                if (($package instanceof AliasPackage) && $package->getAliasOf() === $provider) {
                    $workQueue->enqueue($provider);
                }
            }
        }
    }

    protected function addRulesForPackage(PackageInterface $package, $ignorePlatformReqs)
    {
        $workQueue = new \SplQueue;
        $workQueue->enqueue($package);

        while (!$workQueue->isEmpty()) {
            /** @var PackageInterface $package */
            $package = $workQueue->dequeue();
            if (isset($this->addedMap[$package->id])) {
                continue;
            }

            $this->addedMap[$package->id] = true;

            $this->addedPackages[] = $package;
            foreach ($package->getNames() as $name) {
                $this->addedPackagesByNames[$name][] = $package;
            }

            foreach ($package->getRequires() as $link) {
                if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $link->getTarget())) {
                    continue;
                }

                $possibleRequires = $this->pool->whatProvides($link->getTarget(), $link->getConstraint());

                $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package, $possibleRequires, Rule::RULE_PACKAGE_REQUIRES, $link));

                foreach ($possibleRequires as $require) {
                    $workQueue->enqueue($require);
                }
            }

            $packageName = $package->getName();
            $obsoleteProviders = $this->pool->whatProvides($packageName, null);

            foreach ($obsoleteProviders as $provider) {
                if ($provider === $package) {
                    continue;
                }

                if (($package instanceof AliasPackage) && $package->getAliasOf() === $provider) {
                    $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRequireRule($package, array($provider), Rule::RULE_PACKAGE_ALIAS, $package));
                } elseif (!$this->obsoleteImpossibleForAlias($package, $provider)) {
                    $reason = ($packageName == $provider->getName()) ? Rule::RULE_PACKAGE_SAME_NAME : Rule::RULE_PACKAGE_IMPLICIT_OBSOLETES;
                    $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $provider, $reason, $package));
                }
            }
        }
    }

    protected function addConflictRules($ignorePlatformReqs = false)
    {
        /** @var PackageInterface $package */
        foreach ($this->addedPackages as $package) {
            foreach ($package->getConflicts() as $link) {
                if (!isset($this->addedPackagesByNames[$link->getTarget()])) {
                    continue;
                }

                if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $link->getTarget())) {
                    continue;
                }

                /** @var PackageInterface $possibleConflict */
                foreach ($this->addedPackagesByNames[$link->getTarget()] as $possibleConflict) {
                    $conflictMatch = $this->pool->match($possibleConflict, $link->getTarget(), $link->getConstraint(), true);

                    if ($conflictMatch === Pool::MATCH || $conflictMatch === Pool::MATCH_REPLACE) {
                        $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $possibleConflict, Rule::RULE_PACKAGE_CONFLICT, $link));
                    }

                }
            }

            // check obsoletes and implicit obsoletes of a package
            $isInstalled = isset($this->installedMap[$package->id]);

            foreach ($package->getReplaces() as $link) {
                if (!isset($this->addedPackagesByNames[$link->getTarget()])) {
                    continue;
                }

                /** @var PackageInterface $possibleConflict */
                foreach ($this->addedPackagesByNames[$link->getTarget()] as $provider) {
                    if ($provider === $package) {
                        continue;
                    }

                    if (!$this->obsoleteImpossibleForAlias($package, $provider)) {
                        $reason = $isInstalled ? Rule::RULE_INSTALLED_PACKAGE_OBSOLETES : Rule::RULE_PACKAGE_OBSOLETES;
                        $this->addRule(RuleSet::TYPE_PACKAGE, $this->createRule2Literals($package, $provider, $reason, $link));
                    }
                }
            }
        }
    }

    protected function obsoleteImpossibleForAlias($package, $provider)
    {
        $packageIsAlias = $package instanceof AliasPackage;
        $providerIsAlias = $provider instanceof AliasPackage;

        $impossible = (
            ($packageIsAlias && $package->getAliasOf() === $provider) ||
            ($providerIsAlias && $provider->getAliasOf() === $package) ||
            ($packageIsAlias && $providerIsAlias && $provider->getAliasOf() === $package->getAliasOf())
        );

        return $impossible;
    }

    protected function whitelistFromJobs()
    {
        foreach ($this->jobs as $job) {
            switch ($job['cmd']) {
                case 'install':
                    $packages = $this->pool->whatProvides($job['packageName'], $job['constraint'], true);
                    foreach ($packages as $package) {
                        $this->whitelistFromPackage($package);
                    }
                    break;
            }
        }
    }

    protected function addRulesForJobs($ignorePlatformReqs)
    {
        foreach ($this->jobs as $job) {
            switch ($job['cmd']) {
                case 'install':
                    if (!$job['fixed'] && $ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $job['packageName'])) {
                        break;
                    }

                    $packages = $this->pool->whatProvides($job['packageName'], $job['constraint']);
                    if ($packages) {
                        foreach ($packages as $package) {
                            if (!isset($this->installedMap[$package->id])) {
                                $this->addRulesForPackage($package, $ignorePlatformReqs);
                            }
                        }

                        $rule = $this->createInstallOneOfRule($packages, Rule::RULE_JOB_INSTALL, $job);
                        $this->addRule(RuleSet::TYPE_JOB, $rule);
                    }
                    break;
                case 'remove':
                    // remove all packages with this name including uninstalled
                    // ones to make sure none of them are picked as replacements
                    $packages = $this->pool->whatProvides($job['packageName'], $job['constraint']);
                    foreach ($packages as $package) {
                        $rule = $this->createRemoveRule($package, Rule::RULE_JOB_REMOVE, $job);
                        $this->addRule(RuleSet::TYPE_JOB, $rule);
                    }
                    break;
            }
        }
    }

    public function getRulesFor($jobs, $installedMap, $ignorePlatformReqs = false)
    {
        $this->jobs = $jobs;
        $this->rules = new RuleSet;
        $this->installedMap = $installedMap;

        $this->whitelistedMap = array();
        foreach ($this->installedMap as $package) {
            $this->whitelistFromPackage($package);
        }
        $this->whitelistFromJobs();

        $this->pool->setWhitelist($this->whitelistedMap);

        $this->addedMap = array();
        $this->conflictAddedMap = array();
        $this->addedPackages = array();
        $this->addedPackagesByNames = array();
        foreach ($this->installedMap as $package) {
            $this->addRulesForPackage($package, $ignorePlatformReqs);
        }

        $this->addRulesForJobs($ignorePlatformReqs);

        $this->addConflictRules($ignorePlatformReqs);

        // Remove references to packages
        $this->addedPackages = $this->addedPackagesByNames = null;

        return $this->rules;
    }
}
<?php

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

namespace Composer\DependencyResolver;

/**
 * The RuleWatchGraph efficiently propagates decisions to other rules
 *
 * All rules generated for solving a SAT problem should be inserted into the
 * graph. When a decision on a literal is made, the graph can be used to
 * propagate the decision to all other rules involving the literal, leading to
 * other trivial decisions resulting from unit clauses.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class RuleWatchGraph
{
    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
     */
    public function insert(RuleWatchNode $node)
    {
        if ($node->getRule()->isAssertion()) {
            return;
        }

        foreach (array($node->watch1, $node->watch2) 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, $level, $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();
            $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($literal, current($alternativeLiterals), $node);
                    continue;
                }

                if ($decisions->conflict($otherWatch)) {
                    return $node->getRule();
                }

                $decisions->decide($otherWatch, $level, $node->getRule());
            }

            $chain->next();
        }

        return null;
    }

    /**
     * Moves a rule node from one watch chain to another
     *
     * The rule node's watched literals are updated accordingly.
     *
     * @param int           $fromLiteral A literal the node used to watch
     * @param int           $toLiteral A literal the node should watch now
     * @param RuleWatchNode $node The rule node to be moved
     */
    protected function moveWatch($fromLiteral, $toLiteral, $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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Package\CompletePackage;
use Composer\Package\Link;
use Composer\Package\PackageInterface;

/**
 * @author Nils Adermann <naderman@naderman.de>
 * @author Ruben Gonzalez <rubenrua@gmail.com>
 */
abstract class Rule
{
    // reason constants
    const RULE_INTERNAL_ALLOW_UPDATE = 1;
    const RULE_JOB_INSTALL = 2;
    const RULE_JOB_REMOVE = 3;
    const RULE_PACKAGE_CONFLICT = 6;
    const RULE_PACKAGE_REQUIRES = 7;
    const RULE_PACKAGE_OBSOLETES = 8;
    const RULE_INSTALLED_PACKAGE_OBSOLETES = 9;
    const RULE_PACKAGE_SAME_NAME = 10;
    const RULE_PACKAGE_IMPLICIT_OBSOLETES = 11;
    const RULE_LEARNED = 12;
    const RULE_PACKAGE_ALIAS = 13;

    // bitfield defs
    const BITFIELD_TYPE = 0;
    const BITFIELD_REASON = 8;
    const BITFIELD_DISABLED = 16;

    protected $bitfield;
    protected $job;
    protected $reasonData;

    /**
     * @param int                   $reason     A RULE_* constant describing the reason for generating this rule
     * @param Link|PackageInterface $reasonData
     * @param array                 $job        The job this rule was created from
     */
    public function __construct($reason, $reasonData, $job = null)
    {
        $this->reasonData = $reasonData;

        if ($job) {
            $this->job = $job;
        }

        $this->bitfield = (0 << self::BITFIELD_DISABLED) |
            ($reason << self::BITFIELD_REASON) |
            (255 << self::BITFIELD_TYPE);
    }

    abstract public function getLiterals();

    abstract public function getHash();

    public function getJob()
    {
        return $this->job;
    }

    abstract public function equals(Rule $rule);

    public function getReason()
    {
        return ($this->bitfield & (255 << self::BITFIELD_REASON)) >> self::BITFIELD_REASON;
    }

    public function getReasonData()
    {
        return $this->reasonData;
    }

    public function getRequiredPackage()
    {
        if ($this->getReason() === self::RULE_JOB_INSTALL) {
            return $this->reasonData;
        }

        if ($this->getReason() === self::RULE_PACKAGE_REQUIRES) {
            return $this->reasonData->getTarget();
        }
    }

    public function setType($type)
    {
        $this->bitfield = ($this->bitfield & ~(255 << self::BITFIELD_TYPE)) | ((255 & $type) << self::BITFIELD_TYPE);
    }

    public function getType()
    {
        return ($this->bitfield & (255 << self::BITFIELD_TYPE)) >> self::BITFIELD_TYPE;
    }

    public function disable()
    {
        $this->bitfield = ($this->bitfield & ~(255 << self::BITFIELD_DISABLED)) | (1 << self::BITFIELD_DISABLED);
    }

    public function enable()
    {
        $this->bitfield &= ~(255 << self::BITFIELD_DISABLED);
    }

    public function isDisabled()
    {
        return (bool) (($this->bitfield & (255 << self::BITFIELD_DISABLED)) >> self::BITFIELD_DISABLED);
    }

    public function isEnabled()
    {
        return !(($this->bitfield & (255 << self::BITFIELD_DISABLED)) >> self::BITFIELD_DISABLED);
    }

    abstract public function isAssertion();

    public function getPrettyString(Pool $pool, array $installedMap = array())
    {
        $literals = $this->getLiterals();

        $ruleText = '';
        foreach ($literals as $i => $literal) {
            if ($i != 0) {
                $ruleText .= '|';
            }
            $ruleText .= $pool->literalToPrettyString($literal, $installedMap);
        }

        switch ($this->getReason()) {
            case self::RULE_INTERNAL_ALLOW_UPDATE:
                return $ruleText;

            case self::RULE_JOB_INSTALL:
                return "Install command rule ($ruleText)";

            case self::RULE_JOB_REMOVE:
                return "Remove command rule ($ruleText)";

            case self::RULE_PACKAGE_CONFLICT:
                $package1 = $pool->literalToPackage($literals[0]);
                $package2 = $pool->literalToPackage($literals[1]);

                return $package1->getPrettyString().' conflicts with '.$this->formatPackagesUnique($pool, array($package2)).'.';

            case self::RULE_PACKAGE_REQUIRES:
                $sourceLiteral = array_shift($literals);
                $sourcePackage = $pool->literalToPackage($sourceLiteral);

                $requires = array();
                foreach ($literals as $literal) {
                    $requires[] = $pool->literalToPackage($literal);
                }

                $text = $this->reasonData->getPrettyString($sourcePackage);
                if ($requires) {
                    $text .= ' -> satisfiable by ' . $this->formatPackagesUnique($pool, $requires) . '.';
                } else {
                    $targetName = $this->reasonData->getTarget();

                    if ($targetName === 'php' || $targetName === 'php-64bit' || $targetName === 'hhvm') {
                        // handle php/hhvm
                        if (defined('HHVM_VERSION')) {
                            return $text . ' -> your HHVM version does not satisfy that requirement.';
                        }

                        $packages = $pool->whatProvides($targetName);
                        $package = count($packages) ? current($packages) : phpversion();

                        if ($targetName === 'hhvm') {
                            if ($package instanceof CompletePackage) {
                                return $text . ' -> your HHVM version ('.$package->getPrettyVersion().') does not satisfy that requirement.';
                            } else {
                                return $text . ' -> you are running this with PHP and not HHVM.';
                            }
                        }


                        if (!($package instanceof CompletePackage)) {
                            return $text . ' -> your PHP version ('.phpversion().') does not satisfy that requirement.';
                        }

                        $extra = $package->getExtra();

                        if (!empty($extra['config.platform'])) {
                            $text .= ' -> your PHP version ('.phpversion().') overridden by "config.platform.php" version ('.$package->getPrettyVersion().') does not satisfy that requirement.';
                        } else {
                            $text .= ' -> your PHP version ('.$package->getPrettyVersion().') does not satisfy that requirement.';
                        }

                        return $text;
                    }

                    if (0 === strpos($targetName, 'ext-')) {
                        // handle php extensions
                        $ext = substr($targetName, 4);
                        $error = extension_loaded($ext) ? 'has the wrong version ('.(phpversion($ext) ?: '0').') installed' : 'is missing from your system';

                        return $text . ' -> the requested PHP extension '.$ext.' '.$error.'.';
                    }

                    if (0 === strpos($targetName, 'lib-')) {
                        // handle linked libs
                        $lib = substr($targetName, 4);

                        return $text . ' -> the requested linked library '.$lib.' has the wrong version installed or is missing from your system, make sure to have the extension providing it.';
                    }

                    if ($providers = $pool->whatProvides($targetName, $this->reasonData->getConstraint(), true, true)) {
                        return $text . ' -> satisfiable by ' . $this->formatPackagesUnique($pool, $providers) .' but these conflict with your requirements or minimum-stability.';
                    }

                    return $text . ' -> no matching package found.';
                }

                return $text;

            case self::RULE_PACKAGE_OBSOLETES:
                return $ruleText;
            case self::RULE_INSTALLED_PACKAGE_OBSOLETES:
                return $ruleText;
            case self::RULE_PACKAGE_SAME_NAME:
                return 'Can only install one of: ' . $this->formatPackagesUnique($pool, $literals) . '.';
            case self::RULE_PACKAGE_IMPLICIT_OBSOLETES:
                return $ruleText;
            case self::RULE_LEARNED:
                return 'Conclusion: '.$ruleText;
            case self::RULE_PACKAGE_ALIAS:
                return $ruleText;
            default:
                return '('.$ruleText.')';
        }
    }

    /**
     * @param Pool  $pool
     * @param array $packages
     *
     * @return string
     */
    protected function formatPackagesUnique($pool, array $packages)
    {
        $prepared = array();
        foreach ($packages as $package) {
            if (!is_object($package)) {
                $package = $pool->literalToPackage($package);
            }
            $prepared[$package->getName()]['name'] = $package->getPrettyName();
            $prepared[$package->getName()]['versions'][$package->getVersion()] = $package->getPrettyVersion();
        }
        foreach ($prepared as $name => $package) {
            $prepared[$name] = $package['name'].'['.implode(', ', $package['versions']).']';
        }

        return implode(', ', $prepared);
    }
}
<?php

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

namespace Composer\DependencyResolver;

use Composer\Package\BasePackage;
use Composer\Package\AliasPackage;
use Composer\Package\Version\VersionParser;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\Constraint;
use Composer\Semver\Constraint\EmptyConstraint;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\CompositeRepository;
use Composer\Repository\ComposerRepository;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Repository\PlatformRepository;
use Composer\Package\PackageInterface;

/**
 * A package pool contains repositories that provide packages.
 *
 * @author Nils Adermann <naderman@naderman.de>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Pool implements \Countable
{
    const MATCH_NAME = -1;
    const MATCH_NONE = 0;
    const MATCH = 1;
    const MATCH_PROVIDE = 2;
    const MATCH_REPLACE = 3;
    const MATCH_FILTERED = 4;

    protected $repositories = array();
    protected $providerRepos = array();
    protected $packages = array();
    protected $packageByName = array();
    protected $packageByExactName = array();
    protected $acceptableStabilities;
    protected $stabilityFlags;
    protected $versionParser;
    protected $providerCache = array();
    protected $filterRequires;
    protected $whitelist = null;
    protected $id = 1;

    public function __construct($minimumStability = 'stable', array $stabilityFlags = array(), array $filterRequires = array())
    {
        $this->versionParser = new VersionParser;
        $this->acceptableStabilities = array();
        foreach (BasePackage::$stabilities as $stability => $value) {
            if ($value <= BasePackage::$stabilities[$minimumStability]) {
                $this->acceptableStabilities[$stability] = $value;
            }
        }
        $this->stabilityFlags = $stabilityFlags;
        $this->filterRequires = $filterRequires;
        foreach ($filterRequires as $name => $constraint) {
            if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name)) {
                unset($this->filterRequires[$name]);
            }
        }
    }

    public function setWhitelist($whitelist)
    {
        $this->whitelist = $whitelist;
        $this->providerCache = array();
    }

    /**
     * Adds a repository and its packages to this package pool
     *
     * @param RepositoryInterface $repo        A package repository
     * @param array               $rootAliases
     */
    public function addRepository(RepositoryInterface $repo, $rootAliases = array())
    {
        if ($repo instanceof CompositeRepository) {
            $repos = $repo->getRepositories();
        } else {
            $repos = array($repo);
        }

        foreach ($repos as $repo) {
            $this->repositories[] = $repo;

            $exempt = $repo instanceof PlatformRepository || $repo instanceof InstalledRepositoryInterface;

            if ($repo instanceof ComposerRepository && $repo->hasProviders()) {
                $this->providerRepos[] = $repo;
                $repo->setRootAliases($rootAliases);
                $repo->resetPackageIds();
            } else {
                foreach ($repo->getPackages() as $package) {
                    $names = $package->getNames();
                    $stability = $package->getStability();
                    if ($exempt || $this->isPackageAcceptable($names, $stability)) {
                        $package->setId($this->id++);
                        $this->packages[] = $package;
                        $this->packageByExactName[$package->getName()][$package->id] = $package;

                        foreach ($names as $provided) {
                            $this->packageByName[$provided][] = $package;
                        }

                        // handle root package aliases
                        $name = $package->getName();
                        if (isset($rootAliases[$name][$package->getVersion()])) {
                            $alias = $rootAliases[$name][$package->getVersion()];
                            if ($package instanceof AliasPackage) {
                                $package = $package->getAliasOf();
                            }
                            $aliasPackage = new AliasPackage($package, $alias['alias_normalized'], $alias['alias']);
                            $aliasPackage->setRootPackageAlias(true);
                            $aliasPackage->setId($this->id++);

                            $package->getRepository()->addPackage($aliasPackage);
                            $this->packages[] = $aliasPackage;
                            $this->packageByExactName[$aliasPackage->getName()][$aliasPackage->id] = $aliasPackage;

                            foreach ($aliasPackage->getNames() as $name) {
                                $this->packageByName[$name][] = $aliasPackage;
                            }
                        }
                    }
                }
            }
        }
    }

    public function getPriority(RepositoryInterface $repo)
    {
        $priority = array_search($repo, $this->repositories, true);

        if (false === $priority) {
            throw new \RuntimeException("Could not determine repository priority. The repository was not registered in the pool.");
        }

        return -$priority;
    }

    /**
     * Retrieves the package object for a given package id.
     *
     * @param  int              $id
     * @return PackageInterface
     */
    public function packageById($id)
    {
        return $this->packages[$id - 1];
    }

    /**
     * Returns how many packages have been loaded into the pool
     */
    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
     * @param  bool                $mustMatchName Whether the name of returned packages
     *                                            must match the given name
     * @param  bool                $bypassFilters If enabled, filterRequires and stability matching is ignored
     * @return PackageInterface[]  A set of packages
     */
    public function whatProvides($name, ConstraintInterface $constraint = null, $mustMatchName = false, $bypassFilters = false)
    {
        if ($bypassFilters) {
            return $this->computeWhatProvides($name, $constraint, $mustMatchName, true);
        }

        $key = ((int) $mustMatchName).$constraint;
        if (isset($this->providerCache[$name][$key])) {
            return $this->providerCache[$name][$key];
        }

        return $this->providerCache[$name][$key] = $this->computeWhatProvides($name, $constraint, $mustMatchName, $bypassFilters);
    }

    /**
     * @see whatProvides
     */
    private function computeWhatProvides($name, $constraint, $mustMatchName = false, $bypassFilters = false)
    {
        $candidates = array();

        foreach ($this->providerRepos as $repo) {
            foreach ($repo->whatProvides($this, $name, $bypassFilters) as $candidate) {
                $candidates[] = $candidate;
                if ($candidate->id < 1) {
                    $candidate->setId($this->id++);
                    $this->packages[$this->id - 2] = $candidate;
                }
            }
        }

        if ($mustMatchName) {
            $candidates = array_filter($candidates, function ($candidate) use ($name) {
                return $candidate->getName() == $name;
            });
            if (isset($this->packageByExactName[$name])) {
                $candidates = array_merge($candidates, $this->packageByExactName[$name]);
            }
        } elseif (isset($this->packageByName[$name])) {
            $candidates = array_merge($candidates, $this->packageByName[$name]);
        }

        $matches = $provideMatches = array();
        $nameMatch = false;

        foreach ($candidates as $candidate) {
            $aliasOfCandidate = null;

            // alias packages are not white listed, make sure that the package
            // being aliased is white listed
            if ($candidate instanceof AliasPackage) {
                $aliasOfCandidate = $candidate->getAliasOf();
            }

            if ($this->whitelist !== null && !$bypassFilters && (
                (!($candidate instanceof AliasPackage) && !isset($this->whitelist[$candidate->id])) ||
                ($candidate instanceof AliasPackage && !isset($this->whitelist[$aliasOfCandidate->id]))
            )) {
                continue;
            }
            switch ($this->match($candidate, $name, $constraint, $bypassFilters)) {
                case self::MATCH_NONE:
                    break;

                case self::MATCH_NAME:
                    $nameMatch = true;
                    break;

                case self::MATCH:
                    $nameMatch = true;
                    $matches[] = $candidate;
                    break;

                case self::MATCH_PROVIDE:
                    $provideMatches[] = $candidate;
                    break;

                case self::MATCH_REPLACE:
                    $matches[] = $candidate;
                    break;

                case self::MATCH_FILTERED:
                    break;

                default:
                    throw new \UnexpectedValueException('Unexpected match type');
            }
        }

        // if a package with the required name exists, we ignore providers
        if ($nameMatch) {
            return $matches;
        }

        return array_merge($matches, $provideMatches);
    }

    public function literalToPackage($literal)
    {
        $packageId = abs($literal);

        return $this->packageById($packageId);
    }

    public function literalToPrettyString($literal, $installedMap)
    {
        $package = $this->literalToPackage($literal);

        if (isset($installedMap[$package->id])) {
            $prefix = ($literal > 0 ? 'keep' : 'remove');
        } else {
            $prefix = ($literal > 0 ? 'install' : 'don\'t install');
        }

        return $prefix.' '.$package->getPrettyString();
    }

    public function isPackageAcceptable($name, $stability)
    {
        foreach ((array) $name as $n) {
            // allow if package matches the global stability requirement and has no exception
            if (!isset($this->stabilityFlags[$n]) && isset($this->acceptableStabilities[$stability])) {
                return true;
            }

            // allow if package matches the package-specific stability flag
            if (isset($this->stabilityFlags[$n]) && BasePackage::$stabilities[$stability] <= $this->stabilityFlags[$n]) {
                return true;
            }
        }

        return false;
    }

    /**
     * Checks if the package matches the given constraint directly or through
     * provided or replaced packages
     *
     * @param  PackageInterface       $candidate
     * @param  string                 $name       Name of the package to be matched
     * @param  ConstraintInterface    $constraint The constraint to verify
     * @return int                    One of the MATCH* constants of this class or 0 if there is no match
     */
    public function match($candidate, $name, ConstraintInterface $constraint = null, $bypassFilters)
    {
        $candidateName = $candidate->getName();
        $candidateVersion = $candidate->getVersion();
        $isDev = $candidate->getStability() === 'dev';
        $isAlias = $candidate instanceof AliasPackage;

        if (!$bypassFilters && !$isDev && !$isAlias && isset($this->filterRequires[$name])) {
            $requireFilter = $this->filterRequires[$name];
        } else {
            $requireFilter = new EmptyConstraint;
        }

        if ($candidateName === $name) {
            $pkgConstraint = new Constraint('==', $candidateVersion);

            if ($constraint === null || $constraint->matches($pkgConstraint)) {
                return $requireFilter->matches($pkgConstraint) ? self::MATCH : self::MATCH_FILTERED;
            }

            return self::MATCH_NAME;
        }

        $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 $requireFilter->matches($link->getConstraint()) ? self::MATCH_PROVIDE : self::MATCH_FILTERED;
                }
            }

            foreach ($replaces as $link) {
                if ($link->getTarget() === $name && ($constraint === null || $constraint->matches($link->getConstraint()))) {
                    return $requireFilter->matches($link->getConstraint()) ? self::MATCH_REPLACE : self::MATCH_FILTERED;
                }
            }

            return self::MATCH_NONE;
        }

        if (isset($provides[$name]) && ($constraint === null || $constraint->matches($provides[$name]->getConstraint()))) {
            return $requireFilter->matches($provides[$name]->getConstraint()) ? self::MATCH_PROVIDE : self::MATCH_FILTERED;
        }

        if (isset($replaces[$name]) && ($constraint === null || $constraint->matches($replaces[$name]->getConstraint()))) {
            return $requireFilter->matches($replaces[$name]->getConstraint()) ? self::MATCH_REPLACE : self::MATCH_FILTERED;
        }

        return self::MATCH_NONE;
    }
}
<?php

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

namespace Composer\DependencyResolver;

use Composer\Package\PackageInterface;
use Composer\Package\Link;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
class GenericRule extends Rule
{
    protected $literals;

    /**
     * @param array                 $literals
     * @param int                   $reason     A RULE_* constant describing the reason for generating this rule
     * @param Link|PackageInterface $reasonData
     * @param array                 $job        The job this rule was created from
     */
    public function __construct(array $literals, $reason, $reasonData, $job = null)
    {
        parent::__construct($reason, $reasonData, $job);

        // sort all packages ascending by id
        sort($literals);

        $this->literals = $literals;
    }

    public function getLiterals()
    {
        return $this->literals;
    }

    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();
    }

    public function isAssertion()
    {
        return 1 === 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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

/**
 * Wrapper around a Rule which keeps track of the two literals it watches
 *
 * Used by RuleWatchGraph to store rules in two RuleWatchChains.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class RuleWatchNode
{
    public $watch1;
    public $watch2;

    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)
    {
        $this->rule = $rule;

        $literals = $rule->getLiterals();

        $literalCount = count($literals);
        $this->watch1 = $literalCount > 0 ? $literals[0] : 0;
        $this->watch2 = $literalCount > 1 ? $literals[1] : 0;
    }

    /**
     * Places the second watch on the rule's literal, decided at the highest level
     *
     * Useful for learned rules where the literal for the highest rule is most
     * likely to quickly lead to further decisions.
     *
     * @param Decisions $decisions The decisions made so far by the solver
     */
    public function watch2OnHighest(Decisions $decisions)
    {
        $literals = $this->rule->getLiterals();

        // if there are only 2 elements, both are being watched anyway
        if (count($literals) < 3) {
            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
     */
    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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Package\CompletePackageInterface;

/**
 * Represents a problem detected while solving dependencies
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class Problem
{
    /**
     * A map containing the id of each rule part of this problem as a key
     * @var array
     */
    protected $reasonSeen;

    /**
     * A set of reasons for the problem, each is a rule or a job and a rule
     * @var array
     */
    protected $reasons = array();

    protected $section = 0;

    protected $pool;

    public function __construct(Pool $pool)
    {
        $this->pool = $pool;
    }

    /**
     * Add a rule as a reason
     *
     * @param Rule $rule A rule which is a reason for this problem
     */
    public function addRule(Rule $rule)
    {
        $this->addReason(spl_object_hash($rule), array(
            'rule' => $rule,
            'job' => $rule->getJob(),
        ));
    }

    /**
     * Retrieve all reasons for this problem
     *
     * @return array The problem's reasons
     */
    public function getReasons()
    {
        return $this->reasons;
    }

    /**
     * A human readable textual representation of the problem's reasons
     *
     * @param  array  $installedMap A map of all installed packages
     * @return string
     */
    public function getPrettyString(array $installedMap = array())
    {
        $reasons = call_user_func_array('array_merge', array_reverse($this->reasons));

        if (count($reasons) === 1) {
            reset($reasons);
            $reason = current($reasons);

            $job = $reason['job'];

            $packageName = $job['packageName'];
            $constraint = $job['constraint'];

            if (isset($constraint)) {
                $packages = $this->pool->whatProvides($packageName, $constraint);
            } else {
                $packages = array();
            }

            if ($job && $job['cmd'] === 'install' && empty($packages)) {

                // handle php/hhvm
                if ($packageName === 'php' || $packageName === 'php-64bit' || $packageName === 'hhvm') {
                    $version = phpversion();
                    $available = $this->pool->whatProvides($packageName);

                    if (count($available)) {
                        $firstAvailable = reset($available);
                        $version = $firstAvailable->getPrettyVersion();
                        $extra = $firstAvailable->getExtra();
                        if ($firstAvailable instanceof CompletePackageInterface && isset($extra['config.platform']) && $extra['config.platform'] === true) {
                            $version .= '; ' . $firstAvailable->getDescription();
                        }
                    }

                    $msg = "\n    - This package requires ".$packageName.$this->constraintToText($constraint).' but ';

                    if (defined('HHVM_VERSION') || (count($available) && $packageName === 'hhvm')) {
                        return $msg . 'your HHVM version does not satisfy that requirement.';
                    }

                    if ($packageName === 'hhvm') {
                        return $msg . 'you are running this with PHP and not HHVM.';
                    }

                    return $msg . 'your PHP version ('. $version .') does not satisfy that requirement.';
                }

                // handle php extensions
                if (0 === stripos($packageName, 'ext-')) {
                    if (false !== strpos($packageName, ' ')) {
                        return "\n    - The requested PHP extension ".$packageName.' should be required as '.str_replace(' ', '-', $packageName).'.';
                    }

                    $ext = substr($packageName, 4);
                    $error = extension_loaded($ext) ? 'has the wrong version ('.(phpversion($ext) ?: '0').') installed' : 'is missing from your system';

                    return "\n    - The requested PHP extension ".$packageName.$this->constraintToText($constraint).' '.$error.'. Install or enable PHP\'s '.$ext.' extension.';
                }

                // handle linked libs
                if (0 === stripos($packageName, 'lib-')) {
                    if (strtolower($packageName) === 'lib-icu') {
                        $error = extension_loaded('intl') ? 'has the wrong version installed, try upgrading the intl extension.' : 'is missing from your system, make sure the intl extension is loaded.';

                        return "\n    - The requested linked library ".$packageName.$this->constraintToText($constraint).' '.$error;
                    }

                    return "\n    - The requested linked library ".$packageName.$this->constraintToText($constraint).' has the wrong version installed or is missing from your system, make sure to load the extension providing it.';
                }

                if (!preg_match('{^[A-Za-z0-9_./-]+$}', $packageName)) {
                    $illegalChars = preg_replace('{[A-Za-z0-9_./-]+}', '', $packageName);

                    return "\n    - The requested package ".$packageName.' could not be found, it looks like its name is invalid, "'.$illegalChars.'" is not allowed in package names.';
                }

                if ($providers = $this->pool->whatProvides($packageName, $constraint, true, true)) {
                    return "\n    - The requested package ".$packageName.$this->constraintToText($constraint).' is satisfiable by '.$this->getPackageList($providers).' but these conflict with your requirements or minimum-stability.';
                }

                if ($providers = $this->pool->whatProvides($packageName, null, true, true)) {
                    return "\n    - The requested package ".$packageName.$this->constraintToText($constraint).' exists as '.$this->getPackageList($providers).' but these are rejected by your constraint.';
                }

                return "\n    - The requested package ".$packageName.' could not be found in any version, there may be a typo in the package name.';
            }
        }

        $messages = array();

        foreach ($reasons as $reason) {
            $rule = $reason['rule'];
            $job = $reason['job'];

            if ($job) {
                $messages[] = $this->jobToText($job);
            } elseif ($rule) {
                if ($rule instanceof Rule) {
                    $messages[] = $rule->getPrettyString($this->pool, $installedMap);
                }
            }
        }

        return "\n    - ".implode("\n    - ", $messages);
    }

    /**
     * Store a reason descriptor but ignore duplicates
     *
     * @param string $id     A canonical identifier for the reason
     * @param string $reason The reason descriptor
     */
    protected function addReason($id, $reason)
    {
        if (!isset($this->reasonSeen[$id])) {
            $this->reasonSeen[$id] = true;
            $this->reasons[$this->section][] = $reason;
        }
    }

    public function nextSection()
    {
        $this->section++;
    }

    /**
     * Turns a job into a human readable description
     *
     * @param  array  $job
     * @return string
     */
    protected function jobToText($job)
    {
        $packageName = $job['packageName'];
        $constraint = $job['constraint'];
        switch ($job['cmd']) {
            case 'install':
                $packages = $this->pool->whatProvides($packageName, $constraint);
                if (!$packages) {
                    return 'No package found to satisfy install request for '.$packageName.$this->constraintToText($constraint);
                }

                return 'Installation request for '.$packageName.$this->constraintToText($constraint).' -> satisfiable by '.$this->getPackageList($packages).'.';
            case 'update':
                return 'Update request for '.$packageName.$this->constraintToText($constraint).'.';
            case 'remove':
                return 'Removal request for '.$packageName.$this->constraintToText($constraint).'';
        }

        if (isset($constraint)) {
            $packages = $this->pool->whatProvides($packageName, $constraint);
        } else {
            $packages = array();
        }

        return 'Job(cmd='.$job['cmd'].', target='.$packageName.', packages=['.$this->getPackageList($packages).'])';
    }

    protected function getPackageList($packages)
    {
        $prepared = array();
        foreach ($packages as $package) {
            $prepared[$package->getName()]['name'] = $package->getPrettyName();
            $prepared[$package->getName()]['versions'][$package->getVersion()] = $package->getPrettyVersion();
        }
        foreach ($prepared as $name => $package) {
            $prepared[$name] = $package['name'].'['.implode(', ', $package['versions']).']';
        }

        return implode(', ', $prepared);
    }

    /**
     * Turns a constraint into text usable in a sentence describing a job
     *
     * @param  \Composer\Semver\Constraint\ConstraintInterface $constraint
     * @return string
     */
    protected function constraintToText($constraint)
    {
        return $constraint ? ' '.$constraint->getPrettyString() : '';
    }
}
<?php

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

namespace Composer\DependencyResolver;

/**
 * Stores decisions on installing, removing or keeping packages
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class Decisions implements \Iterator, \Countable
{
    const DECISION_LITERAL = 0;
    const DECISION_REASON = 1;

    protected $pool;
    protected $decisionMap;
    protected $decisionQueue = array();

    public function __construct($pool)
    {
        $this->pool = $pool;
        $this->decisionMap = array();
    }

    public function decide($literal, $level, $why)
    {
        $this->addDecision($literal, $level);
        $this->decisionQueue[] = array(
            self::DECISION_LITERAL => $literal,
            self::DECISION_REASON => $why,
        );
    }

    public function satisfy($literal)
    {
        $packageId = abs($literal);

        return (
            $literal > 0 && isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0 ||
            $literal < 0 && isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] < 0
        );
    }

    public function conflict($literal)
    {
        $packageId = abs($literal);

        return (
            (isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0 && $literal < 0) ||
            (isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] < 0 && $literal > 0)
        );
    }

    public function decided($literalOrPackageId)
    {
        return !empty($this->decisionMap[abs($literalOrPackageId)]);
    }

    public function undecided($literalOrPackageId)
    {
        return empty($this->decisionMap[abs($literalOrPackageId)]);
    }

    public function decidedInstall($literalOrPackageId)
    {
        $packageId = abs($literalOrPackageId);

        return isset($this->decisionMap[$packageId]) && $this->decisionMap[$packageId] > 0;
    }

    public function decisionLevel($literalOrPackageId)
    {
        $packageId = abs($literalOrPackageId);
        if (isset($this->decisionMap[$packageId])) {
            return abs($this->decisionMap[$packageId]);
        }

        return 0;
    }

    public function decisionRule($literalOrPackageId)
    {
        $packageId = abs($literalOrPackageId);

        foreach ($this->decisionQueue as $i => $decision) {
            if ($packageId === abs($decision[self::DECISION_LITERAL])) {
                return $decision[self::DECISION_REASON];
            }
        }

        return null;
    }

    public function atOffset($queueOffset)
    {
        return $this->decisionQueue[$queueOffset];
    }

    public function validOffset($queueOffset)
    {
        return $queueOffset >= 0 && $queueOffset < count($this->decisionQueue);
    }

    public function lastReason()
    {
        return $this->decisionQueue[count($this->decisionQueue) - 1][self::DECISION_REASON];
    }

    public function lastLiteral()
    {
        return $this->decisionQueue[count($this->decisionQueue) - 1][self::DECISION_LITERAL];
    }

    public function reset()
    {
        while ($decision = array_pop($this->decisionQueue)) {
            $this->decisionMap[abs($decision[self::DECISION_LITERAL])] = 0;
        }
    }

    public function resetToOffset($offset)
    {
        while (count($this->decisionQueue) > $offset + 1) {
            $decision = array_pop($this->decisionQueue);
            $this->decisionMap[abs($decision[self::DECISION_LITERAL])] = 0;
        }
    }

    public function revertLast()
    {
        $this->decisionMap[abs($this->lastLiteral())] = 0;
        array_pop($this->decisionQueue);
    }

    public function count()
    {
        return count($this->decisionQueue);
    }

    public function rewind()
    {
        end($this->decisionQueue);
    }

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

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

    public function next()
    {
        return prev($this->decisionQueue);
    }

    public function valid()
    {
        return false !== current($this->decisionQueue);
    }

    public function isEmpty()
    {
        return count($this->decisionQueue) === 0;
    }

    protected function addDecision($literal, $level)
    {
        $packageId = abs($literal);

        $previousDecision = isset($this->decisionMap[$packageId]) ? $this->decisionMap[$packageId] : null;
        if ($previousDecision != 0) {
            $literalString = $this->pool->literalToString($literal);
            $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;
        }
    }

    public function __toString()
    {
        $decisionMap = $this->decisionMap;
        ksort($decisionMap);
        $str = '[';
        foreach ($decisionMap as $packageId => $level) {
            $str .= $packageId.':'.$level.',';
        }
        $str .= ']';
        return $str;
    }
}
<?php

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

namespace Composer\DependencyResolver;

use Composer\IO\IOInterface;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\PlatformRepository;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
class Solver
{
    const BRANCH_LITERALS = 0;
    const BRANCH_LEVEL = 1;

    /** @var PolicyInterface */
    protected $policy;
    /** @var Pool */
    protected $pool;
    /** @var RepositoryInterface */
    protected $installed;
    /** @var RuleSet */
    protected $rules;
    /** @var RuleSetGenerator */
    protected $ruleSetGenerator;
    /** @var array */
    protected $jobs;

    /** @var int[] */
    protected $updateMap = array();
    /** @var RuleWatchGraph */
    protected $watchGraph;
    /** @var Decisions */
    protected $decisions;
    /** @var int[] */
    protected $installedMap;

    /** @var int */
    protected $propagateIndex;
    /** @var array[] */
    protected $branches = array();
    /** @var Problem[] */
    protected $problems = array();
    /** @var array */
    protected $learnedPool = array();
    /** @var array */
    protected $learnedWhy = array();

    /** @var bool */
    public $testFlagLearnedPositiveLiteral = false;

    /** @var IOInterface */
    protected $io;

    /**
     * @param PolicyInterface     $policy
     * @param Pool                $pool
     * @param RepositoryInterface $installed
     * @param IOInterface         $io
     */
    public function __construct(PolicyInterface $policy, Pool $pool, RepositoryInterface $installed, IOInterface $io)
    {
        $this->io = $io;
        $this->policy = $policy;
        $this->pool = $pool;
        $this->installed = $installed;
        $this->ruleSetGenerator = new RuleSetGenerator($policy, $pool);
    }

    /**
     * @return int
     */
    public function getRuleSetSize()
    {
        return count($this->rules);
    }

    // aka solver_makeruledecisions

    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($literal, 1, $rule);
                continue;
            }

            if ($this->decisions->satisfy($literal)) {
                continue;
            }

            // found a conflict
            if (RuleSet::TYPE_LEARNED === $rule->getType()) {
                $rule->disable();
                continue;
            }

            $conflict = $this->decisions->decisionRule($literal);

            if ($conflict && RuleSet::TYPE_PACKAGE === $conflict->getType()) {
                $problem = new Problem($this->pool);

                $problem->addRule($rule);
                $problem->addRule($conflict);
                $this->disableProblem($rule);
                $this->problems[] = $problem;
                continue;
            }

            // conflict with another job
            $problem = new Problem($this->pool);
            $problem->addRule($rule);
            $problem->addRule($conflict);

            // push all of our rules (can only be job rules)
            // asserting this literal on the problem stack
            foreach ($this->rules->getIteratorFor(RuleSet::TYPE_JOB) as $assertRule) {
                if ($assertRule->isDisabled() || !$assertRule->isAssertion()) {
                    continue;
                }

                $assertRuleLiterals = $assertRule->getLiterals();
                $assertRuleLiteral = $assertRuleLiterals[0];

                if (abs($literal) !== abs($assertRuleLiteral)) {
                    continue;
                }

                $problem->addRule($assertRule);
                $this->disableProblem($assertRule);
            }
            $this->problems[] = $problem;

            $this->decisions->resetToOffset($decisionStart);
            $ruleIndex = -1;
        }
    }

    protected function setupInstalledMap()
    {
        $this->installedMap = array();
        foreach ($this->installed->getPackages() as $package) {
            $this->installedMap[$package->id] = $package;
        }
    }

    /**
     * @param bool $ignorePlatformReqs
     */
    protected function checkForRootRequireProblems($ignorePlatformReqs)
    {
        foreach ($this->jobs as $job) {
            switch ($job['cmd']) {
                case 'update':
                    $packages = $this->pool->whatProvides($job['packageName'], $job['constraint']);
                    foreach ($packages as $package) {
                        if (isset($this->installedMap[$package->id])) {
                            $this->updateMap[$package->id] = true;
                        }
                    }
                    break;

                case 'update-all':
                    foreach ($this->installedMap as $package) {
                        $this->updateMap[$package->id] = true;
                    }
                    break;

                case 'install':
                    if ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $job['packageName'])) {
                        break;
                    }

                    if (!$this->pool->whatProvides($job['packageName'], $job['constraint'])) {
                        $problem = new Problem($this->pool);
                        $problem->addRule(new GenericRule(array(), null, null, $job));
                        $this->problems[] = $problem;
                    }
                    break;
            }
        }
    }

    /**
     * @param  Request $request
     * @param  bool    $ignorePlatformReqs
     * @return array
     */
    public function solve(Request $request, $ignorePlatformReqs = false)
    {
        $this->jobs = $request->getJobs();

        $this->setupInstalledMap();
        $this->rules = $this->ruleSetGenerator->getRulesFor($this->jobs, $this->installedMap, $ignorePlatformReqs);
        $this->checkForRootRequireProblems($ignorePlatformReqs);
        $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 job/update assertions */
        $this->makeAssertionRuleDecisions();

        $this->io->writeError('Resolving dependencies through SAT', true, IOInterface::DEBUG);
        $before = microtime(true);
        $this->runSat(true);
        $this->io->writeError('', true, IOInterface::DEBUG);
        $this->io->writeError(sprintf('Dependency resolution completed in %.3f seconds', microtime(true) - $before), true, IOInterface::VERBOSE);

        // decide to remove everything that's installed and undecided
        foreach ($this->installedMap as $packageId => $void) {
            if ($this->decisions->undecided($packageId)) {
                $this->decisions->decide(-$packageId, 1, null);
            }
        }

        if ($this->problems) {
            throw new SolverProblemsException($this->problems, $this->installedMap);
        }

        $transaction = new Transaction($this->policy, $this->pool, $this->installedMap, $this->decisions);

        return $transaction->getOperations();
    }

    /**
     * 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
     */
    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
     * @param  bool       $disableRules
     * @param  Rule       $rule
     * @return int
     */
    private function setPropagateLearn($level, $literal, $disableRules, Rule $rule)
    {
        $level++;

        $this->decisions->decide($literal, $level, $rule);

        while (true) {
            $rule = $this->propagate($level);

            if (!$rule) {
                break;
            }

            if ($level == 1) {
                return $this->analyzeUnsolvable($rule, $disableRules);
            }

            // conflict
            list($learnLiteral, $newLevel, $newRule, $why) = $this->analyze($level, $rule);

            if ($newLevel <= 0 || $newLevel >= $level) {
                throw new SolverBugException(
                    "Trying to revert to invalid level ".(int) $newLevel." from level ".(int) $level."."
                );
            } elseif (!$newRule) {
                throw new SolverBugException(
                    "No rule was learned from analyzing $rule at level $level."
                );
            }

            $level = $newLevel;

            $this->revert($level);

            $this->rules->add($newRule, RuleSet::TYPE_LEARNED);

            $this->learnedWhy[spl_object_hash($newRule)] = $why;

            $ruleNode = new RuleWatchNode($newRule);
            $ruleNode->watch2OnHighest($this->decisions);
            $this->watchGraph->insert($ruleNode);

            $this->decisions->decide($learnLiteral, $level, $newRule);
        }

        return $level;
    }

    /**
     * @param  int   $level
     * @param  array $decisionQueue
     * @param  bool  $disableRules
     * @param  Rule  $rule
     * @return int
     */
    private function selectAndInstall($level, array $decisionQueue, $disableRules, Rule $rule)
    {
        // choose best package to install from decisionQueue
        $literals = $this->policy->selectPreferredPackages($this->pool, $this->installedMap, $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, $disableRules, $rule);
    }

    /**
     * @param  int   $level
     * @param  Rule  $rule
     * @return array
     */
    protected function analyze($level, Rule $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) {
                // skip the one true literal
                if ($this->decisions->satisfy($literal)) {
                    continue;
                }

                if (isset($seen[abs($literal)])) {
                    continue;
                }
                $seen[abs($literal)] = true;

                $l = $this->decisions->decisionLevel($literal);

                if (1 === $l) {
                    $l1num++;
                } elseif ($level === $l) {
                    $num++;
                } else {
                    // not level1 or conflict level, add to new rule
                    $learnedLiterals[] = $literal;

                    if ($l > $ruleLevel) {
                        $ruleLevel = $l;
                    }
                }
            }

            $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 && 0 === --$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;
                }
            }

            $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($learnedLiterals, Rule::RULE_LEARNED, $why);

        return array($learnedLiterals[0], $ruleLevel, $newRule, $why);
    }

    /**
     * @param Problem $problem
     * @param Rule    $conflictRule
     */
    private function analyzeUnsolvableRule(Problem $problem, Rule $conflictRule)
    {
        if ($conflictRule->getType() == RuleSet::TYPE_LEARNED) {
            $why = spl_object_hash($conflictRule);
            $learnedWhy = $this->learnedWhy[$why];
            $problemRules = $this->learnedPool[$learnedWhy];

            foreach ($problemRules as $problemRule) {
                $this->analyzeUnsolvableRule($problem, $problemRule);
            }

            return;
        }

        if ($conflictRule->getType() == RuleSet::TYPE_PACKAGE) {
            // package rules cannot be part of a problem
            return;
        }

        $problem->nextSection();
        $problem->addRule($conflictRule);
    }

    /**
     * @param  Rule $conflictRule
     * @param  bool $disableRules
     * @return int
     */
    private function analyzeUnsolvable(Rule $conflictRule, $disableRules)
    {
        $problem = new Problem($this->pool);
        $problem->addRule($conflictRule);

        $this->analyzeUnsolvableRule($problem, $conflictRule);

        $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);

            $literals = $why->getLiterals();

            foreach ($literals as $literal) {
                // skip the one true literal
                if ($this->decisions->satisfy($literal)) {
                    continue;
                }
                $seen[abs($literal)] = true;
            }
        }

        if ($disableRules) {
            foreach ($this->problems[count($this->problems) - 1] as $reason) {
                $this->disableProblem($reason['rule']);
            }

            $this->resetSolver();

            return 1;
        }

        return 0;
    }

    /**
     * @param Rule $why
     */
    private function disableProblem(Rule $why)
    {
        $job = $why->getJob();

        if (!$job) {
            $why->disable();

            return;
        }

        // disable all rules of this job
        foreach ($this->rules as $rule) {
            /** @var Rule $rule */
            if ($job === $rule->getJob()) {
                $rule->disable();
            }
        }
    }

    private function resetSolver()
    {
        $this->decisions->reset();

        $this->propagateIndex = 0;
        $this->branches = array();

        $this->enableDisableLearnedRules();
        $this->makeAssertionRuleDecisions();
    }

    /**
     * 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.
     */
    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();
            }
        }
    }

    /**
     * @param bool $disableRules
     */
    private function runSat($disableRules = true)
    {
        $this->propagateIndex = 0;

        /*
         * here's the main loop:
         * 1) propagate new decisions (only needed once)
         * 2) fulfill jobs
         * 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
         */

        $decisionQueue = array();
        $decisionSupplementQueue = array();
        /**
         * @todo this makes $disableRules always false; determine the rationale and possibly remove dead code?
         */
        $disableRules = array();

        $level = 1;
        $systemLevel = $level + 1;
        $installedPos = 0;

        while (true) {
            if (1 === $level) {
                $conflictRule = $this->propagate($level);
                if (null !== $conflictRule) {
                    if ($this->analyzeUnsolvable($conflictRule, $disableRules)) {
                        continue;
                    }

                    return;
                }
            }

            // handle job rules
            if ($level < $systemLevel) {
                $iterator = $this->rules->getIteratorFor(RuleSet::TYPE_JOB);
                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 > 0 && $this->decisions->undecided($literal)) {
                                $decisionQueue[] = $literal;
                            }
                        }

                        if ($noneSatisfied && count($decisionQueue)) {
                            // prune all update packages until installed version
                            // except for requested updates
                            if (count($this->installed) != count($this->updateMap)) {
                                $prunedQueue = array();
                                foreach ($decisionQueue as $literal) {
                                    if (isset($this->installedMap[abs($literal)])) {
                                        $prunedQueue[] = $literal;
                                        if (isset($this->updateMap[abs($literal)])) {
                                            $prunedQueue = $decisionQueue;
                                            break;
                                        }
                                    }
                                }
                                $decisionQueue = $prunedQueue;
                            }
                        }

                        if ($noneSatisfied && count($decisionQueue)) {
                            $oLevel = $level;
                            $level = $this->selectAndInstall($level, $decisionQueue, $disableRules, $rule);

                            if (0 === $level) {
                                return;
                            }
                            if ($level <= $oLevel) {
                                break;
                            }
                        }
                    }
                }

                $systemLevel = $level + 1;

                // jobs left
                $iterator->next();
                if ($iterator->valid()) {
                    continue;
                }
            }

            if ($level < $systemLevel) {
                $systemLevel = $level;
            }

            $rulesCount = count($this->rules);
            $pass = 1;

            $this->io->writeError('Looking at all rules.', true, IOInterface::DEBUG);
            for ($i = 0, $n = 0; $n < $rulesCount; $i++, $n++) {
                if ($i == $rulesCount) {
                    if (1 === $pass) {
                        $this->io->writeError("Something's changed, looking at all rules again (pass #$pass)", false, IOInterface::DEBUG);
                    } else {
                        $this->io->overwriteError("Something's changed, looking at all rules again (pass #$pass)", false, null, IOInterface::DEBUG);
                    }

                    $i = 0;
                    $pass++;
                }

                $rule = $this->rules->ruleById[$i];
                $literals = $rule->getLiterals();

                if ($rule->isDisabled()) {
                    continue;
                }

                $decisionQueue = 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, $disableRules, $rule);

                if (0 === $level) {
                    return;
                }

                // something changed, so look at all rules again
                $rulesCount = count($this->rules);
                $n = -1;
            }

            if ($level < $systemLevel) {
                continue;
            }

            // minimization step
            if (count($this->branches)) {
                $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 > 0 && $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, $disableRules, $why);

                    if ($level == 0) {
                        return;
                    }

                    continue;
                }
            }

            break;
        }
    }
}
<?php

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

namespace Composer\DependencyResolver;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
class SolverBugException extends \RuntimeException
{
    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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

use Composer\Package\PackageInterface;
use Composer\Package\AliasPackage;
use Composer\Package\BasePackage;
use Composer\Semver\Constraint\Constraint;

/**
 * @author Nils Adermann <naderman@naderman.de>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class DefaultPolicy implements PolicyInterface
{
    private $preferStable;
    private $preferLowest;

    public function __construct($preferStable = false, $preferLowest = false)
    {
        $this->preferStable = $preferStable;
        $this->preferLowest = $preferLowest;
    }

    public function versionCompare(PackageInterface $a, PackageInterface $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($version, true);
    }

    public function findUpdatePackages(Pool $pool, array $installedMap, PackageInterface $package, $mustMatchName = false)
    {
        $packages = array();

        foreach ($pool->whatProvides($package->getName(), null, $mustMatchName) as $candidate) {
            if ($candidate !== $package) {
                $packages[] = $candidate;
            }
        }

        return $packages;
    }

    public function getPriority(Pool $pool, PackageInterface $package)
    {
        return $pool->getPriority($package->getRepository());
    }

    public function selectPreferredPackages(Pool $pool, array $installedMap, array $literals, $requiredPackage = null)
    {
        $packages = $this->groupLiteralsByNamePreferInstalled($pool, $installedMap, $literals);

        foreach ($packages as &$literals) {
            $policy = $this;
            usort($literals, function ($a, $b) use ($policy, $pool, $installedMap, $requiredPackage) {
                return $policy->compareByPriorityPreferInstalled($pool, $installedMap, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage, true);
            });
        }

        foreach ($packages as &$literals) {
            $literals = $this->pruneToHighestPriorityOrInstalled($pool, $installedMap, $literals);

            $literals = $this->pruneToBestVersion($pool, $literals);

            $literals = $this->pruneRemoteAliases($pool, $literals);
        }

        $selected = call_user_func_array('array_merge', $packages);

        // now sort the result across all packages to respect replaces across packages
        usort($selected, function ($a, $b) use ($policy, $pool, $installedMap, $requiredPackage) {
            return $policy->compareByPriorityPreferInstalled($pool, $installedMap, $pool->literalToPackage($a), $pool->literalToPackage($b), $requiredPackage);
        });

        return $selected;
    }

    protected function groupLiteralsByNamePreferInstalled(Pool $pool, array $installedMap, $literals)
    {
        $packages = array();
        foreach ($literals as $literal) {
            $packageName = $pool->literalToPackage($literal)->getName();

            if (!isset($packages[$packageName])) {
                $packages[$packageName] = array();
            }

            if (isset($installedMap[abs($literal)])) {
                array_unshift($packages[$packageName], $literal);
            } else {
                $packages[$packageName][] = $literal;
            }
        }

        return $packages;
    }

    /**
     * @protected
     */
    public function compareByPriorityPreferInstalled(Pool $pool, array $installedMap, PackageInterface $a, PackageInterface $b, $requiredPackage = null, $ignoreReplace = false)
    {
        if ($a->getRepository() === $b->getRepository()) {
            // 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($requiredPackage, 0, $pos);

                    $aIsSameVendor = substr($a->getName(), 0, $pos) === $requiredVendor;
                    $bIsSameVendor = substr($b->getName(), 0, $pos) === $requiredVendor;

                    if ($bIsSameVendor !== $aIsSameVendor) {
                        return $aIsSameVendor ? -1 : 1;
                    }
                }
            }

            // priority equal, sort by package id to make reproducible
            if ($a->id === $b->id) {
                return 0;
            }

            return ($a->id < $b->id) ? -1 : 1;
        }

        if (isset($installedMap[$a->id])) {
            return -1;
        }

        if (isset($installedMap[$b->id])) {
            return 1;
        }

        return ($this->getPriority($pool, $a) > $this->getPriority($pool, $b)) ? -1 : 1;
    }

    /**
     * Checks if source replaces a package with the same name as target.
     *
     * Replace constraints are ignored. This method should only be used for
     * prioritisation, not for actual constraint verification.
     *
     * @param  PackageInterface $source
     * @param  PackageInterface $target
     * @return bool
     */
    protected function replaces(PackageInterface $source, PackageInterface $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;
    }

    protected function pruneToBestVersion(Pool $pool, $literals)
    {
        $operator = $this->preferLowest ? '<' : '>';
        $bestLiterals = array($literals[0]);
        $bestPackage = $pool->literalToPackage($literals[0]);
        foreach ($literals as $i => $literal) {
            if (0 === $i) {
                continue;
            }

            $package = $pool->literalToPackage($literal);

            if ($this->versionCompare($package, $bestPackage, $operator)) {
                $bestPackage = $package;
                $bestLiterals = array($literal);
            } elseif ($this->versionCompare($package, $bestPackage, '==')) {
                $bestLiterals[] = $literal;
            }
        }

        return $bestLiterals;
    }

    /**
     * Assumes that installed packages come first and then all highest priority packages
     */
    protected function pruneToHighestPriorityOrInstalled(Pool $pool, array $installedMap, array $literals)
    {
        $selected = array();

        $priority = null;

        foreach ($literals as $literal) {
            $package = $pool->literalToPackage($literal);

            if (isset($installedMap[$package->id])) {
                $selected[] = $literal;
                continue;
            }

            if (null === $priority) {
                $priority = $this->getPriority($pool, $package);
            }

            if ($this->getPriority($pool, $package) != $priority) {
                break;
            }

            $selected[] = $literal;
        }

        return $selected;
    }

    /**
     * Assumes that locally aliased (in root package requires) packages take priority over branch-alias ones
     *
     * If no package is a local alias, nothing happens
     */
    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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\DependencyResolver;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
class RuleSet implements \IteratorAggregate, \Countable
{
    // highest priority => lowest number
    const TYPE_PACKAGE = 0;
    const TYPE_JOB = 1;
    const TYPE_LEARNED = 4;

    /**
     * READ-ONLY: Lookup table for rule id to rule object
     *
     * @var Rule[]
     */
    public $ruleById;

    protected static $types = array(
        255 => 'UNKNOWN',
        self::TYPE_PACKAGE => 'PACKAGE',
        self::TYPE_JOB => 'JOB',
        self::TYPE_LEARNED => 'LEARNED',
    );

    protected $rules;
    protected $nextRuleId;

    protected $rulesByHash;

    public function __construct()
    {
        $this->nextRuleId = 0;

        foreach ($this->getTypes() as $type) {
            $this->rules[$type] = array();
        }

        $this->rulesByHash = array();
    }

    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);
        }
    }

    public function count()
    {
        return $this->nextRuleId;
    }

    public function ruleById($id)
    {
        return $this->ruleById[$id];
    }

    public function getRules()
    {
        return $this->rules;
    }

    public function getIterator()
    {
        return new RuleSetIterator($this->getRules());
    }

    public function getIteratorFor($types)
    {
        if (!is_array($types)) {
            $types = array($types);
        }

        $allRules = $this->getRules();
        $rules = array();

        foreach ($types as $type) {
            $rules[$type] = $allRules[$type];
        }

        return new RuleSetIterator($rules);
    }

    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);
    }

    public function getTypes()
    {
        $types = self::$types;
        unset($types[255]);

        return array_keys($types);
    }

    public function getPrettyString(Pool $pool = null)
    {
        $string = "\n";
        foreach ($this->rules as $type => $rules) {
            $string .= str_pad(self::$types[$type], 8, ' ') . ": ";
            foreach ($rules as $rule) {
                $string .= ($pool ? $rule->getPrettyString($pool) : $rule)."\n";
            }
            $string .= "\n\n";
        }

        return $string;
    }

    public function __toString()
    {
        return $this->getPrettyString(null);
    }
}
<?php

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

namespace Composer\DependencyResolver;

use Composer\Semver\Constraint\ConstraintInterface;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
class Request
{
    protected $jobs;

    public function __construct()
    {
        $this->jobs = array();
    }

    public function install($packageName, ConstraintInterface $constraint = null)
    {
        $this->addJob($packageName, 'install', $constraint);
    }

    public function update($packageName, ConstraintInterface $constraint = null)
    {
        $this->addJob($packageName, 'update', $constraint);
    }

    public function remove($packageName, ConstraintInterface $constraint = null)
    {
        $this->addJob($packageName, 'remove', $constraint);
    }

    /**
     * Mark an existing package as being installed and having to remain installed
     *
     * These jobs will not be tempered with by the solver
     *
     * @param string                   $packageName
     * @param ConstraintInterface|null $constraint
     */
    public function fix($packageName, ConstraintInterface $constraint = null)
    {
        $this->addJob($packageName, 'install', $constraint, true);
    }

    protected function addJob($packageName, $cmd, ConstraintInterface $constraint = null, $fixed = false)
    {
        $packageName = strtolower($packageName);

        $this->jobs[] = array(
            'cmd' => $cmd,
            'packageName' => $packageName,
            'constraint' => $constraint,
            'fixed' => $fixed,
        );
    }

    public function updateAll()
    {
        $this->jobs[] = array('cmd' => 'update-all');
    }

    public function getJobs()
    {
        return $this->jobs;
    }
}
<?php

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

namespace Composer\DependencyResolver;

/**
 * An extension of SplDoublyLinkedList with seek and removal of current element
 *
 * SplDoublyLinkedList only allows deleting a particular offset and has no
 * method to set the internal iterator to a particular offset.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class RuleWatchChain extends \SplDoublyLinkedList
{
    protected $offset = 0;

    /**
     * Moves the internal iterator to the specified offset
     *
     * @param int $offset The offset to seek to.
     */
    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.
     */
    public function remove()
    {
        $offset = $this->key();
        $this->offsetUnset($offset);
        $this->seek($offset);
    }
}
<?php

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

namespace Composer\DependencyResolver;

use Composer\Util\IniHelper;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
class SolverProblemsException extends \RuntimeException
{
    protected $problems;
    protected $installedMap;

    public function __construct(array $problems, array $installedMap)
    {
        $this->problems = $problems;
        $this->installedMap = $installedMap;

        parent::__construct($this->createMessage(), 2);
    }

    protected function createMessage()
    {
        $text = "\n";
        $hasExtensionProblems = false;
        foreach ($this->problems as $i => $problem) {
            $text .= "  Problem ".($i + 1).$problem->getPrettyString($this->installedMap)."\n";

            if (!$hasExtensionProblems && $this->hasExtensionProblems($problem->getReasons())) {
                $hasExtensionProblems = true;
            }
        }

        if (strpos($text, 'could not be found') || strpos($text, 'no matching package found')) {
            $text .= "\nPotential 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 ($hasExtensionProblems) {
            $text .= $this->createExtensionHint();
        }

        return $text;
    }

    public function getProblems()
    {
        return $this->problems;
    }

    private function createExtensionHint()
    {
        $paths = IniHelper::getAll();

        if (count($paths) === 1 && empty($paths[0])) {
            return '';
        }

        $text = "\n  To enable extensions, verify that they are enabled in your .ini files:\n    - ";
        $text .= implode("\n    - ", $paths);
        $text .= "\n  You can also run `php --ini` inside terminal to see which files are used by PHP in CLI mode.";

        return $text;
    }

    private function hasExtensionProblems(array $reasonSets)
    {
        foreach ($reasonSets as $reasonSet) {
            foreach ($reasonSet as $reason) {
                if (isset($reason["rule"]) && 0 === strpos($reason["rule"]->getRequiredPackage(), 'ext-')) {
                    return true;
                }
            }
        }

        return false;
    }
}
<?php

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

namespace Composer\DependencyResolver;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
class RuleSetIterator implements \Iterator
{
    protected $rules;
    protected $types;

    protected $currentOffset;
    protected $currentType;
    protected $currentTypeOffset;

    public function __construct(array $rules)
    {
        $this->rules = $rules;
        $this->types = array_keys($rules);
        sort($this->types);

        $this->rewind();
    }

    public function current()
    {
        return $this->rules[$this->currentType][$this->currentOffset];
    }

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

    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]));
        }
    }

    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]));
    }

    public function valid()
    {
        return isset($this->rules[$this->currentType])
               && isset($this->rules[$this->currentType][$this->currentOffset]);
    }
}
<?php

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

namespace Composer\DependencyResolver\Operation;

use Composer\Package\PackageInterface;

/**
 * Solver install operation.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class InstallOperation extends SolverOperation
{
    protected $package;

    /**
     * Initializes operation.
     *
     * @param PackageInterface $package package instance
     * @param string           $reason  operation reason
     */
    public function __construct(PackageInterface $package, $reason = null)
    {
        parent::__construct($reason);

        $this->package = $package;
    }

    /**
     * Returns package instance.
     *
     * @return PackageInterface
     */
    public function getPackage()
    {
        return $this->package;
    }

    /**
     * Returns job type.
     *
     * @return string
     */
    public function getJobType()
    {
        return 'install';
    }

    /**
     * {@inheritDoc}
     */
    public function __toString()
    {
        return 'Installing '.$this->package->getPrettyName().' ('.$this->formatVersion($this->package).')';
    }
}
<?php

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

namespace Composer\DependencyResolver\Operation;

use Composer\Package\PackageInterface;

/**
 * Solver update operation.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class UpdateOperation extends SolverOperation
{
    protected $initialPackage;
    protected $targetPackage;

    /**
     * Initializes update operation.
     *
     * @param PackageInterface $initial initial package
     * @param PackageInterface $target  target package (updated)
     * @param string           $reason  update reason
     */
    public function __construct(PackageInterface $initial, PackageInterface $target, $reason = null)
    {
        parent::__construct($reason);

        $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;
    }

    /**
     * Returns job type.
     *
     * @return string
     */
    public function getJobType()
    {
        return 'update';
    }

    /**
     * {@inheritDoc}
     */
    public function __toString()
    {
        return 'Updating '.$this->initialPackage->getPrettyName().' ('.$this->formatVersion($this->initialPackage).') to '.
            $this->targetPackage->getPrettyName(). ' ('.$this->formatVersion($this->targetPackage).')';
    }
}
<?php

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

namespace Composer\DependencyResolver\Operation;

use Composer\Package\AliasPackage;
use Composer\Package\PackageInterface;

/**
 * Solver install operation.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class MarkAliasUninstalledOperation extends SolverOperation
{
    protected $package;

    /**
     * Initializes operation.
     *
     * @param AliasPackage $package package instance
     * @param string       $reason  operation reason
     */
    public function __construct(AliasPackage $package, $reason = null)
    {
        parent::__construct($reason);

        $this->package = $package;
    }

    /**
     * Returns package instance.
     *
     * @return PackageInterface
     */
    public function getPackage()
    {
        return $this->package;
    }

    /**
     * Returns job type.
     *
     * @return string
     */
    public function getJobType()
    {
        return 'markAliasUninstalled';
    }

    /**
     * {@inheritDoc}
     */
    public function __toString()
    {
        return 'Marking '.$this->package->getPrettyName().' ('.$this->formatVersion($this->package).') as uninstalled, alias of '.$this->package->getAliasOf()->getPrettyName().' ('.$this->formatVersion($this->package->getAliasOf()).')';
    }
}
<?php

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

namespace Composer\DependencyResolver\Operation;

use Composer\Package\PackageInterface;

/**
 * Solver uninstall operation.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class UninstallOperation extends SolverOperation
{
    protected $package;

    /**
     * Initializes operation.
     *
     * @param PackageInterface $package package instance
     * @param string           $reason  operation reason
     */
    public function __construct(PackageInterface $package, $reason = null)
    {
        parent::__construct($reason);

        $this->package = $package;
    }

    /**
     * Returns package instance.
     *
     * @return PackageInterface
     */
    public function getPackage()
    {
        return $this->package;
    }

    /**
     * Returns job type.
     *
     * @return string
     */
    public function getJobType()
    {
        return 'uninstall';
    }

    /**
     * {@inheritDoc}
     */
    public function __toString()
    {
        return 'Uninstalling '.$this->package->getPrettyName().' ('.$this->formatVersion($this->package).')';
    }
}
<?php

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

namespace Composer\DependencyResolver\Operation;

/**
 * Solver operation interface.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
interface OperationInterface
{
    /**
     * Returns job type.
     *
     * @return string
     */
    public function getJobType();

    /**
     * Returns operation reason.
     *
     * @return string
     */
    public function getReason();

    /**
     * Serializes the operation in a human readable format
     *
     * @return string
     */
    public function __toString();
}
<?php

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

namespace Composer\DependencyResolver\Operation;

use Composer\Package\PackageInterface;

/**
 * Abstract solver operation class.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
abstract class SolverOperation implements OperationInterface
{
    protected $reason;

    /**
     * Initializes operation.
     *
     * @param string $reason operation reason
     */
    public function __construct($reason = null)
    {
        $this->reason = $reason;
    }

    /**
     * Returns operation reason.
     *
     * @return string
     */
    public function getReason()
    {
        return $this->reason;
    }

    protected function formatVersion(PackageInterface $package)
    {
        return $package->getFullPrettyVersion();
    }
}
<?php

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

namespace Composer\DependencyResolver\Operation;

use Composer\Package\AliasPackage;
use Composer\Package\PackageInterface;

/**
 * Solver install operation.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class MarkAliasInstalledOperation extends SolverOperation
{
    protected $package;

    /**
     * Initializes operation.
     *
     * @param AliasPackage $package package instance
     * @param string       $reason  operation reason
     */
    public function __construct(AliasPackage $package, $reason = null)
    {
        parent::__construct($reason);

        $this->package = $package;
    }

    /**
     * Returns package instance.
     *
     * @return PackageInterface
     */
    public function getPackage()
    {
        return $this->package;
    }

    /**
     * Returns job type.
     *
     * @return string
     */
    public function getJobType()
    {
        return 'markAliasInstalled';
    }

    /**
     * {@inheritDoc}
     */
    public function __toString()
    {
        return 'Marking '.$this->package->getPrettyName().' ('.$this->formatVersion($this->package).') as installed, alias of '.$this->package->getAliasOf()->getPrettyName().' ('.$this->formatVersion($this->package->getAliasOf()).')';
    }
}
<?php

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

namespace Composer\DependencyResolver;

use Composer\Package\PackageInterface;
use Composer\Package\Link;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
class Rule2Literals extends Rule
{
    protected $literal1;
    protected $literal2;

    /**
     * @param int                   $literal1
     * @param int                   $literal2
     * @param int                   $reason     A RULE_* constant describing the reason for generating this rule
     * @param Link|PackageInterface $reasonData
     * @param array                 $job        The job this rule was created from
     */
    public function __construct($literal1, $literal2, $reason, $reasonData, $job = null)
    {
        parent::__construct($reason, $reasonData, $job);

        if ($literal1 < $literal2) {
            $this->literal1 = $literal1;
            $this->literal2 = $literal2;
        } else {
            $this->literal1 = $literal2;
            $this->literal2 = $literal1;
        }
    }

    public function getLiterals()
    {
        return array($this->literal1, $this->literal2);
    }

    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 (2 != count($literals)) {
            return false;
        }

        if ($this->literal1 !== $literals[0]) {
            return false;
        }

        if ($this->literal2 !== $literals[1]) {
            return false;
        }

        return true;
    }

    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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class RootAliasPackage extends AliasPackage implements RootPackageInterface
{
    public function __construct(RootPackageInterface $aliasOf, $version, $prettyVersion)
    {
        parent::__construct($aliasOf, $version, $prettyVersion);
    }

    /**
     * {@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($require, 'requires');

        $this->aliasOf->setRequires($require);
    }

    /**
     * {@inheritDoc}
     */
    public function setDevRequires(array $devRequire)
    {
        $this->devRequires = $this->replaceSelfVersionDependencies($devRequire, 'devRequires');

        $this->aliasOf->setDevRequires($devRequire);
    }

    /**
     * {@inheritDoc}
     */
    public function setConflicts(array $conflicts)
    {
        $this->conflicts = $this->replaceSelfVersionDependencies($conflicts, 'conflicts');
        $this->aliasOf->setConflicts($conflicts);
    }

    /**
     * {@inheritDoc}
     */
    public function setProvides(array $provides)
    {
        $this->provides = $this->replaceSelfVersionDependencies($provides, 'provides');
        $this->aliasOf->setProvides($provides);
    }

    /**
     * {@inheritDoc}
     */
    public function setReplaces(array $replaces)
    {
        $this->replaces = $this->replaceSelfVersionDependencies($replaces, 'replaces');
        $this->aliasOf->setReplaces($replaces);
    }

    /**
     * {@inheritDoc}
     */
    public function setRepositories($repositories)
    {
        $this->aliasOf->setRepositories($repositories);
    }

    /**
     * {@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 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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Archiver;

/**
 * An exclude filter which processes composer's own exclude rules
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class ComposerExcludeFilter extends BaseExcludeFilter
{
    /**
     * @param string $sourcePath   Directory containing sources to be filtered
     * @param array  $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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Archiver;

use Symfony\Component\Finder;

/**
 * An exclude filter that processes hgignore files
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class HgExcludeFilter extends BaseExcludeFilter
{
    const HG_IGNORE_REGEX = 1;
    const HG_IGNORE_GLOB = 2;

    /**
     * Either HG_IGNORE_REGEX or HG_IGNORE_GLOB
     * @var int
     */
    protected $patternMode;

    /**
     * Parses .hgignore file if it exist
     *
     * @param string $sourcePath
     */
    public function __construct($sourcePath)
    {
        parent::__construct($sourcePath);

        $this->patternMode = self::HG_IGNORE_REGEX;

        if (file_exists($sourcePath.'/.hgignore')) {
            $this->excludePatterns = $this->parseLines(
                file($sourcePath.'/.hgignore'),
                array($this, 'parseHgIgnoreLine')
            );
        }
    }

    /**
     * Callback line parser which process hgignore lines
     *
     * @param string $line A line from .hgignore
     *
     * @return array|null An exclude pattern for filter()
     */
    public function parseHgIgnoreLine($line)
    {
        if (preg_match('#^syntax\s*:\s*(glob|regexp)$#', $line, $matches)) {
            if ($matches[1] === 'glob') {
                $this->patternMode = self::HG_IGNORE_GLOB;
            } else {
                $this->patternMode = self::HG_IGNORE_REGEX;
            }

            return null;
        }

        if ($this->patternMode == self::HG_IGNORE_GLOB) {
            return $this->patternFromGlob($line);
        }

        return $this->patternFromRegex($line);
    }

    /**
     * Generates an exclude pattern for filter() from a hg glob expression
     *
     * @param string $line A line from .hgignore in glob mode
     *
     * @return array An exclude pattern for filter()
     */
    protected function patternFromGlob($line)
    {
        $pattern = '#'.substr(Finder\Glob::toRegex($line), 2, -1).'#';
        $pattern = str_replace('[^/]*', '.*', $pattern);

        return array($pattern, false, true);
    }

    /**
     * Generates an exclude pattern for filter() from a hg regexp expression
     *
     * @param string $line A line from .hgignore in regexp mode
     *
     * @return array An exclude pattern for filter()
     */
    public function patternFromRegex($line)
    {
        // WTF need to escape the delimiter safely
        $pattern = '#'.preg_replace('/((?:\\\\\\\\)*)(\\\\?)#/', '\1\2\2\\#', $line).'#';

        return array($pattern, false, true);
    }
}
<?php

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

namespace Composer\Package\Archiver;

/**
 * @author Till Klampaeckel <till@php.net>
 * @author Matthieu Moquet <matthieu@moquet.net>
 * @author Nils Adermann <naderman@naderman.de>
 */
interface ArchiverInterface
{
    /**
     * Create an archive from the sources.
     *
     * @param string $sources  The sources directory
     * @param string $target   The target file
     * @param string $format   The format used for archive
     * @param array  $excludes A list of patterns for files to exclude
     *
     * @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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Archiver;

use FilterIterator;
use PharData;

class ArchivableFilesFilter extends FilterIterator
{
    private $dirs = array();

    /**
     * @return bool true if the current element is acceptable, otherwise false.
     */
    public function accept()
    {
        $file = $this->getInnerIterator()->current();
        if ($file->isDir()) {
            $this->dirs[] = (string) $file;

            return false;
        }

        return true;
    }

    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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Archiver;

use Symfony\Component\Finder;

/**
 * @author Nils Adermann <naderman@naderman.de>
 */
abstract class BaseExcludeFilter
{
    /**
     * @var string
     */
    protected $sourcePath;

    /**
     * @var array
     */
    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($relativePath, 1);
            } else {
                $path = $relativePath;
            }

            if (preg_match($pattern, $path)) {
                $exclude = !$negate;
            }
        }

        return $exclude;
    }

    /**
     * Processes a file containing exclude rules of different formats per line
     *
     * @param array    $lines      A set of lines to be parsed
     * @param callable $lineParser The parser to be used on each line
     *
     * @return array 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 || 0 === 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 array $rules A list of exclude rules in gitignore syntax
     *
     * @return array 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 An exclude pattern
     */
    protected function generatePattern($rule)
    {
        $negate = false;
        $pattern = '{';

        if (strlen($rule) && $rule[0] === '!') {
            $negate = true;
            $rule = substr($rule, 1);
        }

        if (strlen($rule) && $rule[0] === '/') {
            $pattern .= '^/';
            $rule = substr($rule, 1);
        } elseif (strlen($rule) - 1 === strpos($rule, '/')) {
            $pattern .= '/';
            $rule = substr($rule, 0, -1);
        } elseif (false === strpos($rule, '/')) {
            $pattern .= '/';
        }

        // remove delimiters as well as caret (^) and dollar sign ($) from the regex
        $pattern .= substr(Finder\Glob::toRegex($rule), 2, -2) . '(?=$|/)';

        return array($pattern . '}', $negate, false);
    }
}
<?php

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

namespace Composer\Package\Archiver;

use ZipArchive;
use Composer\Util\Filesystem;

/**
 * @author Jan Prieser <jan@prieser.net>
 */
class ZipArchiver implements ArchiverInterface
{
    protected static $formats = array(
        'zip' => 1,
    );

    /**
     * {@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($target, ZipArchive::CREATE);
        if ($res === true) {
            $files = new ArchivableFilesFinder($sources, $excludes, $ignoreFilters);
            foreach ($files as $file) {
                /** @var \SplFileInfo $file */
                $filepath = strtr($file->getPath()."/".$file->getFilename(), '\\', '/');
                $localname = str_replace($sources.'/', '', $filepath);
                if ($file->isDir()) {
                    $zip->addEmptyDir($localname);
                } else {
                    $zip->addFile($filepath, $localname);
                }
            }
            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();
    }

    private function compressionAvailable()
    {
        return class_exists('ZipArchive');
    }
}
<?php

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

namespace Composer\Package\Archiver;

/**
 * An exclude filter that processes gitignore and gitattributes
 *
 * It respects export-ignore git attributes
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class GitExcludeFilter extends BaseExcludeFilter
{
    /**
     * Parses .gitignore and .gitattributes files if they exist
     *
     * @param string $sourcePath
     */
    public function __construct($sourcePath)
    {
        parent::__construct($sourcePath);

        if (file_exists($sourcePath.'/.gitignore')) {
            $this->excludePatterns = $this->parseLines(
                file($sourcePath.'/.gitignore'),
                array($this, 'parseGitIgnoreLine')
            );
        }
        if (file_exists($sourcePath.'/.gitattributes')) {
            $this->excludePatterns = array_merge(
                $this->excludePatterns,
                $this->parseLines(
                    file($sourcePath.'/.gitattributes'),
                    array($this, 'parseGitAttributesLine')
                )
            );
        }
    }

    /**
     * Callback line parser which process gitignore lines
     *
     * @param string $line A line from .gitignore
     *
     * @return array An exclude pattern for filter()
     */
    public function parseGitIgnoreLine($line)
    {
        return $this->generatePattern($line);
    }

    /**
     * Callback parser which finds export-ignore rules in git attribute lines
     *
     * @param string $line A line from .gitattributes
     *
     * @return array|null An exclude pattern for filter()
     */
    public function parseGitAttributesLine($line)
    {
        $parts = preg_split('#\s+#', $line);

        if (count($parts) == 2 && $parts[1] === 'export-ignore') {
            return $this->generatePattern($parts[0]);
        }

        return null;
    }
}
<?php

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

namespace Composer\Package\Archiver;

use Composer\Downloader\DownloadManager;
use Composer\Package\PackageInterface;
use Composer\Package\RootPackageInterface;
use Composer\Util\Filesystem;
use Composer\Json\JsonFile;

/**
 * @author Matthieu Moquet <matthieu@moquet.net>
 * @author Till Klampaeckel <till@php.net>
 */
class ArchiveManager
{
    protected $downloadManager;

    protected $archivers = array();

    /**
     * @var bool
     */
    protected $overwriteFiles = true;

    /**
     * @param DownloadManager $downloadManager A manager used to download package sources
     */
    public function __construct(DownloadManager $downloadManager)
    {
        $this->downloadManager = $downloadManager;
    }

    /**
     * @param ArchiverInterface $archiver
     */
    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 PackageInterface $package The package to get a name for
     *
     * @return string A filename without an extension
     */
    public function getPackageFilename(PackageInterface $package)
    {
        $nameParts = array(preg_replace('#[^a-z0-9-_]#i', '-', $package->getName()));

        if (preg_match('{^[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()), 0, 6);
        }

        $name = implode('-', array_filter($nameParts, function ($p) {
            return !empty($p);
        }));

        return str_replace('/', '-', $name);
    }

    /**
     * Create an archive of the specified package.
     *
     * @param  PackageInterface          $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(PackageInterface $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 (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;
        }

        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
                $this->downloadManager->download($package, $sourcePath);
            } 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']['exclude'])) {
                    $package->setArchiveExcludes($jsonData['archive']['exclude']);
                }
            }
        }

        // 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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Archiver;

use Composer\Util\Filesystem;
use 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 <naderman@naderman.de>
 */
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 array  $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($sources);

        if ($ignoreFilters) {
            $filters = array();
        } else {
            $filters = array(
                new HgExcludeFilter($sources),
                new GitExcludeFilter($sources),
                new ComposerExcludeFilter($sources, $excludes),
            );
        }

        $this->finder = new Finder();

        $filter = function (\SplFileInfo $file) use ($sources, $filters, $fs) {
            if ($file->isLink() && strpos($file->getLinkTarget(), $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);

        parent::__construct($this->finder->getIterator());
    }

    public function accept()
    {
        /** @var SplFileInfo $current */
        $current = $this->getInnerIterator()->current();

        if (!$current->isDir()) {
            return true;
        }

        $iterator = new FilesystemIterator($current, FilesystemIterator::SKIP_DOTS);

        return !$iterator->valid();
    }
}
<?php

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

namespace Composer\Package\Archiver;

/**
 * @author Till Klampaeckel <till@php.net>
 * @author Nils Adermann <naderman@naderman.de>
 * @author Matthieu Moquet <matthieu@moquet.net>
 */
class PharArchiver implements ArchiverInterface
{
    protected static $formats = array(
        'zip' => \Phar::ZIP,
        'tar' => \Phar::TAR,
        'tar.gz' => \Phar::TAR,
        'tar.bz2' => \Phar::TAR,
    );

    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($target, 0, strrpos($target, $format) - 1);

            // Check if compress format
            if (isset(static::$compressFormats[$format])) {
                // Current compress format supported base on tar
                $target = $filename . '.tar';
            }

            $phar = new \PharData($target, null, null, 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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\LinkConstraint;

use Composer\Semver\Constraint\ConstraintInterface;

trigger_error('The ' . __NAMESPACE__ . '\LinkConstraintInterface interface is deprecated, use Composer\Semver\Constraint\ConstraintInterface instead.', E_USER_DEPRECATED);

/**
 * @deprecated use Composer\Semver\Constraint\ConstraintInterface instead
 */
interface LinkConstraintInterface extends ConstraintInterface
{
}
<?php

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

namespace Composer\Package\LinkConstraint;

use Composer\Semver\Constraint\Constraint;

trigger_error('The ' . __NAMESPACE__ . '\VersionConstraint class is deprecated, use Composer\Semver\Constraint\Constraint instead.', E_USER_DEPRECATED);

/**
 * @deprecated use Composer\Semver\Constraint\Constraint instead
 */
class VersionConstraint extends Constraint implements LinkConstraintInterface
{
}
<?php

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

namespace Composer\Package\LinkConstraint;

use Composer\Semver\Constraint\AbstractConstraint;

trigger_error('The ' . __NAMESPACE__ . '\SpecificConstraint abstract class is deprecated, there is no replacement for it.', E_USER_DEPRECATED);

/**
 * @deprecated use Composer\Semver\Constraint\AbstractConstraint instead
 */
abstract class SpecificConstraint extends AbstractConstraint implements LinkConstraintInterface
{
}
<?php

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

namespace Composer\Package\LinkConstraint;

use Composer\Semver\Constraint\EmptyConstraint as SemverEmptyConstraint;

trigger_error('The ' . __NAMESPACE__ . '\EmptyConstraint class is deprecated, use Composer\Semver\Constraint\EmptyConstraint instead.', E_USER_DEPRECATED);

/**
 * @deprecated use Composer\Semver\Constraint\EmptyConstraint instead
 */
class EmptyConstraint extends SemverEmptyConstraint implements LinkConstraintInterface
{
}
<?php

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

namespace Composer\Package\LinkConstraint;

use Composer\Semver\Constraint\MultiConstraint as SemverMultiConstraint;

trigger_error('The ' . __NAMESPACE__ . '\MultiConstraint class is deprecated, use Composer\Semver\Constraint\MultiConstraint instead.', E_USER_DEPRECATED);

/**
 * @deprecated use Composer\Semver\Constraint\MultiConstraint instead
 */
class MultiConstraint extends SemverMultiConstraint implements LinkConstraintInterface
{
}
<?php

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

namespace Composer\Package\Version;

use Composer\Config;
use Composer\Repository\Vcs\HgDriver;
use Composer\IO\NullIO;
use Composer\Semver\VersionParser as SemverVersionParser;
use Composer\Util\Git as GitUtil;
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 <j.boggiano@seld.be>
 * @author Samuel Roze <samuel.roze@gmail.com>
 */
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 $config, ProcessExecutor $process, SemverVersionParser $versionParser)
    {
        $this->config = $config;
        $this->process = $process;
        $this->versionParser = $versionParser;
    }

    /**
     * @param array  $packageConfig
     * @param string $path          Path to guess into
     *
     * @return null|array versionData, 'version', 'pretty_version' and 'commit' keys
     */
    public function guessVersion(array $packageConfig, $path)
    {
        if (function_exists('proc_open')) {
            $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($packageConfig, $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);
            }
        }
    }

    private function postprocess(array $versionData)
    {
        if ('-dev' === substr($versionData['version'], -4) && preg_match('{\.9{7}}', $versionData['version'])) {
            $versionData['pretty_version'] = preg_replace('{(\.9{7})+}', '.x', $versionData['version']);
        }

        return $versionData;
    }

    private function guessGitVersion(array $packageConfig, $path)
    {
        GitUtil::cleanEnv();
        $commit = null;
        $version = null;
        $prettyVersion = null;
        $isDetached = false;

        // try to fetch current version from git branch
        if (0 === $this->process->execute('git branch --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_match('{^(?:\* ) *(\(no branch\)|\(detached from \S+\)|\(HEAD detached at \S+\)|\S+) *([a-f0-9]+) .*$}', $branch, $match)) {
                    if ($match[1] === '(no branch)' || substr($match[1], 0, 10) === '(detached ' || substr($match[1], 0, 17) === '(HEAD detached at') {
                        $version = 'dev-' . $match[2];
                        $prettyVersion = $version;
                        $isFeatureBranch = true;
                        $isDetached = true;
                    } else {
                        $version = $this->versionParser->normalizeBranch($match[1]);
                        $prettyVersion = 'dev-' . $match[1];
                        $isFeatureBranch = 0 === strpos($version, 'dev-');
                    }

                    if ($match[2]) {
                        $commit = $match[2];
                    }
                }

                if ($branch && !preg_match('{^ *[^/]+/HEAD }', $branch)) {
                    if (preg_match('{^(?:\* )? *(\S+) *([a-f0-9]+) .*$}', $branch, $match)) {
                        $branches[] = $match[1];
                    }
                }
            }

            if ($isFeatureBranch) {
                // 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'];
            }
        }

        if (!$commit) {
            $command = 'git log --pretty="%H" -n1 HEAD';
            if (0 === $this->process->execute($command, $output, $path)) {
                $commit = trim($output) ?: null;
            }
        }

        return array('version' => $version, 'commit' => $commit, 'pretty_version' => $prettyVersion);
    }

    private function versionFromGitTags($path)
    {
        // try to fetch current version from git tags
        if (0 === $this->process->execute('git describe --exact-match --tags', $output, $path)) {
            try {
                $version = $this->versionParser->normalize(trim($output));

                return array('version' => $version, 'pretty_version' => trim($output));
            } catch (\Exception $e) {
            }
        }

        return null;
    }

    private function guessHgVersion(array $packageConfig, $path)
    {
        // try to fetch current version from hg branch
        if (0 === $this->process->execute('hg branch', $output, $path)) {
            $branch = trim($output);
            $version = $this->versionParser->normalizeBranch($branch);
            $isFeatureBranch = 0 === strpos($version, 'dev-');

            if ('9999999-dev' === $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)
            $driver = new HgDriver(array('url' => $path), new NullIO(), $this->config, $this->process);
            $branches = 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'] = '';

            return $result;
        }
    }

    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']) && !isset($packageConfig['extra']['branch-alias'][$version]))
            || strpos(json_encode($packageConfig), '"self.version"')
        ) {
            $branch = preg_replace('{^dev-}', '', $version);
            $length = PHP_INT_MAX;

            $nonFeatureBranches = '';
            if (!empty($packageConfig['non-feature-branches'])) {
                $nonFeatureBranches = implode('|', $packageConfig['non-feature-branches']);
            }

            foreach ($branches as $candidate) {
                // return directly, if branch is configured to be non-feature branch
                if ($candidate === $branch && preg_match('{^(' . $nonFeatureBranches . ')$}', $candidate)) {
                    break;
                }

                // do not compare against itself or other feature branches
                if ($candidate === $branch || !preg_match('{^(' . $nonFeatureBranches . '|master|trunk|default|develop|\d+\..+)$}', $candidate, $match)) {
                    continue;
                }

                $cmdLine = str_replace(array('%candidate%', '%branch%'), array($candidate, $branch), $scmCmdline);
                if (0 !== $this->process->execute($cmdLine, $output, $path)) {
                    continue;
                }

                if (strlen($output) < $length) {
                    $length = strlen($output);
                    $version = $this->versionParser->normalizeBranch($candidate);
                    $prettyVersion = 'dev-' . $match[1];
                }
            }
        }

        return array('version' => $version, 'pretty_version' => $prettyVersion);
    }

    private function guessFossilVersion(array $packageConfig, $path)
    {
        $version = null;
        $prettyVersion = null;

        // try to fetch current version from fossil
        if (0 === $this->process->execute('fossil branch list', $output, $path)) {
            $branch = trim($output);
            $version = $this->versionParser->normalizeBranch($branch);
            $prettyVersion = 'dev-' . $branch;
        }

        // try to fetch current version from fossil tags
        if (0 === $this->process->execute('fossil tag list', $output, $path)) {
            try {
                $version = $this->versionParser->normalize(trim($output));
                $prettyVersion = trim($output);
            } catch (\Exception $e) {
            }
        }

        return array('version' => $version, 'commit' => '', 'pretty_version' => $prettyVersion);
    }

    private function guessSvnVersion(array $packageConfig, $path)
    {
        SvnUtil::cleanEnv();

        // try to fetch current version from svn
        if (0 === $this->process->execute('svn info --xml', $output, $path)) {
            $trunkPath = isset($packageConfig['trunk-path']) ? preg_quote($packageConfig['trunk-path'], '#') : 'trunk';
            $branchesPath = isset($packageConfig['branches-path']) ? preg_quote($packageConfig['branches-path'], '#') : 'branches';
            $tagsPath = isset($packageConfig['tags-path']) ? preg_quote($packageConfig['tags-path'], '#') : 'tags';

            $urlPattern = '#<url>.*/(' . $trunkPath . '|(' . $branchesPath . '|' . $tagsPath . ')/(.*))</url>#';

            if (preg_match($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]);
                $version = $this->versionParser->normalize($prettyVersion);

                return array('version' => $version, 'commit' => '', 'pretty_version' => $prettyVersion);
            }
        }
    }
}
<?php

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

namespace Composer\Package\Version;

use Composer\Repository\PlatformRepository;
use Composer\Semver\VersionParser as SemverVersionParser;
use Composer\Semver\Semver;

class VersionParser extends SemverVersionParser
{
    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 array $pairs a set of package/version pairs separated by ":", "=" or " "
     *
     * @return array[] array of arrays containing a name and (if provided) a version
     */
    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_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $pairs[$i + 1])) {
                $pair .= ' '.$pairs[$i + 1];
                $i++;
            }

            if (strpos($pair, ' ')) {
                list($name, $version) = explode(' ', $pair, 2);
                $result[] = array('name' => $name, 'version' => $version);
            } else {
                $result[] = array('name' => $pair);
            }
        }

        return $result;
    }

    /**
     * @return bool
     */
    public static function isUpgrade($normalizedFrom, $normalizedTo)
    {
        if (substr($normalizedFrom, 0, 4) === 'dev-' || substr($normalizedTo, 0, 4) === 'dev-') {
            return true;
        }

        $sorted = Semver::sort(array($normalizedTo, $normalizedFrom));

        return $sorted[0] === $normalizedFrom;
    }
}
<?php

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

namespace Composer\Package\Version;

use Composer\DependencyResolver\Pool;
use Composer\Package\BasePackage;
use Composer\Package\PackageInterface;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Dumper\ArrayDumper;
use Composer\Semver\Constraint\Constraint;

/**
 * Selects the best possible version for a package
 *
 * @author Ryan Weaver <ryan@knpuniversity.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class VersionSelector
{
    private $pool;

    private $parser;

    public function __construct(Pool $pool)
    {
        $this->pool = $pool;
    }

    /**
     * Given a package name and optional version, returns the latest PackageInterface
     * that matches.
     *
     * @param  string                $packageName
     * @param  string                $targetPackageVersion
     * @param  string                $targetPhpVersion
     * @param  string                $preferredStability
     * @return PackageInterface|bool
     */
    public function findBestCandidate($packageName, $targetPackageVersion = null, $targetPhpVersion = null, $preferredStability = 'stable')
    {
        $constraint = $targetPackageVersion ? $this->getParser()->parseConstraints($targetPackageVersion) : null;
        $candidates = $this->pool->whatProvides(strtolower($packageName), $constraint, true);

        if ($targetPhpVersion) {
            $phpConstraint = new Constraint('==', $this->getParser()->normalize($targetPhpVersion));
            $candidates = array_filter($candidates, function ($pkg) use ($phpConstraint) {
                $reqs = $pkg->getRequires();

                return !isset($reqs['php']) || $reqs['php']->getConstraint()->matches($phpConstraint);
            });
        }

        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;
            }
        }

        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)
    {
        $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 = 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();
    }

    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) == 4 && preg_match('{^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;
    }

    private function getParser()
    {
        if ($this->parser === null) {
            $this->parser = new VersionParser();
        }

        return $this->parser;
    }
}
<?php

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

namespace Composer\Package;

use Composer\Repository\RepositoryInterface;

/**
 * Defines the essential information a package has that is used during solving/installation
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface PackageInterface
{
    /**
     * 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.
     *
     * @return array An array of strings referring to this package
     */
    public function getNames();

    /**
     * Allows the solver to set an id for this package to refer to it.
     *
     * @param int $id
     */
    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 array The package extra data
     */
    public function getExtra();

    /**
     * Sets source from which this package was installed (source/dist).
     *
     * @param string $type source/dist
     */
    public function setInstallationSource($type);

    /**
     * Returns source from which this package was installed (source/dist).
     *
     * @return string source/dist
     */
    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 array
     */
    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|null
     */
    public function getSourceMirrors();

    /**
     * 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 array
     */
    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|null
     */
    public function getDistMirrors();

    /**
     * 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
     * @return string version
     */
    public function getFullPrettyVersion($truncate = true);

    /**
     * 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
     */
    public function getStability();

    /**
     * Returns a set of links to packages which need to be installed before
     * this package can be installed
     *
     * @return Link[] An array of package links defining required packages
     */
    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 Link[] An array of package links defining packages required for development
     */
    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
     */
    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
     */
    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
     */
    public function getDevAutoload();

    /**
     * Returns a list of directories which should get added to PHP's
     * include path.
     *
     * @return array
     */
    public function getIncludePaths();

    /**
     * Stores a reference to the repository that owns the package
     *
     * @param RepositoryInterface $repository
     */
    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 array
     */
    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();

    /**
     * Returns a list of patterns to exclude from package archives
     *
     * @return array
     */
    public function getArchiveExcludes();

    /**
     * Returns a list of options to download package dist files
     *
     * @return array
     */
    public function getTransportOptions();

    /**
     * @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);
}
<?php

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

namespace Composer\Package;

use Composer\Package\Version\VersionParser;
use Composer\Util\ComposerMirror;

/**
 * Core package definitions that are needed to resolve dependencies and install packages
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class Package extends BasePackage
{
    protected $type;
    protected $targetDir;
    protected $installationSource;
    protected $sourceType;
    protected $sourceUrl;
    protected $sourceReference;
    protected $sourceMirrors;
    protected $distType;
    protected $distUrl;
    protected $distReference;
    protected $distSha1Checksum;
    protected $distMirrors;
    protected $version;
    protected $prettyVersion;
    protected $releaseDate;
    protected $extra = array();
    protected $binaries = array();
    protected $dev;
    protected $stability;
    protected $notificationUrl;

    /** @var Link[] */
    protected $requires = array();
    /** @var Link[] */
    protected $conflicts = array();
    /** @var Link[] */
    protected $provides = array();
    /** @var Link[] */
    protected $replaces = array();
    /** @var Link[] */
    protected $devRequires = array();
    protected $suggests = array();
    protected $autoload = array();
    protected $devAutoload = array();
    protected $includePaths = array();
    protected $archiveExcludes = 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
     */
    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
     */
    public function setTargetDir($targetDir)
    {
        $this->targetDir = $targetDir;
    }

    /**
     * {@inheritDoc}
     */
    public function getTargetDir()
    {
        if (null === $this->targetDir) {
            return;
        }

        return ltrim(preg_replace('{ (?:^|[\\\\/]+) \.\.? (?:[\\\\/]+|$) (?:\.\.? (?:[\\\\/]+|$) )*}x', '/', $this->targetDir), '/');
    }

    /**
     * @param array $extra
     */
    public function setExtra(array $extra)
    {
        $this->extra = $extra;
    }

    /**
     * {@inheritDoc}
     */
    public function getExtra()
    {
        return $this->extra;
    }

    /**
     * @param array $binaries
     */
    public function setBinaries(array $binaries)
    {
        $this->binaries = $binaries;
    }

    /**
     * {@inheritDoc}
     */
    public function getBinaries()
    {
        return $this->binaries;
    }

    /**
     * {@inheritDoc}
     */
    public function setInstallationSource($type)
    {
        $this->installationSource = $type;
    }

    /**
     * {@inheritDoc}
     */
    public function getInstallationSource()
    {
        return $this->installationSource;
    }

    /**
     * @param string $type
     */
    public function setSourceType($type)
    {
        $this->sourceType = $type;
    }

    /**
     * {@inheritDoc}
     */
    public function getSourceType()
    {
        return $this->sourceType;
    }

    /**
     * @param string $url
     */
    public function setSourceUrl($url)
    {
        $this->sourceUrl = $url;
    }

    /**
     * {@inheritDoc}
     */
    public function getSourceUrl()
    {
        return $this->sourceUrl;
    }

    /**
     * @param string $reference
     */
    public function setSourceReference($reference)
    {
        $this->sourceReference = $reference;
    }

    /**
     * {@inheritDoc}
     */
    public function getSourceReference()
    {
        return $this->sourceReference;
    }

    /**
     * @param array|null $mirrors
     */
    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
     */
    public function setDistType($type)
    {
        $this->distType = $type;
    }

    /**
     * {@inheritDoc}
     */
    public function getDistType()
    {
        return $this->distType;
    }

    /**
     * @param string $url
     */
    public function setDistUrl($url)
    {
        $this->distUrl = $url;
    }

    /**
     * {@inheritDoc}
     */
    public function getDistUrl()
    {
        return $this->distUrl;
    }

    /**
     * @param string $reference
     */
    public function setDistReference($reference)
    {
        $this->distReference = $reference;
    }

    /**
     * {@inheritDoc}
     */
    public function getDistReference()
    {
        return $this->distReference;
    }

    /**
     * @param string $sha1checksum
     */
    public function setDistSha1Checksum($sha1checksum)
    {
        $this->distSha1Checksum = $sha1checksum;
    }

    /**
     * {@inheritDoc}
     */
    public function getDistSha1Checksum()
    {
        return $this->distSha1Checksum;
    }

    /**
     * @param array|null $mirrors
     */
    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 getVersion()
    {
        return $this->version;
    }

    /**
     * {@inheritDoc}
     */
    public function getPrettyVersion()
    {
        return $this->prettyVersion;
    }

    /**
     * Set the releaseDate
     *
     * @param \DateTime $releaseDate
     */
    public function setReleaseDate(\DateTime $releaseDate)
    {
        $this->releaseDate = $releaseDate;
    }

    /**
     * {@inheritDoc}
     */
    public function getReleaseDate()
    {
        return $this->releaseDate;
    }

    /**
     * Set the required packages
     *
     * @param Link[] $requires A set of package links
     */
    public function setRequires(array $requires)
    {
        $this->requires = $requires;
    }

    /**
     * {@inheritDoc}
     */
    public function getRequires()
    {
        return $this->requires;
    }

    /**
     * Set the conflicting packages
     *
     * @param Link[] $conflicts A set of package links
     */
    public function setConflicts(array $conflicts)
    {
        $this->conflicts = $conflicts;
    }

    /**
     * {@inheritDoc}
     */
    public function getConflicts()
    {
        return $this->conflicts;
    }

    /**
     * Set the provided virtual packages
     *
     * @param Link[] $provides A set of package links
     */
    public function setProvides(array $provides)
    {
        $this->provides = $provides;
    }

    /**
     * {@inheritDoc}
     */
    public function getProvides()
    {
        return $this->provides;
    }

    /**
     * Set the packages this one replaces
     *
     * @param Link[] $replaces A set of package links
     */
    public function setReplaces(array $replaces)
    {
        $this->replaces = $replaces;
    }

    /**
     * {@inheritDoc}
     */
    public function getReplaces()
    {
        return $this->replaces;
    }

    /**
     * Set the recommended packages
     *
     * @param Link[] $devRequires A set of package links
     */
    public function setDevRequires(array $devRequires)
    {
        $this->devRequires = $devRequires;
    }

    /**
     * {@inheritDoc}
     */
    public function getDevRequires()
    {
        return $this->devRequires;
    }

    /**
     * Set the suggested packages
     *
     * @param array $suggests A set of package names/comments
     */
    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
     */
    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
     */
    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 array $includePaths List of directories.
     */
    public function setIncludePaths(array $includePaths)
    {
        $this->includePaths = $includePaths;
    }

    /**
     * {@inheritDoc}
     */
    public function getIncludePaths()
    {
        return $this->includePaths;
    }

    /**
     * Sets the notification URL
     *
     * @param string $notificationUrl
     */
    public function setNotificationUrl($notificationUrl)
    {
        $this->notificationUrl = $notificationUrl;
    }

    /**
     * {@inheritDoc}
     */
    public function getNotificationUrl()
    {
        return $this->notificationUrl;
    }

    /**
     * Sets a list of patterns to be excluded from archives
     *
     * @param array $excludes
     */
    public function setArchiveExcludes(array $excludes)
    {
        $this->archiveExcludes = $excludes;
    }

    /**
     * {@inheritDoc}
     */
    public function getArchiveExcludes()
    {
        return $this->archiveExcludes;
    }

    /**
     * Replaces current version and pretty version with passed values.
     * It also sets stability.
     *
     * @param string $version       The package's normalized version
     * @param string $prettyVersion The package's non-normalized version
     */
    public function replaceVersion($version, $prettyVersion)
    {
        $this->version = $version;
        $this->prettyVersion = $prettyVersion;

        $this->stability = VersionParser::parseStability($version);
        $this->dev = $this->stability === 'dev';
    }

    protected function getUrls($url, $mirrors, $ref, $type, $urlType)
    {
        if (!$url) {
            return array();
        }
        $urls = array($url);
        if ($mirrors) {
            foreach ($mirrors as $mirror) {
                if ($urlType === 'dist') {
                    $mirrorUrl = ComposerMirror::processUrl($mirror['url'], $this->name, $this->version, $ref, $type);
                } 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);
                }
                if (!in_array($mirrorUrl, $urls)) {
                    $func = $mirror['preferred'] ? 'array_unshift' : 'array_push';
                    $func($urls, $mirrorUrl);
                }
            }
        }

        return $urls;
    }
}
<?php

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

namespace Composer\Package\Dumper;

use Composer\Package\BasePackage;
use Composer\Package\PackageInterface;
use Composer\Package\CompletePackageInterface;
use Composer\Package\RootPackageInterface;

/**
 * @author Konstantin Kudryashiv <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ArrayDumper
{
    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;
            }
        }

        if ($package->getArchiveExcludes()) {
            $data['archive']['exclude'] = $package->getArchiveExcludes();
        }

        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);
        }

        $data = $this->dumpValues($package, $keys, $data);

        if ($package instanceof CompletePackageInterface) {
            $keys = array(
                'scripts',
                'license',
                'authors',
                'description',
                'homepage',
                'keywords',
                'repositories',
                'support',
            );

            $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;
    }

    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) && 0 === count($value))) {
                $data[$key] = $value;
            }
        }

        return $data;
    }
}
<?php

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

namespace Composer\Package\Loader;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class InvalidPackageException extends \Exception
{
    private $errors;
    private $warnings;
    private $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)));
    }

    public function getData()
    {
        return $this->data;
    }

    public function getErrors()
    {
        return $this->errors;
    }

    public function getWarnings()
    {
        return $this->warnings;
    }
}
<?php

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

namespace Composer\Package\Loader;

use Composer\Json\JsonFile;

/**
 * @author Konstantin Kudryashiv <ever.zet@gmail.com>
 */
class JsonLoader
{
    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 \Composer\Package\PackageInterface
     */
    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);
        }

        return $this->loader->load($config);
    }
}
<?php

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

namespace Composer\Package\Loader;

use Composer\Package;
use Composer\Package\AliasPackage;
use Composer\Package\Link;
use Composer\Package\RootAliasPackage;
use Composer\Package\RootPackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\Semver\VersionParser as SemverVersionParser;

/**
 * @author Konstantin Kudryashiv <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ArrayLoader implements LoaderInterface
{
    protected $versionParser;
    protected $loadOptions;

    public function __construct(SemverVersionParser $parser = null, $loadOptions = false)
    {
        if (!$parser) {
            $parser = new VersionParser;
        }
        $this->versionParser = $parser;
        $this->loadOptions = $loadOptions;
    }

    public function load(array $config, $class = 'Composer\Package\CompletePackage')
    {
        if (!isset($config['name'])) {
            throw new \UnexpectedValueException('Unknown package has no name defined ('.json_encode($config).').');
        }
        if (!isset($config['version'])) {
            throw new \UnexpectedValueException('Package '.$config['name'].' has no version defined.');
        }

        // handle already normalized versions
        if (isset($config['version_normalized'])) {
            $version = $config['version_normalized'];
        } else {
            $version = $this->versionParser->normalize($config['version']);
        }
        $package = new $class($config['name'], $version, $config['version']);
        $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'])) {
            foreach ((array) $config['bin'] as $key => $bin) {
                $config['bin'][$key] = ltrim($bin, '/');
            }
            $package->setBinaries((array) $config['bin']);
        }

        if (isset($config['installation-source'])) {
            $package->setInstallationSource($config['installation-source']);
        }

        if (isset($config['source'])) {
            if (!isset($config['source']['type']) || !isset($config['source']['url']) || !isset($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'])
             || !isset($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']);
            }
        }

        foreach (Package\BasePackage::$supportedLinkTypes as $type => $opts) {
            if (isset($config[$type])) {
                $method = 'set'.ucfirst($opts['method']);
                $package->{$method}(
                    $this->parseLinks(
                        $package->getName(),
                        $package->getPrettyVersion(),
                        $opts['description'],
                        $config[$type]
                    )
                );
            }
        }

        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_match('/^\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 (!empty($config['archive']['exclude'])) {
            $package->setArchiveExcludes($config['archive']['exclude']);
        }

        if ($package instanceof Package\CompletePackageInterface) {
            if (isset($config['scripts']) && is_array($config['scripts'])) {
                foreach ($config['scripts'] as $event => $listeners) {
                    $config['scripts'][$event] = (array) $listeners;
                }
                if (isset($config['scripts']['composer'])) {
                    trigger_error('The `composer` 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 (isset($config['abandoned'])) {
                $package->setAbandoned($config['abandoned']);
            }
        }

        if ($aliasNormalized = $this->getBranchAlias($config)) {
            if ($package instanceof RootPackageInterface) {
                $package = new RootAliasPackage($package, $aliasNormalized, preg_replace('{(\.9{7})+}', '.x', $aliasNormalized));
            } else {
                $package = new AliasPackage($package, $aliasNormalized, preg_replace('{(\.9{7})+}', '.x', $aliasNormalized));
            }
        }

        if ($this->loadOptions && isset($config['transport-options'])) {
            $package->setTransportOptions($config['transport-options']);
        }

        return $package;
    }

    /**
     * @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  $links         array of package name => constraint mappings
     * @return Link[]
     */
    public function parseLinks($source, $sourceVersion, $description, $links)
    {
        $res = array();
        foreach ($links as $target => $constraint) {
            if (!is_string($constraint)) {
                throw new \UnexpectedValueException('Link constraint in '.$source.' '.$description.' > '.$target.' should be a string, got '.gettype($constraint) . ' (' . var_export($constraint, true) . ')');
            }
            if ('self.version' === $constraint) {
                $parsedConstraint = $this->versionParser->parseConstraints($sourceVersion);
            } else {
                $parsedConstraint = $this->versionParser->parseConstraints($constraint);
            }

            $res[strtolower($target)] = new Link($source, $target, $parsedConstraint, $description, $constraint);
        }

        return $res;
    }

    /**
     * Retrieves a branch alias (dev-master => 1.0.x-dev for example) if it exists
     *
     * @param  array       $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 (('dev-' !== substr($config['version'], 0, 4) && '-dev' !== substr($config['version'], -4))
            || !isset($config['extra']['branch-alias'])
            || !is_array($config['extra']['branch-alias'])
        ) {
            return;
        }

        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
            $validatedTargetBranch = $this->versionParser->normalizeBranch(substr($targetBranch, 0, -4));
            if ('-dev' !== substr($validatedTargetBranch, -4)) {
                continue;
            }

            // ensure that it is the current branch aliasing itself
            if (strtolower($config['version']) !== strtolower($sourceBranch)) {
                continue;
            }

            // If using numeric aliases ensure the alias is a valid subversion
            if (($sourcePrefix = $this->versionParser->parseNumericAliasPrefix($sourceBranch))
                && ($targetPrefix = $this->versionParser->parseNumericAliasPrefix($targetBranch))
                && (stripos($targetPrefix, $sourcePrefix) !== 0)
            ) {
                continue;
            }

            return $validatedTargetBranch;
        }
    }
}
<?php

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

namespace Composer\Package\Loader;

/**
 * Defines a loader that takes an array to create package instances
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface LoaderInterface
{
    /**
     * Converts a package from an array to a real instance
     *
     * @param  array                              $package Package config
     * @param  string                             $class   Package class to use
     * @return \Composer\Package\PackageInterface
     */
    public function load(array $package, $class = 'Composer\Package\CompletePackage');
}
<?php

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

namespace Composer\Package\Loader;

use Composer\Package\BasePackage;
use Composer\Package\AliasPackage;
use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Package\RootPackageInterface;
use Composer\Repository\RepositoryFactory;
use Composer\Package\Version\VersionGuesser;
use Composer\Package\Version\VersionParser;
use Composer\Repository\RepositoryManager;
use Composer\Util\ProcessExecutor;

/**
 * ArrayLoader built for the sole purpose of loading the root package
 *
 * Sets additional defaults and loads repositories
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class RootPackageLoader extends ArrayLoader
{
    /**
     * @var RepositoryManager
     */
    private $manager;

    /**
     * @var Config
     */
    private $config;

    /**
     * @var VersionGuesser
     */
    private $versionGuesser;

    /**
     * @var IOInterface
     */
    private $io;

    public function __construct(RepositoryManager $manager, Config $config, VersionParser $parser = null, VersionGuesser $versionGuesser = null, IOInterface $io = null)
    {
        parent::__construct($parser);

        $this->manager = $manager;
        $this->config = $config;
        $this->versionGuesser = $versionGuesser ?: new VersionGuesser($config, new ProcessExecutor(), $this->versionParser);
        $this->io = $io;
    }

    /**
     * @param  array                $config package data
     * @param  string               $class  FQCN to be instantiated
     * @param  string               $cwd    cwd of the root package to be used to guess the version if it is not provided
     * @return RootPackageInterface
     */
    public function load(array $config, $class = 'Composer\Package\RootPackage', $cwd = null)
    {
        if (!isset($config['name'])) {
            $config['name'] = '__root__';
        } elseif ($this->io) {
            if ($err = ValidatingArrayLoader::hasPackageNamingError($config['name'])) {
                $this->io->writeError('<warning>Deprecation warning: Your package name '.$err.' Make sure you fix this as Composer 2.0 will error.</warning>');
            }
        }
        $autoVersioned = false;
        if (!isset($config['version'])) {
            $commit = null;

            // override with env var if available
            if (getenv('COMPOSER_ROOT_VERSION')) {
                $config['version'] = 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,
                );
            }
        }

        $realPackage = $package = parent::load($config, $class);
        if ($realPackage instanceof AliasPackage) {
            $realPackage = $package->getAliasOf();
        }

        if ($autoVersioned) {
            $realPackage->replaceVersion($realPackage->getVersion(), 'No version set (parsed as 1.0.0)');
        }

        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 = $this->extractStabilityFlags($links, $stabilityFlags, $realPackage->getMinimumStability());
                $references = $this->extractReferences($links, $references);
            }
        }

        if ($this->io) {
            foreach (array_keys(BasePackage::$supportedLinkTypes) as $linkType) {
                if (isset($config[$linkType])) {
                    foreach ($config[$linkType] as $linkName => $constraint) {
                        if ($err = ValidatingArrayLoader::hasPackageNamingError($linkName, true)) {
                            $this->io->writeError('<warning>Deprecation warning: '.$linkType.'.'.$err.' Make sure you fix this as Composer 2.0 will error.</warning>');
                        }
                    }
                }
            }
        }

        if (isset($links[$config['name']])) {
            throw new \InvalidArgumentException(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']));
        }

        $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;
    }

    private function extractAliases(array $requires, array $aliases)
    {
        foreach ($requires as $reqName => $reqVersion) {
            if (preg_match('{^([^,\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),
                );
            }
        }

        return $aliases;
    }

    private function extractStabilityFlags(array $requires, array $stabilityFlags, $minimumStability)
    {
        $stabilities = BasePackage::$stabilities;
        $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_match('{^[^@]*?@('.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_match('{^[^,\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;
    }

    private function extractReferences(array $requires, array $references)
    {
        foreach ($requires as $reqName => $reqVersion) {
            $reqVersion = preg_replace('{^([^,\s@]+) as .+$}', '$1', $reqVersion);
            if (preg_match('{^[^,\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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package\Loader;

use Composer\Package\BasePackage;
use Composer\Semver\Constraint\Constraint;
use Composer\Package\Version\VersionParser;
use Composer\Repository\PlatformRepository;
use Composer\Spdx\SpdxLicenses;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ValidatingArrayLoader implements LoaderInterface
{
    const CHECK_ALL = 3;
    const CHECK_UNBOUND_CONSTRAINTS = 1;
    const CHECK_STRICT_CONSTRAINTS = 2;

    private $loader;
    private $versionParser;
    private $errors;
    private $warnings;
    private $config;
    private $strictName;
    private $flags;

    public function __construct(LoaderInterface $loader, $strictName = true, VersionParser $parser = null, $flags = 0)
    {
        $this->loader = $loader;
        $this->versionParser = $parser ?: new VersionParser();
        $this->strictName = $strictName;
        $this->flags = $flags;
    }

    public function load(array $config, $class = 'Composer\Package\CompletePackage')
    {
        $this->errors = array();
        $this->warnings = array();
        $this->config = $config;

        if ($err = self::hasPackageNamingError($config['name'])) {
            $this->warnings[] = 'Deprecation warning: Your package name '.$err.' Make sure you fix this as Composer 2.0 will error.';
        }

        if ($this->strictName) {
            $this->validateRegex('name', '[A-Za-z0-9][A-Za-z0-9_.-]*/[A-Za-z0-9][A-Za-z0-9_.-]*', true);
        } else {
            $this->validateString('name', true);
        }

        if (!empty($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) {
                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'];

                // 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();
                if (count($licenses) === 1 && !$licenseValidator->validate($licenses) && $licenseValidator->validate(trim($licenses[0]))) {
                    $this->warnings[] = sprintf(
                        'License %s must not contain extra spaces, make sure to trim it.',
                        json_encode($this->config['license'])
                    );
                } elseif (array() !== $licenses && !$licenseValidator->validate($licenses)) {
                    $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($this->config['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'))) {
                $this->warnings[] = 'support.irc : invalid value ('.$this->config['support']['irc'].'), must be a irc://<server>/<channel> 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']);
            }
        }

        $unboundConstraint = new Constraint('=', $this->versionParser->normalize('dev-master'));
        $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 ($err = self::hasPackageNamingError($package, true)) {
                        $this->warnings[] = 'Deprecation warning: '.$linkType.'.'.$err.' Make sure you fix this as Composer 2.0 will error.';
                    } elseif (!preg_match('{^[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)
                            && !preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $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
                            && substr($linkConstraint, 0, 1) === '='
                            && $stableConstraint->versionCompare($stableConstraint, $linkConstraint, '<=')
                        ) {
                            $this->warnings[] = $linkType.'.'.$package.' : exact version constraints ('.$constraint.') should be avoided if the package follows semantic versioning';
                        }
                    }
                }
            }
        }

        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[$this->config['minimum-stability']])) {
                $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']);
        }

        // TODO validate dist
        // TODO validate source

        // 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) {
                    // ensure it is an alias to a -dev package
                    if ('-dev' !== substr($targetBranch, -4)) {
                        $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') must end in -dev';
                        unset($this->config['extra']['branch-alias'][$sourceBranch]);

                        continue;
                    }

                    // normalize without -dev and ensure it's a numeric branch that is parseable
                    $validatedTargetBranch = $this->versionParser->normalizeBranch(substr($targetBranch, 0, -4));
                    if ('-dev' !== substr($validatedTargetBranch, -4)) {
                        $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') must be a parseable number like 2.0-dev';
                        unset($this->config['extra']['branch-alias'][$sourceBranch]);

                        continue;
                    }

                    // If using numeric aliases ensure the alias is a valid subversion
                    if (($sourcePrefix = $this->versionParser->parseNumericAliasPrefix($sourceBranch))
                        && ($targetPrefix = $this->versionParser->parseNumericAliasPrefix($targetBranch))
                        && (stripos($targetPrefix, $sourcePrefix) !== 0)
                    ) {
                        $this->warnings[] = 'extra.branch-alias.'.$sourceBranch.' : the target branch ('.$targetBranch.') is not a valid numeric alias for this version';
                        unset($this->config['extra']['branch-alias'][$sourceBranch]);
                    }
                }
            }
        }

        if ($this->errors) {
            throw new InvalidPackageException($this->errors, $this->warnings, $config);
        }

        $package = $this->loader->load($this->config, $class);
        $this->config = null;

        return $package;
    }

    public function getWarnings()
    {
        return $this->warnings;
    }

    public function getErrors()
    {
        return $this->errors;
    }

    public static function hasPackageNamingError($name, $isLink = false)
    {
        if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name)) {
            return;
        }

        if (!preg_match('{^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9]([_.-]?[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]([_.-]?[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], $reservedNames, true) || in_array($bits[1], $reservedNames, true)) {
            return $name.' is reserved, package and vendor names can not match any of: '.implode(', ', $reservedNames).'.';
        }

        if (preg_match('{\.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_match('{[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.';
        }
    }

    private function validateRegex($property, $regex, $mandatory = false)
    {
        if (!$this->validateString($property, $mandatory)) {
            return false;
        }

        if (!preg_match('{^'.$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;
    }

    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;
    }

    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;
    }

    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_match('{^'.$regex.'$}u', $value)) {
                $this->warnings[] = $property.'.'.$key.' : invalid value ('.$value.'), must match '.$regex;
                unset($this->config[$property][$key]);
                $pass = false;
            }
        }

        return $pass;
    }

    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;
    }

    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'], $schemes, true)) {
            return false;
        }

        return true;
    }
}
<?php

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

namespace Composer\Package\Comparer;

/**
 * class Comparer
 *
 * @author Hector Prats <hectorpratsortega@gmail.com>
 */
class Comparer
{
    private $source;
    private $update;
    private $changed;

    public function setSource($source)
    {
        $this->source = $source;
    }

    public function setUpdate($update)
    {
        $this->update = $update;
    }

    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) {
            foreach ($changed as $sectionKey => $itemSection) {
                foreach ($itemSection as $itemKey => $item) {
                    $changed['string'][] = $item."\r\n";
                }
            }
            $changed = implode("\r\n", $changed['string']);
        }

        return $changed;
    }

    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;
                }
            }
        }
    }

    private function doTree($dir, &$array)
    {
        if ($dh = opendir($dir)) {
            while ($file = readdir($dh)) {
                if ($file !== '.' && $file !== '..') {
                    if (is_dir($dir.'/'.$file)) {
                        if (!count($array)) {
                            $array[0] = 'Temp';
                        }
                        if (!$this->doTree($dir.'/'.$file, $array)) {
                            return false;
                        }
                    } else {
                        if (filesize($dir.'/'.$file)) {
                            set_time_limit(30);
                            $array[$dir][$file] = md5_file($dir.'/'.$file);
                        }
                    }
                }
            }
            if (count($array) > 1 && isset($array['0'])) {
                unset($array['0']);
            }

            return $array;
        }

        return false;
    }
}
<?php

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

namespace Composer\Package;

/**
 * Package containing additional metadata that is not used by the solver
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class CompletePackage extends Package implements CompletePackageInterface
{
    protected $repositories;
    protected $license = array();
    protected $keywords;
    protected $authors;
    protected $description;
    protected $homepage;
    protected $scripts = array();
    protected $support = array();
    protected $abandoned = false;

    /**
     * @param array $scripts
     */
    public function setScripts(array $scripts)
    {
        $this->scripts = $scripts;
    }

    /**
     * {@inheritDoc}
     */
    public function getScripts()
    {
        return $this->scripts;
    }

    /**
     * Set the repositories
     *
     * @param array $repositories
     */
    public function setRepositories($repositories)
    {
        $this->repositories = $repositories;
    }

    /**
     * {@inheritDoc}
     */
    public function getRepositories()
    {
        return $this->repositories;
    }

    /**
     * Set the license
     *
     * @param array $license
     */
    public function setLicense(array $license)
    {
        $this->license = $license;
    }

    /**
     * {@inheritDoc}
     */
    public function getLicense()
    {
        return $this->license;
    }

    /**
     * Set the keywords
     *
     * @param array $keywords
     */
    public function setKeywords(array $keywords)
    {
        $this->keywords = $keywords;
    }

    /**
     * {@inheritDoc}
     */
    public function getKeywords()
    {
        return $this->keywords;
    }

    /**
     * Set the authors
     *
     * @param array $authors
     */
    public function setAuthors(array $authors)
    {
        $this->authors = $authors;
    }

    /**
     * {@inheritDoc}
     */
    public function getAuthors()
    {
        return $this->authors;
    }

    /**
     * Set the description
     *
     * @param string $description
     */
    public function setDescription($description)
    {
        $this->description = $description;
    }

    /**
     * {@inheritDoc}
     */
    public function getDescription()
    {
        return $this->description;
    }

    /**
     * Set the homepage
     *
     * @param string $homepage
     */
    public function setHomepage($homepage)
    {
        $this->homepage = $homepage;
    }

    /**
     * {@inheritDoc}
     */
    public function getHomepage()
    {
        return $this->homepage;
    }

    /**
     * Set the support information
     *
     * @param array $support
     */
    public function setSupport(array $support)
    {
        $this->support = $support;
    }

    /**
     * {@inheritDoc}
     */
    public function getSupport()
    {
        return $this->support;
    }

    /**
     * @return bool
     */
    public function isAbandoned()
    {
        return (bool) $this->abandoned;
    }

    /**
     * @param bool|string $abandoned
     */
    public function setAbandoned($abandoned)
    {
        $this->abandoned = $abandoned;
    }

    /**
     * If the package is abandoned and has a suggested replacement, this method returns it
     *
     * @return string|null
     */
    public function getReplacementPackage()
    {
        return is_string($this->abandoned) ? $this->abandoned : null;
    }
}
<?php

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

namespace Composer\Package;

/**
 * The root package represents the project's composer.json and contains additional metadata
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class RootPackage extends CompletePackage implements RootPackageInterface
{
    protected $minimumStability = 'stable';
    protected $preferStable = false;
    protected $stabilityFlags = array();
    protected $config = array();
    protected $references = array();
    protected $aliases = array();

    /**
     * Set the minimumStability
     *
     * @param string $minimumStability
     */
    public function setMinimumStability($minimumStability)
    {
        $this->minimumStability = $minimumStability;
    }

    /**
     * {@inheritDoc}
     */
    public function getMinimumStability()
    {
        return $this->minimumStability;
    }

    /**
     * Set the stabilityFlags
     *
     * @param array $stabilityFlags
     */
    public function setStabilityFlags(array $stabilityFlags)
    {
        $this->stabilityFlags = $stabilityFlags;
    }

    /**
     * {@inheritDoc}
     */
    public function getStabilityFlags()
    {
        return $this->stabilityFlags;
    }

    /**
     * Set the preferStable
     *
     * @param bool $preferStable
     */
    public function setPreferStable($preferStable)
    {
        $this->preferStable = $preferStable;
    }

    /**
     * {@inheritDoc}
     */
    public function getPreferStable()
    {
        return $this->preferStable;
    }

    /**
     * Set the config
     *
     * @param array $config
     */
    public function setConfig(array $config)
    {
        $this->config = $config;
    }

    /**
     * {@inheritDoc}
     */
    public function getConfig()
    {
        return $this->config;
    }

    /**
     * Set the references
     *
     * @param array $references
     */
    public function setReferences(array $references)
    {
        $this->references = $references;
    }

    /**
     * {@inheritDoc}
     */
    public function getReferences()
    {
        return $this->references;
    }

    /**
     * Set the aliases
     *
     * @param array $aliases
     */
    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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package;

use Composer\Repository\RepositoryInterface;
use Composer\Repository\PlatformRepository;

/**
 * Base class for packages providing name storage and default match implementation
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
abstract class BasePackage implements PackageInterface
{
    public static $supportedLinkTypes = array(
        'require' => array('description' => 'requires', 'method' => 'requires'),
        'conflict' => array('description' => 'conflicts', 'method' => 'conflicts'),
        'provide' => array('description' => 'provides', 'method' => 'provides'),
        'replace' => array('description' => 'replaces', 'method' => 'replaces'),
        'require-dev' => array('description' => 'requires (for development)', 'method' => 'devRequires'),
    );

    const STABILITY_STABLE = 0;
    const STABILITY_RC = 5;
    const STABILITY_BETA = 10;
    const STABILITY_ALPHA = 15;
    const STABILITY_DEV = 20;

    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
     */
    public $id;
    /** @var string */
    protected $name;
    /** @var string */
    protected $prettyName;
    /** @var RepositoryInterface */
    protected $repository;
    /** @var array */
    protected $transportOptions = array();

    /**
     * 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()
    {
        $names = array(
            $this->getName() => true,
        );

        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;
    }

    /**
     * {@inheritDoc}
     */
    public function getTransportOptions()
    {
        return $this->transportOptions;
    }

    /**
     * Configures the list of options to download package dist files
     *
     * @param array $options
     */
    public function setTransportOptions(array $options)
    {
        $this->transportOptions = $options;
    }

    /**
     * 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();
    }

    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)
    {
        if (!$this->isDev() || !in_array($this->getSourceType(), array('hg', 'git'))) {
            return $this->getPrettyVersion();
        }

        // if source reference is a sha1 hash -- truncate
        if ($truncate && strlen($this->getSourceReference()) === 40) {
            return $this->getPrettyVersion() . ' ' . substr($this->getSourceReference(), 0, 7);
        }

        return $this->getPrettyVersion() . ' ' . $this->getSourceReference();
    }

    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 $whiteListedPattern
     * @param  bool $wrap Wrap the cleaned string by the given string
     * @return string
     */
    public static function packageNameToRegexp($whiteListedPattern, $wrap = '{^%s$}i')
    {
        $cleanedWhiteListedPattern = str_replace('\\*', '.*', preg_quote($whiteListedPattern));

        return sprintf($wrap, $cleanedWhiteListedPattern);
    }
}
<?php

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

namespace Composer\Package;

use Composer\Semver\Constraint\Constraint;
use Composer\Package\Version\VersionParser;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class AliasPackage extends BasePackage implements CompletePackageInterface
{
    protected $version;
    protected $prettyVersion;
    protected $dev;
    protected $rootPackageAlias = false;
    protected $stability;

    /** @var PackageInterface */
    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 PackageInterface $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(PackageInterface $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 (array('requires', 'devRequires', 'conflicts', 'provides', 'replaces') as $type) {
            $links = $aliasOf->{'get' . ucfirst($type)}();
            $this->$type = $this->replaceSelfVersionDependencies($links, $type);
        }
    }

    /**
     * @return PackageInterface
     */
    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}
     */
    public function getConflicts()
    {
        return $this->conflicts;
    }

    /**
     * {@inheritDoc}
     */
    public function getProvides()
    {
        return $this->provides;
    }

    /**
     * {@inheritDoc}
     */
    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 string $linkType
     *
     * @return Link[]
     */
    protected function replaceSelfVersionDependencies(array $links, $linkType)
    {
        if (in_array($linkType, array('conflicts', 'provides', 'replaces'), 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(), new Constraint('=', $this->version), $linkType, $this->prettyVersion);
                }
            }
            $links = array_merge($links, $newLinks);
        } else {
            foreach ($links as $index => $link) {
                if ('self.version' === $link->getPrettyConstraint()) {
                    $links[$index] = new Link($link->getSource(), $link->getTarget(), new Constraint('=', $this->version), $linkType, $this->prettyVersion);
                }
            }
        }

        return $links;
    }

    /***************************************
     * 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)
    {
        return $this->aliasOf->setSourceReference($reference);
    }

    public function setSourceMirrors($mirrors)
    {
        return $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)
    {
        return $this->aliasOf->setDistReference($reference);
    }

    public function getDistSha1Checksum()
    {
        return $this->aliasOf->getDistSha1Checksum();
    }

    public function setTransportOptions(array $options)
    {
        return $this->aliasOf->setTransportOptions($options);
    }

    public function getTransportOptions()
    {
        return $this->aliasOf->getTransportOptions();
    }

    public function setDistMirrors($mirrors)
    {
        return $this->aliasOf->setDistMirrors($mirrors);
    }

    public function getDistMirrors()
    {
        return $this->aliasOf->getDistMirrors();
    }

    public function getScripts()
    {
        return $this->aliasOf->getScripts();
    }

    public function getLicense()
    {
        return $this->aliasOf->getLicense();
    }

    public function getAutoload()
    {
        return $this->aliasOf->getAutoload();
    }

    public function getDevAutoload()
    {
        return $this->aliasOf->getDevAutoload();
    }

    public function getIncludePaths()
    {
        return $this->aliasOf->getIncludePaths();
    }

    public function getRepositories()
    {
        return $this->aliasOf->getRepositories();
    }

    public function getReleaseDate()
    {
        return $this->aliasOf->getReleaseDate();
    }

    public function getBinaries()
    {
        return $this->aliasOf->getBinaries();
    }

    public function getKeywords()
    {
        return $this->aliasOf->getKeywords();
    }

    public function getDescription()
    {
        return $this->aliasOf->getDescription();
    }

    public function getHomepage()
    {
        return $this->aliasOf->getHomepage();
    }

    public function getSuggests()
    {
        return $this->aliasOf->getSuggests();
    }

    public function getAuthors()
    {
        return $this->aliasOf->getAuthors();
    }

    public function getSupport()
    {
        return $this->aliasOf->getSupport();
    }

    public function getNotificationUrl()
    {
        return $this->aliasOf->getNotificationUrl();
    }

    public function getArchiveExcludes()
    {
        return $this->aliasOf->getArchiveExcludes();
    }

    public function isAbandoned()
    {
        return $this->aliasOf->isAbandoned();
    }

    public function getReplacementPackage()
    {
        return $this->aliasOf->getReplacementPackage();
    }

    public function __toString()
    {
        return parent::__toString().' (alias of '.$this->aliasOf->getVersion().')';
    }

    public function setDistUrl($url)
    {
        return $this->aliasOf->setDistUrl($url);
    }

    public function setDistType($type)
    {
        return $this->aliasOf->setDistType($type);
    }
}
<?php

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

namespace Composer\Package;

use Composer\Json\JsonFile;
use Composer\Installer\InstallationManager;
use Composer\Repository\RepositoryManager;
use Composer\Util\ProcessExecutor;
use Composer\Repository\ArrayRepository;
use Composer\Package\Dumper\ArrayDumper;
use Composer\Package\Loader\ArrayLoader;
use Composer\Util\Git as GitUtil;
use Composer\IO\IOInterface;
use Seld\JsonLint\ParsingException;

/**
 * Reads/writes project lockfile (composer.lock).
 *
 * @author Konstantin Kudryashiv <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Locker
{
    private $lockFile;
    private $repositoryManager;
    private $installationManager;
    private $hash;
    private $contentHash;
    private $loader;
    private $dumper;
    private $process;
    private $lockDataCache;

    /**
     * Initializes packages locker.
     *
     * @param IOInterface         $io
     * @param JsonFile            $lockFile             lockfile loader
     * @param RepositoryManager   $repositoryManager    repository manager instance
     * @param InstallationManager $installationManager  installation manager instance
     * @param string              $composerFileContents The contents of the composer file
     */
    public function __construct(IOInterface $io, JsonFile $lockFile, RepositoryManager $repositoryManager, InstallationManager $installationManager, $composerFileContents)
    {
        $this->lockFile = $lockFile;
        $this->repositoryManager = $repositoryManager;
        $this->installationManager = $installationManager;
        $this->hash = md5($composerFileContents);
        $this->contentHash = self::getContentHash($composerFileContents);
        $this->loader = new ArrayLoader(null, true);
        $this->dumper = new ArrayDumper();
        $this->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($composerFileContents, true);

        $relevantKeys = array(
            'name',
            'version',
            'require',
            'require-dev',
            'conflict',
            'replace',
            'provide',
            'minimum-stability',
            'prefer-stable',
            'repositories',
            'extra',
        );

        $relevantContent = array();

        foreach (array_intersect($relevantKeys, array_keys($content)) as $key) {
            $relevantContent[$key] = $content[$key];
        }
        if (isset($content['config']['platform'])) {
            $relevantContent['config']['platform'] = $content['config']['platform'];
        }

        ksort($relevantContent);

        return md5(json_encode($relevantContent));
    }

    /**
     * Checks whether locker has been locked (lockfile found).
     *
     * @return bool
     */
    public function isLocked()
    {
        if (!$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\RepositoryInterface
     */
    public function getLockedRepository($withDevReqs = false)
    {
        $lockData = $this->getLockData();
        $packages = new ArrayRepository();

        $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 run update to install those packages.');
            }
        }

        if (empty($lockedPackages)) {
            return $packages;
        }

        if (isset($lockedPackages[0]['name'])) {
            foreach ($lockedPackages as $info) {
                $packages->addPackage($this->loader->load($info));
            }

            return $packages;
        }

        throw new \RuntimeException('Your composer.lock was created before 2012-09-15, and is not supported anymore. Run "composer update" to generate a new one.');
    }

    /**
     * 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',
                'requires',
                isset($lockData['platform']) ? $lockData['platform'] : array()
            );
        }

        if ($withDevReqs && !empty($lockData['platform-dev'])) {
            $devRequirements = $this->loader->parseLinks(
                '__ROOT__',
                '1.0.0',
                'requires',
                isset($lockData['platform-dev']) ? $lockData['platform-dev'] : array()
            );

            $requirements = array_merge($requirements, $devRequirements);
        }

        return $requirements;
    }

    public function getMinimumStability()
    {
        $lockData = $this->getLockData();

        return isset($lockData['minimum-stability']) ? $lockData['minimum-stability'] : 'stable';
    }

    public function getStabilityFlags()
    {
        $lockData = $this->getLockData();

        return isset($lockData['stability-flags']) ? $lockData['stability-flags'] : array();
    }

    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;
    }

    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;
    }

    public function getPlatformOverrides()
    {
        $lockData = $this->getLockData();

        return isset($lockData['platform-overrides']) ? $lockData['platform-overrides'] : array();
    }

    public function getAliases()
    {
        $lockData = $this->getLockData();

        return isset($lockData['aliases']) ? $lockData['aliases'] : array();
    }

    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 array  $packages          array of packages
     * @param mixed  $devPackages       array of dev packages or null if installed without --dev
     * @param array  $platformReqs      array of package name => constraint for required platform packages
     * @param mixed  $platformDevReqs   array of package name => constraint for dev-required platform packages
     * @param array  $aliases           array of aliases
     * @param string $minimumStability
     * @param array  $stabilityFlags
     * @param bool   $preferStable
     * @param bool   $preferLowest
     * @param array  $platformOverrides
     *
     * @return bool
     */
    public function setLockData(array $packages, $devPackages, array $platformReqs, $platformDevReqs, array $aliases, $minimumStability, array $stabilityFlags, $preferStable, $preferLowest, array $platformOverrides)
    {
        $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' => array(),
            'minimum-stability' => $minimumStability,
            'stability-flags' => $stabilityFlags,
            'prefer-stable' => $preferStable,
            'prefer-lowest' => $preferLowest,
        );

        foreach ($aliases as $package => $versions) {
            foreach ($versions as $version => $alias) {
                $lock['aliases'][] = array(
                    'alias' => $alias['alias'],
                    'alias_normalized' => $alias['alias_normalized'],
                    'version' => $version,
                    'package' => $package,
                );
            }
        }

        $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;
        }

        if (empty($lock['packages']) && empty($lock['packages-dev']) && empty($lock['platform']) && empty($lock['platform-dev'])) {
            if ($this->lockFile->exists()) {
                unlink($this->lockFile->getPath());
            }

            return false;
        }

        try {
            $isLocked = $this->isLocked();
        } catch (ParsingException $e) {
            $isLocked = false;
        }
        if (!$isLocked || $lock !== $this->getLockData()) {
            $this->lockFile->write($lock);
            $this->lockDataCache = null;

            return true;
        }

        return false;
    }

    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 (0 !== $comparison) {
                return $comparison;
            }

            // If it is the same package, compare the versions to make the order deterministic
            return strcmp($a['version'], $b['version']);
        });

        return $locked;
    }

    /**
     * Returns the packages's datetime for its source reference.
     *
     * @param  PackageInterface $package The package to scan.
     * @return string|null      The formatted datetime or null if none was found.
     */
    private function getPackageTime(PackageInterface $package)
    {
        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 (0 === $this->process->execute('git log -n1 --pretty=%ct '.ProcessExecutor::escape($sourceRef), $output, $path) && preg_match('{^\s*\d+\s*$}', $output)) {
                        $datetime = new \DateTime('@'.trim($output), new \DateTimeZone('UTC'));
                    }
                    break;

                case 'hg':
                    if (0 === $this->process->execute('hg log --template "{date|hgdate}" -r '.ProcessExecutor::escape($sourceRef), $output, $path) && preg_match('{^\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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Package;

/**
 * Defines additional fields that are only needed for the root package
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface RootPackageInterface extends CompletePackageInterface
{
    /**
     * Returns a set of package names and their aliases
     *
     * @return array
     */
    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
     */
    public function getStabilityFlags();

    /**
     * Returns a set of package names and source references that must be enforced on them
     *
     * array('foo/bar' => 'abcd1234')
     *
     * @return array
     */
    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 array
     */
    public function getConfig();

    /**
     * Set the required packages
     *
     * @param Link[] $requires A set of package links
     */
    public function setRequires(array $requires);

    /**
     * Set the recommended packages
     *
     * @param Link[] $devRequires A set of package links
     */
    public function setDevRequires(array $devRequires);

    /**
     * Set the conflicting packages
     *
     * @param Link[] $conflicts A set of package links
     */
    public function setConflicts(array $conflicts);

    /**
     * Set the provided virtual packages
     *
     * @param Link[] $provides A set of package links
     */
    public function setProvides(array $provides);

    /**
     * Set the packages this one replaces
     *
     * @param Link[] $replaces A set of package links
     */
    public function setReplaces(array $replaces);

    /**
     * Set the repositories
     *
     * @param array $repositories
     */
    public function setRepositories($repositories);

    /**
     * Set the autoload mapping
     *
     * @param array $autoload Mapping of autoloading rules
     */
    public function setAutoload(array $autoload);

    /**
     * Set the dev autoload mapping
     *
     * @param array $devAutoload Mapping of dev autoloading rules
     */
    public function setDevAutoload(array $devAutoload);

    /**
     * Set the stabilityFlags
     *
     * @param array $stabilityFlags
     */
    public function setStabilityFlags(array $stabilityFlags);

    /**
     * Set the suggested packages
     *
     * @param array $suggests A set of package names/comments
     */
    public function setSuggests(array $suggests);

    /**
     * @param array $extra
     */
    public function setExtra(array $extra);
}
<?php

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

namespace Composer\Package;

/**
 * Defines package metadata that is not necessarily needed for solving and installing packages
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
interface CompletePackageInterface extends PackageInterface
{
    /**
     * Returns the scripts of this package
     *
     * @return array array('script name' => array('listeners'))
     */
    public function getScripts();

    /**
     * Returns an array of repositories
     *
     * {"<type>": {<config key/values>}}
     *
     * @return array Repositories
     */
    public function getRepositories();

    /**
     * Returns the package license, e.g. MIT, BSD, GPL
     *
     * @return array The package licenses
     */
    public function getLicense();

    /**
     * Returns an array of keywords relating to the package
     *
     * @return array
     */
    public function getKeywords();

    /**
     * Returns the package description
     *
     * @return string
     */
    public function getDescription();

    /**
     * Returns the package homepage
     *
     * @return string
     */
    public function getHomepage();

    /**
     * Returns an array of authors of the package
     *
     * Each item can contain name/homepage/email keys
     *
     * @return array
     */
    public function getAuthors();

    /**
     * Returns the support information
     *
     * @return array
     */
    public function getSupport();

    /**
     * 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
     */
    public function getReplacementPackage();
}
<?php

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

namespace Composer\Package;

use Composer\Semver\Constraint\ConstraintInterface;

/**
 * Represents a link between two packages, represented by their names
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class Link
{
    /**
     * @var string
     */
    protected $source;

    /**
     * @var string
     */
    protected $target;

    /**
     * @var ConstraintInterface|null
     */
    protected $constraint;

    /**
     * @var string
     */
    protected $description;

    /**
     * @var string|null
     */
    protected $prettyConstraint;

    /**
     * Creates a new package link.
     *
     * @param string                   $source
     * @param string                   $target
     * @param ConstraintInterface|null $constraint       Constraint applying to the target of this link
     * @param string                   $description      Used to create a descriptive string representation
     * @param string|null              $prettyConstraint
     */
    public function __construct($source, $target, ConstraintInterface $constraint = null, $description = 'relates to', $prettyConstraint = null)
    {
        $this->source = strtolower($source);
        $this->target = strtolower($target);
        $this->constraint = $constraint;
        $this->description = $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|null
     */
    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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer;

use Composer\Json\JsonFile;
use Composer\Spdx\SpdxLicenses;
use Composer\CaBundle\CaBundle;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Process\Process;
use Seld\PharUtils\Timestamps;

/**
 * The Compiler class compiles composer into a phar
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Compiler
{
    private $version;
    private $branchAliasVersion = '';
    private $versionDate;

    /**
     * Compiles composer into a single phar file
     *
     * @param  string            $pharFile The full path to the file to create
     * @throws \RuntimeException
     */
    public function compile($pharFile = 'composer.phar')
    {
        if (file_exists($pharFile)) {
            unlink($pharFile);
        }

        $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());

        $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'));

        $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-master (if any)
            $localConfig = __DIR__.'/../../composer.json';
            $file = new JsonFile($localConfig);
            $localConfig = $file->read();
            if (isset($localConfig['extra']['branch-alias']['dev-master'])) {
                $this->branchAliasVersion = $localConfig['extra']['branch-alias']['dev-master'];
            }
        }

        $phar = new \Phar($pharFile, 0, 'composer.phar');
        $phar->setSignatureAlgorithm(\Phar::SHA1);

        $phar->startBuffering();

        $finderSort = function ($a, $b) {
            return strcmp(strtr($a->getRealPath(), '\\', '/'), strtr($b->getRealPath(), '\\', '/'));
        };

        $finder = new Finder();
        $finder->files()
            ->ignoreVCS(true)
            ->name('*.php')
            ->notName('Compiler.php')
            ->notName('ClassLoader.php')
            ->in(__DIR__.'/..')
            ->sort($finderSort)
        ;

        foreach ($finder as $file) {
            $this->addFile($phar, $file);
        }
        $this->addFile($phar, new \SplFileInfo(__DIR__ . '/Autoload/ClassLoader.php'), false);

        $finder = new Finder();
        $finder->files()
            ->name('*.json')
            ->in(__DIR__.'/../../res')
            ->in(SpdxLicenses::getResourcesDir())
            ->sort($finderSort)
        ;

        foreach ($finder as $file) {
            $this->addFile($phar, $file, false);
        }
        $this->addFile($phar, new \SplFileInfo(__DIR__ . '/../../vendor/symfony/console/Resources/bin/hiddeninput.exe'), false);

        $finder = new Finder();
        $finder->files()
            ->ignoreVCS(true)
            ->name('*.php')
            ->name('LICENSE')
            ->exclude('Tests')
            ->exclude('tests')
            ->exclude('docs')
            ->in(__DIR__.'/../../vendor/symfony/')
            ->in(__DIR__.'/../../vendor/seld/jsonlint/')
            ->in(__DIR__.'/../../vendor/justinrainbow/json-schema/')
            ->in(__DIR__.'/../../vendor/composer/spdx-licenses/')
            ->in(__DIR__.'/../../vendor/composer/semver/')
            ->in(__DIR__.'/../../vendor/composer/ca-bundle/')
            ->in(__DIR__.'/../../vendor/composer/xdebug-handler/')
            ->in(__DIR__.'/../../vendor/psr/')
            ->sort($finderSort)
        ;

        foreach ($finder as $file) {
            $this->addFile($phar, $file);
        }

        $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/autoload.php'));
        $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/autoload_namespaces.php'));
        $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/autoload_psr4.php'));
        $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/autoload_classmap.php'));
        $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/autoload_files.php'));
        $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/autoload_real.php'));
        $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/autoload_static.php'));
        if (file_exists(__DIR__.'/../../vendor/composer/include_paths.php')) {
            $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/include_paths.php'));
        }
        $this->addFile($phar, new \SplFileInfo(__DIR__.'/../../vendor/composer/ClassLoader.php'));

        $this->addFile($phar, new \SplFileInfo(CaBundle::getBundledCaBundlePath()), false);

        $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::SHA1);
    }

    /**
     * @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, '', $pos, strlen($pathPrefix)) : $realPath;

        return strtr($relativePath, '\\', '/');
    }

    private function addFile($phar, $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 = str_replace('@package_version@', $this->version, $content);
            $content = str_replace('@package_branch_alias_version@', $this->branchAliasVersion, $content);
            $content = str_replace('@release_date@', $this->versionDate->format('Y-m-d H:i:s'), $content);
            $content = preg_replace('{SOURCE_VERSION = \'[^\']+\';}', 'SOURCE_VERSION = \'\';', $content);
        }

        $phar->addFromString($path, $content);
    }

    private function addComposerBin($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_COMMENT, T_DOC_COMMENT))) {
                $output .= str_repeat("\n", substr_count($token[1], "\n"));
            } elseif (T_WHITESPACE === $token[0]) {
                // reduce wide spaces
                $whitespace = preg_replace('{[ \t]+}', ' ', $token[1]);
                // normalize newlines to \n
                $whitespace = preg_replace('{(?:\r\n|\r|\n)}', "\n", $whitespace);
                // trim leading spaces
                $whitespace = preg_replace('{\n +}', "\n", $whitespace);
                $output .= $whitespace;
            } else {
                $output .= $token[1];
            }
        }

        return $output;
    }

    private function getStub()
    {
        $stub = <<<'EOF'
#!/usr/bin/env php
<?php
/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view
 * the license that is located at the bottom of this file.
 */

// Avoid APC causing random fatal errors per https://github.com/composer/composer/issues/264
if (extension_loaded('apc') && filter_var(ini_get('apc.enable_cli'), FILTER_VALIDATE_BOOLEAN) && filter_var(ini_get('apc.cache_by_default'), FILTER_VALIDATE_BOOLEAN)) {
    if (version_compare(phpversion('apc'), '3.0.12', '>=')) {
        ini_set('apc.cache_by_default', 0);
    } else {
        fwrite(STDERR, 'Warning: APC <= 3.0.12 may cause fatal errors when running composer commands.'.PHP_EOL);
        fwrite(STDERR, 'Update APC, or set apc.enable_cli or apc.cache_by_default to 0 in your php.ini.'.PHP_EOL);
    }
}

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

EOF;

        // add warning once the phar is older than 60 days
        if (preg_match('{^[a-f0-9]+$}', $this->version)) {
            $warningTime = $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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer;

use Composer\Package\RootPackageInterface;
use Composer\Package\Locker;
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 <j.boggiano@seld.be>
 * @author Konstantin Kudryashiv <ever.zet@gmail.com>
 * @author Nils Adermann <naderman@naderman.de>
 */
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 = '1.9.1';
     * const BRANCH_ALIAS_VERSION = '';
     * const RELEASE_DATE = '2019-11-01 17:20:17';
     * const SOURCE_VERSION = '1.8-dev+source';
     */
    const VERSION = '1.9.1';
    const BRANCH_ALIAS_VERSION = '';
    const RELEASE_DATE = '2019-11-01 17:20:17';
    const SOURCE_VERSION = '1.9-dev+source';

    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_match('{^[a-f0-9]{40}$}', self::VERSION)) {
            return self::BRANCH_ALIAS_VERSION.'+'.self::VERSION;
        }

        return self::VERSION;
    }

    /**
     * @var Package\RootPackageInterface
     */
    private $package;

    /**
     * @var Locker
     */
    private $locker;

    /**
     * @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;

    /**
     * @param  Package\RootPackageInterface $package
     * @return void
     */
    public function setPackage(RootPackageInterface $package)
    {
        $this->package = $package;
    }

    /**
     * @return Package\RootPackageInterface
     */
    public function getPackage()
    {
        return $this->package;
    }

    /**
     * @param Config $config
     */
    public function setConfig(Config $config)
    {
        $this->config = $config;
    }

    /**
     * @return Config
     */
    public function getConfig()
    {
        return $this->config;
    }

    /**
     * @param Package\Locker $locker
     */
    public function setLocker(Locker $locker)
    {
        $this->locker = $locker;
    }

    /**
     * @return Package\Locker
     */
    public function getLocker()
    {
        return $this->locker;
    }

    /**
     * @param Repository\RepositoryManager $manager
     */
    public function setRepositoryManager(RepositoryManager $manager)
    {
        $this->repositoryManager = $manager;
    }

    /**
     * @return Repository\RepositoryManager
     */
    public function getRepositoryManager()
    {
        return $this->repositoryManager;
    }

    /**
     * @param Downloader\DownloadManager $manager
     */
    public function setDownloadManager(DownloadManager $manager)
    {
        $this->downloadManager = $manager;
    }

    /**
     * @return Downloader\DownloadManager
     */
    public function getDownloadManager()
    {
        return $this->downloadManager;
    }

    /**
     * @param ArchiveManager $manager
     */
    public function setArchiveManager(ArchiveManager $manager)
    {
        $this->archiveManager = $manager;
    }

    /**
     * @return ArchiveManager
     */
    public function getArchiveManager()
    {
        return $this->archiveManager;
    }

    /**
     * @param Installer\InstallationManager $manager
     */
    public function setInstallationManager(InstallationManager $manager)
    {
        $this->installationManager = $manager;
    }

    /**
     * @return Installer\InstallationManager
     */
    public function getInstallationManager()
    {
        return $this->installationManager;
    }

    /**
     * @param Plugin\PluginManager $manager
     */
    public function setPluginManager(PluginManager $manager)
    {
        $this->pluginManager = $manager;
    }

    /**
     * @return Plugin\PluginManager
     */
    public function getPluginManager()
    {
        return $this->pluginManager;
    }

    /**
     * @param EventDispatcher $eventDispatcher
     */
    public function setEventDispatcher(EventDispatcher $eventDispatcher)
    {
        $this->eventDispatcher = $eventDispatcher;
    }

    /**
     * @return EventDispatcher
     */
    public function getEventDispatcher()
    {
        return $this->eventDispatcher;
    }

    /**
     * @param Autoload\AutoloadGenerator $autoloadGenerator
     */
    public function setAutoloadGenerator(AutoloadGenerator $autoloadGenerator)
    {
        $this->autoloadGenerator = $autoloadGenerator;
    }

    /**
     * @return Autoload\AutoloadGenerator
     */
    public function getAutoloadGenerator()
    {
        return $this->autoloadGenerator;
    }
}
<?php

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

namespace Composer;

use Composer\IO\IOInterface;
use Composer\Util\Filesystem;
use Composer\Util\Silencer;
use Symfony\Component\Finder\Finder;

/**
 * Reads/writes to a filesystem cache
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Cache
{
    private static $cacheCollected = false;
    private $io;
    private $root;
    private $enabled = true;
    private $whitelist;
    private $filesystem;

    /**
     * @param IOInterface $io
     * @param string      $cacheDir   location of the cache
     * @param string      $whitelist  List of characters that are allowed in path names (used in a regex character class)
     * @param Filesystem  $filesystem optional filesystem instance
     */
    public function __construct(IOInterface $io, $cacheDir, $whitelist = 'a-z0-9.', Filesystem $filesystem = null)
    {
        $this->io = $io;
        $this->root = rtrim($cacheDir, '/\\') . '/';
        $this->whitelist = $whitelist;
        $this->filesystem = $filesystem ?: new Filesystem();

        if (!self::isUsable($cacheDir)) {
            $this->enabled = false;

            return;
        }

        if (
            (!is_dir($this->root) && !Silencer::call('mkdir', $this->root, 0777, true))
            || !is_writable($this->root)
        ) {
            $this->io->writeError('<warning>Cannot create cache directory ' . $this->root . ', or directory is not writable. Proceeding without cache</warning>');
            $this->enabled = false;
        }
    }

    public static function isUsable($path)
    {
        return !preg_match('{(^|[\\\\/])(\$null|nul|NUL|/dev/null)([\\\\/]|$)}', $path);
    }

    public function isEnabled()
    {
        return $this->enabled;
    }

    public function getRoot()
    {
        return $this->root;
    }

    public function read($file)
    {
        if ($this->enabled) {
            $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
            if (file_exists($this->root . $file)) {
                $this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG);

                return file_get_contents($this->root . $file);
            }
        }

        return false;
    }

    public function write($file, $contents)
    {
        if ($this->enabled) {
            $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);

            $this->io->writeError('Writing '.$this->root . $file.' into cache', true, IOInterface::DEBUG);

            try {
                return file_put_contents($this->root . $file, $contents);
            } catch (\ErrorException $e) {
                $this->io->writeError('<warning>Failed to write into cache: '.$e->getMessage().'</warning>', true, IOInterface::DEBUG);
                if (preg_match('{^file_put_contents\(\): Only ([0-9]+) of ([0-9]+) bytes written}', $e->getMessage(), $m)) {
                    // Remove partial file.
                    unlink($this->root . $file);

                    $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>',
                        $this->root . $file,
                        $m[1],
                        $m[2],
                        @disk_free_space($this->root . dirname($file))
                    );

                    $this->io->writeError($message);

                    return false;
                }

                throw $e;
            }
        }

        return false;
    }

    /**
     * Copy a file into the cache
     */
    public function copyFrom($file, $source)
    {
        if ($this->enabled) {
            $file = preg_replace('{[^'.$this->whitelist.']}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
     */
    public function copyTo($file, $target)
    {
        if ($this->enabled) {
            $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
            if (file_exists($this->root . $file)) {
                try {
                    touch($this->root . $file, filemtime($this->root . $file), time());
                } catch (\ErrorException $e) {
                    // fallback in case the above failed due to incorrect ownership
                    // see https://github.com/composer/composer/issues/4070
                    Silencer::call('touch', $this->root . $file);
                }

                $this->io->writeError('Reading '.$this->root . $file.' from cache', true, IOInterface::DEBUG);

                return copy($this->root . $file, $target);
            }
        }

        return false;
    }

    public function gcIsNecessary()
    {
        return (!self::$cacheCollected && !mt_rand(0, 50));
    }

    public function remove($file)
    {
        if ($this->enabled) {
            $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
            if (file_exists($this->root . $file)) {
                return $this->filesystem->unlink($this->root . $file);
            }
        }

        return false;
    }

    public function clear()
    {
        if ($this->enabled) {
            $this->filesystem->emptyDirectory($this->root);
            return true;
        }

        return false;
    }

    public function gc($ttl, $maxSize)
    {
        if ($this->enabled) {
            $expire = new \DateTime();
            $expire->modify('-'.$ttl.' seconds');

            $finder = $this->getFinder()->date('until '.$expire->format('Y-m-d H:i:s'));
            foreach ($finder as $file) {
                $this->filesystem->unlink($file->getPathname());
            }

            $totalSize = $this->filesystem->size($this->root);
            if ($totalSize > $maxSize) {
                $iterator = $this->getFinder()->sortByAccessedTime()->getIterator();
                while ($totalSize > $maxSize && $iterator->valid()) {
                    $filepath = $iterator->current()->getPathname();
                    $totalSize -= $this->filesystem->size($filepath);
                    $this->filesystem->unlink($filepath);
                    $iterator->next();
                }
            }

            self::$cacheCollected = true;

            return true;
        }

        return false;
    }

    public function sha1($file)
    {
        if ($this->enabled) {
            $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
            if (file_exists($this->root . $file)) {
                return sha1_file($this->root . $file);
            }
        }

        return false;
    }

    public function sha256($file)
    {
        if ($this->enabled) {
            $file = preg_replace('{[^'.$this->whitelist.']}i', '-', $file);
            if (file_exists($this->root . $file)) {
                return hash_file('sha256', $this->root . $file);
            }
        }

        return false;
    }

    protected function getFinder()
    {
        return Finder::create()->in($this->root)->files();
    }
}
<?php

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

namespace Composer\Command;

use Composer\Cache;
use Composer\Factory;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author David Neilsen <petah.p@gmail.com>
 */
class ClearCacheCommand extends BaseCommand
{
    protected function configure()
    {
        $this
            ->setName('clear-cache')
            ->setAliases(array('clearcache'))
            ->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-
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $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);
            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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Package\Link;
use Composer\Package\PackageInterface;
use Composer\Semver\Constraint\Constraint;
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 Composer\Repository\PlatformRepository;

class CheckPlatformReqsCommand extends BaseCommand
{
    protected function configure()
    {
        $this->setName('check-platform-reqs')
            ->setDescription('Check that platform requirements are satisfied.')
            ->setDefinition(array(
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables checking of require-dev packages requirements.'),
            ))
            ->setHelp(
                <<<EOT
Checks that your PHP and extensions versions match the platform requirements of the installed packages.

Unlike update/install, this command will ignore config.platform settings and check the real platform packages so you can be certain you have the required platform dependencies.

<info>php composer.phar check-platform-reqs</info>

EOT
            );
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $composer = $this->getComposer();

        $requires = $composer->getPackage()->getRequires();
        if ($input->getOption('no-dev')) {
            $dependencies = $composer->getLocker()->getLockedRepository(!$input->getOption('no-dev'))->getPackages();
        } else {
            $dependencies = $composer->getRepositoryManager()->getLocalRepository()->getPackages();
            // fallback to lockfile if installed repo is empty
            if (!$dependencies) {
                $dependencies = $composer->getLocker()->getLockedRepository(true)->getPackages();
            }
            $requires += $composer->getPackage()->getDevRequires();
        }
        foreach ($requires as $require => $link) {
            $requires[$require] = array($link);
        }

        foreach ($dependencies as $package) {
            foreach ($package->getRequires() as $require => $link) {
                $requires[$require][] = $link;
            }
        }

        ksort($requires);

        $platformRepo = new PlatformRepository(array(), array());
        $currentPlatformPackages = $platformRepo->getPackages();
        $currentPlatformPackageMap = array();

        /**
         * @var PackageInterface $currentPlatformPackage
         */
        foreach ($currentPlatformPackages as $currentPlatformPackage) {
            $currentPlatformPackageMap[$currentPlatformPackage->getName()] = $currentPlatformPackage;
        }

        $results = array();

        $exitCode = 0;

        /**
         * @var Link[] $links
         */
        foreach ($requires as $require => $links) {
            if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $require)) {
                if (isset($currentPlatformPackageMap[$require])) {
                    $pass = true;
                    $version = $currentPlatformPackageMap[$require]->getVersion();

                    foreach ($links as $link) {
                        if (!$link->getConstraint()->matches(new Constraint('=', $version))) {
                            $results[] = array(
                                $currentPlatformPackageMap[$require]->getPrettyName(),
                                $currentPlatformPackageMap[$require]->getPrettyVersion(),
                                $link,
                                '<error>failed</error>',
                            );
                            $pass = false;

                            $exitCode = max($exitCode, 1);
                        }
                    }

                    if ($pass) {
                        $results[] = array(
                            $currentPlatformPackageMap[$require]->getPrettyName(),
                            $currentPlatformPackageMap[$require]->getPrettyVersion(),
                            null,
                            '<info>success</info>',
                        );
                    }
                } else {
                    $results[] = array(
                        $require,
                        'n/a',
                        $links[0],
                        '<error>missing</error>',
                    );

                    $exitCode = max($exitCode, 2);
                }
            }
        }

        $this->printTable($output, $results);

        return $exitCode;
    }

    protected function printTable(OutputInterface $output, $results)
    {
        $table = array();
        $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,
            );
        }
        $table = array_merge($rows, $table);

        // Render table
        $renderer = new Table($output);
        $renderer->setStyle('compact');
        $rendererStyle = $renderer->getStyle();
        $rendererStyle->setVerticalBorderChar('');
        $rendererStyle->setCellRowContentFormat('%s  ');
        $renderer->setRows($table)->render();
    }
}
<?php

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

namespace Composer\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 <j.boggiano@seld.be>
 */
class OutdatedCommand extends ShowCommand
{
    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('direct', 'D', InputOption::VALUE_NONE, 'Shows only packages that are directly required by the root package'),
                new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'),
                new InputOption('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', null, InputOption::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.'),
            ))
            ->setHelp(
                <<<EOT
The outdated command is just a proxy for `composer show -l`

The color coding (or signage if you have ANSI colors disabled) for dependency versions is as such:

- <info>green</info> (=): Dependency is in the latest version and is up to date.
- <comment>yellow</comment> (~): Dependency has a new version available that includes backwards
  compatibility breaks according to semver, so upgrade when you can but it
  may involve work.
- <highlight>red</highlight> (!): Dependency has a new version that is semver-compatible and you should upgrade it.

Read more at https://getcomposer.org/doc/03-cli.md#outdated
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $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;
        }
        $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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\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 <Joshua.Estes@iostudio.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ConfigCommand extends BaseCommand
{
    /**
     * @var Config
     */
    protected $config;

    /**
     * @var JsonFile
     */
    protected $configFile;

    /**
     * @var JsonConfigSource
     */
    protected $configSource;

    /**
     * @var JsonFile
     */
    protected $authConfigFile;

    /**
     * @var JsonConfigSource
     */
    protected $authConfigSource;

    /**
     * {@inheritDoc}
     */
    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', null, InputOption::VALUE_NONE, 'Unset the given setting-key'),
                new InputOption('list', 'l', InputOption::VALUE_NONE, 'List configuration settings'),
                new InputOption('file', 'f', InputOption::VALUE_REQUIRED, 'If you want to choose a different composer.json or config.json'),
                new InputOption('absolute', null, InputOption::VALUE_NONE, 'Returns absolute paths when fetching *-dir config values instead of relative'),
                new 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 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
            )
        ;
    }

    /**
     * {@inheritDoc}
     */
    protected function initialize(InputInterface $input, OutputInterface $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($configFile, null, $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($authConfigFile, null, $io);
        $this->authConfigSource = new JsonConfigSource($this->authConfigFile, true);

        // Initialize the global file if it's not there, ignoring any warnings or notices
        if ($input->getOption('global') && !$this->configFile->exists()) {
            touch($this->configFile->getPath());
            $this->configFile->write(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));
            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));
        }
    }

    /**
     * {@inheritDoc}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        // Open file in editor
        if ($input->getOption('editor')) {
            $editor = escapeshellcmd(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 (!$input->getOption('global')) {
            $this->config->merge($this->configFile->read());
            $this->config->merge(array('config' => $this->authConfigFile->exists() ? $this->authConfigFile->read() : array()));
        }

        // List the configuration of the file settings
        if ($input->getOption('list')) {
            $this->listConfiguration($this->config->all(), $this->config->raw(), $output);

            return 0;
        }

        $settingKey = $input->getArgument('setting-key');
        if (!$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_match('/^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') ? 0 : Config::RELATIVE_PATHS);
            } elseif (in_array($settingKey, $properties, true) && isset($rawData[$settingKey])) {
                $value = $rawData[$settingKey];
            } else {
                throw new \RuntimeException($settingKey.' is not defined');
            }

            if (is_array($value)) {
                $value = json_encode($value);
            }

            $this->getIO()->write($value, true, IOInterface::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;
                },
            ),
            '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_match('/^\s*([0-9.]+)\s*(?:([kmg])(?:i?b)?)?\s*$/i', $val) > 0;
                },
                function ($val) {
                    return $val;
                },
            ),
            'bin-compat' => array(
                function ($val) {
                    return in_array($val, array('auto', 'full'));
                },
                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) && is_readable($val);
                },
                function ($val) {
                    return $val === 'null' ? null : $val;
                },
            ),
            'capath' => array(
                function ($val) {
                    return is_dir($val) && is_readable($val);
                },
                function ($val) {
                    return $val === 'null' ? null : $val;
                },
            ),
            'github-expose-hostname' => array($booleanValidator, $booleanNormalizer),
            'htaccess-protect' => array($booleanValidator, $booleanNormalizer),
        );
        $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 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]) || substr($settingKey, 0, 6) === 'extra.')) {
            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_match('/^repos?(?:itories)?\.(.+)/', $settingKey, $matches)) {
            if ($input->getOption('unset')) {
                $this->configSource->removeRepository($matches[1]);

                return 0;
            }

            if (2 === count($values)) {
                $this->configSource->addRepository($matches[1], array(
                    'type' => $values[0],
                    'url' => $values[1],
                ));

                return 0;
            }

            if (1 === count($values)) {
                $value = strtolower($values[0]);
                if (true === $booleanValidator($value)) {
                    if (false === $booleanNormalizer($value)) {
                        $this->configSource->addRepository($matches[1], false);

                        return 0;
                    }
                } else {
                    $value = JsonFile::parseJson($values[0]);
                    $this->configSource->addRepository($matches[1], $value);

                    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_match('/^extra\.(.+)/', $settingKey, $matches)) {
            if ($input->getOption('unset')) {
                $this->configSource->removeProperty($settingKey);

                return 0;
            }

            $this->configSource->addProperty($settingKey, $values[0]);

            return 0;
        }

        // handle platform
        if (preg_match('/^platform\.(.+)/', $settingKey, $matches)) {
            if ($input->getOption('unset')) {
                $this->configSource->removeConfigSetting($settingKey);

                return 0;
            }

            $this->configSource->addConfigSetting($settingKey, $values[0]);

            return 0;
        }
        if ($settingKey === 'platform' && $input->getOption('unset')) {
            $this->configSource->removeConfigSetting($settingKey);

            return 0;
        }

        // handle auth
        if (preg_match('/^(bitbucket-oauth|github-oauth|gitlab-oauth|gitlab-token|http-basic)\.(.+)/', $settingKey, $matches)) {
            if ($input->getOption('unset')) {
                $this->authConfigSource->removeConfigSetting($matches[1].'.'.$matches[2]);
                $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);

                return 0;
            }

            if ($matches[1] === 'bitbucket-oauth') {
                if (2 !== count($values)) {
                    throw new \RuntimeException('Expected two arguments (consumer-key, consumer-secret), got '.count($values));
                }
                $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
                $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], array('consumer-key' => $values[0], 'consumer-secret' => $values[1]));
            } elseif (in_array($matches[1], array('github-oauth', 'gitlab-oauth', 'gitlab-token'), true)) {
                if (1 !== count($values)) {
                    throw new \RuntimeException('Too many arguments, expected only one token');
                }
                $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
                $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], $values[0]);
            } elseif ($matches[1] === 'http-basic') {
                if (2 !== count($values)) {
                    throw new \RuntimeException('Expected two arguments (username, password), got '.count($values));
                }
                $this->configSource->removeConfigSetting($matches[1].'.'.$matches[2]);
                $this->authConfigSource->addConfigSetting($matches[1].'.'.$matches[2], array('username' => $values[0], 'password' => $values[1]));
            }

            return 0;
        }

        // handle script
        if (preg_match('/^scripts\.(.+)/', $settingKey, $matches)) {
            if ($input->getOption('unset')) {
                $this->configSource->removeProperty($settingKey);

                return 0;
            }

            $this->configSource->addProperty($settingKey, count($values) > 1 ? $values : $values[0]);

            return 0;
        }

        throw new \InvalidArgumentException('Setting '.$settingKey.' does not exist or is not supported by this command');
    }

    protected function handleSingleValue($key, array $callbacks, array $values, $method)
    {
        list($validator, $normalizer) = $callbacks;
        if (1 !== count($values)) {
            throw new \RuntimeException('You can only pass one value. Example: php composer.phar config process-timeout 300');
        }

        if (true !== $validation = $validator($values[0])) {
            throw new \RuntimeException(sprintf(
                '"%s" is an invalid value'.($validation ? ' ('.$validation.')' : ''),
                $values[0]
            ));
        }

        $normalizedValue = $normalizer($values[0]);

        if ($key === 'disable-tls') {
            if (!$normalizedValue && $this->config->get('disable-tls')) {
                $this->getIO()->writeError('<info>You are now running Composer with SSL/TLS protection enabled.</info>');
            } elseif ($normalizedValue && !$this->config->get('disable-tls')) {
                $this->getIO()->writeError('<warning>You are now running Composer with SSL/TLS protection disabled.</warning>');
            }
        }

        return call_user_func(array($this->configSource, $method), $key, $normalizedValue);
    }

    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)
            ));
        }

        return call_user_func(array($this->configSource, $method), $key, $normalizer($values));
    }

    /**
     * Display the contents of the file in a pretty formatted way
     *
     * @param array           $contents
     * @param array           $rawContents
     * @param OutputInterface $output
     * @param string|null     $k
     */
    protected function listConfiguration(array $contents, array $rawContents, OutputInterface $output, $k = null)
    {
        $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);
                $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($value, true);
            }

            if (is_string($rawVal) && $rawVal != $value) {
                $io->write('[<comment>' . $k . $key . '</comment>] <info>' . $rawVal . ' (' . $value . ')</info>', true, IOInterface::QUIET);
            } else {
                $io->write('[<comment>' . $k . $key . '</comment>] <info>' . $value . '</info>', true, IOInterface::QUIET);
            }
        }
    }
}
<?php

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

namespace Composer\Command;

use Composer\DependencyResolver\Pool;
use Composer\Factory;
use Composer\Json\JsonFile;
use Composer\Package\BasePackage;
use Composer\Package\Version\VersionParser;
use Composer\Package\Version\VersionSelector;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryFactory;
use Composer\Util\ProcessExecutor;
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;

/**
 * @author Justin Rainbow <justin.rainbow@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class InitCommand extends BaseCommand
{
    /** @var CompositeRepository */
    protected $repos;

    /** @var array */
    private $gitConfig;

    /** @var Pool[] */
    private $pools;

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this
            ->setName('init')
            ->setDescription('Creates a basic composer.json file in current directory.')
            ->setDefinition(array(
                new InputOption('name', null, InputOption::VALUE_REQUIRED, 'Name of the package'),
                new InputOption('description', null, InputOption::VALUE_REQUIRED, 'Description of package'),
                new InputOption('author', null, InputOption::VALUE_REQUIRED, 'Author name of package'),
                // new InputOption('version', null, InputOption::VALUE_NONE, 'Version of package'),
                new InputOption('type', null, InputOption::VALUE_OPTIONAL, 'Type of package (e.g. library, project, metapackage, composer-plugin)'),
                new InputOption('homepage', null, InputOption::VALUE_REQUIRED, 'Homepage of package'),
                new InputOption('require', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'),
                new InputOption('require-dev', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Package to require for development with a version constraint, e.g. foo/bar:1.0.0 or foo/bar=1.0.0 or "foo/bar 1.0.0"'),
                new InputOption('stability', 's', InputOption::VALUE_REQUIRED, 'Minimum stability (empty or one of: '.implode(', ', array_keys(BasePackage::$stabilities)).')'),
                new InputOption('license', 'l', InputOption::VALUE_REQUIRED, 'License of package'),
                new InputOption('repository', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Add custom repositories, either by URL or using JSON arrays'),
            ))
            ->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}
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $io = $this->getIO();

        $whitelist = array('name', 'description', 'author', 'type', 'homepage', 'require', 'require-dev', 'stability', 'license');
        $options = array_filter(array_intersect_key($input->getOptions(), array_flip($whitelist)));

        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, $repo);
            }
        }

        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;
            }
        }

        $file = new JsonFile(Factory::getComposerFile());
        $json = $file->encode($options);

        if ($input->isInteractive()) {
            $io->writeError(array('', $json, ''));
            if (!$io->askConfirmation('Do you confirm generation [<comment>yes</comment>]? ', true)) {
                $io->writeError('<error>Command aborted</error>');

                return 1;
            }
        }

        $file->write($options);

        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, true)) {
                    $this->addVendorIgnore($ignoreFile);
                }
            }
        }

        $question = 'Would you like to install dependencies now [<comment>yes</comment>]? ';
        if ($input->isInteractive() && $this->hasDependencies($options) && $io->askConfirmation($question, true)) {
            $this->installDependencies($output);
        }

        return 0;
    }

    /**
     * {@inheritdoc}
     */
    protected function interact(InputInterface $input, OutputInterface $output)
    {
        $git = $this->getGitConfig();
        $io = $this->getIO();
        $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, $repo);
                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);
        } else {
            if (!preg_match('{^[a-z0-9_.-]+/[a-z0-9_.-]+$}D', $name)) {
                throw new \InvalidArgumentException(
                    'The package name '.$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_.-]+'
                );
            }
        }

        $name = $io->askAndValidate(
            'Package name (<vendor>/<name>) [<comment>'.$name.'</comment>]: ',
            function ($value) use ($name) {
                if (null === $value) {
                    return $name;
                }

                if (!preg_match('{^[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) && isset($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';
        $phpVersion = $repos->findPackage('php', '*')->getPrettyVersion();

        $question = 'Would you like to define your dependencies (require) interactively [<comment>yes</comment>]? ';
        $require = $input->getOption('require');
        $requirements = array();
        if ($require || $io->askConfirmation($question, true)) {
            $requirements = $this->determineRequirements($input, $output, $require, $phpVersion, $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, true)) {
            $devRequirements = $this->determineRequirements($input, $output, $requireDev, $phpVersion, $preferredStability);
        }
        $input->setOption('require-dev', $devRequirements);
    }

    /**
     * @private
     * @param  string $author
     * @return array
     */
    public function parseAuthorString($author)
    {
        if (preg_match('/^(?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 <john@example.com>'
        );
    }

    protected function findPackages($name)
    {
        return $this->getRepos()->search($name);
    }

    protected function getRepos()
    {
        if (!$this->repos) {
            $this->repos = new CompositeRepository(array_merge(
                array(new PlatformRepository),
                RepositoryFactory::defaultRepos($this->getIO())
            ));
        }

        return $this->repos;
    }

    protected function determineRequirements(InputInterface $input, OutputInterface $output, $requires = array(), $phpVersion = null, $preferredStability = 'stable', $checkProvidedVersions = true)
    {
        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'], $phpVersion, $preferredStability);
                    $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, $version) = $this->findBestVersionAndNameForPackage($input, $requirement['name'], $phpVersion, $preferredStability, $checkProvidedVersions ? $requirement['version'] : null, 'dev');

                    // replace package name from packagist.org
                    $requirement['name'] = $name;
                }

                $result[] = $requirement['name'] . ' ' . $requirement['version'];
            }

            return $result;
        }

        $versionParser = new VersionParser();
        $io = $this->getIO();
        while (null !== $package = $io->ask('Search for a package: ')) {
            $matches = $this->findPackages($package);

            if (count($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_match('{^\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($name, $constraint) = $this->findBestVersionAndNameForPackage($input, $package, $phpVersion, $preferredStability);

                        $io->writeError(sprintf(
                            'Using version <info>%s</info> for <info>%s</info>',
                            $constraint,
                            $package
                        ));
                    }

                    $package .= ' '.$constraint;
                }

                if (false !== $package) {
                    $requires[] = $package;
                }
            }
        }

        return $requires;
    }

    protected function formatAuthors($author)
    {
        return array($this->parseAuthorString($author));
    }

    protected function formatRequirements(array $requirements)
    {
        $requires = array();
        $requirements = $this->normalizeRequirements($requirements);
        foreach ($requirements as $requirement) {
            $requires[$requirement['name']] = $requirement['version'];
        }

        return $requires;
    }

    protected function getGitConfig()
    {
        if (null !== $this->gitConfig) {
            return $this->gitConfig;
        }

        $finder = new ExecutableFinder();
        $gitBin = $finder->find('git');

        // TODO in v3 always call with an array
        if (method_exists('Symfony\Component\Process\Process', 'fromShellCommandline')) {
            $cmd = new Process(array($gitBin, 'config', '-l'));
        } else {
            $cmd = new Process(sprintf('%s config -l', ProcessExecutor::escape($gitBin)));
        }
        $cmd->run();

        if ($cmd->isSuccessful()) {
            $this->gitConfig = array();
            preg_match_all('{^([^=]+)=(.*)$}m', $cmd->getOutput(), $matches, PREG_SET_ORDER);
            foreach ($matches as $match) {
                $this->gitConfig[$match[1]] = $match[2];
            }

            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($ignoreFile, FILE_IGNORE_NEW_LINES);
        foreach ($lines as $line) {
            if (preg_match($pattern, $line)) {
                return true;
            }
        }

        return false;
    }

    protected function normalizeRequirements(array $requirements)
    {
        $parser = new VersionParser();

        return $parser->parseNameVersionPairs($requirements);
    }

    protected function addVendorIgnore($ignoreFile, $vendor = '/vendor/')
    {
        $contents = "";
        if (file_exists($ignoreFile)) {
            $contents = file_get_contents($ignoreFile);

            if ("\n" !== substr($contents, 0, -1)) {
                $contents .= "\n";
            }
        }

        file_put_contents($ignoreFile, $contents . $vendor. "\n");
    }

    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($email, FILTER_VALIDATE_EMAIL);
    }

    private function getPool(InputInterface $input, $minimumStability = null)
    {
        $key = $minimumStability ?: 'default';

        if (!isset($this->pools[$key])) {
            $this->pools[$key] = $pool = new Pool($minimumStability ?: $this->getMinimumStability($input));
            $pool->addRepository($this->getRepos());
        }

        return $this->pools[$key];
    }

    private function getMinimumStability(InputInterface $input)
    {
        if ($input->hasOption('stability')) {
            return $input->getOption('stability') ?: 'stable';
        }

        $file = Factory::getComposerFile();
        if (is_file($file) && is_readable($file) && is_array($composer = json_decode(file_get_contents($file), true))) {
            if (!empty($composer['minimum-stability'])) {
                return $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  InputInterface            $input
     * @param  string                    $name
     * @param  string|null               $phpVersion
     * @param  string                    $preferredStability
     * @param  string|null               $requiredVersion
     * @param  string                    $minimumStability
     * @throws \InvalidArgumentException
     * @return array                     name version
     */
    private function findBestVersionAndNameForPackage(InputInterface $input, $name, $phpVersion, $preferredStability = 'stable', $requiredVersion = null, $minimumStability = null)
    {
        // find the latest version allowed in this pool
        $versionSelector = new VersionSelector($this->getPool($input, $minimumStability));
        $ignorePlatformReqs = $input->hasOption('ignore-platform-reqs') && $input->getOption('ignore-platform-reqs');

        // ignore phpVersion if platform requirements are ignored
        if ($ignorePlatformReqs) {
            $phpVersion = null;
        }

        $package = $versionSelector->findBestCandidate($name, $requiredVersion, $phpVersion, $preferredStability);

        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 ($ignorePlatformReqs && preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name)) {
                return array($name, $requiredVersion ?: '*');
            }

            // Check whether the PHP version was the problem
            if ($phpVersion && $versionSelector->findBestCandidate($name, $requiredVersion, null, $preferredStability)) {
                throw new \InvalidArgumentException(sprintf(
                    'Package %s at version %s has a PHP requirement incompatible with your PHP version (%s)',
                    $name,
                    $requiredVersion,
                    $phpVersion
                ));
            }
            // Check whether the required version was the problem
            if ($requiredVersion && $versionSelector->findBestCandidate($name, null, $phpVersion, $preferredStability)) {
                throw new \InvalidArgumentException(sprintf(
                    'Could not find package %s in a version matching %s',
                    $name,
                    $requiredVersion
                ));
            }
            // Check whether the PHP version was the problem
            if ($phpVersion && $versionSelector->findBestCandidate($name)) {
                throw new \InvalidArgumentException(sprintf(
                    'Could not find package %s in any version matching your PHP version (%s)',
                    $name,
                    $phpVersion
                ));
            }

            // Check for similar names/typos
            $similar = $this->findSimilar($name);
            if ($similar) {
                // Check whether the minimum stability was the problem but the package exists
                if ($requiredVersion === null && in_array($name, $similar, true)) {
                    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,
                        $this->getMinimumStability($input)
                    ));
                }

                throw new \InvalidArgumentException(sprintf(
                    "Could not find package %s.\n\nDid you mean " . (count($similar) > 1 ? 'one of these' : 'this') . "?\n    %s",
                    $name,
                    implode("\n    ", $similar)
                ));
            }

            throw new \InvalidArgumentException(sprintf(
                'Could not find a matching version of package %s. Check the package spelling, your version constraint and that the package is available in a stability which matches your minimum-stability (%s).',
                $name,
                $this->getMinimumStability($input)
            ));
        }

        return array(
            $package->getPrettyName(),
            $versionSelector->findRecommendedRequireVersion($package),
        );
    }

    private function findSimilar($package)
    {
        try {
            $results = $this->repos->search($package);
        } catch (\Exception $e) {
            // ignore search errors
            return array();
        }
        $similarPackages = array();

        foreach ($results as $result) {
            $similarPackages[$result['name']] = levenshtein($package, $result['name']);
        }
        asort($similarPackages);

        return array_keys(array_slice($similarPackages, 0, 5));
    }

    private function installDependencies($output)
    {
        try {
            $installCommand = $this->getApplication()->find('install');
            $installCommand->run(new ArrayInput(array()), $output);
        } catch (\Exception $e) {
            $this->getIO()->writeError('Could not install dependencies. Run `composer install` to see more information.');
        }

    }

    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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Composer;
use Composer\DependencyResolver\DefaultPolicy;
use Composer\DependencyResolver\Pool;
use Composer\Json\JsonFile;
use Composer\Package\BasePackage;
use Composer\Package\CompletePackageInterface;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\Package\Version\VersionSelector;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Repository\ArrayRepository;
use Composer\Repository\ComposerRepository;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\RepositoryFactory;
use Composer\Repository\RepositoryInterface;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Semver;
use Composer\Spdx\SpdxLicenses;
use Composer\Util\Platform;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Terminal;

/**
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Jérémy Romey <jeremyFreeAgent>
 * @author Mihai Plasoianu <mihai@plasoianu.de>
 */
class ShowCommand extends BaseCommand
{
    /** @var VersionParser */
    protected $versionParser;
    protected $colors;

    /** @var Pool */
    private $pool;

    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', null, InputOption::VALUE_NONE, 'List all packages'),
                new InputOption('installed', 'i', InputOption::VALUE_NONE, 'List installed packages only (enabled by default, only present for BC).'),
                new InputOption('platform', 'p', InputOption::VALUE_NONE, 'List platform packages only'),
                new InputOption('available', 'a', InputOption::VALUE_NONE, 'List available packages only'),
                new InputOption('self', 's', InputOption::VALUE_NONE, 'Show the root package information'),
                new InputOption('name-only', 'N', InputOption::VALUE_NONE, 'List package names only'),
                new InputOption('path', 'P', InputOption::VALUE_NONE, 'Show package paths'),
                new InputOption('tree', 't', InputOption::VALUE_NONE, 'List the dependencies as a tree'),
                new InputOption('latest', 'l', InputOption::VALUE_NONE, 'Show the latest version'),
                new InputOption('outdated', 'o', InputOption::VALUE_NONE, 'Show the latest version but only for packages that are outdated'),
                new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). 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', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'),
                new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text'),
            ))
            ->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 $input, OutputInterface $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;
        }

        $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;
        }

        // init repos
        $platformOverrides = array();
        if ($composer) {
            $platformOverrides = $composer->getConfig()->get('platform') ?: array();
        }
        $platformRepo = new PlatformRepository(array(), $platformOverrides);
        $phpVersion = $platformRepo->findPackage('php', '*')->getVersion();

        if ($input->getOption('self')) {
            $package = $this->getComposer()->getPackage();
            $repos = $installedRepo = new ArrayRepository(array($package));
        } elseif ($input->getOption('platform')) {
            $repos = $installedRepo = $platformRepo;
        } elseif ($input->getOption('available')) {
            $installedRepo = $platformRepo;
            if ($composer) {
                $repos = new CompositeRepository($composer->getRepositoryManager()->getRepositories());
            } 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();
            $installedRepo = new CompositeRepository(array($localRepo, $platformRepo));
            $repos = new CompositeRepository(array_merge(array($installedRepo), $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 = $platformRepo;
            $repos = new CompositeRepository(array_merge(array($installedRepo), $defaultRepos));
        } else {
            $repos = $installedRepo = $this->getComposer()->getRepositoryManager()->getLocalRepository();
            $rootPkg = $this->getComposer()->getPackage();
            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')) {
                        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, $phpVersion);
                }
                if ($input->getOption('outdated') && $input->getOption('strict') && $latestPackage && $latestPackage->getFullPrettyVersion() !== $package->getFullPrettyVersion() && !$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;
                }
                $this->printMeta($package, $versions, $installedRepo, $latestPackage ?: null);
                $this->printLinks($package, 'requires');
                $this->printLinks($package, 'devRequires', 'requires (dev)');
                if ($package->getSuggests()) {
                    $io->write("\n<info>suggests</info>");
                    foreach ($package->getSuggests() as $suggested => $reason) {
                        $io->write($suggested . ' <comment>' . $reason . '</comment>');
                    }
                }
                $this->printLinks($package, 'provides');
                $this->printLinks($package, 'conflicts');
                $this->printLinks($package, 'replaces');
            }

            return $exitCode;
        }

        // show tree view if requested
        if ($input->getOption('tree')) {
            $rootRequires = $this->getRootRequires();
            $packages = $installedRepo->getPackages();
            usort($packages, 'strcmp');
            $arrayTree = array();
            foreach ($packages as $package) {
                if (in_array($package->getName(), $rootRequires, true)) {
                    $arrayTree[] = $this->generatePackageTree($package, $installedRepo, $repos);
                }
            }

            if ('json' === $format) {
                $io->write(JsonFile::encode(array('installed' => $arrayTree)));
            } else {
                $this->displayPackageTree($arrayTree);
            }

            return 0;
        }

        if ($repos instanceof CompositeRepository) {
            $repos = $repos->getRepositories();
        } elseif (!is_array($repos)) {
            $repos = array($repos);
        }

        // list packages
        $packages = array();
        if (null !== $packageFilter) {
            $packageFilter = '{^'.str_replace('\\*', '.*?', preg_quote($packageFilter)).'$}i';
        }

        $packageListFilter = array();
        if ($input->getOption('direct')) {
            $packageListFilter = $this->getRootRequires();
        }

        if (class_exists('Symfony\Component\Console\Terminal')) {
            $terminal = new Terminal();
            $width = $terminal->getWidth();
        } else {
            // For versions of Symfony console before 3.2
            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);
        }

        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 as $repo) {
            if ($repo === $platformRepo) {
                $type = 'platform';
            } elseif (
                $repo === $installedRepo
                || ($installedRepo instanceof CompositeRepository && in_array($repo, $installedRepo->getRepositories(), true))
            ) {
                $type = 'installed';
            } else {
                $type = 'available';
            }
            if ($repo instanceof ComposerRepository && $repo->hasProviders()) {
                foreach ($repo->getProviderNames() as $name) {
                    if (!$packageFilter || preg_match($packageFilter, $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(), '<')
                    ) {
                        if (!$packageFilter || preg_match($packageFilter, $package->getName())) {
                            if (!$packageListFilter || in_array($package->getName(), $packageListFilter, true)) {
                                $packages[$type][$package->getName()] = $package;
                            }
                        }
                    }
                }
            }
        }

        $showAllTypes = $input->getOption('all');
        $showLatest = $input->getOption('latest');
        $showMinorOnly = $input->getOption('minor-only');
        $ignoredPackages = array_map('strtolower', $input->getOption('ignore'));
        $indent = $showAllTypes ? '  ' : '';
        $latestPackages = array();
        $exitCode = 0;
        $viewData = array();
        $viewMetaData = array();
        foreach (array('platform' => 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, $phpVersion, $showMinorOnly);
                            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->isAbandoned();
                        $packageIsIgnored = \in_array($package->getPrettyName(), $ignoredPackages, true);
                        if ($input->getOption('outdated') && ($packageIsUpToDate || $packageIsIgnored)) {
                            continue;
                        } elseif ($input->getOption('outdated') || $input->getOption('strict')) {
                            $hasOutdatedPackages = true;
                        }

                        $packageViewData['name'] = $package->getPrettyName();
                        $nameLength = max($nameLength, strlen($package->getPrettyName()));
                        if ($writeVersion) {
                            $packageViewData['version'] = $package->getFullPrettyVersion();
                            $versionLength = max($versionLength, strlen($package->getFullPrettyVersion()));
                        }
                        if ($writeLatest && $latestPackage) {
                            $packageViewData['latest'] = $latestPackage->getFullPrettyVersion();
                            $packageViewData['latest-status'] = $this->getUpdateStatus($latestPackage, $package);
                            $latestLength = max($latestLength, strlen($latestPackage->getFullPrettyVersion()));
                        }
                        if ($writeDescription) {
                            $packageViewData['description'] = $package->getDescription();
                        }
                        if ($writePath) {
                            $packageViewData['path'] = strtok(realpath($composer->getInstallationManager()->getInstallPath($package)), "\r\n");
                        }

                        if ($latestPackage && $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($nameLength, strlen($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 {
            foreach ($viewData as $type => $packages) {
                $nameLength = $viewMetaData[$type]['nameLength'];
                $versionLength = $viewMetaData[$type]['versionLength'];
                $latestLength = $viewMetaData[$type]['latestLength'];

                $writeVersion = $nameLength + $versionLength + 3 <= $width;
                $writeLatest = $nameLength + $versionLength + $latestLength + 3 <= $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($description, 0, $remaining - 3) . '...';
                        }
                        $io->write(' ' . $description, false);
                    }
                    if (isset($package['path'])) {
                        $io->write(' ' . $package['path'], false);
                    }
                    $io->write('');
                    if (isset($package['warning'])) {
                        $io->writeError('<warning>' . $package['warning'] . '</warning>');
                    }
                }

                if ($showAllTypes) {
                    $io->write('');
                }
            }
        }

        return $exitCode;
    }

    protected function getRootRequires()
    {
        $rootPackage = $this->getComposer()->getPackage();

        return array_map(
            'strtolower',
            array_keys(array_merge($rootPackage->getRequires(), $rootPackage->getDevRequires()))
        );
    }

    protected function getVersionStyle(PackageInterface $latestPackage, PackageInterface $package)
    {
        return $this->updateStatusToVersionStyle($this->getUpdateStatus($latestPackage, $package));
    }

    /**
     * finds a package by name and version if provided
     *
     * @param  RepositoryInterface        $installedRepo
     * @param  RepositoryInterface        $repos
     * @param  string                     $name
     * @param  ConstraintInterface|string $version
     * @throws \InvalidArgumentException
     * @return array                      array(CompletePackageInterface, array of versions)
     */
    protected function getPackage(RepositoryInterface $installedRepo, RepositoryInterface $repos, $name, $version = null)
    {
        $name = strtolower($name);
        $constraint = is_string($version) ? $this->versionParser->parseConstraints($version) : $version;

        $policy = new DefaultPolicy();
        $pool = new Pool('dev');
        $pool->addRepository($repos);

        $matchedPackage = null;
        $versions = array();
        $matches = $pool->whatProvides($name, $constraint);
        foreach ($matches as $index => $package) {
            // skip providers/replacers
            if ($package->getName() !== $name) {
                unset($matches[$index]);
                continue;
            }

            // 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, array(), $matches)) {
            $matchedPackage = $pool->literalToPackage($preferred[0]);
        }

        return array($matchedPackage, $versions);
    }

    /**
     * Prints package metadata.
     *
     * @param CompletePackageInterface $package
     * @param array                    $versions
     * @param RepositoryInterface      $installedRepo
     */
    protected function printMeta(CompletePackageInterface $package, array $versions, RepositoryInterface $installedRepo, PackageInterface $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>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->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>");
            foreach ($package->getAutoload() as $type => $autoloads) {
                $io->write('<comment>' . $type . '</comment>');

                if ($type === 'psr-0') {
                    foreach ($autoloads as $name => $path) {
                        $io->write(($name ?: '*') . ' => ' . (is_array($path) ? implode(', ', $path) : ($path ?: '.')));
                    }
                } elseif ($type === 'psr-4') {
                    foreach ($autoloads as $name => $path) {
                        $io->write(($name ?: '*') . ' => ' . (is_array($path) ? implode(', ', $path) : ($path ?: '.')));
                    }
                } elseif ($type === 'classmap') {
                    $io->write(implode(', ', $autoloads));
                }
            }
            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 CompletePackageInterface $package
     * @param array                    $versions
     * @param RepositoryInterface      $installedRepo
     */
    protected function printVersions(CompletePackageInterface $package, array $versions, RepositoryInterface $installedRepo)
    {
        uasort($versions, 'version_compare');
        $versions = array_keys(array_reverse($versions));

        // highlight installed version
        if ($installedRepo->hasPackage($package)) {
            $installedVersion = $package->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 CompletePackageInterface $package
     * @param string                   $linkType
     * @param string                   $title
     */
    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
     *
     * @param CompletePackageInterface $package
     */
    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);
        }
    }

    /**
     * Init styles for tree
     *
     * @param OutputInterface $output
     */
    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 $arrayTree
     */
    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
     *
     * @param  PackageInterface $package
     * @param  RepositoryInterface     $installedRepo
     * @param  RepositoryInterface     $distantRepos
     * @return array
     */
    protected function generatePackageTree(
        PackageInterface $package,
        RepositoryInterface $installedRepo,
        RepositoryInterface $distantRepos
    ) {
        $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, $distantRepos, $packagesInTree);

            if ($deepChildren) {
                $treeChildDesc['requires'] = $deepChildren;
            }

            $children[] = $treeChildDesc;
        }
        $tree = array(
            'name' => $package->getPrettyName(),
            'version' => $package->getPrettyVersion(),
            'description' => $package->getDescription(),
        );

        if ($children) {
            $tree['requires'] = $children;
        }

        return $tree;
    }

    /**
     * Display a package tree
     *
     * @param PackageInterface|string $package
     * @param array                   $packagesInTree
     * @param string                  $previousTreeBar
     * @param int                     $level
     */
    protected function displayTree(
        $package,
        array $packagesInTree,
        $previousTreeBar = '├',
        $level = 1
    ) {
        $previousTreeBar = str_replace('├', '│', $previousTreeBar);
        if (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  PackageInterface|string $package
     * @param  RepositoryInterface     $installedRepo
     * @param  RepositoryInterface     $distantRepos
     * @param  array                   $packagesInTree
     * @return array
     */
    protected function addTree(
        $name,
        $package,
        RepositoryInterface $installedRepo,
        RepositoryInterface $distantRepos,
        array $packagesInTree
    ) {
        $children = array();
        list($package, $versions) = $this->getPackage(
            $installedRepo,
            $distantRepos,
            $name,
            $package->getPrettyConstraint() === 'self.version' ? $package->getConstraint() : $package->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, $currentTree, true)) {
                    $currentTree[] = $requireName;
                    $deepChildren = $this->addTree($requireName, $require, $installedRepo, $distantRepos, $currentTree);
                    if ($deepChildren) {
                        $treeChildDesc['requires'] = $deepChildren;
                    }
                }

                $children[] = $treeChildDesc;
            }
        }

        return $children;
    }

    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);
    }

    private function getUpdateStatus(PackageInterface $latestPackage, PackageInterface $package)
    {
        if ($latestPackage->getFullPrettyVersion() === $package->getFullPrettyVersion()) {
            return 'up-to-date';
        }

        $constraint = $package->getVersion();
        if (0 !== strpos($constraint, 'dev-')) {
            $constraint = '^'.$constraint;
        }
        if ($latestPackage->getVersion() && Semver::satisfies($latestPackage->getVersion(), $constraint)) {
            // it needs an immediate semver-compliant upgrade
            return 'semver-safe-update';
        }

        // it needs an upgrade but has potential BC breaks so is not urgent
        return 'update-possible';
    }

    private function writeTreeLine($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 PackageInterface $package
     * @param Composer         $composer
     * @param string           $phpVersion
     * @param bool             $minorOnly
     *
     * @return PackageInterface|null
     */
    private function findLatestPackage(PackageInterface $package, Composer $composer, $phpVersion, $minorOnly = false)
    {
        // find the latest version allowed in this pool
        $name = $package->getName();
        $versionSelector = new VersionSelector($this->getPool($composer));
        $stability = $composer->getPackage()->getMinimumStability();
        $flags = $composer->getPackage()->getStabilityFlags();
        if (isset($flags[$name])) {
            $stability = array_search($flags[$name], BasePackage::$stabilities, true);
        }

        $bestStability = $stability;
        if ($composer->getPackage()->getPreferStable()) {
            $bestStability = $package->getStability();
        }

        $targetVersion = null;
        if (0 === strpos($package->getVersion(), 'dev-')) {
            $targetVersion = $package->getVersion();
        }

        if ($targetVersion === null && $minorOnly) {
            $targetVersion = '^' . $package->getVersion();
        }

        return $versionSelector->findBestCandidate($name, $targetVersion, $phpVersion, $bestStability);
    }

    private function getPool(Composer $composer)
    {
        if (!$this->pool) {
            $this->pool = new Pool($composer->getPackage()->getMinimumStability(), $composer->getPackage()->getStabilityFlags());
            $this->pool->addRepository(new CompositeRepository($composer->getRepositoryManager()->getRepositories()));
        }

        return $this->pool;
    }
}
<?php

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

namespace Composer\Command;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Niels Keurentjes <niels.keurentjes@omines.com>
 */
class DependsCommand extends BaseDependencyCommand
{
    /**
     * Configure command metadata.
     */
    protected function configure()
    {
        parent::configure();

        $this
            ->setName('depends')
            ->setAliases(array('why'))
            ->setDescription('Shows which packages cause the given package to be installed.')
            ->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.
     *
     * @param  InputInterface  $input
     * @param  OutputInterface $output
     * @return int
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        return parent::doExecute($input, $output, false);
    }
}
<?php

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

namespace Composer\Command;

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\Json\JsonFile;
use Composer\Json\JsonManipulator;
use Composer\Package\Version\VersionParser;
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 <jeremy@free-agent.fr>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class RequireCommand extends InitCommand
{
    private $newlyCreated;
    private $json;
    private $file;
    private $composerBackup;

    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', null, InputOption::VALUE_NONE, 'Add requirement to require-dev.'),
                new InputOption('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
                new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'),
                new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'Do not show package suggestions.'),
                new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies.'),
                new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
                new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'),
                new InputOption('update-with-dependencies', null, InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated, except those that are root requirements.'),
                new InputOption('update-with-all-dependencies', null, InputOption::VALUE_NONE, 'Allows all inherited dependencies to be updated, including those that are root requirements.'),
                new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'),
                new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies.'),
                new InputOption('prefer-lowest', null, InputOption::VALUE_NONE, 'Prefer lowest versions of dependencies.'),
                new InputOption('sort-packages', null, InputOption::VALUE_NONE, 'Sorts packages when adding/updating a new dependency'),
                new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'),
                new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'),
                new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'),
            ))
            ->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
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        if (function_exists('pcntl_async_signals')) {
            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();

        $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;
        }
        // check for readability by reading the file as is_readable 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_readable($this->file) && false === Silencer::call('file_get_contents', $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->composerBackup = file_get_contents($this->json->getPath());

        // 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;
        }

        $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(new PlatformRepository(array(), $platformOverrides)),
            $repos
        ));

        if ($composer->getPackage()->getPreferStable()) {
            $preferredStability = 'stable';
        } else {
            $preferredStability = $composer->getPackage()->getMinimumStability();
        }

        $phpVersion = $this->repos->findPackage('php', '*')->getPrettyVersion();
        try {
            $requirements = $this->determineRequirements($input, $output, $input->getArgument('packages'), $phpVersion, $preferredStability, !$input->getOption('no-update'));
        } catch (\Exception $e) {
            if ($this->newlyCreated) {
                throw new \RuntimeException('No composer.json present in the current directory, 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);
        }

        $sortPackages = $input->getOption('sort-packages') || $composer->getConfig()->get('sort-packages');

        if (!$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]);
            }
            $this->json->write($composerDefinition);
        }

        $io->writeError('<info>'.$this->file.' has been '.($this->newlyCreated ? 'created' : 'updated').'</info>');

        if ($input->getOption('no-update')) {
            return 0;
        }

        try {
            return $this->doUpdate($input, $output, $io, $requirements);
        } catch (\Exception $e) {
            $this->revertComposerFile(false);
            throw $e;
        }
    }

    private function doUpdate(InputInterface $input, OutputInterface $output, IOInterface $io, array $requirements)
    {
        // Update packages
        $this->resetComposer();
        $composer = $this->getComposer(true, $input->getOption('no-plugins'));
        $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));

        $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');
        $apcu = $input->getOption('apcu-autoloader') || $composer->getConfig()->get('apcu-autoloader');

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'require', $input, $output);
        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        $install = Installer::create($io, $composer);

        $install
            ->setVerbose($input->getOption('verbose'))
            ->setPreferSource($input->getOption('prefer-source'))
            ->setPreferDist($input->getOption('prefer-dist'))
            ->setDevMode($updateDevMode)
            ->setRunScripts(!$input->getOption('no-scripts'))
            ->setSkipSuggest($input->getOption('no-suggest'))
            ->setOptimizeAutoloader($optimize)
            ->setClassMapAuthoritative($authoritative)
            ->setApcuAutoloader($apcu)
            ->setUpdate(true)
            ->setUpdateWhitelist(array_keys($requirements))
            ->setWhitelistTransitiveDependencies($input->getOption('update-with-dependencies'))
            ->setWhitelistAllDependencies($input->getOption('update-with-all-dependencies'))
            ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
            ->setPreferStable($input->getOption('prefer-stable'))
            ->setPreferLowest($input->getOption('prefer-lowest'))
        ;

        $status = $install->run();
        if ($status !== 0) {
            $this->revertComposerFile(false);
        }

        return $status;
    }

    private function updateFileCleanly($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;
            }
        }

        file_put_contents($json->getPath(), $manipulator->getContents());

        return true;
    }

    protected function interact(InputInterface $input, OutputInterface $output)
    {
        return;
    }

    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());
        } else {
            $io->writeError("\n".'<error>Installation failed, reverting '.$this->file.' to its original content.</error>');
            file_put_contents($this->json->getPath(), $this->composerBackup);
        }

        if ($hardExit) {
            exit(1);
        }
    }
}
<?php

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

namespace Composer\Command;

use Composer\Package\CompletePackageInterface;
use Composer\Repository\RepositoryInterface;
use Composer\Repository\ArrayRepository;
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 <seroscho@googlemail.com>
 */
class HomeCommand extends BaseCommand
{
    /**
     * {@inheritDoc}
     */
    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 $input, OutputInterface $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;
    }

    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($url, FILTER_VALIDATE_URL)) {
            return false;
        }

        if ($showOnly) {
            $this->getIO()->write(sprintf('<info>%s</info>', $url));
        } else {
            $this->openBrowser($url);
        }

        return true;
    }

    /**
     * opens a url in your system default browser
     *
     * @param string $url
     */
    private function openBrowser($url)
    {
        $url = ProcessExecutor::escape($url);

        $process = new ProcessExecutor($this->getIO());
        if (Platform::isWindows()) {
            return $process->execute('start "web" explorer "' . $url . '"', $output);
        }

        $linux = $process->execute('which xdg-open', $output);
        $osx = $process->execute('which open', $output);

        if (0 === $linux) {
            $process->execute('xdg-open ' . $url, $output);
        } elseif (0 === $osx) {
            $process->execute('open ' . $url, $output);
        } else {
            $this->getIO()->writeError('No suitable browser opening command found, open yourself: ' . $url);
        }
    }

    /**
     * Initializes repositories
     *
     * Returns an array of repos in order they should be checked in
     *
     * @return RepositoryInterface[]
     */
    private function initializeRepos()
    {
        $composer = $this->getComposer(false);

        if ($composer) {
            return array_merge(
                array(new ArrayRepository(array($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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class AboutCommand extends BaseCommand
{
    protected function configure()
    {
        $this
            ->setName('about')
            ->setDescription('Shows the short information about Composer.')
            ->setHelp(
                <<<EOT
<info>php composer.phar about</info>
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $this->getIO()->write(
            <<<EOT
<info>Composer - Dependency Manager for PHP</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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Factory;
use Composer\Package\Loader\ValidatingArrayLoader;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Util\ConfigValidator;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * ValidateCommand
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ValidateCommand extends BaseCommand
{
    /**
     * configure
     */
    protected function configure()
    {
        $this
            ->setName('validate')
            ->setDescription('Validates a composer.json and composer.lock.')
            ->setDefinition(array(
                new InputOption('no-check-all', null, InputOption::VALUE_NONE, 'Do not validate requires for overly strict/loose constraints'),
                new InputOption('no-check-lock', null, InputOption::VALUE_NONE, 'Do not check if lock file is up to date'),
                new InputOption('no-check-publish', null, InputOption::VALUE_NONE, 'Do not check for publish errors'),
                new InputOption('with-dependencies', 'A', InputOption::VALUE_NONE, 'Also validate the composer.json of all installed dependencies'),
                new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code for warnings as well as errors'),
                new InputArgument('file', InputArgument::OPTIONAL, 'path to composer.json file'),
            ))
            ->setHelp(
                <<<EOT
The validate command validates a given composer.json and composer.lock

Exit codes in case of errors are:
1 validation warning(s), only when --strict is given
2 validation error(s)
3 file unreadable or missing

Read more at https://getcomposer.org/doc/03-cli.md#validate
EOT
            );
    }

    /**
     * @param InputInterface  $input
     * @param OutputInterface $output
     *
     * @return int
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $file = $input->getArgument('file') ?: Factory::getComposerFile();
        $io = $this->getIO();

        if (!file_exists($file)) {
            $io->writeError('<error>' . $file . ' not found.</error>');

            return 3;
        }
        if (!is_readable($file)) {
            $io->writeError('<error>' . $file . ' is not readable.</error>');

            return 3;
        }

        $validator = new ConfigValidator($io);
        $checkAll = $input->getOption('no-check-all') ? 0 : ValidatingArrayLoader::CHECK_ALL;
        $checkPublish = !$input->getOption('no-check-publish');
        $checkLock = !$input->getOption('no-check-lock');
        $isStrict = $input->getOption('strict');
        list($errors, $publishErrors, $warnings) = $validator->validate($file, $checkAll);

        $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`.';
        }

        $this->outputResult($io, $file, $errors, $warnings, $checkPublish, $publishErrors, $checkLock, $lockErrors, true, $isStrict);

        // $errors include publish and lock errors when exists
        $exitCode = $errors ? 2 : ($isStrict && $warnings ? 1 : 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);
                    $this->outputResult($io, $package->getPrettyName(), $errors, $warnings, $checkPublish, $publishErrors);

                    $depCode = $errors ? 2 : ($isStrict && $warnings ? 1 : 0);
                    $exitCode = max($depCode, $exitCode);
                }
            }
        }

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'validate', $input, $output);
        $eventCode = $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
        $exitCode = max($eventCode, $exitCode);

        return $exitCode;
    }

    private function outputResult($io, $name, &$errors, &$warnings, $checkPublish = false, $publishErrors = array(), $checkLock = false, $lockErrors = array(), $printSchemaUrl = false, $isStrict = false)
    {
        if (!$errors && !$publishErrors && !$warnings) {
            $io->write('<info>' . $name . ' is valid</info>');
        } elseif (!$errors && !$publishErrors) {
            $io->writeError('<info>' . $name . ' is valid, but with a few warnings</info>');
            if ($printSchemaUrl) {
                $io->writeError('<warning>See https://getcomposer.org/doc/04-schema.md for details on the schema</warning>');
            }
        } elseif (!$errors) {
            $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>');
            if ($printSchemaUrl) {
                $io->writeError('<warning>See https://getcomposer.org/doc/04-schema.md for details on the schema</warning>');
            }
        } else {
            $io->writeError('<error>' . $name . ' is invalid, the following errors/warnings were found:</error>');
        }

        // If checking publish errors, display them as errors, otherwise just show them as warnings
        // Skip when it is a strict check and we don't want to check publish errors
        if ($checkPublish) {
            $errors = array_merge($errors, $publishErrors);
        } elseif (!$isStrict) {
            $warnings = array_merge($warnings, $publishErrors);
        }

        // If checking lock errors, display them as errors, otherwise just show them as warnings
        // Skip when it is a strict check and we don't want to check lock errors
        if ($checkLock) {
            $errors = array_merge($errors, $lockErrors);
        } elseif (!$isStrict) {
            $warnings = array_merge($warnings, $lockErrors);
        }

        $messages = array(
            'error' => $errors,
            'warning' => $warnings,
        );

        foreach ($messages as $style => $msgs) {
            foreach ($msgs as $msg) {
                $io->writeError('<' . $style . '>' . $msg . '</' . $style . '>');
            }
        }
    }
}
<?php

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

namespace Composer\Command;

use Composer\DependencyResolver\Pool;
use Composer\Package\Link;
use Composer\Package\PackageInterface;
use Composer\Repository\ArrayRepository;
use Composer\Repository\CompositeRepository;
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\Helper\Table;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Base implementation for commands mapping dependency relationships.
 *
 * @author Niels Keurentjes <niels.keurentjes@omines.com>
 */
class BaseDependencyCommand extends BaseCommand
{
    const ARGUMENT_PACKAGE = 'package';
    const ARGUMENT_CONSTRAINT = 'constraint';
    const OPTION_RECURSIVE = 'recursive';
    const OPTION_TREE = 'tree';

    protected $colors;

    /**
     * Set common options and arguments.
     */
    protected function configure()
    {
        $this->setDefinition(array(
            new InputArgument(self::ARGUMENT_PACKAGE, InputArgument::REQUIRED, 'Package to inspect'),
            new InputArgument(self::ARGUMENT_CONSTRAINT, InputArgument::OPTIONAL, 'Optional version constraint', '*'),
            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'),
        ));
    }

    /**
     * Execute the command.
     *
     * @param  InputInterface  $input
     * @param  OutputInterface $output
     * @param  bool            $inverted Whether to invert matching process (why-not vs why behaviour)
     * @return int             Exit code of the operation.
     */
    protected function doExecute(InputInterface $input, OutputInterface $output, $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);

        // Prepare repositories and set up a pool
        $platformOverrides = $composer->getConfig()->get('platform') ?: array();
        $repository = new CompositeRepository(array(
            new ArrayRepository(array($composer->getPackage())),
            $composer->getRepositoryManager()->getLocalRepository(),
            new PlatformRepository(array(), $platformOverrides),
        ));
        $pool = new Pool();
        $pool->addRepository($repository);

        // Parse package name and constraint
        list($needle, $textConstraint) = array_pad(
            explode(':', $input->getArgument(self::ARGUMENT_PACKAGE)),
            2,
            $input->getArgument(self::ARGUMENT_CONSTRAINT)
        );

        // Find packages that are or provide the requested package first
        $packages = $pool->whatProvides(strtolower($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 (!$repository->findPackage($needle, $textConstraint)) {
            $defaultRepos = new CompositeRepository(RepositoryFactory::defaultRepos($this->getIO()));
            if ($match = $defaultRepos->findPackage($needle, $textConstraint)) {
                $repository->addRepository(new ArrayRepository(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($needles, array_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 = $repository->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->getDescription()));
            $this->printTree($results);
        } else {
            $this->printTable($output, $results);
        }

        return 0;
    }

    /**
     * Assembles and prints a bottom-up table of the dependencies.
     *
     * @param OutputInterface $output
     * @param array           $results
     */
    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 = (strpos($package->getPrettyVersion(), 'No version set') === 0) ? '-' : $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));

        // Render table
        $renderer = new Table($output);
        $renderer->setStyle('compact');
        $rendererStyle = $renderer->getStyle();
        $rendererStyle->setVerticalBorderChar('');
        $rendererStyle->setCellRowContentFormat('%s  ');
        $renderer->setRows($table)->render();
    }

    /**
     * Init styles for tree
     *
     * @param OutputInterface $output
     */
    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  $results Results to be printed at this level.
     * @param string $prefix  Prefix of the current tree level.
     * @param int    $level   Current level of recursion.
     */
    protected function printTree($results, $prefix = '', $level = 1)
    {
        $count = count($results);
        $idx = 0;
        foreach ($results as $result) {
            /**
             * @var PackageInterface $package
             * @var Link             $link
             * @var array|bool       $children
             */
            list($package, $link, $children) = $result;

            $color = $this->colors[$level % count($this->colors)];
            $prevColor = $this->colors[($level - 1) % count($this->colors)];
            $isLast = (++$idx == $count);
            $versionText = (strpos($package->getPrettyVersion(), 'No version set') === 0) ? '' : $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);
            }
        }
    }

    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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class DumpAutoloadCommand extends BaseCommand
{
    protected function configure()
    {
        $this
            ->setName('dump-autoload')
            ->setAliases(array('dumpautoload'))
            ->setDescription('Dumps the autoloader.')
            ->setDefinition(array(
                new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
                new InputOption('optimize', 'o', InputOption::VALUE_NONE, 'Optimizes PSR0 and PSR4 packages to be loaded with classmaps too, good for production.'),
                new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize`.'),
                new InputOption('apcu', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables autoload-dev rules.'),
            ))
            ->setHelp(
                <<<EOT
<info>php composer.phar dump-autoload</info>

Read more at https://getcomposer.org/doc/03-cli.md#dump-autoload-dumpautoload-
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $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');
        $apcu = $input->getOption('apcu') || $config->get('apcu-autoloader');

        if ($authoritative) {
            $this->getIO()->writeError('<info>Generating optimized autoload files (authoritative)</info>', false);
        } elseif ($optimize) {
            $this->getIO()->writeError('<info>Generating optimized autoload files</info>', false);
        } else {
            $this->getIO()->writeError('<info>Generating autoload files</info>', false);
        }

        $generator = $composer->getAutoloadGenerator();
        $generator->setDevMode(!$input->getOption('no-dev'));
        $generator->setClassMapAuthoritative($authoritative);
        $generator->setApcu($apcu);
        $generator->setRunScripts(!$input->getOption('no-scripts'));
        $numberOfClasses = $generator->dump($config, $localRepo, $package, $installationManager, 'composer', $optimize);

        if ($authoritative) {
            $this->getIO()->overwriteError('<info>Generated optimized autoload files (authoritative) containing '. $numberOfClasses .' classes</info>');
        } elseif ($optimize) {
            $this->getIO()->overwriteError('<info>Generated optimized autoload files containing '. $numberOfClasses .' classes</info>');
        } else {
            $this->getIO()->overwriteError('<info>Generated autoload files containing '. $numberOfClasses .' classes</info>');
        }

        return 0;
    }
}
<?php

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

namespace Composer\Command;

use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\Config;
use Composer\Composer;
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 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 <naderman@naderman.de>
 */
class ArchiveCommand extends BaseCommand
{
    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 or zip'),
                new InputOption('dir', null, InputOption::VALUE_REQUIRED, 'Write the archive to this directory'),
                new InputOption('file', null, InputOption::VALUE_REQUIRED, 'Write the archive with the given file name.'
                    .' Note that the format will be appended.'),
                new InputOption('ignore-filters', false, InputOption::VALUE_NONE, 'Ignore filters when saving package'),
            ))
            ->setHelp(
                <<<EOT
The <info>archive</info> command creates an archive of the specified format
containing the files and directories of the Composer project or the specified
package in the specified version and writes it to the specified directory.

<info>php composer.phar archive [--format=zip] [--dir=/foo] [package [version]]</info>

Read more at https://getcomposer.org/doc/03-cli.md#archive
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $config = Factory::createConfig();
        $composer = $this->getComposer(false);
        if ($composer) {
            $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'archive', $input, $output);
            $eventDispatcher = $composer->getEventDispatcher();
            $eventDispatcher->dispatch($commandEvent->getName(), $commandEvent);
            $eventDispatcher->dispatchScript(ScriptEvents::PRE_ARCHIVE_CMD);
        }

        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 (0 === $returnCode && $composer) {
            $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_ARCHIVE_CMD);
        }

        return $returnCode;
    }

    protected function archive(IOInterface $io, Config $config, $packageName = null, $version = null, $format = 'tar', $dest = '.', $fileName = null, $ignoreFilters = false, Composer $composer = null)
    {
        if ($composer) {
            $archiveManager = $composer->getArchiveManager();
        } else {
            $factory = new Factory;
            $downloadManager = $factory->createDownloadManager($io, $config);
            $archiveManager = $factory->createArchiveManager($config, $downloadManager);
        }

        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(), $packagePath, true);

        $io->writeError('Created: ', false);
        $io->write(strlen($shortPath) < strlen($packagePath) ? $shortPath : $packagePath);

        return 0;
    }

    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;
        }

        return $package;
    }
}
<?php

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

namespace Composer\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 <me@daveyshafik.com>
 */
class ExecCommand extends BaseCommand
{
    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
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $composer = $this->getComposer();
        $binDir = $composer->getConfig()->get('bin-dir');
        if ($input->getOption('list') || !$input->getArgument('binary')) {
            $bins = glob($binDir . '/*');
            $bins = array_merge($bins, array_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 ($output->getVerbosity() === OutputInterface::VERBOSITY_NORMAL) {
            $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
        }

        return $dispatcher->dispatchScript('__exec_command', true, $input->getArgument('args'));
    }
}
<?php

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

namespace Composer\Command;

use Composer\Json\JsonFile;
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;

/**
 * @author Benoît Merlet <benoit.merlet@gmail.com>
 */
class LicensesCommand extends BaseCommand
{
    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 or json', 'text'),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'),
            ))
            ->setHelp(
                <<<EOT
The license command displays detailed information about the licenses of
the installed dependencies.

Read more at https://getcomposer.org/doc/03-cli.md#licenses
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $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();
                $tableStyle->setVerticalBorderChar('');
                $tableStyle->setCellRowContentFormat('%s  ');
                $table->setHeaders(array('Name', 'Version', 'License'));
                foreach ($packages as $package) {
                    $table->addRow(array(
                        $package->getPrettyName(),
                        $package->getFullPrettyVersion(),
                        implode(', ', $package->getLicense()) ?: 'none',
                    ));
                }
                $table->render();
                break;

            case 'json':
                $dependencies = array();
                foreach ($packages as $package) {
                    $dependencies[$package->getPrettyName()] = array(
                        'version' => $package->getFullPrettyVersion(),
                        'license' => $package->getLicense(),
                    );
                }

                $io->write(JsonFile::encode(array(
                    'name' => $root->getPrettyName(),
                    'version' => $root->getFullPrettyVersion(),
                    'license' => $root->getLicense(),
                    'dependencies' => $dependencies,
                )));
                break;

            default:
                throw new \RuntimeException(sprintf('Unsupported format "%s".  See help for supported formats.', $format));
        }

        return 0;
    }

    /**
     * Find package requires and child requires
     *
     * @param  RepositoryInterface $repo
     * @param  PackageInterface    $package
     * @param  array               $bucket
     * @return array
     */
    private function filterRequiredPackages(RepositoryInterface $repo, PackageInterface $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  array $packages the list of packages to add
     * @param  array $bucket   the list to add packages to
     * @return array
     */
    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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Niels Keurentjes <niels.keurentjes@omines.com>
 */
class ProhibitsCommand extends BaseDependencyCommand
{
    /**
     * Configure command metadata.
     */
    protected function configure()
    {
        parent::configure();

        $this
            ->setName('prohibits')
            ->setAliases(array('why-not'))
            ->setDescription('Shows which packages prevent the given package from being installed.')
            ->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.
     *
     * @param  InputInterface  $input
     * @param  OutputInterface $output
     * @return int
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        return parent::doExecute($input, $output, true);
    }
}
<?php

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

namespace Composer\Command;

use Composer\Installer;
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 <j.boggiano@seld.be>
 * @author Ryan Weaver <ryan@knplabs.com>
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Nils Adermann <naderman@naderman.de>
 */
class InstallCommand extends BaseCommand
{
    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', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
                new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'),
                new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'),
                new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of require-dev packages (enabled by default, only present for BC).'),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'),
                new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'),
                new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'),
                new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
                new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'Do not show package suggestions.'),
                new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'),
                new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'),
                new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'),
                new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'),
                new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'),
                new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Should not be provided, use composer require instead to add a given package to composer.json.'),
            ))
            ->setHelp(
                <<<EOT
The <info>install</info> command reads the composer.lock file from
the current directory, processes it, and downloads and installs all the
libraries and dependencies outlined in that file. If the file does not
exist it will look for composer.json and do the same.

<info>php composer.phar install</info>

Read more at https://getcomposer.org/doc/03-cli.md#install-i
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $io = $this->getIO();
        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-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->getOption('dev')) {
            $io->writeError('<warning>You are using the deprecated option "dev". Dev packages are installed by default now.</warning>');
        }

        $composer = $this->getComposer(true, $input->getOption('no-plugins'));
        $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));

        $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');
        $apcu = $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader');

        $install
            ->setDryRun($input->getOption('dry-run'))
            ->setVerbose($input->getOption('verbose'))
            ->setPreferSource($preferSource)
            ->setPreferDist($preferDist)
            ->setDevMode(!$input->getOption('no-dev'))
            ->setDumpAutoloader(!$input->getOption('no-autoloader'))
            ->setRunScripts(!$input->getOption('no-scripts'))
            ->setSkipSuggest($input->getOption('no-suggest'))
            ->setOptimizeAutoloader($optimize)
            ->setClassMapAuthoritative($authoritative)
            ->setApcuAutoloader($apcu)
            ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
        ;

        if ($input->getOption('no-plugins')) {
            $install->disablePlugins();
        }

        return $install->run();
    }
}
<?php

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

namespace Composer\Command;

use Composer\Factory;
use Composer\Util\Filesystem;
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 <j.boggiano@seld.be>
 */
class GlobalCommand extends BaseCommand
{
    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
            )
        ;
    }

    public function run(InputInterface $input, OutputInterface $output)
    {
        // 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);
        }

        // 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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Composer;
use Composer\Config;
use Composer\Console\Application;
use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\IO\NullIO;
use Composer\Plugin\PreCommandRunEvent;
use Composer\Plugin\PluginEvents;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Command\Command;

/**
 * Base class for Composer commands
 *
 * @author Ryan Weaver <ryan@knplabs.com>
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
abstract class BaseCommand extends Command
{
    /**
     * @var Composer|null
     */
    private $composer;

    /**
     * @var IOInterface
     */
    private $io;

    /**
     * @param  bool              $required
     * @param  bool|null         $disablePlugins
     * @throws \RuntimeException
     * @return Composer
     */
    public function getComposer($required = true, $disablePlugins = null)
    {
        if (null === $this->composer) {
            $application = $this->getApplication();
            if ($application instanceof Application) {
                /* @var $application    Application */
                $this->composer = $application->getComposer($required, $disablePlugins);
            } 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;
    }

    /**
     * @param Composer $composer
     */
    public function setComposer(Composer $composer)
    {
        $this->composer = $composer;
    }

    /**
     * Removes the cached composer instance
     */
    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) {
                /* @var $application    Application */
                $this->io = $application->getIO();
            } else {
                $this->io = new NullIO();
            }
        }

        return $this->io;
    }

    /**
     * @param IOInterface $io
     */
    public function setIO(IOInterface $io)
    {
        $this->io = $io;
    }

    /**
     * {@inheritDoc}
     */
    protected function initialize(InputInterface $input, OutputInterface $output)
    {
        // initialize a plugin-enabled Composer instance, either local or global
        $disablePlugins = $input->hasParameterOption('--no-plugins');
        $composer = $this->getComposer(false, $disablePlugins);
        if (null === $composer) {
            $composer = Factory::createGlobal($this->getIO(), $disablePlugins);
        }
        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);
        }

        parent::initialize($input, $output);
    }

    /**
     * Returns preferSource and preferDist values based on the configuration.
     *
     * @param Config         $config
     * @param InputInterface $input
     * @param bool           $keepVcsRequiresPreferSource
     *
     * @return bool[] An array composed of the preferSource and preferDist values
     */
    protected function getPreferredInstallOptions(Config $config, InputInterface $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->getOption('prefer-source') || $input->getOption('prefer-dist') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs'))) {
            $preferSource = $input->getOption('prefer-source') || ($keepVcsRequiresPreferSource && $input->hasOption('keep-vcs') && $input->getOption('keep-vcs'));
            $preferDist = $input->getOption('prefer-dist');
        }

        return array($preferSource, $preferDist);
    }
}
<?php

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

namespace Composer\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 <tiago.ribeiro@seegno.com>
 * @author Rui Marinho <rui.marinho@seegno.com>
 */
class StatusCommand extends BaseCommand
{
    const EXIT_CODE_ERRORS = 1;
    const EXIT_CODE_UNPUSHED_CHANGES = 2;
    const EXIT_CODE_VERSION_CHANGES = 4;

    /**
     * @throws \Symfony\Component\Console\Exception\InvalidArgumentException
     */
    protected function configure()
    {
        $this
            ->setName('status')
            ->setDescription('Shows a list of locally modified packages, for packages installed from source.')
            ->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
            )
        ;
    }

    /**
     * @param  InputInterface  $input
     * @param  OutputInterface $output
     * @return int
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        // init repos
        $composer = $this->getComposer();

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'status', $input, $output);
        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        $installedRepo = $composer->getRepositoryManager()->getLocalRepository();

        $dm = $composer->getDownloadManager();
        $im = $composer->getInstallationManager();

        // Dispatch pre-status-command
        $composer->getEventDispatcher()->dispatchScript(ScriptEvents::PRE_STATUS_CMD, true);

        $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->getDownloaderForInstalledPackage($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 ($currentRef = $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');
        }

        // Dispatch post-status-command
        $composer->getEventDispatcher()->dispatchScript(ScriptEvents::POST_STATUS_CMD, true);

        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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Script\Event as ScriptEvent;
use Composer\Script\ScriptEvents;
use Composer\Util\ProcessExecutor;
use 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\Helper\Table;

/**
 * @author Fabien Potencier <fabien.potencier@gmail.com>
 */
class RunScriptCommand extends BaseCommand
{
    /**
     * @var array 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,
    );

    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', null, InputOption::VALUE_REQUIRED, 'Sets script timeout in seconds, or 0 for never.'),
                new InputOption('dev', null, InputOption::VALUE_NONE, 'Sets the dev mode.'),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables the dev mode.'),
                new InputOption('list', 'l', InputOption::VALUE_NONE, 'List scripts.'),
            ))
            ->setHelp(
                <<<EOT
The <info>run-script</info> command runs scripts defined in composer.json:

<info>php composer.phar run-script post-update-cmd</info>

Read more at https://getcomposer.org/doc/03-cli.md#run-script
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        if ($input->getOption('list')) {
            return $this->listScripts($output);
        } elseif (!$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);
        }

        return $composer->getEventDispatcher()->dispatchScript($script, $devMode, $args);
    }

    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);
        }

        $renderer = new Table($output);
        $renderer->setStyle('compact');
        $rendererStyle = $renderer->getStyle();
        $rendererStyle->setVerticalBorderChar('');
        $rendererStyle->setCellRowContentFormat('%s  ');
        $renderer->setRows($table)->render();

        return 0;
    }
}
<?php

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

namespace Composer\Command;

use Composer\Config;
use Composer\Factory;
use Composer\Installer;
use Composer\Installer\ProjectInstaller;
use Composer\Installer\InstallationManager;
use Composer\Installer\SuggestedPackagesReporter;
use Composer\IO\IOInterface;
use Composer\Package\BasePackage;
use Composer\DependencyResolver\Pool;
use Composer\DependencyResolver\Operation\InstallOperation;
use Composer\Package\Version\VersionSelector;
use Composer\Package\AliasPackage;
use Composer\Repository\RepositoryFactory;
use Composer\Repository\CompositeRepository;
use Composer\Repository\PlatformRepository;
use Composer\Repository\InstalledFilesystemRepository;
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\Package\Version\VersionParser;

/**
 * Install a package as new project into new directory.
 *
 * @author Benjamin Eberlei <kontakt@beberlei.de>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Tobias Munk <schmunk@usrbin.de>
 * @author Nils Adermann <naderman@naderman.de>
 */
class CreateProjectCommand extends BaseCommand
{
    /**
     * @var SuggestedPackagesReporter
     */
    protected $suggestedPackagesReporter;

    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', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
                new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'),
                new InputOption('repository', null, InputOption::VALUE_REQUIRED, 'Pick a different repository (as url or json config) to look for the package.'),
                new InputOption('repository-url', null, InputOption::VALUE_REQUIRED, 'DEPRECATED: Use --repository instead.'),
                new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of require-dev packages (enabled by default, only present for BC).'),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'),
                new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'),
                new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Whether to prevent execution of all defined scripts in the root package.'),
                new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                new InputOption('no-secure-http', null, InputOption::VALUE_NONE, 'Disable the secure-http config option temporarily while installing the root package. Use at your own risk. Using this flag is a bad idea.'),
                new InputOption('keep-vcs', null, InputOption::VALUE_NONE, 'Whether to prevent deleting the vcs folder.'),
                new InputOption('remove-vcs', null, InputOption::VALUE_NONE, 'Whether to force deletion of the vcs folder without prompting.'),
                new InputOption('no-install', null, InputOption::VALUE_NONE, 'Whether to skip installation of the package dependencies.'),
                new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'),
            ))
            ->setHelp(
                <<<EOT
The <info>create-project</info> command creates a new project from a given
package into a new directory. If executed without params and in a directory
with a composer.json file it installs the packages for the current project.

You can use this command to bootstrap new projects or setup a clean
version-controlled installation for developers of your project.

<info>php composer.phar create-project vendor/project target-directory [version]</info>

You can also specify the version with the package name using = or : as separator.

<info>php composer.phar create-project vendor/project:version target-directory</info>

To install unstable packages, either specify the version you want, or use the
--stability=dev (where dev can be one of RC, beta, alpha or dev).

To setup a developer workable version you should create the project using the source
controlled code by appending the <info>'--prefer-source'</info> flag.

To install a package from another repository than the default one you
can pass the <info>'--repository=https://myrepository.org'</info> flag.

Read more at https://getcomposer.org/doc/03-cli.md#create-project
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $config = Factory::createConfig();
        $io = $this->getIO();

        list($preferSource, $preferDist) = $this->getPreferredInstallOptions($config, $input, true);

        if ($input->getOption('dev')) {
            $io->writeError('<warning>You are using the deprecated option "dev". Dev packages are installed by default now.</warning>');
        }
        if ($input->getOption('no-custom-installers')) {
            $io->writeError('<warning>You are using the deprecated option "no-custom-installers". Use "no-plugins" instead.</warning>');
            $input->setOption('no-plugins', true);
        }

        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'),
            $input->getOption('ignore-platform-reqs'),
            !$input->getOption('no-secure-http')
        );
    }

    public function installProject(IOInterface $io, Config $config, InputInterface $input, $packageName, $directory = null, $packageVersion = null, $stability = 'stable', $preferSource = false, $preferDist = false, $installDevPackages = false, $repository = null, $disablePlugins = false, $noScripts = false, $noProgress = false, $noInstall = false, $ignorePlatformReqs = false, $secureHttp = true)
    {
        $oldCwd = getcwd();

        // 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, $directory, $packageVersion, $stability, $preferSource, $preferDist, $installDevPackages, $repository, $disablePlugins, $noScripts, $noProgress, $ignorePlatformReqs, $secureHttp);
        } else {
            $installedFromVcs = false;
        }

        $composer = Factory::create($io, null, $disablePlugins);
        $composer->getDownloadManager()->setOutputProgress(!$noProgress);

        $fs = new Filesystem();

        if ($noScripts === false) {
            // 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) {
            $installer = Installer::create($io, $composer);
            $installer->setPreferSource($preferSource)
                ->setPreferDist($preferDist)
                ->setDevMode($installDevPackages)
                ->setRunScripts(!$noScripts)
                ->setIgnorePlatformRequirements($ignorePlatformReqs)
                ->setSuggestedPackagesReporter($this->suggestedPackagesReporter)
                ->setOptimizeAutoloader($config->get('optimize-autoloader'))
                ->setClassMapAuthoritative($config->get('classmap-authoritative'))
                ->setApcuAutoloader($config->get('apcu-autoloader'));

            if ($disablePlugins) {
                $installer->disablePlugins();
            }

            $status = $installer->run();
            if (0 !== $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>]? ', true)
            )
        ) {
            $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());
                    }
                }
            }
        }

        if ($noScripts === false) {
            // 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;
    }

    protected function installRootPackage(IOInterface $io, Config $config, $packageName, $directory = null, $packageVersion = null, $stability = 'stable', $preferSource = false, $preferDist = false, $installDevPackages = false, $repository = null, $disablePlugins = false, $noScripts = false, $noProgress = false, $ignorePlatformReqs = false, $secureHttp = true)
    {
        if (!$secureHttp) {
            $config->merge(array('config' => array('secure-http' => false)));
        }

        if (null === $repository) {
            $sourceRepo = new CompositeRepository(RepositoryFactory::defaultRepos($io, $config));
        } else {
            $sourceRepo = RepositoryFactory::fromString($io, $config, $repository, true);
        }

        $parser = new VersionParser();
        $requirements = $parser->parseNameVersionPairs(array($packageName));
        $name = strtolower($requirements[0]['name']);
        if (!$packageVersion && isset($requirements[0]['version'])) {
            $packageVersion = $requirements[0]['version'];
        }

        if (null === $stability) {
            if (preg_match('{^[^,\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)));
        }

        $pool = new Pool($stability);
        $pool->addRepository($sourceRepo);

        $phpVersion = null;
        $prettyPhpVersion = null;
        if (!$ignorePlatformReqs) {
            $platformOverrides = $config->get('platform') ?: array();
            // initialize $this->repos as it is used by the parent InitCommand
            $platform = new PlatformRepository(array(), $platformOverrides);
            $phpPackage = $platform->findPackage('php', '*');
            $phpVersion = $phpPackage->getVersion();
            $prettyPhpVersion = $phpPackage->getPrettyVersion();
        }

        // find the latest version if there are multiple
        $versionSelector = new VersionSelector($pool);
        $package = $versionSelector->findBestCandidate($name, $packageVersion, $phpVersion, $stability);

        if (!$package) {
            $errorMessage = "Could not find package $name with " . ($packageVersion ? "version $packageVersion" : "stability $stability");
            if ($phpVersion && $versionSelector->findBestCandidate($name, $packageVersion, null, $stability)) {
                throw new \InvalidArgumentException($errorMessage .' in a version installable using your PHP version '.$prettyPhpVersion.'.');
            }

            throw new \InvalidArgumentException($errorMessage .'.');
        }

        if (null === $directory) {
            $parts = explode("/", $name, 2);
            $directory = getcwd() . DIRECTORY_SEPARATOR . array_pop($parts);
        }

        // handler Ctrl+C for unix-like systems
        if (function_exists('pcntl_async_signals')) {
            @mkdir($directory, 0777, true);
            if ($realDir = realpath($directory)) {
                pcntl_async_signals(true);
                pcntl_signal(SIGINT, function () use ($realDir) {
                    $fs = new Filesystem();
                    $fs->removeDirectory($realDir);
                    exit(130);
                });
            }
        }

        $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 = $this->createDownloadManager($io, $config);
        $dm->setPreferSource($preferSource)
            ->setPreferDist($preferDist)
            ->setOutputProgress(!$noProgress);

        $projectInstaller = new ProjectInstaller($directory, $dm);
        $im = $this->createInstallationManager();
        $im->addInstaller($projectInstaller);
        $im->install(new InstalledFilesystemRepository(new JsonFile('php://memory')), 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);

        $_SERVER['COMPOSER_ROOT_VERSION'] = $package->getPrettyVersion();
        putenv('COMPOSER_ROOT_VERSION='.$_SERVER['COMPOSER_ROOT_VERSION']);

        return $installedFromVcs;
    }

    protected function createDownloadManager(IOInterface $io, Config $config)
    {
        $factory = new Factory();

        return $factory->createDownloadManager($io, $config);
    }

    protected function createInstallationManager()
    {
        return new InstallationManager();
    }
}
<?php

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

namespace Composer\Command;

use Composer\Composer;
use Composer\Installer;
use Composer\IO\IOInterface;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
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 <j.boggiano@seld.be>
 * @author Nils Adermann <naderman@naderman.de>
 */
class UpdateCommand extends BaseCommand
{
    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('prefer-source', null, InputOption::VALUE_NONE, 'Forces installation from package sources when possible, including VCS information.'),
                new InputOption('prefer-dist', null, InputOption::VALUE_NONE, 'Forces installation from package dist even for dev versions.'),
                new InputOption('dry-run', null, InputOption::VALUE_NONE, 'Outputs the operations but will not execute anything (implicitly enables --verbose).'),
                new InputOption('dev', null, InputOption::VALUE_NONE, 'Enables installation of require-dev packages (enabled by default, only present for BC).'),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables installation of require-dev packages.'),
                new InputOption('lock', null, InputOption::VALUE_NONE, 'Only updates the lock file hash to suppress warning about the lock file being out of date.'),
                new InputOption('no-custom-installers', null, InputOption::VALUE_NONE, 'DEPRECATED: Use no-plugins instead.'),
                new InputOption('no-autoloader', null, InputOption::VALUE_NONE, 'Skips autoloader generation'),
                new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
                new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                new InputOption('no-suggest', null, InputOption::VALUE_NONE, 'Do not show package suggestions.'),
                new InputOption('with-dependencies', null, InputOption::VALUE_NONE, 'Add also dependencies of whitelisted packages to the whitelist, except those defined in root package.'),
                new InputOption('with-all-dependencies', null, InputOption::VALUE_NONE, 'Add also all dependencies of whitelisted packages to the whitelist, including those defined in root package.'),
                new InputOption('verbose', 'v|vv|vvv', InputOption::VALUE_NONE, 'Shows more details including new commits pulled in when updating packages.'),
                new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump.'),
                new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'),
                new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'),
                new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'),
                new InputOption('prefer-stable', null, InputOption::VALUE_NONE, 'Prefer stable versions of dependencies.'),
                new InputOption('prefer-lowest', null, InputOption::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', null, InputOption::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 select packages names interactively with auto-completion use <info>-i</info>.

Read more at https://getcomposer.org/doc/03-cli.md#update-u
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $io = $this->getIO();
        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->getOption('dev')) {
            $io->writeError('<warning>You are using the deprecated option "dev". Dev packages are installed by default now.</warning>');
        }

        $composer = $this->getComposer(true, $input->getOption('no-plugins'));

        $packages = $input->getArgument('packages');

        if ($input->getOption('interactive')) {
            $packages = $this->getPackagesInteractively($io, $input, $output, $composer, $packages);
        }

        if ($input->getOption('root-reqs')) {
            $require = array_keys($composer->getPackage()->getRequires());
            if (!$input->getOption('no-dev')) {
                $requireDev = array_keys($composer->getPackage()->getDevRequires());
                $require = array_merge($require, $requireDev);
            }

            if (!empty($packages)) {
                $packages = array_intersect($packages, $require);
            } else {
                $packages = $require;
            }
        }

        $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'update', $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');
        $apcu = $input->getOption('apcu-autoloader') || $config->get('apcu-autoloader');

        $install
            ->setDryRun($input->getOption('dry-run'))
            ->setVerbose($input->getOption('verbose'))
            ->setPreferSource($preferSource)
            ->setPreferDist($preferDist)
            ->setDevMode(!$input->getOption('no-dev'))
            ->setDumpAutoloader(!$input->getOption('no-autoloader'))
            ->setRunScripts(!$input->getOption('no-scripts'))
            ->setSkipSuggest($input->getOption('no-suggest'))
            ->setOptimizeAutoloader($optimize)
            ->setClassMapAuthoritative($authoritative)
            ->setApcuAutoloader($apcu)
            ->setUpdate(true)
            ->setUpdateWhitelist($input->getOption('lock') ? array('lock') : $packages)
            ->setWhitelistTransitiveDependencies($input->getOption('with-dependencies'))
            ->setWhitelistAllDependencies($input->getOption('with-all-dependencies'))
            ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
            ->setPreferStable($input->getOption('prefer-stable'))
            ->setPreferLowest($input->getOption('prefer-lowest'))
        ;

        if ($input->getOption('no-plugins')) {
            $install->disablePlugins();
        }

        return $install->run();
    }

    private function getPackagesInteractively(IOInterface $io, InputInterface $input, OutputInterface $output, Composer $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>]? ',
            1 === count($packages) ? '' : 's'
        ), true)) {
            return $packages;
        }

        throw new \RuntimeException('Installation aborted.');
    }
}
<?php

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

namespace Composer\Command;

use Composer\Factory;
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 <seroscho@googlemail.com>
 */
class SearchCommand extends BaseCommand
{
    protected $matches;
    protected $lowMatches = array();
    protected $tokens;
    protected $output;
    protected $onlyName;

    protected function configure()
    {
        $this
            ->setName('search')
            ->setDescription('Searches for packages.')
            ->setDefinition(array(
                new InputOption('only-name', 'N', InputOption::VALUE_NONE, 'Search only in name'),
                new InputOption('type', 't', InputOption::VALUE_REQUIRED, 'Search for a specific package type'),
                new InputArgument('tokens', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'tokens to search for'),
            ))
            ->setHelp(
                <<<EOT
The search command searches for packages by its name
<info>php composer.phar search symfony composer</info>

Read more at https://getcomposer.org/doc/03-cli.md#search
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        // init repos
        $platformRepo = new PlatformRepository;
        $io = $this->getIO();
        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);

        $onlyName = $input->getOption('only-name');
        $type = $input->getOption('type') ?: null;

        $flags = $onlyName ? RepositoryInterface::SEARCH_NAME : RepositoryInterface::SEARCH_FULLTEXT;
        $results = $repos->search(implode(' ', $input->getArgument('tokens')), $flags, $type);

        foreach ($results as $result) {
            $io->write($result['name'] . (isset($result['description']) ? ' '. $result['description'] : ''));
        }

        return 0;
    }
}
<?php

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

namespace Composer\Command;

use Composer\Composer;
use Composer\Factory;
use Composer\Config;
use Composer\Util\Filesystem;
use Composer\SelfUpdate\Keys;
use Composer\SelfUpdate\Versions;
use Composer\IO\IOInterface;
use Composer\Downloader\FilesystemException;
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 <igor@wiedler.ch>
 * @author Kevin Ran <kran@adobe.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class SelfUpdateCommand extends BaseCommand
{
    const HOMEPAGE = 'getcomposer.org';
    const OLD_INSTALL_EXT = '-old.phar';

    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', null, InputOption::VALUE_NONE, 'Delete old backups during an update. This makes the current version of composer the only backup available after the update'),
                new InputArgument('version', InputArgument::OPTIONAL, 'The version to update to'),
                new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                new InputOption('update-keys', null, InputOption::VALUE_NONE, 'Prompt user for a key update'),
                new InputOption('stable', null, InputOption::VALUE_NONE, 'Force an update to the stable channel'),
                new InputOption('preview', null, InputOption::VALUE_NONE, 'Force an update to the preview channel'),
                new InputOption('snapshot', null, InputOption::VALUE_NONE, 'Force an update to the snapshot channel'),
                new InputOption('set-channel-only', null, InputOption::VALUE_NONE, 'Only store the channel as the default one and then exit'),
            ))
            ->setHelp(
                <<<EOT
The <info>self-update</info> command checks getcomposer.org for newer
versions of composer and if found, installs the latest.

<info>php composer.phar self-update</info>

Read more at https://getcomposer.org/doc/03-cli.md#self-update-selfupdate-
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $config = Factory::createConfig();

        if ($config->get('disable-tls') === true) {
            $baseUrl = 'http://' . self::HOMEPAGE;
        } else {
            $baseUrl = 'https://' . self::HOMEPAGE;
        }

        $io = $this->getIO();
        $remoteFilesystem = Factory::createRemoteFilesystem($io, $config);

        $versionsUtil = new Versions($config, $remoteFilesystem);

        // switch channel if requested
        foreach (array('stable', 'preview', 'snapshot') as $channel) {
            if ($input->getOption($channel)) {
                $versionsUtil->setChannel($channel);
            }
        }

        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')) {
            return $this->fetchKeys($io, $config);
        }

        // 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']) && isset($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();
        $latestVersion = $latest['version'];
        $updateVersion = $input->getArgument('version') ?: $latestVersion;

        if (preg_match('{^[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;
        }

        if (Composer::VERSION === $updateVersion) {
            $io->writeError(sprintf('<info>You are already using composer version %s (%s channel).</info>', $updateVersion, $versionsUtil->getChannel()));

            // 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.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_match('{^[0-9a-f]{40}$}', $updateVersion);

        $io->write(sprintf("Updating to version <info>%s</info> (%s channel).", $updateVersion, $versionsUtil->getChannel()));
        $remoteFilename = $baseUrl . ($updatingToTag ? "/download/{$updateVersion}/composer.phar" : '/composer.phar');
        $signature = $remoteFilesystem->getContents(self::HOMEPAGE, $remoteFilename.'.sig', false);
        $io->writeError('   ', false);
        $remoteFilesystem->copy(self::HOMEPAGE, $remoteFilename, $tempFilename, !$input->getOption('no-progress'));
        $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($signature, true);
            $signature = base64_decode($signature['sha384']);
            $verified = 1 === openssl_verify(file_get_contents($tempFilename), $signature, $pubkeyid, $algo);
            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 ($err = $this->setLocalPhar($localFilename, $tempFilename, $backupFile)) {
            @unlink($tempFilename);
            $io->writeError('<error>The file is corrupted ('.$err->getMessage().').</error>');
            $io->writeError('<error>Please re-run the self-update command to try again.</error>');

            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;
    }

    protected function fetchKeys(IOInterface $io, Config $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_match('{^-----BEGIN PUBLIC KEY-----$}', trim($value))) {
                throw new \UnexpectedValueException('Invalid input');
            }

            return trim($value)."\n";
        };

        $devKey = '';
        while (!preg_match('{(-----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_match('{(-----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'));
    }

    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 (!is_readable($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 ($err = $this->setLocalPhar($localFilename, $oldFile)) {
            $io->writeError('<error>The backup file was corrupted ('.$err->getMessage().').</error>');

            return 1;
        }

        return 0;
    }

    /**
     * @param  string                                        $localFilename
     * @param  string                                        $newFilename
     * @param  string                                        $backupTarget
     * @throws \Exception
     * @return \UnexpectedValueException|\PharException|null
     */
    protected function setLocalPhar($localFilename, $newFilename, $backupTarget = null)
    {
        try {
            @chmod($newFilename, fileperms($localFilename));
            if (!ini_get('phar.readonly')) {
                // test the phar validity
                $phar = new \Phar($newFilename);
                // free the variable to unlock the file
                unset($phar);
            }

            // copy current file into installations dir
            if ($backupTarget && file_exists($localFilename)) {
                @copy($localFilename, $backupTarget);
            }

            rename($newFilename, $localFilename);

            return null;
        } catch (\Exception $e) {
            if (!$e instanceof \UnexpectedValueException && !$e instanceof \PharException) {
                throw $e;
            }

            return $e;
        }
    }

    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);
        }
    }

    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;
    }

    protected function getOldInstallationFinder($rollbackDir)
    {
        $finder = Finder::create()
            ->depth(0)
            ->files()
            ->name('*' . self::OLD_INSTALL_EXT)
            ->in($rollbackDir);

        return $finder;
    }
}
<?php

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

namespace Composer\Command;

use Composer\Repository\PlatformRepository;
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
{
    protected function configure()
    {
        $this
            ->setName('suggests')
            ->setDescription('Shows package suggestions.')
            ->setDefinition(array(
                new InputOption('by-package', null, InputOption::VALUE_NONE, 'Groups output by suggesting package'),
                new InputOption('by-suggestion', null, InputOption::VALUE_NONE, 'Groups output by suggested package'),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Exclude suggestions from require-dev packages'),
                new InputArgument('packages', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, 'Packages that you want to list suggestions from.'),
            ))
            ->setHelp(
                <<<EOT

The <info>%command.name%</info> command shows a sorted list of suggested packages.

Enabling <info>-v</info> implies <info>--by-package --by-suggestion</info>, showing both lists.

Read more at https://getcomposer.org/doc/03-cli.md#suggests
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $lock = $this->getComposer()->getLocker()->getLockData();

        if (empty($lock)) {
            throw new \RuntimeException('Lockfile seems to be empty?');
        }

        $packages = $lock['packages'];

        if (!$input->getOption('no-dev')) {
            $packages += $lock['packages-dev'];
        }

        $filter = $input->getArgument('packages');

        // First assemble lookup list of packages that are installed, replaced or provided
        $installed = array();
        foreach ($packages as $package) {
            $installed[] = $package['name'];

            if (!empty($package['provide'])) {
                $installed = array_merge($installed, array_keys($package['provide']));
            }

            if (!empty($package['replace'])) {
                $installed = array_merge($installed, array_keys($package['replace']));
            }
        }

        // Undub and sort the install list into a sorted lookup array
        $installed = array_flip($installed);
        ksort($installed);

        // Init platform repo
        $platform = new PlatformRepository(array(), $this->getComposer()->getConfig()->get('platform') ?: array());

        // Next gather all suggestions that are not in that list
        $suggesters = array();
        $suggested = array();
        foreach ($packages as $package) {
            $packageName = $package['name'];
            if ((!empty($filter) && !in_array($packageName, $filter)) || empty($package['suggest'])) {
                continue;
            }
            foreach ($package['suggest'] as $suggestion => $reason) {
                if (false === strpos('/', $suggestion) && null !== $platform->findPackage($suggestion, '*')) {
                    continue;
                }
                if (!isset($installed[$suggestion])) {
                    $suggesters[$packageName][$suggestion] = $reason;
                    $suggested[$suggestion][$packageName] = $reason;
                }
            }
        }
        ksort($suggesters);
        ksort($suggested);

        // Determine output mode
        $mode = 0;
        $io = $this->getIO();
        if ($input->getOption('by-package') || $io->isVerbose()) {
            $mode |= 1;
        }
        if ($input->getOption('by-suggestion')) {
            $mode |= 2;
        }

        // Simple mode
        if ($mode === 0) {
            foreach (array_keys($suggested) as $suggestion) {
                $io->write(sprintf('<info>%s</info>', $suggestion));
            }

            return 0;
        }

        // Grouped by package
        if ($mode & 1) {
            foreach ($suggesters as $suggester => $suggestions) {
                $io->write(sprintf('<comment>%s</comment> suggests:', $suggester));

                foreach ($suggestions as $suggestion => $reason) {
                    $io->write(sprintf(' - <info>%s</info>: %s', $suggestion, $reason ?: '*'));
                }
                $io->write('');
            }
        }

        // Grouped by suggestion
        if ($mode & 2) {
            // Improve readability in full mode
            if ($mode & 1) {
                $io->write(str_repeat('-', 78));
            }
            foreach ($suggested as $suggestion => $suggesters) {
                $io->write(sprintf('<comment>%s</comment> is suggested by:', $suggestion));

                foreach ($suggesters as $suggester => $reason) {
                    $io->write(sprintf(' - <info>%s</info>: %s', $suggester, $reason ?: '*'));
                }
                $io->write('');
            }
        }

        return 0;
    }
}
<?php

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

namespace Composer\Command;

use Composer\Config\JsonConfigSource;
use Composer\Installer;
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 <pdples@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class RemoveCommand extends BaseCommand
{
    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', null, InputOption::VALUE_NONE, 'Removes a package from the require-dev section.'),
                new InputOption('no-progress', null, InputOption::VALUE_NONE, 'Do not output download progress.'),
                new InputOption('no-update', null, InputOption::VALUE_NONE, 'Disables the automatic update of the dependencies.'),
                new InputOption('no-scripts', null, InputOption::VALUE_NONE, 'Skips the execution of all scripts defined in composer.json file.'),
                new InputOption('update-no-dev', null, InputOption::VALUE_NONE, 'Run the dependency update with the --no-dev option.'),
                new InputOption('update-with-dependencies', null, InputOption::VALUE_NONE, 'Allows inherited dependencies to be updated with explicit dependencies. (Deprecrated, is now default behavior)'),
                new InputOption('no-update-with-dependencies', null, InputOption::VALUE_NONE, 'Does not allow inherited dependencies to be updated with explicit dependencies.'),
                new InputOption('ignore-platform-reqs', null, InputOption::VALUE_NONE, 'Ignore platform requirements (php & ext- packages).'),
                new InputOption('optimize-autoloader', 'o', InputOption::VALUE_NONE, 'Optimize autoloader during autoloader dump'),
                new InputOption('classmap-authoritative', 'a', InputOption::VALUE_NONE, 'Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.'),
                new InputOption('apcu-autoloader', null, InputOption::VALUE_NONE, 'Use APCu to cache found/not-found classes.'),
            ))
            ->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
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $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;
                }
            }
        }

        foreach ($packages as $package) {
            if (isset($composer[$type][$package])) {
                $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>]? ', true)) {
                        $json->removeLink($altType, $composer[$altType][$package]);
                    }
                }
            } elseif (isset($composer[$type]) && $matches = preg_grep(BasePackage::packageNameToRegexp($package), array_keys($composer[$type]))) {
                foreach ($matches as $matchedPackage) {
                    $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>]? ', true)) {
                            $json->removeLink($altType, $matchedPackage);
                        }
                    }
                }
            } else {
                $io->writeError('<warning>'.$package.' is not required in your composer.json and has not been removed</warning>');
            }
        }

        if ($input->getOption('no-update')) {
            return 0;
        }

        // Update packages
        $this->resetComposer();
        $composer = $this->getComposer(true, $input->getOption('no-plugins'));
        $composer->getDownloadManager()->setOutputProgress(!$input->getOption('no-progress'));

        $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'remove', $input, $output);
        $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);

        $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');
        $apcu = $input->getOption('apcu-autoloader') || $composer->getConfig()->get('apcu-autoloader');

        $install
            ->setVerbose($input->getOption('verbose'))
            ->setDevMode($updateDevMode)
            ->setOptimizeAutoloader($optimize)
            ->setClassMapAuthoritative($authoritative)
            ->setApcuAutoloader($apcu)
            ->setUpdate(true)
            ->setUpdateWhitelist($packages)
            ->setWhitelistTransitiveDependencies(!$input->getOption('no-update-with-dependencies'))
            ->setIgnorePlatformRequirements($input->getOption('ignore-platform-reqs'))
            ->setRunScripts(!$input->getOption('no-scripts'))
        ;

        $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);
        }

        return $status;
    }
}
<?php

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

namespace Composer\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 <j.boggiano@seld.be>
 */
class ScriptAliasCommand extends BaseCommand
{
    private $script;
    private $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();
    }

    protected function configure()
    {
        $this
            ->setName($this->script)
            ->setDescription($this->description)
            ->setDefinition(array(
                new InputOption('dev', null, InputOption::VALUE_NONE, 'Sets the dev mode.'),
                new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables the dev mode.'),
                new InputArgument('args', InputArgument::IS_ARRAY | InputArgument::OPTIONAL, ''),
            ))
            ->setHelp(
                <<<EOT
The <info>run-script</info> command runs scripts defined in composer.json:

<info>php composer.phar run-script post-update-cmd</info>

Read more at https://getcomposer.org/doc/03-cli.md#run-script
EOT
            )
        ;
    }

    protected function execute(InputInterface $input, OutputInterface $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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Command;

use Composer\Composer;
use Composer\Factory;
use Composer\Config;
use Composer\Downloader\TransportException;
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\RemoteFilesystem;
use Composer\Util\StreamContextFactory;
use Composer\SelfUpdate\Keys;
use Composer\SelfUpdate\Versions;
use Composer\IO\NullIO;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class DiagnoseCommand extends BaseCommand
{
    /** @var RemoteFilesystem */
    protected $rfs;

    /** @var ProcessExecutor */
    protected $process;

    /** @var int */
    protected $exitCode = 0;

    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
            )
        ;
    }

    /**
     * {@inheritdoc}
     */
    protected function execute(InputInterface $input, OutputInterface $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->prohibitUrlByConfig('http://repo.packagist.org', new NullIO);

        $this->rfs = Factory::createRemoteFilesystem($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());
            $io->write('Checking HTTP proxy support for request_fulluri: ', false);
            $this->outputResult($this->checkHttpProxyFullUriRequestParam());
            $io->write('Checking HTTPS proxy support for request_fulluri: ', false);
            $this->outputResult($this->checkHttpsProxyFullUriRequestParam());
        }

        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 ('phar:' === substr(__FILE__, 0, 5)) {
            $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::VERSION));

        $platformOverrides = $config->get('platform') ?: array();
        $platformRepo = new PlatformRepository(array(), $platformOverrides);
        $phpPkg = $platformRepo->findPackage('php', '*');
        $phpVersion = $phpPkg->getPrettyVersion();
        if (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));
        }

        return $this->exitCode;
    }

    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;
    }

    private function checkGit()
    {
        $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;
    }

    private function checkHttp($proto, Config $config)
    {
        $result = $this->checkConnectivity();
        if ($result !== true) {
            return $result;
        }

        $disableTls = false;
        $result = array();
        if ($proto === 'https' && $config->get('disable-tls') === true) {
            $disableTls = true;
            $result[] = '<warning>Composer is configured to disable SSL/TLS protection. This will leave remote HTTPS requests vulnerable to Man-In-The-Middle attacks.</warning>';
        }
        if ($proto === 'https' && !extension_loaded('openssl') && !$disableTls) {
            $result[] = '<error>Composer is configured to use SSL/TLS protection but the openssl extension is not available.</error>';
        }

        try {
            $this->rfs->getContents('packagist.org', $proto . '://repo.packagist.org/packages.json', false);
        } catch (TransportException $e) {
            if (false !== strpos($e->getMessage(), 'cafile')) {
                $result[] = '<error>[' . get_class($e) . '] ' . $e->getMessage() . '</error>';
                $result[] = '<error>Unable to locate a valid CA certificate file. You must set a valid \'cafile\' option.</error>';
                $result[] = '<error>You can alternatively disable this error, at your own risk, by enabling the \'disable-tls\' option.</error>';
            } else {
                array_unshift($result, '[' . get_class($e) . '] ' . $e->getMessage());
            }
        }

        if (count($result) > 0) {
            return $result;
        }

        return true;
    }

    private function checkHttpProxy()
    {
        $result = $this->checkConnectivity();
        if ($result !== true) {
            return $result;
        }

        $protocol = extension_loaded('openssl') ? 'https' : 'http';
        try {
            $json = json_decode($this->rfs->getContents('packagist.org', $protocol . '://repo.packagist.org/packages.json', false), true);
            $hash = reset($json['provider-includes']);
            $hash = $hash['sha256'];
            $path = str_replace('%hash%', $hash, key($json['provider-includes']));
            $provider = $this->rfs->getContents('packagist.org', $protocol . '://repo.packagist.org/'.$path, false);

            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;
    }

    /**
     * Due to various proxy servers configurations, some servers can't handle non-standard HTTP "http_proxy_request_fulluri" parameter,
     * and will return error 500/501 (as not implemented), see discussion @ https://github.com/composer/composer/pull/1825.
     * This method will test, if you need to disable this parameter via setting extra environment variable in your system.
     *
     * @return bool|string
     */
    private function checkHttpProxyFullUriRequestParam()
    {
        $result = $this->checkConnectivity();
        if ($result !== true) {
            return $result;
        }

        $url = 'http://repo.packagist.org/packages.json';
        try {
            $this->rfs->getContents('packagist.org', $url, false);
        } catch (TransportException $e) {
            try {
                $this->rfs->getContents('packagist.org', $url, false, array('http' => array('request_fulluri' => false)));
            } catch (TransportException $e) {
                return 'Unable to assess the situation, maybe packagist.org is down ('.$e->getMessage().')';
            }

            return 'It seems there is a problem with your proxy server, try setting the "HTTP_PROXY_REQUEST_FULLURI" and "HTTPS_PROXY_REQUEST_FULLURI" environment variables to "false"';
        }

        return true;
    }

    /**
     * Due to various proxy servers configurations, some servers can't handle non-standard HTTP "http_proxy_request_fulluri" parameter,
     * and will return error 500/501 (as not implemented), see discussion @ https://github.com/composer/composer/pull/1825.
     * This method will test, if you need to disable this parameter via setting extra environment variable in your system.
     *
     * @return bool|string
     */
    private function checkHttpsProxyFullUriRequestParam()
    {
        $result = $this->checkConnectivity();
        if ($result !== true) {
            return $result;
        }

        if (!extension_loaded('openssl')) {
            return 'You need the openssl extension installed for this check';
        }

        $url = 'https://api.github.com/repos/Seldaek/jsonlint/zipball/1.0.0';
        try {
            $this->rfs->getContents('github.com', $url, false);
        } catch (TransportException $e) {
            try {
                $this->rfs->getContents('github.com', $url, false, array('http' => array('request_fulluri' => false)));
            } catch (TransportException $e) {
                return 'Unable to assess the situation, maybe github is down ('.$e->getMessage().')';
            }

            return 'It seems there is a problem with your proxy server, try setting the "HTTPS_PROXY_REQUEST_FULLURI" environment variable to "false"';
        }

        return true;
    }

    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/';

            return $this->rfs->getContents($domain, $url, false, array(
                'retry-auth-failure' => false,
            )) ? true : 'Unexpected error';
        } 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 array|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';
        $json = $this->rfs->getContents($domain, $url, false, array('retry-auth-failure' => false));
        $data = json_decode($json, true);

        return $data['resources']['core'];
    }

    private function checkDiskSpace($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;
    }

    private function checkPubKeys($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;
    }

    private function checkVersion($config)
    {
        $result = $this->checkConnectivity();
        if ($result !== true) {
            return $result;
        }

        $versionsUtil = new Versions($config, $this->rfs);
        $latest = $versionsUtil->getLatest();

        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;
    }

    /**
     * @param bool|string|\Exception $result
     */
    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->exitCode, 2);
        } elseif ($hadWarning) {
            $io->write('<warning>WARNING</warning>');
            $this->exitCode = max($this->exitCode, 1);
        }

        if ($result) {
            foreach ($result as $message) {
                $io->write($message);
            }
        }
    }

    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_match('{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 (extension_loaded('xdebug')) {
            $warnings['xdebug_loaded'] = true;
        }

        if (!empty($errors)) {
            foreach ($errors as $error => $current) {
                switch ($error) {
                    case 'json':
                        $text = PHP_EOL."The json extension is missing.".PHP_EOL;
                        $text .= "Install it or recompile php without --disable-json";
                        break;

                    case 'phar':
                        $text = PHP_EOL."The phar extension is missing.".PHP_EOL;
                        $text .= "Install it or recompile php without --disable-phar";
                        break;

                    case 'filter':
                        $text = PHP_EOL."The filter extension is missing.".PHP_EOL;
                        $text .= "Install it or recompile php without --disable-filter";
                        break;

                    case 'hash':
                        $text = PHP_EOL."The hash extension is missing.".PHP_EOL;
                        $text .= "Install it or recompile php without --disable-hash";
                        break;

                    case 'iconv_mbstring':
                        $text = PHP_EOL."The iconv OR mbstring extension is required and both are missing.".PHP_EOL;
                        $text .= "Install either of them or recompile php without --disable-iconv";
                        break;

                    case 'unicode':
                        $text = PHP_EOL."The detect_unicode setting must be disabled.".PHP_EOL;
                        $text .= "Add the following to the end of your `php.ini`:".PHP_EOL;
                        $text .= "    detect_unicode = Off";
                        $displayIniMessage = true;
                        break;

                    case 'suhosin':
                        $text = PHP_EOL."The suhosin.executor.include.whitelist setting is incorrect.".PHP_EOL;
                        $text .= "Add the following to the end of your `php.ini` or suhosin.ini (Example path [for Debian]: /etc/php5/cli/conf.d/suhosin.ini):".PHP_EOL;
                        $text .= "    suhosin.executor.include.whitelist = phar ".$current;
                        $displayIniMessage = true;
                        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;
                }
                $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;
                }
                $out($text, 'comment');
            }
        }

        if ($displayIniMessage) {
            $out($iniMessage, 'comment');
        }

        return !$warnings && !$errors ? true : $output;
    }


    /**
     * Check if allow_url_fopen is ON
     *
     * @return bool|string
     */
    private function checkConnectivity()
    {
        if (!ini_get('allow_url_fopen')) {
            $result = '<info>Skipped because allow_url_fopen is missing.</info>';
            return $result;
        }

        return true;
    }
}
<?php

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

namespace Composer\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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Plugin;

use Composer\EventDispatcher\Event;
use Symfony\Component\Console\Input\InputInterface;

/**
 * The pre command run event.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PreCommandRunEvent extends Event
{
    /**
     * @var InputInterface
     */
    private $input;

    /**
     * @var string
     */
    private $command;

    /**
     * Constructor.
     *
     * @param string         $name    The event name
     * @param InputInterface $input
     * @param string         $command The command about to be executed
     */
    public function __construct($name, InputInterface $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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Plugin;

use Composer\Composer;
use Composer\IO\IOInterface;

/**
 * Plugin interface
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
interface PluginInterface
{
    /**
     * Version number of the internal composer-plugin-api package
     *
     * @var string
     */
    const PLUGIN_API_VERSION = '1.1.0';

    /**
     * Apply plugin modifications to Composer
     *
     * @param Composer    $composer
     * @param IOInterface $io
     */
    public function activate(Composer $composer, IOInterface $io);
}
<?php

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

namespace Composer\Plugin;

use Composer\EventDispatcher\Event;
use Composer\Util\RemoteFilesystem;

/**
 * The pre file download event.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class PreFileDownloadEvent extends Event
{
    /**
     * @var RemoteFilesystem
     */
    private $rfs;

    /**
     * @var string
     */
    private $processedUrl;

    /**
     * Constructor.
     *
     * @param string           $name         The event name
     * @param RemoteFilesystem $rfs
     * @param string           $processedUrl
     */
    public function __construct($name, RemoteFilesystem $rfs, $processedUrl)
    {
        parent::__construct($name);
        $this->rfs = $rfs;
        $this->processedUrl = $processedUrl;
    }

    /**
     * Returns the remote filesystem
     *
     * @return RemoteFilesystem
     */
    public function getRemoteFilesystem()
    {
        return $this->rfs;
    }

    /**
     * Sets the remote filesystem
     *
     * @param RemoteFilesystem $rfs
     */
    public function setRemoteFilesystem(RemoteFilesystem $rfs)
    {
        $this->rfs = $rfs;
    }

    /**
     * Retrieves the processed URL this remote filesystem will be used for
     *
     * @return string
     */
    public function getProcessedUrl()
    {
        return $this->processedUrl;
    }
}
<?php

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

namespace Composer\Plugin;

/**
 * The Plugin Events.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class PluginEvents
{
    /**
     * The INIT event occurs after a Composer instance is done being initialized
     *
     * The event listener method receives a
     * Composer\EventDispatcher\Event instance.
     *
     * @var string
     */
    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 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';
}
<?php

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

namespace Composer\Plugin;

use Composer\EventDispatcher\Event;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * An event for all commands.
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class CommandEvent extends Event
{
    /**
     * @var string
     */
    private $commandName;

    /**
     * @var InputInterface
     */
    private $input;

    /**
     * @var OutputInterface
     */
    private $output;

    /**
     * Constructor.
     *
     * @param string          $name        The event name
     * @param string          $commandName The command name
     * @param InputInterface  $input
     * @param OutputInterface $output
     * @param array           $args        Arguments passed by the user
     * @param array           $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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Plugin\Capability;

/**
 * Commands Provider Interface
 *
 * This capability will receive an array with 'composer' and 'io' keys as
 * constructor argument. Those contain Composer\Composer and Composer\IO\IOInterface
 * instances. It also contains a 'plugin' key containing the plugin instance that
 * created the capability.
 *
 * @author Jérémy Derussé <jeremy@derusse.com>
 */
interface CommandProvider extends Capability
{
    /**
     * Retrieves an array of commands
     *
     * @return \Composer\Command\BaseCommand[]
     */
    public function getCommands();
}
<?php

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

namespace Composer\Plugin\Capability;

/**
 * Marker interface for Plugin capabilities.
 * Every new Capability which is added to the Plugin API must implement this interface.
 *
 * @api
 */
interface Capability
{
}
<?php

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

namespace Composer\Plugin;

use Composer\Composer;
use Composer\EventDispatcher\EventSubscriberInterface;
use Composer\IO\IOInterface;
use Composer\Package\CompletePackage;
use Composer\Package\Package;
use Composer\Package\Version\VersionParser;
use Composer\Repository\RepositoryInterface;
use Composer\Package\PackageInterface;
use Composer\Package\Link;
use Composer\Semver\Constraint\Constraint;
use Composer\DependencyResolver\Pool;
use Composer\Plugin\Capability\Capability;
use Composer\Util\PackageSorter;

/**
 * Plugin manager
 *
 * @author Nils Adermann <naderman@naderman.de>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PluginManager
{
    protected $composer;
    protected $io;
    protected $globalComposer;
    protected $versionParser;
    protected $disablePlugins = false;

    protected $plugins = array();
    protected $registeredPlugins = array();

    private static $classCounter = 0;

    /**
     * Initializes plugin manager
     *
     * @param IOInterface $io
     * @param Composer    $composer
     * @param Composer    $globalComposer
     * @param bool        $disablePlugins
     */
    public function __construct(IOInterface $io, Composer $composer, Composer $globalComposer = null, $disablePlugins = false)
    {
        $this->io = $io;
        $this->composer = $composer;
        $this->globalComposer = $globalComposer;
        $this->versionParser = new VersionParser();
        $this->disablePlugins = $disablePlugins;
    }

    /**
     * Loads all plugins from currently installed plugin packages
     */
    public function loadInstalledPlugins()
    {
        if ($this->disablePlugins) {
            return;
        }

        $repo = $this->composer->getRepositoryManager()->getLocalRepository();
        $globalRepo = $this->globalComposer ? $this->globalComposer->getRepositoryManager()->getLocalRepository() : null;
        if ($repo) {
            $this->loadRepository($repo);
        }
        if ($globalRepo) {
            $this->loadRepository($globalRepo);
        }
    }

    /**
     * Gets all currently active plugin instances
     *
     * @return array 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
     *
     * @throws \UnexpectedValueException
     */
    public function registerPackage(PackageInterface $package, $failOnMissingClasses = false)
    {
        if ($this->disablePlugins) {
            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() === '1.0.0' && $this->getPluginApiVersion() === '1.0.0') {
                $this->io->writeError('<warning>The "' . $package->getName() . '" plugin requires composer-plugin-api 1.0.0, this *WILL* break in the future and it should be fixed ASAP (require ^1.0 for example).</warning>');
            } elseif (!$requiresComposer->matches($currentPluginApiConstraint)) {
                $this->io->writeError('<warning>The "' . $package->getName() . '" plugin 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;
            }
        }

        $oldInstallerPlugin = ($package->getType() === 'composer-installer');

        if (in_array($package->getName(), $this->registeredPlugins)) {
            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 ? $this->globalComposer->getRepositoryManager()->getLocalRepository() : null;

        $pool = new Pool('dev');
        $pool->addRepository($localRepo);
        if ($globalRepo) {
            $pool->addRepository($globalRepo);
        }

        $autoloadPackages = array($package->getName() => $package);
        $autoloadPackages = $this->collectDependencies($pool, $autoloadPackages, $package);

        $generator = $this->composer->getAutoloadGenerator();
        $autoloads = array();
        foreach ($autoloadPackages as $autoloadPackage) {
            $downloadPath = $this->getInstallPath($autoloadPackage, $globalRepo && $globalRepo->hasPackage($autoloadPackage));
            $autoloads[] = array($autoloadPackage, $downloadPath);
        }

        $map = $generator->parseAutoloads($autoloads, new Package('dummy', '1.0.0.0', '1.0.0'));
        $classLoader = $generator->createLoader($map);
        $classLoader->register();

        foreach ($classes as $class) {
            if (class_exists($class, false)) {
                $class = trim($class, '\\');
                $path = $classLoader->findFile($class);
                $code = file_get_contents($path);
                $separatorPos = strrpos($class, '\\');
                $className = $class;
                if ($separatorPos) {
                    $className = substr($class, $separatorPos + 1);
                }
                $code = preg_replace('{^((?:final\s+)?(?:\s*))class\s+('.preg_quote($className).')}mi', '$1class $2_composer_tmp'.self::$classCounter, $code, 1);
                $code = str_replace('__FILE__', var_export($path, true), $code);
                $code = str_replace('__DIR__', var_export(dirname($path), true), $code);
                $code = str_replace('__CLASS__', var_export($class, true), $code);
                $code = preg_replace('/^\s*<\?(php)?/i', '', $code, 1);
                eval($code);
                $class .= '_composer_tmp'.self::$classCounter;
                self::$classCounter++;
            }

            if ($oldInstallerPlugin) {
                $installer = new $class($this->io, $this->composer);
                $this->composer->getInstallationManager()->addInstaller($installer);
            } elseif (class_exists($class)) {
                $plugin = new $class();
                $this->addPlugin($plugin);
                $this->registeredPlugins[] = $package->getName();
            } elseif ($failOnMissingClasses) {
                throw new \UnexpectedValueException('Plugin '.$package->getName().' could not be initialized, class not found: '.$class);
            }
        }
    }

    /**
     * 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
     */
    public function addPlugin(PluginInterface $plugin)
    {
        $this->io->writeError('Loading plugin '.get_class($plugin), true, IOInterface::DEBUG);
        $this->plugins[] = $plugin;
        $plugin->activate($this->composer, $this->io);

        if ($plugin instanceof EventSubscriberInterface) {
            $this->composer->getEventDispatcher()->addSubscriber($plugin);
        }
    }

    /**
     * Load all plugins and installers from a repository
     *
     * 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
     *
     * @throws \RuntimeException
     */
    private function loadRepository(RepositoryInterface $repo)
    {
        $packages = $repo->getPackages();
        $sortedPackages = array_reverse(PackageSorter::sortPackages($packages));
        foreach ($sortedPackages as $package) {
            if (!($package instanceof CompletePackage)) {
                continue;
            }
            if ('composer-plugin' === $package->getType()) {
                $this->registerPackage($package);
            // Backward compatibility
            } elseif ('composer-installer' === $package->getType()) {
                $this->registerPackage($package);
            }
        }
    }

    /**
     * Recursively generates a map of package names to packages for all deps
     *
     * @param Pool             $pool      Package pool of installed packages
     * @param array            $collected Current state of the map for recursion
     * @param PackageInterface $package   The package to analyze
     *
     * @return array Map of package names to packages
     */
    private function collectDependencies(Pool $pool, array $collected, PackageInterface $package)
    {
        $requires = array_merge(
            $package->getRequires(),
            $package->getDevRequires()
        );

        foreach ($requires as $requireLink) {
            $requiredPackage = $this->lookupInstalledPackage($pool, $requireLink);
            if ($requiredPackage && !isset($collected[$requiredPackage->getName()])) {
                $collected[$requiredPackage->getName()] = $requiredPackage;
                $collected = $this->collectDependencies($pool, $collected, $requiredPackage);
            }
        }

        return $collected;
    }

    /**
     * Resolves a package link to a package in the installed pool
     *
     * Since dependencies are already installed this should always find one.
     *
     * @param Pool $pool Pool of installed packages only
     * @param Link $link Package link to look up
     *
     * @return PackageInterface|null The found package
     */
    private function lookupInstalledPackage(Pool $pool, Link $link)
    {
        $packages = $pool->whatProvides($link->getTarget(), $link->getConstraint());

        return !empty($packages) ? $packages[0] : null;
    }

    /**
     * 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], 1));
        }
    }

    /**
     * @param  PluginInterface $plugin
     * @param  string          $capabilityClassName The fully qualified name of the API interface which the plugin may provide
     *                                              an implementation of.
     * @param  array           $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
     */
    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;
        }
    }

    /**
     * @param  string       $capabilityClassName The fully qualified name of the API interface which the plugin may provide
     *                                           an implementation of.
     * @param  array        $ctorArgs            Arguments passed to Capability's constructor.
     *                                           Keeping it an array will allow future values to be passed w\o changing the signature.
     * @return Capability[]
     */
    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;
    }
}
<?php

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

namespace Composer;

use Composer\Config\JsonConfigSource;
use Composer\Json\JsonFile;
use Composer\IO\IOInterface;
use Composer\Package\Archiver;
use Composer\Package\Version\VersionGuesser;
use Composer\Repository\RepositoryManager;
use Composer\Repository\RepositoryFactory;
use Composer\Repository\WritableRepositoryInterface;
use Composer\Util\Filesystem;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Util\RemoteFilesystem;
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 Seld\JsonLint\JsonParser;

/**
 * Creates a configured instance of composer.
 *
 * @author Ryan Weaver <ryan@knplabs.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Igor Wiedler <igor@wiedler.ch>
 * @author Nils Adermann <naderman@naderman.de>
 */
class Factory
{
    /**
     * @throws \RuntimeException
     * @return string
     */
    protected static function getHomeDir()
    {
        $home = getenv('COMPOSER_HOME');
        if ($home) {
            return $home;
        }

        if (Platform::isWindows()) {
            if (!getenv('APPDATA')) {
                throw new \RuntimeException('The APPDATA or COMPOSER_HOME environment variable must be set for composer to run correctly');
            }

            return rtrim(strtr(getenv('APPDATA'), '\\', '/'), '/') . '/Composer';
        }

        $userDir = self::getUserDir();
        if (is_dir($userDir . '/.composer')) {
            return $userDir . '/.composer';
        }

        if (self::useXdg()) {
            // XDG Base Directory Specifications
            $xdgConfig = getenv('XDG_CONFIG_HOME') ?: $userDir . '/.config';

            return $xdgConfig . '/composer';
        }

        return $userDir . '/.composer';
    }

    /**
     * @param  string $home
     * @return string
     */
    protected static function getCacheDir($home)
    {
        $cacheDir = getenv('COMPOSER_CACHE_DIR');
        if ($cacheDir) {
            return $cacheDir;
        }

        $homeEnv = getenv('COMPOSER_HOME');
        if ($homeEnv) {
            return $homeEnv . '/cache';
        }

        if (Platform::isWindows()) {
            if ($cacheDir = getenv('LOCALAPPDATA')) {
                $cacheDir .= '/Composer';
            } else {
                $cacheDir = $home . '/cache';
            }

            return rtrim(strtr($cacheDir, '\\', '/'), '/');
        }

        $userDir = self::getUserDir();
        if ($home === $userDir . '/.composer' && is_dir($home . '/cache')) {
            return $home . '/cache';
        }

        if (self::useXdg()) {
            $xdgCache = getenv('XDG_CACHE_HOME') ?: $userDir . '/.cache';

            return $xdgCache . '/composer';
        }

        return $home . '/cache';
    }

    /**
     * @param  string $home
     * @return string
     */
    protected static function getDataDir($home)
    {
        $homeEnv = getenv('COMPOSER_HOME');
        if ($homeEnv) {
            return $homeEnv;
        }

        if (Platform::isWindows()) {
            return strtr($home, '\\', '/');
        }

        $userDir = self::getUserDir();
        if ($home !== $userDir . '/.composer' && self::useXdg()) {
            $xdgData = getenv('XDG_DATA_HOME') ?: $userDir . '/.local/share';

            return $xdgData . '/composer';
        }

        return $home;
    }

    /**
     * @param  IOInterface|null $io
     * @return Config
     */
    public static function createConfig(IOInterface $io = null, $cwd = null)
    {
        $cwd = $cwd ?: 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),
        )));

        // 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());
        }
        $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', $dir, 0777, true);
                    }
                    Silencer::call('file_put_contents', $dir . '/.htaccess', 'Deny from all');
                }
            }
        }

        // load global auth file
        $file = new JsonFile($config->get('home').'/auth.json');
        if ($file->exists()) {
            if ($io && $io->isDebug()) {
                $io->writeError('Loading config file ' . $file->getPath());
            }
            $config->merge(array('config' => $file->read()));
        }
        $config->setAuthConfigSource(new JsonConfigSource($file, true));

        // load COMPOSER_AUTH environment variable if set
        if ($composerAuthEnv = getenv('COMPOSER_AUTH')) {
            $authData = json_decode($composerAuthEnv, true);

            if (null === $authData) {
                throw new \UnexpectedValueException('COMPOSER_AUTH environment variable is malformed, should be a valid JSON object');
            }

            if ($io && $io->isDebug()) {
                $io->writeError('Loading auth config from COMPOSER_AUTH');
            }
            $config->merge(array('config' => $authData));
        }

        return $config;
    }

    public static function getComposerFile()
    {
        return trim(getenv('COMPOSER')) ?: './composer.json';
    }

    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_NORMAL, null, $formatter);
    }

    /**
     * @deprecated Use Composer\Repository\RepositoryFactory::defaultRepos instead
     */
    public static function createDefaultRepositories(IOInterface $io = null, Config $config = null, RepositoryManager $rm = null)
    {
        return RepositoryFactory::defaultRepos($io, $config, $rm);
    }

    /**
     * Creates a Composer instance
     *
     * @param  IOInterface               $io             IO instance
     * @param  array|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                      $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)
    {
        $cwd = $cwd ?: getcwd();

        // load Composer configuration
        if (null === $localConfig) {
            $localConfig = static::getComposerFile();
        }

        if (is_string($localConfig)) {
            $composerFile = $localConfig;

            $file = new JsonFile($localConfig, null, $io);

            if (!$file->exists()) {
                if ($localConfig === './composer.json' || $localConfig === 'composer.json') {
                    $message = 'Composer could not find a composer.json file in '.$cwd;
                } else {
                    $message = 'Composer could not find the config file: '.$localConfig;
                }
                $instructions = 'To initialize a project, please create a composer.json file as described in the https://getcomposer.org/ "Getting Started" section';
                throw new \InvalidArgumentException($message.PHP_EOL.$instructions);
            }

            $file->validateSchema(JsonFile::LAX_SCHEMA);
            $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();
        }

        // Load config and override with local config/auth config
        $config = static::createConfig($io, $cwd);
        $config->merge($localConfig);
        if (isset($composerFile)) {
            $io->writeError('Loading config file ' . $composerFile, true, IOInterface::DEBUG);
            $config->setConfigSource(new JsonConfigSource(new JsonFile(realpath($composerFile), null, $io)));

            $localAuthFile = new JsonFile(dirname(realpath($composerFile)) . '/auth.json', null, $io);
            if ($localAuthFile->exists()) {
                $io->writeError('Loading config file ' . $localAuthFile->getPath(), true, IOInterface::DEBUG);
                $config->merge(array('config' => $localAuthFile->read()));
                $config->setAuthConfigSource(new JsonConfigSource($localAuthFile, true));
            }
        }

        $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);
        }

        $rfs = self::createRemoteFilesystem($io, $config);

        // initialize event dispatcher
        $dispatcher = new EventDispatcher($composer, $io);
        $composer->setEventDispatcher($dispatcher);

        // initialize repository manager
        $rm = RepositoryFactory::manager($io, $config, $dispatcher, $rfs);
        $composer->setRepositoryManager($rm);

        // load local repository
        $this->addLocalRepository($io, $rm, $vendorDir);

        // 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, new ProcessExecutor($io), $parser);
        $loader = new Package\Loader\RootPackageLoader($rm, $config, $parser, $guesser, $io);
        $package = $loader->load($localConfig, 'Composer\Package\RootPackage', $cwd);
        $composer->setPackage($package);

        // initialize installation manager
        $im = $this->createInstallationManager();
        $composer->setInstallationManager($im);

        if ($fullLoad) {
            // initialize download manager
            $dm = $this->createDownloadManager($io, $config, $dispatcher, $rfs);
            $composer->setDownloadManager($dm);

            // initialize autoload generator
            $generator = new AutoloadGenerator($dispatcher, $io);
            $composer->setAutoloadGenerator($generator);

            // initialize archive manager
            $am = $this->createArchiveManager($config, $dm);
            $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);

        if ($fullLoad) {
            $globalComposer = null;
            if (realpath($config->get('home')) !== $cwd) {
                $globalComposer = $this->createGlobalComposer($io, $config, $disablePlugins);
            }

            $pm = $this->createPluginManager($io, $composer, $globalComposer, $disablePlugins);
            $composer->setPluginManager($pm);

            $pm->loadInstalledPlugins();
        }

        // init locker if possible
        if ($fullLoad && isset($composerFile)) {
            $lockFile = "json" === pathinfo($composerFile, PATHINFO_EXTENSION)
                ? substr($composerFile, 0, -4).'lock'
                : $composerFile . '.lock';

            $locker = new Package\Locker($io, new JsonFile($lockFile, null, $io), $rm, $im, file_get_contents($composerFile));
            $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
            if ($rm->getLocalRepository()) {
                $this->purgePackages($rm->getLocalRepository(), $im);
            }
        }

        return $composer;
    }

    /**
     * @param  IOInterface $io             IO instance
     * @param  bool        $disablePlugins Whether plugins should not be loaded
     * @return Composer
     */
    public static function createGlobal(IOInterface $io, $disablePlugins = false)
    {
        $factory = new static();

        return $factory->createGlobalComposer($io, static::createConfig($io), $disablePlugins, true);
    }

    /**
     * @param Repository\RepositoryManager $rm
     * @param string                       $vendorDir
     */
    protected function addLocalRepository(IOInterface $io, RepositoryManager $rm, $vendorDir)
    {
        $rm->setLocalRepository(new Repository\InstalledFilesystemRepository(new JsonFile($vendorDir.'/composer/installed.json', null, $io)));
    }

    /**
     * @param  Config        $config
     * @return Composer|null
     */
    protected function createGlobalComposer(IOInterface $io, Config $config, $disablePlugins, $fullLoad = false)
    {
        $composer = null;
        try {
            $composer = $this->createComposer($io, $config->get('home') . '/composer.json', $disablePlugins, $config->get('home'), $fullLoad);
        } catch (\Exception $e) {
            $io->writeError('Failed to initialize global composer: '.$e->getMessage(), true, IOInterface::DEBUG);
        }

        return $composer;
    }

    /**
     * @param  IO\IOInterface             $io
     * @param  Config                     $config
     * @param  EventDispatcher            $eventDispatcher
     * @return Downloader\DownloadManager
     */
    public function createDownloadManager(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, RemoteFilesystem $rfs = null)
    {
        $cache = null;
        if ($config->get('cache-files-ttl') > 0) {
            $cache = new Cache($io, $config->get('cache-files-dir'), 'a-z0-9_./');
        }

        $dm = new Downloader\DownloadManager($io);
        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);
        }

        $executor = new ProcessExecutor($io);
        $fs = new Filesystem($executor);

        $dm->setDownloader('git', new Downloader\GitDownloader($io, $config, $executor, $fs));
        $dm->setDownloader('svn', new Downloader\SvnDownloader($io, $config, $executor, $fs));
        $dm->setDownloader('fossil', new Downloader\FossilDownloader($io, $config, $executor, $fs));
        $dm->setDownloader('hg', new Downloader\HgDownloader($io, $config, $executor, $fs));
        $dm->setDownloader('perforce', new Downloader\PerforceDownloader($io, $config));
        $dm->setDownloader('zip', new Downloader\ZipDownloader($io, $config, $eventDispatcher, $cache, $executor, $rfs));
        $dm->setDownloader('rar', new Downloader\RarDownloader($io, $config, $eventDispatcher, $cache, $executor, $rfs));
        $dm->setDownloader('tar', new Downloader\TarDownloader($io, $config, $eventDispatcher, $cache, $rfs));
        $dm->setDownloader('gzip', new Downloader\GzipDownloader($io, $config, $eventDispatcher, $cache, $executor, $rfs));
        $dm->setDownloader('xz', new Downloader\XzDownloader($io, $config, $eventDispatcher, $cache, $executor, $rfs));
        $dm->setDownloader('phar', new Downloader\PharDownloader($io, $config, $eventDispatcher, $cache, $rfs));
        $dm->setDownloader('file', new Downloader\FileDownloader($io, $config, $eventDispatcher, $cache, $rfs));
        $dm->setDownloader('path', new Downloader\PathDownloader($io, $config, $eventDispatcher, $cache, $rfs));

        return $dm;
    }

    /**
     * @param  Config                     $config The configuration
     * @param  Downloader\DownloadManager $dm     Manager use to download sources
     * @return Archiver\ArchiveManager
     */
    public function createArchiveManager(Config $config, Downloader\DownloadManager $dm = null)
    {
        if (null === $dm) {
            $io = new IO\NullIO();
            $io->loadConfiguration($config);
            $dm = $this->createDownloadManager($io, $config);
        }

        $am = new Archiver\ArchiveManager($dm);
        $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 $io, Composer $composer, Composer $globalComposer = null, $disablePlugins = false)
    {
        return new Plugin\PluginManager($io, $composer, $globalComposer, $disablePlugins);
    }

    /**
     * @return Installer\InstallationManager
     */
    protected function createInstallationManager()
    {
        return new Installer\InstallationManager();
    }

    /**
     * @param Installer\InstallationManager $im
     * @param Composer                      $composer
     * @param IO\IOInterface                $io
     */
    protected function createDefaultInstallers(Installer\InstallationManager $im, Composer $composer, IOInterface $io)
    {
        $im->addInstaller(new Installer\LibraryInstaller($io, $composer, null));
        $im->addInstaller(new Installer\PearInstaller($io, $composer, 'pear-library'));
        $im->addInstaller(new Installer\PluginInstaller($io, $composer));
        $im->addInstaller(new Installer\MetapackageInstaller($io));
    }

    /**
     * @param WritableRepositoryInterface   $repo repository to purge packages from
     * @param Installer\InstallationManager $im   manager to check whether packages are still installed
     */
    protected function purgePackages(WritableRepositoryInterface $repo, Installer\InstallationManager $im)
    {
        foreach ($repo->getPackages() as $package) {
            if (!$im->isPackageInstalled($repo, $package)) {
                $repo->removePackage($package);
            }
        }
    }

    /**
     * @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
     * @return Composer
     */
    public static function create(IOInterface $io, $config = null, $disablePlugins = false)
    {
        $factory = new static();

        return $factory->createComposer($io, $config, $disablePlugins);
    }

    /**
     * @param  IOInterface      $io      IO instance
     * @param  Config           $config  Config instance
     * @param  array            $options Array of options passed directly to RemoteFilesystem constructor
     * @return RemoteFilesystem
     */
    public static function createRemoteFilesystem(IOInterface $io, Config $config = null, $options = array())
    {
        static $warned = false;
        $disableTls = false;
        if ($config && $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.');
        }
        $remoteFilesystemOptions = array();
        if ($disableTls === false) {
            if ($config && $config->get('cafile')) {
                $remoteFilesystemOptions['ssl']['cafile'] = $config->get('cafile');
            }
            if ($config && $config->get('capath')) {
                $remoteFilesystemOptions['ssl']['capath'] = $config->get('capath');
            }
            $remoteFilesystemOptions = array_replace_recursive($remoteFilesystemOptions, $options);
        }
        try {
            $remoteFilesystem = new RemoteFilesystem($io, $config, $remoteFilesystemOptions, $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 $remoteFilesystem;
    }

    /**
     * @return bool
     */
    private static function useXdg()
    {
        foreach (array_keys($_SERVER) as $key) {
            if (substr($key, 0, 4) === 'XDG_') {
                return true;
            }
        }

        return false;
    }

    /**
     * @throws \RuntimeException
     * @return string
     */
    private static function getUserDir()
    {
        $home = 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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use Composer\Package\PackageInterface;

/**
 * ChangeReport interface.
 *
 * @author Sascha Egerer <sascha.egerer@dkd.de>
 */
interface ChangeReportInterface
{
    /**
     * Checks for changes to the local copy
     *
     * @param  PackageInterface $package package instance
     * @param  string           $path    package directory
     * @return string|null      changes or null
     */
    public function getLocalChanges(PackageInterface $package, $path);
}
<?php

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

namespace Composer\Downloader;

use Composer\Package\PackageInterface;
use Composer\Util\ProcessExecutor;

/**
 * @author BohwaZ <http://bohwaz.net/>
 */
class FossilDownloader extends VcsDownloader
{
    /**
     * {@inheritDoc}
     */
    public function doDownload(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', $url, ProcessExecutor::escape($repoFile));
        if (0 !== $this->process->execute($command, $ignoredOutput)) {
            throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
        }
        $command = sprintf('fossil open %s --nested', ProcessExecutor::escape($repoFile));
        if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) {
            throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
        }
        $command = sprintf('fossil update %s', $ref);
        if (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) {
            throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
        }
    }

    /**
     * {@inheritDoc}
     */
    public function doUpdate(PackageInterface $initial, PackageInterface $target, $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($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 (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) {
            throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
        }
    }

    /**
     * {@inheritDoc}
     */
    public function getLocalChanges(PackageInterface $package, $path)
    {
        if (!$this->hasMetadataRepository($path)) {
            return null;
        }

        $this->process->execute('fossil changes', $output, realpath($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 (0 !== $this->process->execute($command, $output, realpath($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_match($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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class TransportException extends \RuntimeException
{
    protected $headers;
    protected $response;
    protected $statusCode;

    public function setHeaders($headers)
    {
        $this->headers = $headers;
    }

    public function getHeaders()
    {
        return $this->headers;
    }

    public function setResponse($response)
    {
        $this->response = $response;
    }

    public function getResponse()
    {
        return $this->response;
    }

    public function setStatusCode($statusCode)
    {
        $this->statusCode = $statusCode;
    }

    public function getStatusCode()
    {
        return $this->statusCode;
    }
}
<?php

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

namespace Composer\Downloader;

use Composer\Package\PackageInterface;

/**
 * VCS Capable Downloader interface.
 *
 * @author Steve Buzonas <steve@fancyguy.com>
 */
interface VcsCapableDownloaderInterface
{
    /**
     * Gets the VCS Reference for the package at path
     *
     * @param  PackageInterface $package package 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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use Composer\Config;
use Composer\Cache;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Util\IniHelper;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Util\RemoteFilesystem;
use Composer\IO\IOInterface;
use RarArchive;

/**
 * RAR archive downloader.
 *
 * Based on previous work by Jordi Boggiano ({@see ZipDownloader}).
 *
 * @author Derrick Nelson <drrcknlsn@gmail.com>
 */
class RarDownloader extends ArchiveDownloader
{
    protected $process;

    public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, RemoteFilesystem $rfs = null)
    {
        $this->process = $process ?: new ProcessExecutor($io);
        parent::__construct($io, $config, $eventDispatcher, $cache, $rfs);
    }

    protected function extract($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 (0 === $this->process->execute($command, $ignoredOutput)) {
                return;
            }

            $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();
    }
}
<?php

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

namespace Composer\Downloader;

use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Util\Filesystem;
use Composer\Util\Git as GitUtil;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Cache;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class GitDownloader extends VcsDownloader implements DvcsDownloaderInterface
{
    private $hasStashedChanges = false;
    private $hasDiscardedChanges = false;
    private $gitUtil;

    public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, Filesystem $fs = null)
    {
        parent::__construct($io, $config, $process, $fs);
        $this->gitUtil = new GitUtil($this->io, $this->config, $this->process, $this->filesystem);
    }

    /**
     * {@inheritDoc}
     */
    public function doDownload(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 ' : '';

        // --dissociate option is only available since git 2.3.0-rc0
        $gitVersion = $this->gitUtil->getVersion();
        $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 ($gitVersion && version_compare($gitVersion, '2.3.0-rc0', '>=') && Cache::isUsable($cachePath)) {
            $this->io->writeError('', true, IOInterface::DEBUG);
            $this->io->writeError(sprintf('    Cloning to cache at %s', ProcessExecutor::escape($cachePath)), true, IOInterface::DEBUG);
            try {
                if (!$this->gitUtil->fetchRefOrSyncMirror($url, $cachePath, $ref)) {
                    $this->io->writeError('<error>Failed to update '.$url.' in cache, package installation for '.$package->getPrettyName().' might fail.</error>');
                }
                if (is_dir($cachePath)) {
                    $command =
                        'git clone --no-checkout %cachePath% %path% --dissociate --reference %cachePath% '
                        . '&& cd '.$flag.'%path% '
                        . '&& git remote set-url origin %sanitizedUrl% && git remote add composer %sanitizedUrl%';
                    $msg = "Cloning ".$this->getShortHash($ref).' from cache';
                }
            } catch (\RuntimeException $e) {
                if (0 === strpos(get_class($e), 'PHPUnit')) {
                    throw $e;
                }
            }
        }
        $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, $path, true);
        if ($url !== $package->getSourceUrl()) {
            $this->updateOriginUrl($path, $package->getSourceUrl());
        } 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);
        }
    }

    /**
     * {@inheritDoc}
     */
    public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
    {
        GitUtil::cleanEnv();
        if (!$this->hasMetadataRepository($path)) {
            throw new \RuntimeException('The .git directory is missing from '.$path.', see https://getcomposer.org/commit-deps for more information');
        }

        $updateOriginUrl = false;
        if (
            0 === $this->process->execute('git remote -v', $output, $path)
            && preg_match('{^origin\s+(?P<url>\S+)}m', $output, $originMatch)
            && preg_match('{^composer\s+(?P<url>\S+)}m', $output, $composerMatch)
        ) {
            if ($originMatch['url'] === $composerMatch['url'] && $composerMatch['url'] !== $target->getSourceUrl()) {
                $updateOriginUrl = true;
            }
        }

        $ref = $target->getSourceReference();
        $this->io->writeError(" Checking out ".$this->getShortHash($ref));
        $command = '(git remote set-url composer %s && git rev-parse --quiet --verify %s || (git fetch composer && git fetch --tags composer)) && git remote set-url composer %s';

        $commandCallable = function ($url) use ($command, $ref) {
            return sprintf(
                $command,
                ProcessExecutor::escape($url),
                ProcessExecutor::escape($ref.'^{commit}'),
                ProcessExecutor::escape(preg_replace('{://([^@]+?):(.+?)@}', '://', $url))
            );
        };

        $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);
        }

        if ($updateOriginUrl) {
            $this->updateOriginUrl($path, $target->getSourceUrl());
        }
    }

    /**
     * {@inheritDoc}
     */
    public function getLocalChanges(PackageInterface $package, $path)
    {
        GitUtil::cleanEnv();
        if (!$this->hasMetadataRepository($path)) {
            return;
        }

        $command = 'git status --porcelain --untracked-files=no';
        if (0 !== $this->process->execute($command, $output, $path)) {
            throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
        }

        return trim($output) ?: null;
    }

    public function getUnpushedChanges(PackageInterface $package, $path)
    {
        GitUtil::cleanEnv();
        $path = $this->normalizePath($path);
        if (!$this->hasMetadataRepository($path)) {
            return;
        }

        $command = 'git show-ref --head -d';
        if (0 !== $this->process->execute($command, $output, $path)) {
            throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
        }

        $refs = trim($output);
        if (!preg_match('{^([a-f0-9]+) HEAD$}mi', $refs, $match)) {
            // could not match the HEAD for some reason
            return;
        }

        $headRef = $match[1];
        if (!preg_match_all('{^'.$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;
        }

        // use the first match as branch name for now
        $branch = $matches[1][0];
        $unpushedChanges = null;

        // do two passes, as if we find anything we want to fetch and then re-try
        for ($i = 0; $i <= 1; $i++) {
            // try to find the a matching branch name in the composer remote
            foreach ($matches[1] as $candidate) {
                if (preg_match('{^[a-f0-9]+ refs/remotes/((?:composer|origin)/'.preg_quote($candidate).')$}mi', $refs, $match)) {
                    $branch = $candidate;
                    $remoteBranch = $match[1];
                    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 (!isset($remoteBranch)) {
                $unpushedChanges = 'Branch ' . $branch . ' could not be found on the origin remote and appears to be unpushed';
            } else {
                $command = sprintf('git diff --name-status %s...%s --', $remoteBranch, $branch);
                if (0 !== $this->process->execute($command, $output, $path)) {
                    throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
                }

                $unpushedChanges = trim($output) ?: null;
            }

            // first pass and we found unpushed changes, fetch from both 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 composer && git fetch origin', $output, $path);
            }

            // 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;
        }

        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>The package has modified files:</error>');
        $this->io->writeError(array_slice($changes, 0, 10));
        if (count($changes) > 10) {
            $this->io->writeError('    <info>' . (count($changes) - 10) . ' more files modified, choose "v" to view the full list</info>');
        }

        while (true) {
            switch ($this->io->ask('    <info>Discard changes [y,n,v,d,'.($update ? 's,' : '').'?]?</info> ', '?')) {
                case 'y':
                    $this->discardChanges($path);
                    break 2;

                case 's':
                    if (!$update) {
                        goto help;
                    }

                    $this->stashChanges($path);
                    break 2;

                case 'n':
                    throw new \RuntimeException('Update aborted');

                case 'v':
                    $this->io->writeError($changes);
                    break;

                case 'd':
                    $this->viewDiff($path);
                    break;

                case '?':
                default:
                    help:
                    $this->io->writeError(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;
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    protected function reapplyChanges($path)
    {
        $path = $this->normalizePath($path);
        if ($this->hasStashedChanges) {
            $this->hasStashedChanges = false;
            $this->io->writeError('    <info>Re-applying stashed changes</info>');
            if (0 !== $this->process->execute('git stash pop', $output, $path)) {
                throw new \RuntimeException("Failed to apply stashed changes:\n\n".$this->process->getErrorOutput());
            }
        }

        $this->hasDiscardedChanges = false;
    }

    /**
     * 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 = $this->hasDiscardedChanges || $this->hasStashedChanges ? '-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 (0 === $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_match('{^[a-f0-9]{40}$}', $reference)
            && $branches
            && preg_match('{^\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 (0 === $this->process->execute($command, $output, $path)) {
                return;
            }
        }

        // try to checkout branch by name and then reset it so it's on the proper branch name
        if (preg_match('{^[a-f0-9]{40}$}', $reference)) {
            // add 'v' in front of the branch if it was stripped when generating the pretty name
            if (!preg_match('{^\s+composer/'.preg_quote($branch).'$}m', $branches) && preg_match('{^\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));
            if (0 === $this->process->execute($command, $output, $path)
                || 0 === $this->process->execute($fallbackCommand, $output, $path)
            ) {
                $command = sprintf('git reset --hard %s --', ProcessExecutor::escape($reference));
                if (0 === $this->process->execute($command, $output, $path)) {
                    return;
                }
            }
        }

        $command = sprintf($template, ProcessExecutor::escape($gitRef));
        if (0 === $this->process->execute($command, $output, $path)) {
            return;
        }

        // 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(GitUtil::sanitizeUrl('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()));
    }

    protected function updateOriginUrl($path, $url)
    {
        $this->process->execute(sprintf('git remote set-url origin %s', ProcessExecutor::escape($url)), $output, $path);
        $this->setPushUrl($path, $url);
    }

    protected function setPushUrl($path, $url)
    {
        // set push url for github projects
        if (preg_match('{^(?:https?|git)://'.GitUtil::getGitHubDomainsRegex($this->config).'/([^/]+)/([^/]+?)(?:\.git)?$}', $url, $match)) {
            $protocols = $this->config->get('github-protocols');
            $pushUrl = 'git@'.$match[1].':'.$match[2].'/'.$match[3].'.git';
            if (!in_array('ssh', $protocols, true)) {
                $pushUrl = 'https://' . $match[1] . '/'.$match[2].'/'.$match[3].'.git';
            }
            $cmd = 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"', ProcessExecutor::escape($fromReference), ProcessExecutor::escape($toReference));

        if (0 !== $this->process->execute($command, $output, $path)) {
            throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
        }

        return $output;
    }

    /**
     * @param string $path
     * @throws \RuntimeException
     */
    protected function discardChanges($path)
    {
        $path = $this->normalizePath($path);
        if (0 !== $this->process->execute('git reset --hard', $output, $path)) {
            throw new \RuntimeException("Could not reset changes\n\n:".$this->process->getErrorOutput());
        }

        $this->hasDiscardedChanges = true;
    }

    /**
     * @param string $path
     * @throws \RuntimeException
     */
    protected function stashChanges($path)
    {
        $path = $this->normalizePath($path);
        if (0 !== $this->process->execute('git stash --include-untracked', $output, $path)) {
            throw new \RuntimeException("Could not stash changes\n\n:".$this->process->getErrorOutput());
        }

        $this->hasStashedChanges = true;
    }

    /**
     * @param string $path
     * @throws \RuntimeException
     */
    protected function viewDiff($path)
    {
        $path = $this->normalizePath($path);
        if (0 !== $this->process->execute('git diff HEAD', $output, $path)) {
            throw new \RuntimeException("Could not view diff\n\n:".$this->process->getErrorOutput());
        }

        $this->io->writeError($output);
    }

    protected function normalizePath($path)
    {
        if (Platform::isWindows() && strlen($path) > 0) {
            $basePath = $path;
            $removed = array();

            while (!is_dir($basePath) && $basePath !== '\\') {
                array_unshift($removed, basename($basePath));
                $basePath = dirname($basePath);
            }

            if ($basePath === '\\') {
                return $path;
            }

            $path = rtrim(realpath($basePath) . '/' . implode('/', $removed), '/');
        }

        return $path;
    }

    /**
     * {@inheritDoc}
     */
    protected function hasMetadataRepository($path)
    {
        $path = $this->normalizePath($path);

        return is_dir($path.'/.git');
    }

    protected function getShortHash($reference)
    {
        if (!$this->io->isVerbose() && preg_match('{^[0-9a-f]{40}$}', $reference)) {
            return substr($reference, 0, 10);
        }

        return $reference;
    }
}
<?php

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

namespace Composer\Downloader;

use Composer\Package\PackageInterface;
use Composer\Util\Svn as SvnUtil;
use Composer\Repository\VcsRepository;
use Composer\Util\ProcessExecutor;

/**
 * @author Ben Bieker <mail@ben-bieker.de>
 * @author Till Klampaeckel <till@php.net>
 */
class SvnDownloader extends VcsDownloader
{
    protected $cacheCredentials = true;

    /**
     * {@inheritDoc}
     */
    public function doDownload(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($url, "svn co", sprintf("%s/%s", $url, $ref), null, $path);
    }

    /**
     * {@inheritDoc}
     */
    public function doUpdate(PackageInterface $initial, PackageInterface $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);
        $flags = "";
        if (version_compare($util->binaryVersion(), '1.7.0', '>=')) {
            $flags .= ' --ignore-ancestry';
        }

        $this->io->writeError(" Checking out " . $ref);
        $this->execute($url, "svn switch" . $flags, sprintf("%s/%s", $url, $ref), $path);
    }

    /**
     * {@inheritDoc}
     */
    public function getLocalChanges(PackageInterface $package, $path)
    {
        if (!$this->hasMetadataRepository($path)) {
            return null;
        }

        $this->process->execute('svn status --ignore-externals', $output, $path);

        return preg_match('{^ *[^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($baseUrl, $command, $url, $cwd = null, $path = null)
    {
        $util = new SvnUtil($baseUrl, $this->io, $this->config);
        $util->setCacheCredentials($this->cacheCredentials);
        try {
            return $util->execute($command, $url, $cwd, $path, $this->io->isVerbose());
        } catch (\RuntimeException $e) {
            throw new \RuntimeException(
                'Package could not be downloaded, '.$e->getMessage()
            );
        }
    }

    /**
     * {@inheritDoc}
     */
    protected function cleanChanges(PackageInterface $package, $path, $update)
    {
        if (!$changes = $this->getLocalChanges($package, $path)) {
            return;
        }

        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>The package has modified file%s:</error>', $countChanges === 1 ? '' : 's'));
        $this->io->writeError(array_slice($changes, 0, 10));
        if ($countChanges > 10) {
            $remaingChanges = $countChanges - 10;
            $this->io->writeError(
                sprintf(
                    '    <info>'.$remaingChanges.' more file%s modified, choose "v" to view the full list</info>',
                    $remaingChanges === 1 ? '' : 's'
                )
            );
        }

        while (true) {
            switch ($this->io->ask('    <info>Discard changes [y,n,v,?]?</info> ', '?')) {
                case 'y':
                    $this->discardChanges($path);
                    break 2;

                case 'n':
                    throw new \RuntimeException('Update aborted');

                case 'v':
                    $this->io->writeError($changes);
                    break;

                case '?':
                default:
                    $this->io->writeError(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;
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    protected function getCommitLogs($fromReference, $toReference, $path)
    {
        if (preg_match('{.*@(\d+)$}', $fromReference) && preg_match('{.*@(\d+)$}', $toReference)) {
            // retrieve the svn base url from the checkout folder
            $command = sprintf('svn info --non-interactive --xml %s', ProcessExecutor::escape($path));
            if (0 !== $this->process->execute($command, $output, $path)) {
                throw new \RuntimeException(
                    'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput()
                );
            }

            $urlPattern = '#<url>(.*)</url>#';
            if (preg_match($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);
            $util->setCacheCredentials($this->cacheCredentials);
            try {
                return $util->executeLocal($command, $path, null, $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";
    }

    protected function discardChanges($path)
    {
        if (0 !== $this->process->execute('svn revert -R .', $output, $path)) {
            throw new \RuntimeException("Could not reset changes\n\n:".$this->process->getErrorOutput());
        }
    }

    /**
     * {@inheritDoc}
     */
    protected function hasMetadataRepository($path)
    {
        return is_dir($path.'/.svn');
    }
}
<?php

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

namespace Composer\Downloader;

use Composer\Config;
use Composer\Cache;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Package\PackageInterface;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Util\RemoteFilesystem;
use Composer\IO\IOInterface;

/**
 * GZip archive downloader.
 *
 * @author Pavel Puchkin <i@neoascetic.me>
 */
class GzipDownloader extends ArchiveDownloader
{
    protected $process;

    public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, RemoteFilesystem $rfs = null)
    {
        $this->process = $process ?: new ProcessExecutor($io);
        parent::__construct($io, $config, $eventDispatcher, $cache, $rfs);
    }

    protected function extract($file, $path)
    {
        $targetFilepath = $path . DIRECTORY_SEPARATOR . basename(substr($file, 0, -3));

        // Try to use gunzip on *nix
        if (!Platform::isWindows()) {
            $command = 'gzip -cd ' . ProcessExecutor::escape($file) . ' > ' . ProcessExecutor::escape($targetFilepath);

            if (0 === $this->process->execute($command, $ignoredOutput)) {
                return;
            }

            if (extension_loaded('zlib')) {
                // Fallback to using the PHP extension.
                $this->extractUsingExt($file, $targetFilepath);

                return;
            }

            $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);
    }

    /**
     * {@inheritdoc}
     */
    protected function getFileName(PackageInterface $package, $path)
    {
        return $path.'/'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME);
    }

    private function extractUsingExt($file, $targetFilepath)
    {
        $archiveFile = gzopen($file, 'rb');
        $targetFile = fopen($targetFilepath, 'wb');
        while ($string = gzread($archiveFile, 4096)) {
            fwrite($targetFile, $string, Platform::strlen($string));
        }
        gzclose($archiveFile);
        fclose($targetFile);
    }
}
<?php

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

namespace Composer\Downloader;

/**
 * Downloader for tar files: tar, tar.gz or tar.bz2
 *
 * @author Kirill chEbba Chebunin <iam@chebba.org>
 */
class TarDownloader extends ArchiveDownloader
{
    /**
     * {@inheritDoc}
     */
    protected function extract($file, $path)
    {
        // Can throw an UnexpectedValueException
        $archive = new \PharData($file);
        $archive->extractTo($path, null, true);
    }
}
<?php

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

namespace Composer\Downloader;

use Composer\Package\PackageInterface;

/**
 * DVCS Downloader interface.
 *
 * @author James Titcumb <james@asgrim.com>
 */
interface DvcsDownloaderInterface
{
    /**
     * Checks for unpushed changes to a current branch
     *
     * @param  PackageInterface $package package 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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use Composer\Package\PackageInterface;
use Symfony\Component\Finder\Finder;
use Composer\IO\IOInterface;

/**
 * Base downloader for archives
 *
 * @author Kirill chEbba Chebunin <iam@chebba.org>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 */
abstract class ArchiveDownloader extends FileDownloader
{
    /**
     * {@inheritDoc}
     * @throws \RuntimeException
     * @throws \UnexpectedValueException
     */
    public function download(PackageInterface $package, $path, $output = true)
    {
        $temporaryDir = $this->config->get('vendor-dir').'/composer/'.substr(md5(uniqid('', true)), 0, 8);
        $retries = 3;
        while ($retries--) {
            $fileName = parent::download($package, $path, $output);

            if ($output) {
                $this->io->writeError(' Extracting archive', false, IOInterface::VERBOSE);
            }

            try {
                $this->filesystem->ensureDirectoryExists($temporaryDir);
                try {
                    $this->extract($fileName, $temporaryDir);
                } catch (\Exception $e) {
                    // remove cache if the file was corrupted
                    parent::clearLastCacheWrite($package);
                    throw $e;
                }

                $this->filesystem->unlink($fileName);

                $contentDir = $this->getFolderContent($temporaryDir);

                // only one dir in the archive, extract its contents out of it
                if (1 === count($contentDir) && is_dir(reset($contentDir))) {
                    $contentDir = $this->getFolderContent((string) reset($contentDir));
                }

                // move files back out of the temp dir
                foreach ($contentDir as $file) {
                    $file = (string) $file;
                    $this->filesystem->rename($file, $path . '/' . basename($file));
                }

                $this->filesystem->removeDirectory($temporaryDir);
                if ($this->filesystem->isDirEmpty($this->config->get('vendor-dir').'/composer/')) {
                    $this->filesystem->removeDirectory($this->config->get('vendor-dir').'/composer/');
                }
                if ($this->filesystem->isDirEmpty($this->config->get('vendor-dir'))) {
                    $this->filesystem->removeDirectory($this->config->get('vendor-dir'));
                }
            } catch (\Exception $e) {
                // clean up
                $this->filesystem->removeDirectory($path);
                $this->filesystem->removeDirectory($temporaryDir);

                // retry downloading if we have an invalid zip file
                if ($retries && $e instanceof \UnexpectedValueException && class_exists('ZipArchive') && $e->getCode() === \ZipArchive::ER_NOZIP) {
                    $this->io->writeError('');
                    if ($this->io->isDebug()) {
                        $this->io->writeError('    Invalid zip file ('.$e->getMessage().'), retrying...');
                    } else {
                        $this->io->writeError('    Invalid zip file, retrying...');
                    }
                    usleep(500000);
                    continue;
                }

                throw $e;
            }

            break;
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function getFileName(PackageInterface $package, $path)
    {
        return rtrim($path.'/'.md5($path.spl_object_hash($package)).'.'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_EXTENSION), '.');
    }

    /**
     * Extract file to directory
     *
     * @param string $file Extracted file
     * @param string $path Directory
     *
     * @throws \UnexpectedValueException If can not extract downloaded file to path
     */
    abstract protected function extract($file, $path);

    /**
     * Returns the folder content, excluding dotfiles
     *
     * @param  string         $dir Directory
     * @return \SplFileInfo[]
     */
    private function getFolderContent($dir)
    {
        $finder = Finder::create()
            ->ignoreVCS(false)
            ->ignoreDotFiles(false)
            ->notName('.DS_Store')
            ->depth(0)
            ->in($dir);

        return iterator_to_array($finder);
    }
}
<?php

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

namespace Composer\Downloader;

use Composer\Package\PackageInterface;
use Composer\IO\IOInterface;
use Composer\Util\Filesystem;

/**
 * Downloaders manager.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class DownloadManager
{
    private $io;
    private $preferDist = false;
    private $preferSource = false;
    private $packagePreferences = array();
    private $filesystem;
    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 = false, Filesystem $filesystem = null)
    {
        $this->io = $io;
        $this->preferSource = $preferSource;
        $this->filesystem = $filesystem ?: new Filesystem();
    }

    /**
     * Makes downloader prefer source installation over the dist.
     *
     * @param  bool            $preferSource prefer downloading from source
     * @return DownloadManager
     */
    public function setPreferSource($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           $preferences array of preferences by package patterns
     * @return DownloadManager
     */
    public function setPreferences(array $preferences)
    {
        $this->packagePreferences = $preferences;

        return $this;
    }

    /**
     * Sets whether to output download progress information for all registered
     * downloaders
     *
     * @param  bool            $outputProgress
     * @return DownloadManager
     */
    public function setOutputProgress($outputProgress)
    {
        foreach ($this->downloaders as $downloader) {
            $downloader->setOutputProgress($outputProgress);
        }

        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($type, DownloaderInterface $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.', $type, implode(', ', array_keys($this->downloaders))));
        }

        return $this->downloaders[$type];
    }

    /**
     * Returns downloader for already installed package.
     *
     * @param  PackageInterface          $package package instance
     * @throws \InvalidArgumentException if package has no installation source specified
     * @throws \LogicException           if specific downloader used to load package with
     *                                           wrong type
     * @return DownloaderInterface|null
     */
    public function getDownloaderForInstalledPackage(PackageInterface $package)
    {
        $installationSource = $package->getInstallationSource();

        if ('metapackage' === $package->getType()) {
            return;
        }

        if ('dist' === $installationSource) {
            $downloader = $this->getDownloader($package->getDistType());
        } elseif ('source' === $installationSource) {
            $downloader = $this->getDownloader($package->getSourceType());
        } else {
            throw new \InvalidArgumentException(
                'Package '.$package.' seems not been installed properly'
            );
        }

        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;
    }

    /**
     * Downloads package into target dir.
     *
     * @param PackageInterface $package      package instance
     * @param string           $targetDir    target dir
     * @param bool             $preferSource prefer installation from source
     *
     * @throws \InvalidArgumentException if package have no urls to download from
     * @throws \RuntimeException
     */
    public function download(PackageInterface $package, $targetDir, $preferSource = null)
    {
        $preferSource = null !== $preferSource ? $preferSource : $this->preferSource;
        $sourceType = $package->getSourceType();
        $distType = $package->getDistType();

        $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 (!$preferSource && ($this->preferDist || 'dist' === $this->resolvePackageInstallPreference($package))) {
            $sources = array_reverse($sources);
        }

        $this->filesystem->ensureDirectoryExists($targetDir);

        foreach ($sources as $i => $source) {
            if (isset($e)) {
                $this->io->writeError('    <warning>Now trying to download from ' . $source . '</warning>');
            }
            $package->setInstallationSource($source);
            try {
                $downloader = $this->getDownloaderForInstalledPackage($package);
                if ($downloader) {
                    $downloader->download($package, $targetDir);
                }
                break;
            } catch (\RuntimeException $e) {
                if ($i === count($sources) - 1) {
                    throw $e;
                }

                $this->io->writeError(
                    '    <warning>Failed to download '.
                    $package->getPrettyName().
                    ' from ' . $source . ': '.
                    $e->getMessage().'</warning>'
                );
            }
        }
    }

    /**
     * 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
     */
    public function update(PackageInterface $initial, PackageInterface $target, $targetDir)
    {
        $downloader = $this->getDownloaderForInstalledPackage($initial);
        if (!$downloader) {
            return;
        }

        $installationSource = $initial->getInstallationSource();

        if ('dist' === $installationSource) {
            $initialType = $initial->getDistType();
            $targetType = $target->getDistType();
        } else {
            $initialType = $initial->getSourceType();
            $targetType = $target->getSourceType();
        }

        // upgrading from a dist stable package to a dev package, force source reinstall
        if ($target->isDev() && 'dist' === $installationSource) {
            $downloader->remove($initial, $targetDir);
            $this->download($target, $targetDir);

            return;
        }

        if ($initialType === $targetType) {
            $target->setInstallationSource($installationSource);
            try {
                $downloader->update($initial, $target, $targetDir);

                return;
            } 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>]? ', true)) {
                    throw $e;
                }
            }
        }

        $downloader->remove($initial, $targetDir);
        $this->download($target, $targetDir, 'source' === $installationSource);
    }

    /**
     * Removes package from target dir.
     *
     * @param PackageInterface $package   package instance
     * @param string           $targetDir target dir
     */
    public function remove(PackageInterface $package, $targetDir)
    {
        $downloader = $this->getDownloaderForInstalledPackage($package);
        if ($downloader) {
            $downloader->remove($package, $targetDir);
        }
    }

    /**
     * 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_match($pattern, $package->getName())) {
                if ('dist' === $preference || (!$package->isDev() && 'auto' === $preference)) {
                    return 'dist';
                }

                return 'source';
            }
        }

        return $package->isDev() ? 'source' : 'dist';
    }
}
<?php

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

namespace Composer\Downloader;

use Composer\Package\PackageInterface;

/**
 * Downloader interface.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface DownloaderInterface
{
    /**
     * Returns installation source (either source or dist).
     *
     * @return string "source" or "dist"
     */
    public function getInstallationSource();

    /**
     * Downloads specific package into specific folder.
     *
     * @param PackageInterface $package package instance
     * @param string           $path    download path
     */
    public function download(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
     */
    public function update(PackageInterface $initial, PackageInterface $target, $path);

    /**
     * Removes specific package from specific folder.
     *
     * @param PackageInterface $package package instance
     * @param string           $path    download path
     */
    public function remove(PackageInterface $package, $path);

    /**
     * Sets whether to output download progress information or not
     *
     * @param  bool                $outputProgress
     * @return DownloaderInterface
     */
    public function setOutputProgress($outputProgress);
}
<?php

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

namespace Composer\Downloader;

use Composer\Config;
use Composer\Cache;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Package\PackageInterface;
use Composer\Util\IniHelper;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Util\RemoteFilesystem;
use Composer\IO\IOInterface;
use Symfony\Component\Process\ExecutableFinder;
use ZipArchive;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ZipDownloader extends ArchiveDownloader
{
    protected static $hasSystemUnzip;
    private static $hasZipArchive;
    private static $isWindows;

    protected $process;
    private $zipArchiveObject;

    public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, RemoteFilesystem $rfs = null)
    {
        $this->process = $process ?: new ProcessExecutor($io);
        parent::__construct($io, $config, $eventDispatcher, $cache, $rfs);
    }

    /**
     * {@inheritDoc}
     */
    public function download(PackageInterface $package, $path, $output = true)
    {
        if (null === self::$hasSystemUnzip) {
            $finder = new ExecutableFinder;
            self::$hasSystemUnzip = (bool) $finder->find('unzip');
        }

        if (null === self::$hasZipArchive) {
            self::$hasZipArchive = class_exists('ZipArchive');
        }

        if (!self::$hasZipArchive && !self::$hasSystemUnzip) {
            // php.ini path is added to the error message to help users find the correct file
            $iniMessage = IniHelper::getMessage();
            $error = "The zip extension and unzip command are both missing, skipping.\n" . $iniMessage;

            throw new \RuntimeException($error);
        }

        if (null === self::$isWindows) {
            self::$isWindows = Platform::isWindows();

            if (!self::$isWindows && !self::$hasSystemUnzip) {
                $this->io->writeError("<warning>As there is no 'unzip' 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' may remediate them.</warning>");
            }
        }

        return parent::download($package, $path, $output);
    }

    /**
     * extract $file to $path with "unzip" command
     *
     * @param  string $file         File to extract
     * @param  string $path         Path where to extract file
     * @param  bool   $isLastChance If true it is called as a fallback and should throw an exception
     * @return bool   Success status
     */
    protected function extractWithSystemUnzip($file, $path, $isLastChance)
    {
        if (!self::$hasZipArchive) {
            // Force Exception throwing if the Other alternative is not available
            $isLastChance = true;
        }

        if (!self::$hasSystemUnzip && !$isLastChance) {
            // This was call as the favorite extract way, but is not available
            // We switch to the alternative
            return $this->extractWithZipArchive($file, $path, true);
        }

        $processError = null;
        // When called after a ZipArchive failed, perhaps there is some files to overwrite
        $overwrite = $isLastChance ? '-o' : '';

        $command = 'unzip -qq '.$overwrite.' '.ProcessExecutor::escape($file).' -d '.ProcessExecutor::escape($path);

        try {
            if (0 === $this->process->execute($command, $ignoredOutput)) {
                return true;
            }

            $processError = new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
        } catch (\Exception $e) {
            $processError = $e;
        }

        if ($isLastChance) {
            throw $processError;
        }

        $this->io->writeError('    '.$processError->getMessage());
        $this->io->writeError('    The archive may contain identical file names with different capitalization (which fails on case insensitive filesystems)');
        $this->io->writeError('    Unzip with unzip command failed, falling back to ZipArchive class');

        return $this->extractWithZipArchive($file, $path, true);
    }

    /**
     * extract $file to $path with ZipArchive
     *
     * @param  string $file         File to extract
     * @param  string $path         Path where to extract file
     * @param  bool   $isLastChance If true it is called as a fallback and should throw an exception
     * @return bool   Success status
     */
    protected function extractWithZipArchive($file, $path, $isLastChance)
    {
        if (!self::$hasSystemUnzip) {
            // Force Exception throwing if the Other alternative is not available
            $isLastChance = true;
        }

        if (!self::$hasZipArchive && !$isLastChance) {
            // This was call as the favorite extract way, but is not available
            // We switch to the alternative
            return $this->extractWithSystemUnzip($file, $path, true);
        }

        $processError = null;
        $zipArchive = $this->zipArchiveObject ?: new ZipArchive();

        try {
            if (true === ($retval = $zipArchive->open($file))) {
                $extractResult = $zipArchive->extractTo($path);

                if (true === $extractResult) {
                    $zipArchive->close();

                    return true;
                }

                $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;
        }

        if ($isLastChance) {
            throw $processError;
        }

        $this->io->writeError('    '.$processError->getMessage());
        $this->io->writeError('    Unzip with ZipArchive class failed, falling back to unzip command');

        return $this->extractWithSystemUnzip($file, $path, true);
    }

    /**
     * extract $file to $path
     *
     * @param string $file File to extract
     * @param string $path Path where to extract file
     */
    public function extract($file, $path)
    {
        // Each extract calls its alternative if not available or fails
        if (self::$isWindows) {
            $this->extractWithZipArchive($file, $path, false);
        } else {
            $this->extractWithSystemUnzip($file, $path, false);
        }
    }

    /**
     * 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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

use Composer\Util\Filesystem;

/**
 * Extractor for pear packages.
 *
 * Composer cannot rely on tar files structure when place it inside package target dir. Correct source files
 * disposition must be read from package.xml
 * This extract pear package source files to target dir.
 *
 * @author Alexey Prilipko <palex@farpost.com>
 */
class PearPackageExtractor
{
    private static $rolesWithoutPackageNamePrefix = array('php', 'script', 'www');
    /** @var Filesystem */
    private $filesystem;
    private $file;

    public function __construct($file)
    {
        if (!is_file($file)) {
            throw new \UnexpectedValueException('PEAR package file is not found at '.$file);
        }

        $this->filesystem = new Filesystem();
        $this->file = $file;
    }

    /**
     * Installs PEAR source files according to package.xml definitions and removes extracted files
     *
     * @param  string                    $target target install location. all source installation would be performed relative to target path.
     * @param  array                     $roles  types of files to install. default role for PEAR source files are 'php'.
     * @param  array                     $vars   used for replacement tasks
     * @throws \RuntimeException
     * @throws \UnexpectedValueException
     */
    public function extractTo($target, array $roles = array('php' => '/', 'script' => '/bin'), $vars = array())
    {
        $extractionPath = $target.'/tarball';

        try {
            $archive = new \PharData($this->file);
            $archive->extractTo($extractionPath, null, true);

            if (!is_file($this->combine($extractionPath, '/package.xml'))) {
                throw new \RuntimeException('Invalid PEAR package. It must contain package.xml file.');
            }

            $fileCopyActions = $this->buildCopyActions($extractionPath, $roles, $vars);
            $this->copyFiles($fileCopyActions, $extractionPath, $target, $roles, $vars);
            $this->filesystem->removeDirectory($extractionPath);
        } catch (\Exception $exception) {
            throw new \UnexpectedValueException(sprintf('Failed to extract PEAR package %s to %s. Reason: %s', $this->file, $target, $exception->getMessage()), 0, $exception);
        }
    }

    /**
     * Perform copy actions on files
     *
     * @param array $files array of copy actions ('from', 'to') with relative paths
     * @param string $source path to source dir.
     * @param string $target path to destination dir
     * @param array $roles array [role => roleRoot] relative root for files having that role
     * @param array $vars  list of values can be used for replacement tasks
     */
    private function copyFiles($files, $source, $target, $roles, $vars)
    {
        foreach ($files as $file) {
            $from = $this->combine($source, $file['from']);
            $to = $this->combine($target, $roles[$file['role']]);
            $to = $this->combine($to, $file['to']);
            $tasks = $file['tasks'];
            $this->copyFile($from, $to, $tasks, $vars);
        }
    }

    private function copyFile($from, $to, $tasks, $vars)
    {
        if (!is_file($from)) {
            throw new \RuntimeException('Invalid PEAR package. package.xml defines file that is not located inside tarball.');
        }

        $this->filesystem->ensureDirectoryExists(dirname($to));

        if (0 == count($tasks)) {
            $copied = copy($from, $to);
        } else {
            $content = file_get_contents($from);
            $replacements = array();
            foreach ($tasks as $task) {
                $pattern = $task['from'];
                $varName = $task['to'];
                if (isset($vars[$varName])) {
                    if ($varName === 'php_bin' && false === strpos($to, '.bat')) {
                        $replacements[$pattern] = preg_replace('{\.bat$}', '', $vars[$varName]);
                    } else {
                        $replacements[$pattern] = $vars[$varName];
                    }
                }
            }
            $content = strtr($content, $replacements);

            $copied = file_put_contents($to, $content);
        }

        if (false === $copied) {
            throw new \RuntimeException(sprintf('Failed to copy %s to %s', $from, $to));
        }
    }

    /**
     * Builds list of copy and list of remove actions that would transform extracted PEAR tarball into installed package.
     *
     * @param  string            $source string path to extracted files
     * @param  array             $roles  array [role => roleRoot] relative root for files having that role
     * @param  array             $vars   list of values can be used for replacement tasks
     * @throws \RuntimeException
     * @return array             array of 'source' => 'target', where source is location of file in the tarball (relative to source
     *                                  path, and target is destination of file (also relative to $source path)
     */
    private function buildCopyActions($source, array $roles, $vars)
    {
        /** @var \SimpleXmlElement $package */
        $package = simplexml_load_string(file_get_contents($this->combine($source, 'package.xml')));
        if (false === $package) {
            throw new \RuntimeException('Package definition file is not valid.');
        }

        $packageSchemaVersion = $package['version'];
        if ('1.0' == $packageSchemaVersion) {
            $children = $package->release->filelist->children();
            $packageName = (string) $package->name;
            $packageVersion = (string) $package->release->version;
            $sourceDir = $packageName . '-' . $packageVersion;
            $result = $this->buildSourceList10($children, $roles, $sourceDir, '', null, $packageName);
        } elseif ('2.0' == $packageSchemaVersion || '2.1' == $packageSchemaVersion) {
            $children = $package->contents->children();
            $packageName = (string) $package->name;
            $packageVersion = (string) $package->version->release;
            $sourceDir = $packageName . '-' . $packageVersion;
            $result = $this->buildSourceList20($children, $roles, $sourceDir, '', null, $packageName);

            $namespaces = $package->getNamespaces();
            $package->registerXPathNamespace('ns', $namespaces['']);
            $releaseNodes = $package->xpath('ns:phprelease');
            $this->applyRelease($result, $releaseNodes, $vars);
        } else {
            throw new \RuntimeException('Unsupported schema version of package definition file.');
        }

        return $result;
    }

    private function applyRelease(&$actions, $releaseNodes, $vars)
    {
        foreach ($releaseNodes as $releaseNode) {
            $requiredOs = $releaseNode->installconditions && $releaseNode->installconditions->os && $releaseNode->installconditions->os->name ? (string) $releaseNode->installconditions->os->name : '';
            if ($requiredOs && $vars['os'] != $requiredOs) {
                continue;
            }

            if ($releaseNode->filelist) {
                foreach ($releaseNode->filelist->children() as $action) {
                    if ('install' == $action->getName()) {
                        $name = (string) $action['name'];
                        $as = (string) $action['as'];
                        if (isset($actions[$name])) {
                            $actions[$name]['to'] = $as;
                        }
                    } elseif ('ignore' == $action->getName()) {
                        $name = (string) $action['name'];
                        unset($actions[$name]);
                    } else {
                        // unknown action
                    }
                }
            }
            break;
        }
    }

    private function buildSourceList10($children, $targetRoles, $source, $target, $role, $packageName)
    {
        $result = array();

        // enumerating files
        foreach ($children as $child) {
            /** @var $child \SimpleXMLElement */
            if ($child->getName() == 'dir') {
                $dirSource = $this->combine($source, (string) $child['name']);
                $dirTarget = $child['baseinstalldir'] ?: $target;
                $dirRole = $child['role'] ?: $role;
                $dirFiles = $this->buildSourceList10($child->children(), $targetRoles, $dirSource, $dirTarget, $dirRole, $packageName);
                $result = array_merge($result, $dirFiles);
            } elseif ($child->getName() == 'file') {
                $fileRole = (string) $child['role'] ?: $role;
                if (isset($targetRoles[$fileRole])) {
                    $fileName = (string) ($child['name'] ?: $child[0]); // $child[0] means text content
                    $fileSource = $this->combine($source, $fileName);
                    $fileTarget = $this->combine((string) $child['baseinstalldir'] ?: $target, $fileName);
                    if (!in_array($fileRole, self::$rolesWithoutPackageNamePrefix)) {
                        $fileTarget = $packageName . '/' . $fileTarget;
                    }
                    $result[(string) $child['name']] = array('from' => $fileSource, 'to' => $fileTarget, 'role' => $fileRole, 'tasks' => array());
                }
            }
        }

        return $result;
    }

    private function buildSourceList20($children, $targetRoles, $source, $target, $role, $packageName)
    {
        $result = array();

        // enumerating files
        foreach ($children as $child) {
            /** @var $child \SimpleXMLElement */
            if ('dir' == $child->getName()) {
                $dirSource = $this->combine($source, $child['name']);
                $dirTarget = $child['baseinstalldir'] ?: $target;
                $dirRole = $child['role'] ?: $role;
                $dirFiles = $this->buildSourceList20($child->children(), $targetRoles, $dirSource, $dirTarget, $dirRole, $packageName);
                $result = array_merge($result, $dirFiles);
            } elseif ('file' == $child->getName()) {
                $fileRole = (string) $child['role'] ?: $role;
                if (isset($targetRoles[$fileRole])) {
                    $fileSource = $this->combine($source, (string) $child['name']);
                    $fileTarget = $this->combine((string) ($child['baseinstalldir'] ?: $target), (string) $child['name']);
                    $fileTasks = array();
                    foreach ($child->children('http://pear.php.net/dtd/tasks-1.0') as $taskNode) {
                        if ('replace' == $taskNode->getName()) {
                            $fileTasks[] = array('from' => (string) $taskNode->attributes()->from, 'to' => (string) $taskNode->attributes()->to);
                        }
                    }
                    if (!in_array($fileRole, self::$rolesWithoutPackageNamePrefix)) {
                        $fileTarget = $packageName . '/' . $fileTarget;
                    }
                    $result[(string) $child['name']] = array('from' => $fileSource, 'to' => $fileTarget, 'role' => $fileRole, 'tasks' => $fileTasks);
                }
            }
        }

        return $result;
    }

    private function combine($left, $right)
    {
        return rtrim($left, '/') . '/' . ltrim($right, '/');
    }
}
<?php

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

namespace Composer\Downloader;

/**
 * Downloader for phar files
 *
 * @author Kirill chEbba Chebunin <iam@chebba.org>
 */
class PharDownloader extends ArchiveDownloader
{
    /**
     * {@inheritDoc}
     */
    protected function extract($file, $path)
    {
        // Can throw an UnexpectedValueException
        $archive = new \Phar($file);
        $archive->extractTo($path, null, true);
        /* TODO: handle openssl signed phars
         * https://github.com/composer/composer/pull/33#issuecomment-2250768
         * https://github.com/koto/phar-util
         * http://blog.kotowicz.net/2010/08/hardening-php-how-to-securely-include.html
         */
    }
}
<?php

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

namespace Composer\Downloader;

use Composer\Config;
use Composer\Cache;
use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\IO\NullIO;
use Composer\Package\Comparer\Comparer;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\Plugin\PluginEvents;
use Composer\Plugin\PreFileDownloadEvent;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Util\Filesystem;
use Composer\Util\RemoteFilesystem;
use Composer\Util\Url as UrlUtil;

/**
 * Base downloader for files
 *
 * @author Kirill chEbba Chebunin <iam@chebba.org>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 * @author Nils Adermann <naderman@naderman.de>
 */
class FileDownloader implements DownloaderInterface, ChangeReportInterface
{
    protected $io;
    protected $config;
    protected $rfs;
    protected $filesystem;
    protected $cache;
    protected $outputProgress = true;
    private $lastCacheWrites = array();
    private $eventDispatcher;

    /**
     * Constructor.
     *
     * @param IOInterface      $io              The IO instance
     * @param Config           $config          The config
     * @param EventDispatcher  $eventDispatcher The event dispatcher
     * @param Cache            $cache           Optional cache instance
     * @param RemoteFilesystem $rfs             The remote filesystem
     * @param Filesystem       $filesystem      The filesystem
     */
    public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, RemoteFilesystem $rfs = null, Filesystem $filesystem = null)
    {
        $this->io = $io;
        $this->config = $config;
        $this->eventDispatcher = $eventDispatcher;
        $this->rfs = $rfs ?: Factory::createRemoteFilesystem($this->io, $config);
        $this->filesystem = $filesystem ?: new Filesystem();
        $this->cache = $cache;

        if ($this->cache && $this->cache->gcIsNecessary()) {
            $this->cache->gc($config->get('cache-files-ttl'), $config->get('cache-files-maxsize'));
        }
    }

    /**
     * {@inheritDoc}
     */
    public function getInstallationSource()
    {
        return 'dist';
    }

    /**
     * {@inheritDoc}
     */
    public function download(PackageInterface $package, $path, $output = true)
    {
        if (!$package->getDistUrl()) {
            throw new \InvalidArgumentException('The given package is missing url information');
        }

        if ($output) {
            $this->io->writeError("  - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>): ", false);
        }

        $urls = $package->getDistUrls();
        while ($url = array_shift($urls)) {
            try {
                $fileName = $this->doDownload($package, $path, $url);
                break;
            } catch (\Exception $e) {
                if ($this->io->isDebug()) {
                    $this->io->writeError('');
                    $this->io->writeError('Failed: ['.get_class($e).'] '.$e->getCode().': '.$e->getMessage());
                } elseif (count($urls)) {
                    $this->io->writeError('');
                    $this->io->writeError(' Failed, trying the next URL ('.$e->getCode().': '.$e->getMessage().')', false);
                }

                if (!count($urls)) {
                    throw $e;
                }
            }
        }

        if ($output) {
            $this->io->writeError('');
        }

        return $fileName;
    }

    protected function doDownload(PackageInterface $package, $path, $url)
    {
        $this->filesystem->emptyDirectory($path);

        $fileName = $this->getFileName($package, $path);

        $processedUrl = $this->processUrl($package, $url);
        $origin = RemoteFilesystem::getOrigin($processedUrl);

        $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->rfs, $processedUrl);
        if ($this->eventDispatcher) {
            $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent);
        }
        $rfs = $preFileDownloadEvent->getRemoteFilesystem();

        try {
            $checksum = $package->getDistSha1Checksum();
            $cacheKey = $this->getCacheKey($package, $processedUrl);

            // use from cache if it is present and has a valid checksum or we have no checksum to check against
            if ($this->cache && (!$checksum || $checksum === $this->cache->sha1($cacheKey)) && $this->cache->copyTo($cacheKey, $fileName)) {
                $this->io->writeError('Loading from cache', false);
            } else {
                // download if cache restore failed
                if (!$this->outputProgress) {
                    $this->io->writeError('Downloading', false);
                }

                // try to download 3 times then fail hard
                $retries = 3;
                while ($retries--) {
                    try {
                        $rfs->copy($origin, $processedUrl, $fileName, $this->outputProgress, $package->getTransportOptions());
                        break;
                    } catch (TransportException $e) {
                        // if we got an http response with a proper code, then requesting again will probably not help, abort
                        if ((0 !== $e->getCode() && !in_array($e->getCode(), array(500, 502, 503, 504))) || !$retries) {
                            throw $e;
                        }
                        $this->io->writeError('');
                        $this->io->writeError('    Download failed, retrying...', true, IOInterface::VERBOSE);
                        usleep(500000);
                    }
                }

                if (!$this->outputProgress) {
                    $this->io->writeError(' (<comment>100%</comment>)', false);
                }

                if ($this->cache) {
                    $this->lastCacheWrites[$package->getName()] = $cacheKey;
                    $this->cache->copyFrom($cacheKey, $fileName);
                }
            }

            if (!file_exists($fileName)) {
                throw new \UnexpectedValueException($url.' 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.')');
            }
        } catch (\Exception $e) {
            // clean up
            $this->filesystem->removeDirectory($path);
            $this->clearLastCacheWrite($package);
            throw $e;
        }

        return $fileName;
    }

    /**
     * {@inheritDoc}
     */
    public function setOutputProgress($outputProgress)
    {
        $this->outputProgress = $outputProgress;

        return $this;
    }

    protected function clearLastCacheWrite(PackageInterface $package)
    {
        if ($this->cache && isset($this->lastCacheWrites[$package->getName()])) {
            $this->cache->remove($this->lastCacheWrites[$package->getName()]);
            unset($this->lastCacheWrites[$package->getName()]);
        }
    }

    /**
     * {@inheritDoc}
     */
    public function update(PackageInterface $initial, PackageInterface $target, $path)
    {
        $name = $target->getName();
        $from = $initial->getFullPrettyVersion();
        $to = $target->getFullPrettyVersion();

        $actionName = VersionParser::isUpgrade($initial->getVersion(), $target->getVersion()) ? 'Updating' : 'Downgrading';
        $this->io->writeError("  - " . $actionName . " <info>" . $name . "</info> (<comment>" . $from . "</comment> => <comment>" . $to . "</comment>): ", false);

        $this->remove($initial, $path, false);
        $this->download($target, $path, false);

        $this->io->writeError('');
    }

    /**
     * {@inheritDoc}
     */
    public function remove(PackageInterface $package, $path, $output = true)
    {
        if ($output) {
            $this->io->writeError("  - Removing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");
        }
        if (!$this->filesystem->removeDirectory($path)) {
            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 $path.'/'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME);
    }

    /**
     * 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') && 0 === 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;
    }

    private function getCacheKey(PackageInterface $package, $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 = sha1($processedUrl);

        return $package->getName().'/'.$cacheKey.'.'.$package->getDistType();
    }

    /**
     * {@inheritDoc}
     * @throws \RuntimeException
     */
    public function getLocalChanges(PackageInterface $package, $targetDir)
    {
        $prevIO = $this->io;
        $prevProgress = $this->outputProgress;

        $this->io = new NullIO;
        $this->io->loadConfiguration($this->config);
        $this->outputProgress = false;
        $e = null;

        try {
            $this->download($package, $targetDir.'_compare', false);

            $comparer = new Comparer();
            $comparer->setSource($targetDir.'_compare');
            $comparer->setUpdate($targetDir);
            $comparer->doCompare();
            $output = $comparer->getChanged(true, true);
            $this->filesystem->removeDirectory($targetDir.'_compare');
        } catch (\Exception $e) {
        }

        $this->io = $prevIO;
        $this->outputProgress = $prevProgress;

        if ($e) {
            throw $e;
        }

        return trim($output);
    }
}
<?php

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

namespace Composer\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;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
abstract class VcsDownloader implements DownloaderInterface, ChangeReportInterface, VcsCapableDownloaderInterface
{
    /** @var IOInterface */
    protected $io;
    /** @var Config */
    protected $config;
    /** @var ProcessExecutor */
    protected $process;
    /** @var Filesystem */
    protected $filesystem;

    public function __construct(IOInterface $io, Config $config, ProcessExecutor $process = null, Filesystem $fs = null)
    {
        $this->io = $io;
        $this->config = $config;
        $this->process = $process ?: new ProcessExecutor($io);
        $this->filesystem = $fs ?: new Filesystem($this->process);
    }

    /**
     * {@inheritDoc}
     */
    public function getInstallationSource()
    {
        return 'source';
    }

    /**
     * {@inheritDoc}
     */
    public function download(PackageInterface $package, $path)
    {
        if (!$package->getSourceReference()) {
            throw new \InvalidArgumentException('Package '.$package->getPrettyName().' is missing reference information');
        }

        $this->io->writeError("  - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>): ", false);
        $this->filesystem->emptyDirectory($path);

        $urls = $package->getSourceUrls();
        while ($url = array_shift($urls)) {
            try {
                if (Filesystem::isLocalPath($url)) {
                    // realpath() below will not understand
                    // url that starts with "file://"
                    $needle = 'file://';
                    $isFileProtocol = false;
                    if (0 === strpos($url, $needle)) {
                        $url = substr($url, strlen($needle));
                        $isFileProtocol = true;
                    }

                    // realpath() below will not understand %20 spaces etc.
                    if (false !== strpos($url, '%')) {
                        $url = rawurldecode($url);
                    }

                    $url = realpath($url);

                    if ($isFileProtocol) {
                        $url = $needle . $url;
                    }
                }
                $this->doDownload($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;
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public function update(PackageInterface $initial, PackageInterface $target, $path)
    {
        if (!$target->getSourceReference()) {
            throw new \InvalidArgumentException('Package '.$target->getPrettyName().' is missing reference information');
        }

        $name = $target->getName();
        if ($initial->getPrettyVersion() == $target->getPrettyVersion()) {
            if ($target->getSourceType() === 'svn') {
                $from = $initial->getSourceReference();
                $to = $target->getSourceReference();
            } else {
                $from = substr($initial->getSourceReference(), 0, 7);
                $to = substr($target->getSourceReference(), 0, 7);
            }
            $name .= ' '.$initial->getPrettyVersion();
        } else {
            $from = $initial->getFullPrettyVersion();
            $to = $target->getFullPrettyVersion();
        }

        $actionName = VersionParser::isUpgrade($initial->getVersion(), $target->getVersion()) ? 'Updating' : 'Downgrading';
        $this->io->writeError("  - " . $actionName . " <info>" . $name . "</info> (<comment>" . $from . "</comment> => <comment>" . $to . "</comment>): ", false);

        $this->cleanChanges($initial, $path, true);
        $urls = $target->getSourceUrls();

        $exception = null;
        while ($url = array_shift($urls)) {
            try {
                if (Filesystem::isLocalPath($url)) {
                    $url = realpath($url);
                }
                $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');
                }
            }
        }

        $this->reapplyChanges($path);

        // 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;
        }
    }

    /**
     * {@inheritDoc}
     */
    public function remove(PackageInterface $package, $path)
    {
        $this->io->writeError("  - Removing <info>" . $package->getName() . "</info> (<comment>" . $package->getPrettyVersion() . "</comment>)");
        $this->cleanChanges($package, $path, false);
        if (!$this->filesystem->removeDirectory($path)) {
            throw new \RuntimeException('Could not completely delete '.$path.', aborting.');
        }
    }

    /**
     * Download progress information is not available for all VCS downloaders.
     * {@inheritDoc}
     */
    public function setOutputProgress($outputProgress)
    {
        return $this;
    }

    /**
     * {@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'];
        }
    }

    /**
     * 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
     * @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.');
        }
    }

    /**
     * Guarantee that no changes have been made to the local copy
     *
     * @param  string            $path
     * @throws \RuntimeException in case the operation must be aborted or the patch does not apply cleanly
     */
    protected function reapplyChanges($path)
    {
    }

    /**
     * Downloads specific package into specific folder.
     *
     * @param PackageInterface $package package instance
     * @param string           $path    download path
     * @param string           $url     package url
     */
    abstract protected function doDownload(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
     */
    abstract protected function doUpdate(PackageInterface $initial, PackageInterface $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);
}
<?php

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

namespace Composer\Downloader;

use Composer\Package\PackageInterface;
use Composer\Util\ProcessExecutor;
use Composer\Util\Hg as HgUtils;

/**
 * @author Per Bernhardt <plb@webfactory.de>
 */
class HgDownloader extends VcsDownloader
{
    /**
     * {@inheritDoc}
     */
    public function doDownload(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 (0 !== $this->process->execute($command, $ignoredOutput, realpath($path))) {
            throw new \RuntimeException('Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput());
        }
    }

    /**
     * {@inheritDoc}
     */
    public function doUpdate(PackageInterface $initial, PackageInterface $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);
    }

    /**
     * {@inheritDoc}
     */
    public function getLocalChanges(PackageInterface $package, $path)
    {
        if (!is_dir($path.'/.hg')) {
            return null;
        }

        $this->process->execute('hg st', $output, realpath($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 (0 !== $this->process->execute($command, $output, realpath($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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Downloader;

/**
 * Exception thrown when issues exist on local filesystem
 *
 * @author Javier Spagnoletti <jspagnoletti@javierspagnoletti.com.ar>
 */
class FilesystemException extends \Exception
{
    public function __construct($message = '', $code = 0, \Exception $previous = null)
    {
        parent::__construct("Filesystem exception: \n".$message, $code, $previous);
    }
}
<?php

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

namespace Composer\Downloader;

use Composer\Package\PackageInterface;
use Composer\Repository\VcsRepository;
use Composer\Util\Perforce;

/**
 * @author Matt Whittom <Matt.Whittom@veteransunited.com>
 */
class PerforceDownloader extends VcsDownloader
{
    /** @var Perforce */
    protected $perforce;

    /**
     * {@inheritDoc}
     */
    public function doDownload(PackageInterface $package, $path, $url)
    {
        $ref = $package->getSourceReference();
        $label = $this->getLabelFromSourceReference($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();
    }

    private function getLabelFromSourceReference($ref)
    {
        $pos = strpos($ref, '@');
        if (false !== $pos) {
            return substr($ref, $pos + 1);
        }

        return null;
    }

    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);
    }

    private function getRepoConfig(VcsRepository $repository)
    {
        return $repository->getRepoConfig();
    }

    /**
     * {@inheritDoc}
     */
    public function doUpdate(PackageInterface $initial, PackageInterface $target, $path, $url)
    {
        $this->doDownload($target, $path, $url);
    }

    /**
     * {@inheritDoc}
     */
    public function getLocalChanges(PackageInterface $package, $path)
    {
        $this->io->writeError('Perforce driver does not check for local changes before overriding', true);
    }

    /**
     * {@inheritDoc}
     */
    protected function getCommitLogs($fromReference, $toReference, $path)
    {
        return $this->perforce->getCommitLogs($fromReference, $toReference);
    }

    public function setPerforce($perforce)
    {
        $this->perforce = $perforce;
    }

    /**
     * {@inheritDoc}
     */
    protected function hasMetadataRepository($path)
    {
        return true;
    }
}
<?php

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

namespace Composer\Downloader;

use Composer\Config;
use Composer\Cache;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Package\PackageInterface;
use Composer\Util\ProcessExecutor;
use Composer\Util\RemoteFilesystem;
use Composer\IO\IOInterface;

/**
 * Xz archive downloader.
 *
 * @author Pavel Puchkin <i@neoascetic.me>
 * @author Pierre Rudloff <contact@rudloff.pro>
 */
class XzDownloader extends ArchiveDownloader
{
    protected $process;

    public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, Cache $cache = null, ProcessExecutor $process = null, RemoteFilesystem $rfs = null)
    {
        $this->process = $process ?: new ProcessExecutor($io);

        parent::__construct($io, $config, $eventDispatcher, $cache, $rfs);
    }

    protected function extract($file, $path)
    {
        $command = 'tar -xJf ' . ProcessExecutor::escape($file) . ' -C ' . ProcessExecutor::escape($path);

        if (0 === $this->process->execute($command, $ignoredOutput)) {
            return;
        }

        $processError = 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput();

        throw new \RuntimeException($processError);
    }

    /**
     * {@inheritdoc}
     */
    protected function getFileName(PackageInterface $package, $path)
    {
        return $path.'/'.pathinfo(parse_url($package->getDistUrl(), PHP_URL_PATH), PATHINFO_BASENAME);
    }
}
<?php

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

namespace Composer\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\ProcessExecutor;
use Composer\Util\Filesystem as ComposerFilesystem;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Filesystem\Filesystem;

/**
 * Download a package from a local path.
 *
 * @author Samuel Roze <samuel.roze@gmail.com>
 * @author Johann Reinke <johann.reinke@gmail.com>
 */
class PathDownloader extends FileDownloader implements VcsCapableDownloaderInterface
{
    const STRATEGY_SYMLINK = 10;
    const STRATEGY_MIRROR = 20;

    /**
     * {@inheritdoc}
     */
    public function download(PackageInterface $package, $path, $output = true)
    {
        $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) {
            if ($output) {
                $this->io->writeError(sprintf(
                    '  - Installing <info>%s</info> (<comment>%s</comment>): Source already present',
                    $package->getName(),
                    $package->getFullPrettyVersion()
                ));
            }

            return;
        }

        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
            ));
        }

        // Get the transport options with default values
        $transportOptions = $package->getTransportOptions() + array('symlink' => null);

        // When symlink transport option is null, both symlink and mirror are allowed
        $currentStrategy = self::STRATEGY_SYMLINK;
        $allowedStrategies = array(self::STRATEGY_SYMLINK, self::STRATEGY_MIRROR);

        $mirrorPathRepos = getenv('COMPOSER_MIRROR_PATH_REPOS');
        if ($mirrorPathRepos) {
            $currentStrategy = self::STRATEGY_MIRROR;
        }

        if (true === $transportOptions['symlink']) {
            $currentStrategy = self::STRATEGY_SYMLINK;
            $allowedStrategies = array(self::STRATEGY_SYMLINK);
        } elseif (false === $transportOptions['symlink']) {
            $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);
        }

        $fileSystem = new Filesystem();
        $this->filesystem->removeDirectory($path);

        if ($output) {
            $this->io->writeError(sprintf(
                '  - Installing <info>%s</info> (<comment>%s</comment>): ',
                $package->getName(),
                $package->getFullPrettyVersion()
            ), false);
        }

        $isFallback = false;
        if (self::STRATEGY_SYMLINK == $currentStrategy) {
            try {
                if (Platform::isWindows()) {
                    // Implement symlinks as NTFS junctions on Windows
                    $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, "/");
                    $this->io->writeError(sprintf('Symlinking from %s', $url), false);
                    $fileSystem->symlink($shortestPath, $path);
                }
            } catch (IOException $e) {
                if (in_array(self::STRATEGY_MIRROR, $allowedStrategies)) {
                    $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) {
            $fs = new ComposerFilesystem();
            $realUrl = $fs->normalizePath($realUrl);

            $this->io->writeError(sprintf('%sMirroring from %s', $isFallback ? '    ' : '', $url), false);
            $iterator = new ArchivableFilesFinder($realUrl, array());
            $fileSystem->mirror($realUrl, $path, $iterator);
        }

        $this->io->writeError('');
    }

    /**
     * {@inheritDoc}
     */
    public function remove(PackageInterface $package, $path, $output = true)
    {
        $realUrl = realpath($package->getDistUrl());

        if (realpath($path) === $realUrl) {
            if ($output) {
                $this->io->writeError("  - Removing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>), source is still present in $path");
            }

            return;
        }

        /**
         * 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("  - Removing junction for <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");
            }
            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());
            }
        } else {
            parent::remove($package, $path, $output);
        }
    }

    /**
     * {@inheritDoc}
     */
    public function getVcsReference(PackageInterface $package, $path)
    {
        $parser = new VersionParser;
        $guesser = new VersionGuesser($this->config, new ProcessExecutor($this->io), $parser);
        $dumper = new ArrayDumper;

        $packageConfig = $dumper->dump($package);
        if ($packageVersion = $guesser->guessVersion($packageConfig, $path)) {
            return $packageVersion['commit'];
        }
    }

    /**
     * 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 > 6 ||
            (PHP_WINDOWS_VERSION_MAJOR === 6 && PHP_WINDOWS_VERSION_MINOR >= 1));
    }
}
<?php

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

namespace Composer;

use Composer\Config\ConfigSourceInterface;
use Composer\Downloader\TransportException;
use Composer\IO\IOInterface;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class Config
{
    const RELATIVE_PATHS = 1;

    public static $defaultConfig = array(
        'process-timeout' => 300,
        'use-include-path' => false,
        'preferred-install' => 'auto',
        'notify-on-install' => true,
        'github-protocols' => array('https', 'ssh', 'git'),
        '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',
        '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,
        '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,
        // valid keys without defaults (auth config stuff):
        // bitbucket-oauth
        // github-oauth
        // gitlab-oauth
        // gitlab-token
        // http-basic
    );

    public static $defaultRepositories = array(
        'packagist.org' => array(
            'type' => 'composer',
            'url' => 'https?://repo.packagist.org',
            'allow_ssl_downgrade' => true,
        ),
    );

    private $config;
    private $baseDir;
    private $repositories;
    /** @var ConfigSourceInterface */
    private $configSource;
    /** @var ConfigSourceInterface */
    private $authConfigSource;
    private $useEnvironment;
    private $warnedHosts = 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;
        $this->repositories = static::$defaultRepositories;
        $this->useEnvironment = (bool) $useEnvironment;
        $this->baseDir = $baseDir;
    }

    public function setConfigSource(ConfigSourceInterface $source)
    {
        $this->configSource = $source;
    }

    public function getConfigSource()
    {
        return $this->configSource;
    }

    public function setAuthConfigSource(ConfigSourceInterface $source)
    {
        $this->authConfigSource = $source;
    }

    public function getAuthConfigSource()
    {
        return $this->authConfigSource;
    }

    /**
     * Merges new config values with the existing ones (overriding)
     *
     * @param array $config
     */
    public function merge($config)
    {
        // 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')) && isset($this->config[$key])) {
                    $this->config[$key] = array_merge($this->config[$key], $val);
                } 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->config[$key] = array_merge($this->config[$key], $val);
                        // 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;
                    }
                } else {
                    $this->config[$key] = $val;
                }
            }
        }

        if (!empty($config['repositories']) && is_array($config['repositories'])) {
            $this->repositories = array_reverse($this->repositories, true);
            $newRepos = array_reverse($config['repositories'], true);
            foreach ($newRepos as $name => $repository) {
                // disable a repository by name
                if (false === $repository) {
                    $this->disableRepoByName($name);
                    continue;
                }

                // disable a repository with an anonymous {"name": false} repo
                if (is_array($repository) && 1 === count($repository) && false === current($repository)) {
                    $this->disableRepoByName(key($repository));
                    continue;
                }

                // store repo
                if (is_int($name)) {
                    $this->repositories[] = $repository;
                } else {
                    if ($name === 'packagist') { // BC support for default "packagist" named repo
                        $this->repositories[$name . '.org'] = $repository;
                    } else {
                        $this->repositories[$name] = $repository;
                    }
                }
            }
            $this->repositories = array_reverse($this->repositories, true);
        }
    }

    /**
     * @return array
     */
    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) {
            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);
                $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);

            case 'htaccess-protect':
                $value = $this->getComposerEnv('COMPOSER_HTACCESS_PROTECT');
                if (false === $value) {
                    $value = $this->config[$key];
                }
                return $value !== 'false' && (bool) $value;

            case 'cache-ttl':
                return (int) $this->config[$key];

            case 'cache-files-maxsize':
                if (!preg_match('/^\s*([0-9.]+)\s*(?:([kmg])(?:i?b)?)?\s*$/i', $this->config[$key], $matches)) {
                    throw new \RuntimeException(
                        "Could not parse the value of 'cache-files-maxsize': {$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;

            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(getenv('HOME') ?: 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'))) {
                    throw new \RuntimeException(
                        "Invalid value for 'bin-compat': {$value}. Expected auto, full"
                    );
                }

                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(true, false, 'stash'), true)) {
                    throw new \RuntimeException(
                        "Invalid value for 'discard-changes': {$this->config[$key]}. Expected true, false or stash"
                    );
                }

                return $this->config[$key];

            case 'github-protocols':
                $protos = $this->config['github-protocols'];
                if ($this->config['secure-http'] && false !== ($index = array_search('git', $protos))) {
                    unset($protos[$index]);
                }
                if (reset($protos) === 'http') {
                    throw new \RuntimeException('The http protocol for github is not available anymore, update your config\'s github-protocols to use "https", "git" or "ssh"');
                }

                return $protos;

            case 'disable-tls':
                return $this->config[$key] !== 'false' && (bool) $this->config[$key];
            case 'secure-http':
                return $this->config[$key] !== 'false' && (bool) $this->config[$key];
            case 'use-github-api':
                return $this->config[$key] !== 'false' && (bool) $this->config[$key];
            default:
                if (!isset($this->config[$key])) {
                    return null;
                }

                return $this->process($this->config[$key], $flags);
        }
    }

    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;
    }

    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_replace_callback('#\{\$(.+)\}#', 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_match('{^(?:/|[a-z]:|[a-z0-9.]+://)}i', $path)) {
            return $path;
        }

        return $this->baseDir . '/' . $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 getenv($var);
        }

        return false;
    }

    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
     */
    public function prohibitUrlByConfig($url, IOInterface $io = null)
    {
        // Return right away if the URL is malformed or custom (see issue #5173)
        if (false === filter_var($url, FILTER_VALIDATE_URL)) {
            return;
        }

        // Extract scheme and throw exception on known insecure protocols
        $scheme = parse_url($url, PHP_URL_SCHEME);
        if (in_array($scheme, array('http', 'git', 'ftp', 'svn'))) {
            if ($this->get('secure-http')) {
                throw new TransportException("Your configuration does not allow connections to $url. See https://getcomposer.org/doc/06-config.md#secure-http for details.");
            } elseif ($io) {
                $host = parse_url($url, PHP_URL_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"
     *   ]
     * }
     */
    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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

use Composer\Composer;
use Composer\DependencyResolver\PolicyInterface;
use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\DependencyResolver\Pool;
use Composer\DependencyResolver\Request;
use Composer\EventDispatcher\Event;
use Composer\IO\IOInterface;
use Composer\Repository\CompositeRepository;

/**
 * An event for all installer.
 *
 * @author François Pluchino <francois.pluchino@gmail.com>
 */
class InstallerEvent extends Event
{
    /**
     * @var Composer
     */
    private $composer;

    /**
     * @var IOInterface
     */
    private $io;

    /**
     * @var bool
     */
    private $devMode;

    /**
     * @var PolicyInterface
     */
    private $policy;

    /**
     * @var Pool
     */
    private $pool;

    /**
     * @var CompositeRepository
     */
    private $installedRepo;

    /**
     * @var Request
     */
    private $request;

    /**
     * @var OperationInterface[]
     */
    private $operations;

    /**
     * Constructor.
     *
     * @param string               $eventName
     * @param Composer             $composer
     * @param IOInterface          $io
     * @param bool                 $devMode
     * @param PolicyInterface      $policy
     * @param Pool                 $pool
     * @param CompositeRepository  $installedRepo
     * @param Request              $request
     * @param OperationInterface[] $operations
     */
    public function __construct($eventName, Composer $composer, IOInterface $io, $devMode, PolicyInterface $policy, Pool $pool, CompositeRepository $installedRepo, Request $request, array $operations = array())
    {
        parent::__construct($eventName);

        $this->composer = $composer;
        $this->io = $io;
        $this->devMode = $devMode;
        $this->policy = $policy;
        $this->pool = $pool;
        $this->installedRepo = $installedRepo;
        $this->request = $request;
        $this->operations = $operations;
    }

    /**
     * @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 PolicyInterface
     */
    public function getPolicy()
    {
        return $this->policy;
    }

    /**
     * @return Pool
     */
    public function getPool()
    {
        return $this->pool;
    }

    /**
     * @return CompositeRepository
     */
    public function getInstalledRepo()
    {
        return $this->installedRepo;
    }

    /**
     * @return Request
     */
    public function getRequest()
    {
        return $this->request;
    }

    /**
     * @return OperationInterface[]
     */
    public function getOperations()
    {
        return $this->operations;
    }
}
<?php

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

namespace Composer\Installer;

use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Package\PackageInterface;

/**
 * Installer for plugin packages
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Nils Adermann <naderman@naderman.de>
 */
class PluginInstaller extends LibraryInstaller
{
    private $installationManager;

    /**
     * Initializes Plugin installer.
     *
     * @param IOInterface $io
     * @param Composer    $composer
     */
    public function __construct(IOInterface $io, Composer $composer)
    {
        parent::__construct($io, $composer, 'composer-plugin');
        $this->installationManager = $composer->getInstallationManager();
    }

    /**
     * {@inheritDoc}
     */
    public function supports($packageType)
    {
        return $packageType === 'composer-plugin' || $packageType === 'composer-installer';
    }

    /**
     * {@inheritDoc}
     */
    public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        $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.');
        }

        parent::install($repo, $package);
        try {
            $this->composer->getPluginManager()->registerPackage($package, true);
        } catch (\Exception $e) {
            // Rollback installation
            $this->io->writeError('Plugin installation failed, rolling back');
            parent::uninstall($repo, $package);
            throw $e;
        }
    }

    /**
     * {@inheritDoc}
     */
    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
    {
        $extra = $target->getExtra();
        if (empty($extra['class'])) {
            throw new \UnexpectedValueException('Error while installing '.$target->getPrettyName().', composer-plugin packages should have a class defined in their extra key to be usable.');
        }

        parent::update($repo, $initial, $target);
        $this->composer->getPluginManager()->registerPackage($target, true);
    }
}
<?php

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

namespace Composer\Installer;

/**
 * Package Events.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PackageEvents
{
    /**
     * The PRE_PACKAGE_INSTALL event occurs before a package is installed.
     *
     * The event listener method receives a Composer\Installer\PackageEvent instance.
     *
     * @var string
     */
    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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\DependencyResolver\PolicyInterface;
use Composer\DependencyResolver\Pool;
use Composer\DependencyResolver\Request;
use Composer\Repository\CompositeRepository;

/**
 * The Package Event.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PackageEvent extends InstallerEvent
{
    /**
     * @var OperationInterface The package instance
     */
    private $operation;

    /**
     * Constructor.
     *
     * @param string               $eventName
     * @param Composer             $composer
     * @param IOInterface          $io
     * @param bool                 $devMode
     * @param PolicyInterface      $policy
     * @param Pool                 $pool
     * @param CompositeRepository  $installedRepo
     * @param Request              $request
     * @param OperationInterface[] $operations
     * @param OperationInterface   $operation
     */
    public function __construct($eventName, Composer $composer, IOInterface $io, $devMode, PolicyInterface $policy, Pool $pool, CompositeRepository $installedRepo, Request $request, array $operations, OperationInterface $operation)
    {
        parent::__construct($eventName, $composer, $io, $devMode, $policy, $pool, $installedRepo, $request, $operations);

        $this->operation = $operation;
    }

    /**
     * Returns the package instance.
     *
     * @return OperationInterface
     */
    public function getOperation()
    {
        return $this->operation;
    }
}
<?php

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

namespace Composer\Installer;

use Composer\Package\PackageInterface;
use Composer\Repository\InstalledRepositoryInterface;
use InvalidArgumentException;

/**
 * Interface for the package installation manager.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface InstallerInterface
{
    /**
     * Decides if the installer supports the given type
     *
     * @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 $repo, PackageInterface $package);

    /**
     * Installs specific package.
     *
     * @param InstalledRepositoryInterface $repo    repository in which to check
     * @param PackageInterface             $package package instance
     */
    public function install(InstalledRepositoryInterface $repo, PackageInterface $package);

    /**
     * Updates specific package.
     *
     * @param InstalledRepositoryInterface $repo    repository in which to check
     * @param PackageInterface             $initial already installed package version
     * @param PackageInterface             $target  updated version
     *
     * @throws InvalidArgumentException if $initial package is not installed
     */
    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target);

    /**
     * Uninstalls specific package.
     *
     * @param InstalledRepositoryInterface $repo    repository in which to check
     * @param PackageInterface             $package package instance
     */
    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package);

    /**
     * Returns the installation path of a package
     *
     * @param  PackageInterface $package
     * @return string           path
     */
    public function getInstallPath(PackageInterface $package);
}
<?php

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

namespace Composer\Installer;

use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Util\Filesystem;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;
use Composer\Util\Silencer;

/**
 * Utility to handle installation of package "bin"/binaries
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Helmut Hummel <info@helhum.io>
 */
class BinaryInstaller
{
    protected $binDir;
    protected $binCompat;
    protected $io;
    protected $filesystem;

    /**
     * @param IOInterface $io
     * @param string      $binDir
     * @param string      $binCompat
     * @param Filesystem  $filesystem
     */
    public function __construct(IOInterface $io, $binDir, $binCompat, Filesystem $filesystem = null)
    {
        $this->binDir = $binDir;
        $this->binCompat = $binCompat;
        $this->io = $io;
        $this->filesystem = $filesystem ?: new Filesystem();
    }

    public function installBinaries(PackageInterface $package, $installPath, $warnOnOverwrite = true)
    {
        $binaries = $this->getBinaries($package);
        if (!$binaries) {
            return;
        }
        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;
            }

            // 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)) {
                    // likely leftover from a previous install, make sure
                    // that the target is still executable in case this
                    // is a fresh install of the vendor.
                    Silencer::call('chmod', $link, 0777 & ~umask());
                }
                if ($warnOnOverwrite) {
                    $this->io->writeError('    Skipped installation of bin '.$bin.' for package '.$package->getName().': name conflicts with an existing file');
                }
                continue;
            }

            if ($this->binCompat === "auto") {
                if (Platform::isWindows()) {
                    $this->installFullBinaries($binPath, $link, $bin, $package);
                } else {
                    $this->installSymlinkBinaries($binPath, $link);
                }
            } elseif ($this->binCompat === "full") {
                $this->installFullBinaries($binPath, $link, $bin, $package);
            }
            Silencer::call('chmod', $link, 0777 & ~umask());
        }
    }

    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)) {
                $this->filesystem->unlink($link);
            }
            if (file_exists($link.'.bat')) {
                $this->filesystem->unlink($link.'.bat');
            }
        }

        // attempt removing the bin dir in case it is left empty
        if (is_dir($this->binDir) && $this->filesystem->isDirEmpty($this->binDir)) {
            Silencer::call('rmdir', $this->binDir);
        }
    }

    public static function determineBinaryCaller($bin)
    {
        if ('.bat' === substr($bin, -4) || '.exe' === substr($bin, -4)) {
            return 'call';
        }

        $handle = fopen($bin, 'r');
        $line = fgets($handle);
        fclose($handle);
        if (preg_match('{^#!/(?:usr/bin/env )?(?:[^/]+/)*(.+)$}m', $line, $match)) {
            return trim($match[1]);
        }

        return 'php';
    }

    protected function getBinaries(PackageInterface $package)
    {
        return $package->getBinaries();
    }

    protected function installFullBinaries($binPath, $link, $bin, PackageInterface $package)
    {
        // add unixy support for cygwin and similar environments
        if ('.bat' !== substr($binPath, -4)) {
            $this->installUnixyProxyBinaries($binPath, $link);
            @chmod($link, 0777 & ~umask());
            $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));
        }
    }

    protected function installSymlinkBinaries($binPath, $link)
    {
        if (!$this->filesystem->relativeSymlink($binPath, $link)) {
            $this->installUnixyProxyBinaries($binPath, $link);
        }
    }

    protected function installUnixyProxyBinaries($binPath, $link)
    {
        file_put_contents($link, $this->generateUnixyProxyCode($binPath, $link));
    }

    protected function initializeBinDir()
    {
        $this->filesystem->ensureDirectoryExists($this->binDir);
        $this->binDir = realpath($this->binDir);
    }

    protected function generateWindowsProxyCode($bin, $link)
    {
        $binPath = $this->filesystem->findShortestPath($link, $bin);
        $caller = self::determineBinaryCaller($bin);

        return "@ECHO OFF\r\n".
            "setlocal DISABLEDELAYEDEXPANSION\r\n".
            "SET BIN_TARGET=%~dp0/".trim(ProcessExecutor::escape($binPath), '"\'')."\r\n".
            "{$caller} \"%BIN_TARGET%\" %*\r\n";
    }

    protected function generateUnixyProxyCode($bin, $link)
    {
        $binPath = $this->filesystem->findShortestPath($link, $bin);

        $binDir = ProcessExecutor::escape(dirname($binPath));
        $binFile = basename($binPath);

        $proxyCode = <<<PROXY
#!/usr/bin/env sh

dir=\$(cd "\${0%[/\\\\]*}" > /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

"\${dir}/$binFile" "\$@"

PROXY;

        return $proxyCode;
    }
}
<?php

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

namespace Composer\Installer;

use Composer\Package\PackageInterface;

/**
 * Interface for the package installation manager that handle binary installation.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface BinaryPresenceInterface
{
    /**
     * Make sure binaries are installed for a given package.
     *
     * @param PackageInterface $package package instance
     */
    public function ensureBinariesPresence(PackageInterface $package);
}
<?php

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

namespace Composer\Installer;

use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Package\AliasPackage;
use Composer\Repository\RepositoryInterface;
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\Util\StreamContextFactory;

/**
 * Package operation manager.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Nils Adermann <naderman@naderman.de>
 */
class InstallationManager
{
    private $installers = array();
    private $cache = array();
    private $notifiablePackages = array();

    public function reset()
    {
        $this->notifiablePackages = array();
    }

    /**
     * Adds installer
     *
     * @param InstallerInterface $installer installer instance
     */
    public function addInstaller(InstallerInterface $installer)
    {
        array_unshift($this->installers, $installer);
        $this->cache = array();
    }

    /**
     * Removes installer
     *
     * @param InstallerInterface $installer installer instance
     */
    public function removeInstaller(InstallerInterface $installer)
    {
        if (false !== ($key = array_search($installer, $this->installers, true))) {
            array_splice($this->installers, $key, 1);
            $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.
     */
    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 $repo, PackageInterface $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
     */
    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 RepositoryInterface $repo      repository in which to check
     * @param OperationInterface  $operation operation instance
     */
    public function execute(RepositoryInterface $repo, OperationInterface $operation)
    {
        $method = $operation->getJobType();
        $this->$method($repo, $operation);
    }

    /**
     * Executes install operation.
     *
     * @param RepositoryInterface $repo      repository in which to check
     * @param InstallOperation    $operation operation instance
     */
    public function install(RepositoryInterface $repo, InstallOperation $operation)
    {
        $package = $operation->getPackage();
        $installer = $this->getInstaller($package->getType());
        $installer->install($repo, $package);
        $this->markForNotification($package);
    }

    /**
     * Executes update operation.
     *
     * @param RepositoryInterface $repo      repository in which to check
     * @param UpdateOperation     $operation operation instance
     */
    public function update(RepositoryInterface $repo, UpdateOperation $operation)
    {
        $initial = $operation->getInitialPackage();
        $target = $operation->getTargetPackage();

        $initialType = $initial->getType();
        $targetType = $target->getType();

        if ($initialType === $targetType) {
            $installer = $this->getInstaller($initialType);
            $installer->update($repo, $initial, $target);
            $this->markForNotification($target);
        } else {
            $this->getInstaller($initialType)->uninstall($repo, $initial);
            $this->getInstaller($targetType)->install($repo, $target);
        }
    }

    /**
     * Uninstalls package.
     *
     * @param RepositoryInterface $repo      repository in which to check
     * @param UninstallOperation  $operation operation instance
     */
    public function uninstall(RepositoryInterface $repo, UninstallOperation $operation)
    {
        $package = $operation->getPackage();
        $installer = $this->getInstaller($package->getType());
        $installer->uninstall($repo, $package);
    }

    /**
     * Executes markAliasInstalled operation.
     *
     * @param RepositoryInterface         $repo      repository in which to check
     * @param MarkAliasInstalledOperation $operation operation instance
     */
    public function markAliasInstalled(RepositoryInterface $repo, MarkAliasInstalledOperation $operation)
    {
        $package = $operation->getPackage();

        if (!$repo->hasPackage($package)) {
            $repo->addPackage(clone $package);
        }
    }

    /**
     * Executes markAlias operation.
     *
     * @param RepositoryInterface           $repo      repository in which to check
     * @param MarkAliasUninstalledOperation $operation operation instance
     */
    public function markAliasUninstalled(RepositoryInterface $repo, MarkAliasUninstalledOperation $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);
    }

    public function notifyInstalls(IOInterface $io)
    {
        foreach ($this->notifiablePackages as $repoUrl => $packages) {
            $repositoryName = parse_url($repoUrl, PHP_URL_HOST);
            if ($io->hasAuthentication($repositoryName)) {
                $auth = $io->getAuthentication($repositoryName);
                $authStr = base64_encode($auth['username'] . ':' . $auth['password']);
                $authHeader = 'Authorization: Basic '.$authStr;
            }

            // 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('http' =>
                        array(
                            'method' => 'POST',
                            'header' => array('Content-type: application/x-www-form-urlencoded'),
                            'content' => http_build_query($params, '', '&'),
                            'timeout' => 3,
                        ),
                    );
                    if (isset($authHeader)) {
                        $opts['http']['header'][] = $authHeader;
                    }

                    $context = StreamContextFactory::getContext($url, $opts);
                    @file_get_contents($url, false, $context);
                }

                continue;
            }

            $postData = array('downloads' => array());
            foreach ($packages as $package) {
                $postData['downloads'][] = array(
                    'name' => $package->getPrettyName(),
                    'version' => $package->getVersion(),
                );
            }

            $opts = array('http' =>
                array(
                    'method' => 'POST',
                    'header' => array('Content-Type: application/json'),
                    'content' => json_encode($postData),
                    'timeout' => 6,
                ),
            );
            if (isset($authHeader)) {
                $opts['http']['header'][] = $authHeader;
            }

            $context = StreamContextFactory::getContext($repoUrl, $opts);
            @file_get_contents($repoUrl, false, $context);
        }

        $this->reset();
    }

    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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Package\PackageInterface;
use Composer\Util\Filesystem;
use Composer\Util\Silencer;
use Composer\Util\Platform;

/**
 * Package installation manager.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class LibraryInstaller implements InstallerInterface, BinaryPresenceInterface
{
    protected $composer;
    protected $vendorDir;
    protected $binDir;
    protected $downloadManager;
    protected $io;
    protected $type;
    protected $filesystem;
    protected $binCompat;
    protected $binaryInstaller;

    /**
     * Initializes library installer.
     *
     * @param IOInterface     $io
     * @param Composer        $composer
     * @param string          $type
     * @param Filesystem      $filesystem
     * @param BinaryInstaller $binaryInstaller
     */
    public function __construct(IOInterface $io, Composer $composer, $type = 'library', Filesystem $filesystem = null, BinaryInstaller $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->io, rtrim($composer->getConfig()->get('bin-dir'), '/'), $composer->getConfig()->get('bin-compat'), $this->filesystem);
    }

    /**
     * {@inheritDoc}
     */
    public function supports($packageType)
    {
        return $packageType === $this->type || null === $this->type;
    }

    /**
     * {@inheritDoc}
     */
    public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        if (!$repo->hasPackage($package)) {
            return false;
        }

        $installPath = $this->getInstallPath($package);

        if (is_readable($installPath)) {
            return true;
        }

        return (Platform::isWindows() && $this->filesystem->isJunction($installPath)) || is_link($installPath);
    }

    /**
     * {@inheritDoc}
     */
    public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        $this->initializeVendorDir();
        $downloadPath = $this->getInstallPath($package);

        // remove the binaries if it appears the package files are missing
        if (!is_readable($downloadPath) && $repo->hasPackage($package)) {
            $this->binaryInstaller->removeBinaries($package);
        }

        $this->installCode($package);
        $this->binaryInstaller->installBinaries($package, $this->getInstallPath($package));
        if (!$repo->hasPackage($package)) {
            $repo->addPackage(clone $package);
        }
    }

    /**
     * {@inheritDoc}
     */
    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
    {
        if (!$repo->hasPackage($initial)) {
            throw new \InvalidArgumentException('Package is not installed: '.$initial);
        }

        $this->initializeVendorDir();

        $this->binaryInstaller->removeBinaries($initial);
        $this->updateCode($initial, $target);
        $this->binaryInstaller->installBinaries($target, $this->getInstallPath($target));
        $repo->removePackage($initial);
        if (!$repo->hasPackage($target)) {
            $repo->addPackage(clone $target);
        }
    }

    /**
     * {@inheritDoc}
     */
    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        if (!$repo->hasPackage($package)) {
            throw new \InvalidArgumentException('Package is not installed: '.$package);
        }

        $this->removeCode($package);
        $this->binaryInstaller->removeBinaries($package);
        $repo->removePackage($package);

        $downloadPath = $this->getPackageBasePath($package);
        if (strpos($package->getName(), '/')) {
            $packageVendorDir = dirname($downloadPath);
            if (is_dir($packageVendorDir) && $this->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;
    }

    protected function installCode(PackageInterface $package)
    {
        $downloadPath = $this->getInstallPath($package);
        $this->downloadManager->download($package, $downloadPath);
    }

    protected function updateCode(PackageInterface $initial, PackageInterface $target)
    {
        $initialDownloadPath = $this->getInstallPath($initial);
        $targetDownloadPath = $this->getInstallPath($target);
        if ($targetDownloadPath !== $initialDownloadPath) {
            // if the target and initial dirs intersect, we force a remove + install
            // to avoid the rename wiping the target dir as part of the initial dir cleanup
            if (substr($initialDownloadPath, 0, strlen($targetDownloadPath)) === $targetDownloadPath
                || substr($targetDownloadPath, 0, strlen($initialDownloadPath)) === $initialDownloadPath
            ) {
                $this->removeCode($initial);
                $this->installCode($target);

                return;
            }

            $this->filesystem->rename($initialDownloadPath, $targetDownloadPath);
        }
        $this->downloadManager->update($initial, $target, $targetDownloadPath);
    }

    protected function removeCode(PackageInterface $package)
    {
        $downloadPath = $this->getPackageBasePath($package);
        $this->downloadManager->remove($package, $downloadPath);
    }

    protected function initializeVendorDir()
    {
        $this->filesystem->ensureDirectoryExists($this->vendorDir);
        $this->vendorDir = realpath($this->vendorDir);
    }
}
<?php

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

namespace Composer\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 <kontakt@beberlei.de>
 */
class ProjectInstaller implements InstallerInterface
{
    private $installPath;
    private $downloadManager;
    private $filesystem;

    public function __construct($installPath, DownloadManager $dm)
    {
        $this->installPath = rtrim(strtr($installPath, '\\', '/'), '/').'/';
        $this->downloadManager = $dm;
        $this->filesystem = new Filesystem;
    }

    /**
     * Decides if the installer supports the given type
     *
     * @param  string $packageType
     * @return bool
     */
    public function supports($packageType)
    {
        return true;
    }

    /**
     * {@inheritDoc}
     */
    public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        return false;
    }

    /**
     * {@inheritDoc}
     */
    public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        $installPath = $this->installPath;
        if (file_exists($installPath) && !$this->filesystem->isDirEmpty($installPath)) {
            throw new \InvalidArgumentException("Project directory $installPath is not empty.");
        }
        if (!is_dir($installPath)) {
            mkdir($installPath, 0777, true);
        }
        $this->downloadManager->download($package, $installPath);
    }

    /**
     * {@inheritDoc}
     */
    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
    {
        throw new \InvalidArgumentException("not supported");
    }

    /**
     * {@inheritDoc}
     */
    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Installer;

use Composer\Repository\InstalledRepositoryInterface;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\IO\IOInterface;

/**
 * Metapackage installation manager.
 *
 * @author Martin Hasoň <martin.hason@gmail.com>
 */
class MetapackageInstaller implements InstallerInterface
{
    private $io;

    public function __construct(IOInterface $io)
    {
        $this->io = $io;
    }

    /**
     * {@inheritDoc}
     */
    public function supports($packageType)
    {
        return $packageType === 'metapackage';
    }

    /**
     * {@inheritDoc}
     */
    public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        return $repo->hasPackage($package);
    }

    /**
     * {@inheritDoc}
     */
    public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        $this->io->writeError("  - Installing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");

        $repo->addPackage(clone $package);
    }

    /**
     * {@inheritDoc}
     */
    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
    {
        if (!$repo->hasPackage($initial)) {
            throw new \InvalidArgumentException('Package is not installed: '.$initial);
        }

        $name = $target->getName();
        $from = $initial->getFullPrettyVersion();
        $to = $target->getFullPrettyVersion();
        $actionName = VersionParser::isUpgrade($initial->getVersion(), $target->getVersion()) ? 'Updating' : 'Downgrading';
        $this->io->writeError("  - " . $actionName . " <info>" . $name . "</info> (<comment>" . $from . "</comment> => <comment>" . $to . "</comment>)");

        $repo->removePackage($initial);
        $repo->addPackage(clone $target);
    }

    /**
     * {@inheritDoc}
     */
    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        if (!$repo->hasPackage($package)) {
            throw new \InvalidArgumentException('Package is not installed: '.$package);
        }

        $this->io->writeError("  - Removing <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)");

        $repo->removePackage($package);
    }

    /**
     * {@inheritDoc}
     */
    public function getInstallPath(PackageInterface $package)
    {
        return '';
    }
}
<?php

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

namespace Composer\Installer;

use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Util\Filesystem;
use Composer\Util\ProcessExecutor;

/**
 * Utility to handle installation of package "bin"/binaries for PEAR packages
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PearBinaryInstaller extends BinaryInstaller
{
    private $installer;
    private $vendorDir;

    /**
     * @param IOInterface   $io
     * @param string        $binDir
     * @param string        $vendorDir
     * @param string        $binCompat
     * @param Filesystem    $filesystem
     * @param PearInstaller $installer
     */
    public function __construct(IOInterface $io, $binDir, $vendorDir, $binCompat, Filesystem $filesystem, PearInstaller $installer)
    {
        parent::__construct($io, $binDir, $binCompat, $filesystem);
        $this->installer = $installer;
        $this->vendorDir = $vendorDir;
    }

    protected function getBinaries(PackageInterface $package)
    {
        $binariesPath = $this->installer->getInstallPath($package) . '/bin/';
        $binaries = array();
        if (file_exists($binariesPath)) {
            foreach (new \FilesystemIterator($binariesPath, \FilesystemIterator::KEY_AS_FILENAME | \FilesystemIterator::CURRENT_AS_FILEINFO) as $fileName => $value) {
                if (!$value->isDir()) {
                    $binaries[] = 'bin/'.$fileName;
                }
            }
        }

        return $binaries;
    }

    protected function initializeBinDir()
    {
        parent::initializeBinDir();
        file_put_contents($this->binDir.'/composer-php', $this->generateUnixyPhpProxyCode());
        @chmod($this->binDir.'/composer-php', 0777 & ~umask());
        file_put_contents($this->binDir.'/composer-php.bat', $this->generateWindowsPhpProxyCode());
        @chmod($this->binDir.'/composer-php.bat', 0777 & ~umask());
    }

    protected function generateWindowsProxyCode($bin, $link)
    {
        $binPath = $this->filesystem->findShortestPath($link, $bin);
        if ('.bat' === substr($bin, -4)) {
            $caller = 'call';
        } else {
            $handle = fopen($bin, 'r');
            $line = fgets($handle);
            fclose($handle);
            if (preg_match('{^#!/(?:usr/bin/env )?(?:[^/]+/)*(.+)$}m', $line, $match)) {
                $caller = trim($match[1]);
            } else {
                $caller = 'php';
            }

            if ($caller === 'php') {
                return "@echo off\r\n".
                    "pushd .\r\n".
                    "cd %~dp0\r\n".
                    "set PHP_PROXY=%CD%\\composer-php.bat\r\n".
                    "cd ".ProcessExecutor::escape(dirname($binPath))."\r\n".
                    "set BIN_TARGET=%CD%\\".basename($binPath)."\r\n".
                    "popd\r\n".
                    "%PHP_PROXY% \"%BIN_TARGET%\" %*\r\n";
            }
        }

        return "@echo off\r\n".
            "pushd .\r\n".
            "cd %~dp0\r\n".
            "cd ".ProcessExecutor::escape(dirname($binPath))."\r\n".
            "set BIN_TARGET=%CD%\\".basename($binPath)."\r\n".
            "popd\r\n".
            $caller." \"%BIN_TARGET%\" %*\r\n";
    }

    private function generateWindowsPhpProxyCode()
    {
        $binToVendor = $this->filesystem->findShortestPath($this->binDir, $this->vendorDir, true);

        return
            "@echo off\r\n" .
            "setlocal enabledelayedexpansion\r\n" .
            "set BIN_DIR=%~dp0\r\n" .
            "set VENDOR_DIR=%BIN_DIR%\\".$binToVendor."\r\n" .
            "set DIRS=.\r\n" .
            "FOR /D %%V IN (%VENDOR_DIR%\\*) DO (\r\n" .
            "    FOR /D %%P IN (%%V\\*) DO (\r\n" .
            "        set DIRS=!DIRS!;%%~fP\r\n" .
            "    )\r\n" .
            ")\r\n" .
            "php.exe -d include_path=!DIRS! %*\r\n";
    }

    private function generateUnixyPhpProxyCode()
    {
        $binToVendor = $this->filesystem->findShortestPath($this->binDir, $this->vendorDir, true);

        return
            "#!/usr/bin/env sh\n".
            "SRC_DIR=`pwd`\n".
            "BIN_DIR=`dirname $0`\n".
            "VENDOR_DIR=\$BIN_DIR/".escapeshellarg($binToVendor)."\n".
            "DIRS=\"\"\n".
            "for vendor in \$VENDOR_DIR/*; do\n".
            "    if [ -d \"\$vendor\" ]; then\n".
            "        for package in \$vendor/*; do\n".
            "            if [ -d \"\$package\" ]; then\n".
            "                DIRS=\"\${DIRS}:\${package}\"\n".
            "            fi\n".
            "        done\n".
            "    fi\n".
            "done\n".
            "php -d include_path=\".\$DIRS\" $@\n";
    }
}
<?php

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

namespace Composer\Installer;

use Composer\IO\IOInterface;
use Composer\Composer;
use Composer\Downloader\PearPackageExtractor;
use Composer\Repository\InstalledRepositoryInterface;
use Composer\Package\PackageInterface;
use Composer\Util\Platform;
use Composer\Util\Filesystem;

/**
 * Package installation manager.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class PearInstaller extends LibraryInstaller
{
    /**
     * Initializes library installer.
     *
     * @param IOInterface $io       io instance
     * @param Composer    $composer
     * @param string      $type     package type that this installer handles
     */
    public function __construct(IOInterface $io, Composer $composer, $type = 'pear-library')
    {
        $filesystem = new Filesystem();
        $binaryInstaller = new PearBinaryInstaller($io, rtrim($composer->getConfig()->get('bin-dir'), '/'), rtrim($composer->getConfig()->get('vendor-dir'), '/'), $composer->getConfig()->get('bin-compat'), $filesystem, $this);

        parent::__construct($io, $composer, $type, $filesystem, $binaryInstaller);
    }

    /**
     * {@inheritDoc}
     */
    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
    {
        $this->uninstall($repo, $initial);
        $this->install($repo, $target);
    }

    protected function installCode(PackageInterface $package)
    {
        parent::installCode($package);

        $isWindows = Platform::isWindows();
        $php_bin = $this->binDir . ($isWindows ? '/composer-php.bat' : '/composer-php');

        if (!$isWindows) {
            $php_bin = '/usr/bin/env ' . $php_bin;
        }

        $installPath = $this->getInstallPath($package);
        $vars = array(
            'os' => $isWindows ? 'windows' : 'linux',
            'php_bin' => $php_bin,
            'pear_php' => $installPath,
            'php_dir' => $installPath,
            'bin_dir' => $installPath . '/bin',
            'data_dir' => $installPath . '/data',
            'version' => $package->getPrettyVersion(),
        );

        $packageArchive = $this->getInstallPath($package).'/'.pathinfo($package->getDistUrl(), PATHINFO_BASENAME);
        $pearExtractor = new PearPackageExtractor($packageArchive);
        $pearExtractor->extractTo($this->getInstallPath($package), array('php' => '/', 'script' => '/bin', 'data' => '/data'), $vars);

        $this->io->writeError('    Cleaning up', true, IOInterface::VERBOSE);
        $this->filesystem->unlink($packageArchive);
    }
}
<?php

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

namespace Composer\Installer;

use Composer\Repository\InstalledRepositoryInterface;
use Composer\Package\PackageInterface;

/**
 * Does not install anything but marks packages installed in the repo
 *
 * Useful for dry runs
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class NoopInstaller implements InstallerInterface
{
    /**
     * {@inheritDoc}
     */
    public function supports($packageType)
    {
        return true;
    }

    /**
     * {@inheritDoc}
     */
    public function isInstalled(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        return $repo->hasPackage($package);
    }

    /**
     * {@inheritDoc}
     */
    public function install(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        if (!$repo->hasPackage($package)) {
            $repo->addPackage(clone $package);
        }
    }

    /**
     * {@inheritDoc}
     */
    public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target)
    {
        if (!$repo->hasPackage($initial)) {
            throw new \InvalidArgumentException('Package is not installed: '.$initial);
        }

        $repo->removePackage($initial);
        if (!$repo->hasPackage($target)) {
            $repo->addPackage(clone $target);
        }
    }

    /**
     * {@inheritDoc}
     */
    public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package)
    {
        if (!$repo->hasPackage($package)) {
            throw new \InvalidArgumentException('Package is not installed: '.$package);
        }
        $repo->removePackage($package);
    }

    /**
     * {@inheritDoc}
     */
    public function getInstallPath(PackageInterface $package)
    {
        $targetDir = $package->getTargetDir();

        return $package->getPrettyName() . ($targetDir ? '/'.$targetDir : '');
    }
}
<?php

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

namespace Composer\Installer;

/**
 * The Installer Events.
 *
 * @author François Pluchino <francois.pluchino@gmail.com>
 */
class InstallerEvents
{
    /**
     * The PRE_DEPENDENCIES_SOLVING event occurs as a installer begins
     * resolve operations.
     *
     * The event listener method receives a
     * Composer\Installer\InstallerEvent instance.
     *
     * @var string
     */
    const PRE_DEPENDENCIES_SOLVING = 'pre-dependencies-solving';

    /**
     * The POST_DEPENDENCIES_SOLVING event occurs as a installer after
     * resolve operations.
     *
     * The event listener method receives a
     * Composer\Installer\InstallerEvent instance.
     *
     * @var string
     */
    const POST_DEPENDENCIES_SOLVING = 'post-dependencies-solving';
}
<?php

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

namespace Composer\Installer;

use Composer\IO\IOInterface;
use Composer\Package\PackageInterface;
use Composer\Repository\RepositoryInterface;
use Symfony\Component\Console\Formatter\OutputFormatter;

/**
 * Add suggested packages from different places to output them in the end.
 *
 * @author Haralan Dobrev <hkdobrev@gmail.com>
 */
class SuggestedPackagesReporter
{
    /**
     * @var array
     */
    protected $suggestedPackages = array();

    /**
     * @var IOInterface
     */
    private $io;

    public function __construct(IOInterface $io)
    {
        $this->io = $io;
    }

    /**
     * @return array 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  RepositoryInterface       $installedRepo Installed packages
     * @return SuggestedPackagesReporter
     */
    public function output(RepositoryInterface $installedRepo = null)
    {
        $suggestedPackages = $this->getPackages();
        $installedPackages = array();
        if (null !== $installedRepo && ! empty($suggestedPackages)) {
            foreach ($installedRepo->getPackages() as $package) {
                $installedPackages = array_merge(
                    $installedPackages,
                    $package->getNames()
                );
            }
        }

        foreach ($suggestedPackages as $suggestion) {
            if (in_array($suggestion['target'], $installedPackages)) {
                continue;
            }

            $this->io->writeError(sprintf(
                '%s suggests installing %s%s',
                $suggestion['source'],
                $this->escapeOutput($suggestion['target']),
                $this->escapeOutput('' !== $suggestion['reason'] ? ' ('.$suggestion['reason'].')' : '')
            ));
        }

        return $this;
    }

    /**
     * @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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Exception;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class NoSslException extends \RuntimeException
{
}
<?php

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

namespace Composer\Repository;

use Composer\Downloader\TransportException;
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\IO\IOInterface;
use Composer\Config;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class VcsRepository extends ArrayRepository implements ConfigurableRepositoryInterface
{
    protected $url;
    protected $packageName;
    protected $isVerbose;
    protected $isVeryVerbose;
    protected $io;
    protected $config;
    protected $versionParser;
    protected $type;
    protected $loader;
    protected $repoConfig;
    protected $branchErrorOccurred = false;
    private $drivers;
    /** @var VcsDriverInterface */
    private $driver;
    /** @var VersionCacheInterface */
    private $versionCache;
    private $emptyReferences = array();

    public function __construct(array $repoConfig, IOInterface $io, Config $config, EventDispatcher $dispatcher = null, array $drivers = null, VersionCacheInterface $versionCache = null)
    {
        parent::__construct();
        $this->drivers = $drivers ?: array(
            'github' => 'Composer\Repository\Vcs\GitHubDriver',
            'gitlab' => 'Composer\Repository\Vcs\GitLabDriver',
            'git-bitbucket' => 'Composer\Repository\Vcs\GitBitbucketDriver',
            'git' => 'Composer\Repository\Vcs\GitDriver',
            'hg-bitbucket' => 'Composer\Repository\Vcs\HgBitbucketDriver',
            '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;
    }

    public function getRepoConfig()
    {
        return $this->repoConfig;
    }

    public function setLoader(LoaderInterface $loader)
    {
        $this->loader = $loader;
    }

    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->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->driver->initialize();

                return $this->driver;
            }
        }

        foreach ($this->drivers as $driver) {
            if ($driver::supports($this->io, $this->config, $this->url, true)) {
                $this->driver = new $driver($this->repoConfig, $this->io, $this->config);
                $this->driver->initialize();

                return $this->driver;
            }
        }
    }

    public function hadInvalidBranches()
    {
        return $this->branchErrorOccurred;
    }

    public function getEmptyReferences()
    {
        return $this->emptyReferences;
    }

    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);
        }

        try {
            if ($driver->hasComposerFile($driver->getRootIdentifier())) {
                $data = $driver->getComposerInformation($driver->getRootIdentifier());
                $this->packageName = !empty($data['name']) ? $data['name'] : null;
            }
        } catch (\Exception $e) {
            if ($isVeryVerbose) {
                $this->io->writeError('<error>Skipped parsing '.$driver->getRootIdentifier().', '.$e->getMessage().'</error>');
            }
        }

        foreach ($driver->getTags() as $tag => $identifier) {
            $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($msg, false);
            }

            // 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;
            } elseif ($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']);

                // broken package, version doesn't match tag
                if ($data['version_normalized'] !== $parsedTag) {
                    if ($isVeryVerbose) {
                        $this->io->writeError('<warning>Skipped tag '.$tag.', tag ('.$parsedTag.') does not match version ('.$data['version_normalized'].') in composer.json</warning>');
                    }
                    continue;
                }

                $tagPackageName = isset($data['name']) ? $data['name'] : $this->packageName;
                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 && $e->getCode() === 404) {
                    $this->emptyReferences[] = $identifier;
                }
                if ($isVeryVerbose) {
                    $this->io->writeError('<warning>Skipped tag '.$tag.', '.($e instanceof TransportException ? 'no composer file was found' : $e->getMessage()).'</warning>');
                }
                continue;
            }
        }

        if (!$isVeryVerbose) {
            $this->io->overwriteError('', false);
        }

        $branches = $driver->getBranches();
        foreach ($branches as $branch => $identifier) {
            $msg = 'Reading composer.json of <info>' . ($this->packageName ?: $this->url) . '</info> (<comment>' . $branch . '</comment>)';
            if ($isVeryVerbose) {
                $this->io->writeError($msg);
            } elseif ($isVerbose) {
                $this->io->overwriteError($msg, false);
            }

            if ($branch === 'trunk' && isset($branches['master'])) {
                if ($isVeryVerbose) {
                    $this->io->writeError('<warning>Skipped branch '.$branch.', can not parse both master and trunk branches as they both resolve to 9999999-dev internally</warning>');
                }
                continue;
            }

            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 ('dev-' === substr($parsedBranch, 0, 4) || '9999999-dev' === $parsedBranch) {
                $version = 'dev-' . $branch;
            } else {
                $prefix = substr($branch, 0, 1) === 'v' ? 'v' : '';
                $version = $prefix . preg_replace('{(\.9{7})+}', '.x', $parsedBranch);
            }

            $cachedPackage = $this->getCachedPackageVersion($version, $identifier, $isVerbose, $isVeryVerbose);
            if ($cachedPackage) {
                $this->addPackage($cachedPackage);

                continue;
            } elseif ($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;

                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) {
                if ($e->getCode() === 404) {
                    $this->emptyReferences[] = $identifier;
                }
                if ($isVeryVerbose) {
                    $this->io->writeError('<warning>Skipped branch '.$branch.', no composer file was found</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.');
        }
    }

    protected function preProcess(VcsDriverInterface $driver, array $data, $identifier)
    {
        // keep the name of the main identifier for all packages
        $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;
    }

    private function validateBranch($branch)
    {
        try {
            return $this->versionParser->normalizeBranch($branch);
        } catch (\Exception $e) {
        }

        return false;
    }

    private function validateTag($version)
    {
        try {
            return $this->versionParser->normalize($version);
        } catch (\Exception $e) {
        }

        return false;
    }

    private function getCachedPackageVersion($version, $identifier, $isVerbose, $isVeryVerbose)
    {
        if (!$this->versionCache) {
            return;
        }

        $cachedPackage = $this->versionCache->getVersionPackage($version, $identifier);
        if ($cachedPackage === false) {
            if ($isVeryVerbose) {
                $this->io->writeError('<warning>Skipped '.$version.', no composer file (cached from ref '.$identifier.')</warning>');
            }

            return false;
        }

        if ($cachedPackage) {
            $msg = 'Found cached composer.json of <info>' . ($this->packageName ?: $this->url) . '</info> (<comment>' . $version . '</comment>)';
            if ($isVeryVerbose) {
                $this->io->writeError($msg);
            } elseif ($isVerbose) {
                $this->io->overwriteError($msg, false);
            }

            if ($existingPackage = $this->findPackage($cachedPackage['name'], $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;
    }
}
<?php

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

namespace Composer\Repository;

/**
 * Installed filesystem repository.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class InstalledFilesystemRepository extends FilesystemRepository implements InstalledRepositoryInterface
{
}
<?php

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

namespace Composer\Repository;

use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Loader\LoaderInterface;
use Composer\Util\Zip;

/**
 * @author Serge Smertin <serg.smertin@gmail.com>
 */
class ArtifactRepository extends ArrayRepository implements ConfigurableRepositoryInterface
{
    /** @var LoaderInterface */
    protected $loader;

    protected $lookup;
    protected $repoConfig;
    private $io;

    public function __construct(array $repoConfig, IOInterface $io)
    {
        parent::__construct();
        if (!extension_loaded('zip')) {
            throw new \RuntimeException('The artifact repository requires PHP\'s zip extension');
        }

        $this->loader = new ArrayLoader();
        $this->lookup = $repoConfig['url'];
        $this->io = $io;
        $this->repoConfig = $repoConfig;
    }

    public function getRepoConfig()
    {
        return $this->repoConfig;
    }

    protected function initialize()
    {
        parent::initialize();

        $this->scanDirectory($this->lookup);
    }

    private function scanDirectory($path)
    {
        $io = $this->io;

        $directory = new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::FOLLOW_SYMLINKS);
        $iterator = new \RecursiveIteratorIterator($directory);
        $regex = new \RegexIterator($iterator, '/^.+\.(zip|phar)$/i');
        foreach ($regex as $file) {
            /* @var $file \SplFileInfo */
            if (!$file->isFile()) {
                continue;
            }

            $package = $this->getComposerInformation($file);
            if (!$package) {
                $io->writeError("File <comment>{$file->getBasename()}</comment> doesn't seem to hold a package", true, IOInterface::VERBOSE);
                continue;
            }

            $template = 'Found package <info>%s</info> (<comment>%s</comment>) in file <info>%s</info>';
            $io->writeError(sprintf($template, $package->getName(), $package->getPrettyVersion(), $file->getBasename()), true, IOInterface::VERBOSE);

            $this->addPackage($package);
        }
    }

    private function getComposerInformation(\SplFileInfo $file)
    {
        $json = Zip::getComposerJson($file->getPathname());

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

        $package = JsonFile::parseJson($json, $file->getPathname().'#composer.json');
        $package['dist'] = array(
            'type' => 'zip',
            '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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

interface VersionCacheInterface
{
    /**
     * @param string $version
     * @param string $identifier
     * @return array|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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Package\AliasPackage;
use Composer\Package\PackageInterface;
use Composer\Package\CompletePackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\Constraint;

/**
 * A repository implementation that simply stores packages in an array
 *
 * @author Nils Adermann <naderman@naderman.de>
 */
class ArrayRepository extends BaseRepository
{
    /** @var PackageInterface[] */
    protected $packages;

    public function __construct(array $packages = array())
    {
        foreach ($packages as $package) {
            $this->addPackage($package);
        }
    }

    /**
     * {@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()) {
                $pkgConstraint = new Constraint('==', $package->getVersion());
                if (null === $constraint || $constraint->matches($pkgConstraint)) {
                    $packages[] = $package;
                }
            }
        }

        return $packages;
    }

    /**
     * {@inheritDoc}
     */
    public function search($query, $mode = 0, $type = null)
    {
        $regex = '{(?:'.implode('|', preg_split('{\s+}', $query)).')}i';

        $matches = array();
        foreach ($this->getPackages() as $package) {
            $name = $package->getName();
            if (isset($matches[$name])) {
                continue;
            }
            if (preg_match($regex, $name)
                || ($mode === self::SEARCH_FULLTEXT && $package instanceof CompletePackageInterface && preg_match($regex, implode(' ', (array) $package->getKeywords()) . ' ' . $package->getDescription()))
            ) {
                if (null !== $type && $package->getType() !== $type) {
                    continue;
                }

                $matches[$name] = array(
                    'name' => $package->getPrettyName(),
                    'description' => $package instanceof CompletePackageInterface ? $package->getDescription() : null,
                );
            }
        }

        return array_values($matches);
    }

    /**
     * {@inheritDoc}
     */
    public function hasPackage(PackageInterface $package)
    {
        $packageId = $package->getUniqueName();

        foreach ($this->getPackages() as $repoPackage) {
            if ($packageId === $repoPackage->getUniqueName()) {
                return true;
            }
        }

        return false;
    }

    /**
     * Adds a new package to the repository
     *
     * @param PackageInterface $package
     */
    public function addPackage(PackageInterface $package)
    {
        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);
            }
        }
    }

    protected function createAliasPackage(PackageInterface $package, $alias, $prettyAlias)
    {
        return new AliasPackage($package instanceof AliasPackage ? $package->getAliasOf() : $package, $alias, $prettyAlias);
    }

    /**
     * Removes package from repository.
     *
     * @param PackageInterface $package package instance
     */
    public function removePackage(PackageInterface $package)
    {
        $packageId = $package->getUniqueName();

        foreach ($this->getPackages() as $key => $repoPackage) {
            if ($packageId === $repoPackage->getUniqueName()) {
                array_splice($this->packages, $key, 1);

                return;
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public function getPackages()
    {
        if (null === $this->packages) {
            $this->initialize();
        }

        return $this->packages;
    }

    /**
     * Returns the number of packages in this repository
     *
     * @return int Number of packages
     */
    public function count()
    {
        return count($this->packages);
    }

    /**
     * Initializes the packages array. Mostly meant as an extension point.
     */
    protected function initialize()
    {
        $this->packages = array();
    }
}
<?php

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

namespace Composer\Repository;

use Composer\Package\PackageInterface;

/**
 * Composite repository.
 *
 * @author Beau Simensen <beau@dflydev.com>
 */
class CompositeRepository extends BaseRepository
{
    /**
     * List of repositories
     * @var array
     */
    private $repositories;

    /**
     * Constructor
     * @param array $repositories
     */
    public function __construct(array $repositories)
    {
        $this->repositories = array();
        foreach ($repositories as $repo) {
            $this->addRepository($repo);
        }
    }

    /**
     * Returns all the wrapped repositories
     *
     * @return array
     */
    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 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 removePackage(PackageInterface $package)
    {
        foreach ($this->repositories as $repository) {
            /* @var $repository RepositoryInterface */
            $repository->removePackage($package);
        }
    }

    /**
     * {@inheritdoc}
     */
    public function count()
    {
        $total = 0;
        foreach ($this->repositories as $repository) {
            /* @var $repository RepositoryInterface */
            $total += $repository->count();
        }

        return $total;
    }

    /**
     * Add a repository.
     * @param RepositoryInterface $repository
     */
    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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository\Vcs;

use Composer\Config;
use Composer\IO\IOInterface;

/**
 * @author Per Bernhardt <plb@webfactory.de>
 */
class GitBitbucketDriver extends BitbucketDriver
{
    /**
     * {@inheritDoc}
     */
    public function getRootIdentifier()
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getRootIdentifier();
        }

        if (null === $this->rootIdentifier) {
            if (! $this->getRepoData()) {
                return $this->fallbackDriver->getRootIdentifier();
            }

            if ($this->vcsType !== 'git') {
                throw new \RuntimeException(
                    $this->url.' does not appear to be a git repository, use '.
                    $this->cloneHttpsUrl.' if this is a mercurial bitbucket repository'
                );
            }

            $mainBranchData = $this->getMainBranchData();
            $this->rootIdentifier = !empty($mainBranchData['name']) ? $mainBranchData['name'] : 'master';
        }

        return $this->rootIdentifier;
    }

    /**
     * {@inheritDoc}
     */
    public static function supports(IOInterface $io, Config $config, $url, $deep = false)
    {
        if (!preg_match('#^https?://bitbucket\.org/([^/]+)/(.+?)\.git$#i', $url)) {
            return false;
        }

        if (!extension_loaded('openssl')) {
            $io->writeError('Skipping Bitbucket git driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE);

            return false;
        }

        return true;
    }

    /**
     * {@inheritdoc}
     */
    protected function setupFallbackDriver($url)
    {
        $this->fallbackDriver = new GitDriver(
            array('url' => $url),
            $this->io,
            $this->config,
            $this->process,
            $this->remoteFilesystem
        );
        $this->fallbackDriver->initialize();
    }

    /**
     * {@inheritdoc}
     */
    protected function generateSshUrl()
    {
        return 'git@' . $this->originUrl . ':' . $this->owner.'/'.$this->repository.'.git';
    }
}
<?php

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

namespace Composer\Repository\Vcs;

use Composer\Config;
use Composer\IO\IOInterface;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface VcsDriverInterface
{
    /**
     * Initializes the driver (git clone, svn checkout, fetch info etc)
     */
    public function initialize();

    /**
     * Return the composer.json file information
     *
     * @param  string $identifier Any identifier to a specific branch/tag/commit
     * @return array  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
     */
    public function getFileContent($file, $identifier);

    /**
     * Get the changedate for $identifier.
     *
     * @param  string    $identifier
     * @return \DateTime
     */
    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 Branch names as keys, identifiers as values
     */
    public function getBranches();

    /**
     * Return list of tags in the repository
     *
     * @return array Tag names as keys, identifiers as values
     */
    public function getTags();

    /**
     * @param  string $identifier Any identifier to a specific branch/tag/commit
     * @return array  With type, url reference and shasum keys.
     */
    public function getDist($identifier);

    /**
     * @param  string $identifier Any identifier to a specific branch/tag/commit
     * @return array  With type, url and reference keys.
     */
    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
     */
    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 $io, Config $config, $url, $deep = false);
}
<?php

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

namespace Composer\Repository\Vcs;

use Composer\Cache;
use Composer\Downloader\TransportException;
use Composer\Json\JsonFile;
use Composer\Util\Bitbucket;

abstract class BitbucketDriver extends VcsDriver
{
    /** @var Cache */
    protected $cache;
    protected $owner;
    protected $repository;
    protected $hasIssues;
    protected $rootIdentifier;
    protected $tags;
    protected $branches;
    protected $infoCache = array();
    protected $branchesUrl = '';
    protected $tagsUrl = '';
    protected $homeUrl = '';
    protected $website = '';
    protected $cloneHttpsUrl = '';

    /**
     * @var VcsDriver
     */
    protected $fallbackDriver;
    /** @var string|null if set either git or hg */
    protected $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,
            ))
        );
    }

    /**
     * {@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
     */
    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'),
                null,
                '&'
            )
        );

        $repoData = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource, true), $resource);
        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)) {
                return $this->infoCache[$identifier] = JsonFile::parseJson($res);
            }

            $composer = $this->getBaseComposerInformation($identifier);

            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;

            if ($this->shouldCache($identifier)) {
                $this->cache->write($identifier, json_encode($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->getContentsWithOAuthCredentials($resource);
    }

    /**
     * {@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 = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource);

        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) {
            $this->tags = array();
            $resource = sprintf(
                '%s?%s',
                $this->tagsUrl,
                http_build_query(
                    array(
                        'pagelen' => 100,
                        'fields' => 'values.name,values.target.hash,next',
                        'sort' => '-target.date',
                    ),
                    null,
                    '&'
                )
            );
            $hasNext = true;
            while ($hasNext) {
                $tagsData = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource);
                foreach ($tagsData['values'] as $data) {
                    $this->tags[$data['name']] = $data['target']['hash'];
                }
                if (empty($tagsData['next'])) {
                    $hasNext = false;
                } else {
                    $resource = $tagsData['next'];
                }
            }
            if ($this->vcsType === 'hg') {
                unset($this->tags['tip']);
            }
        }

        return $this->tags;
    }

    /**
     * {@inheritDoc}
     */
    public function getBranches()
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getBranches();
        }

        if (null === $this->branches) {
            $this->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',
                    ),
                    null,
                    '&'
                )
            );
            $hasNext = true;
            while ($hasNext) {
                $branchData = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource);
                foreach ($branchData['values'] as $data) {
                    // skip headless branches which seem to be deleted branches that bitbucket nevertheless returns in the API
                    if ($this->vcsType === 'hg' && empty($data['heads'])) {
                        continue;
                    }

                    $this->branches[$data['name']] = $data['target']['hash'];
                }
                if (empty($branchData['next'])) {
                    $hasNext = false;
                } else {
                    $resource = $branchData['next'];
                }
            }
        }

        return $this->branches;
    }

    /**
     * Get the remote content.
     *
     * @param string $url              The URL of content
     * @param bool   $fetchingRepoData
     *
     * @return mixed The result
     */
    protected function getContentsWithOAuthCredentials($url, $fetchingRepoData = false)
    {
        try {
            return parent::getContents($url);
        } catch (TransportException $e) {
            $bitbucketUtil = new Bitbucket($this->io, $this->config, $this->process, $this->remoteFilesystem);

            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) {
                    return $this->attemptCloneFallback();
                }
            }

            throw $e;
        }
    }

    /**
     * Generate an SSH URL
     *
     * @return string
     */
    abstract protected function generateSshUrl();

    protected function attemptCloneFallback()
    {
        try {
            $this->setupFallbackDriver($this->generateSshUrl());
        } 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
     */
    abstract protected function setupFallbackDriver($url);

    /**
     * @param  array $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|null
     */
    protected function getMainBranchData()
    {
        $resource = sprintf(
            'https://api.bitbucket.org/2.0/repositories/%s/%s?fields=mainbranch',
            $this->owner,
            $this->repository
        );

        $data = JsonFile::parseJson($this->getContentsWithOAuthCredentials($resource), $resource);
        if (isset($data['mainbranch'])) {
            return $data['mainbranch'];
        }

        return null;
    }
}
<?php

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

namespace Composer\Repository\Vcs;

use Composer\Util\ProcessExecutor;
use Composer\Util\Filesystem;
use Composer\Util\Git as GitUtil;
use Composer\IO\IOInterface;
use Composer\Cache;
use Composer\Config;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class GitDriver extends VcsDriver
{
    protected $cache;
    protected $tags;
    protected $branches;
    protected $rootIdentifier;
    protected $repoDir;
    protected $infoCache = array();

    /**
     * {@inheritDoc}
     */
    public function initialize()
    {
        if (Filesystem::isLocalPath($this->url)) {
            $this->url = preg_replace('{[\\/]\.git/?$}', '', $this->url);
            $this->repoDir = $this->url;
            $cacheUrl = realpath($this->url);
        } else {
            if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) {
                throw new \RuntimeException('GitDriver requires a usable cache directory, and it looks like you set it to be disabled');
            }

            $this->repoDir = $this->config->get('cache-vcs-dir') . '/' . preg_replace('{[^a-z0-9.]}i', '-', $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_match('{^ssh://[^@]+@[^:]+:[^0-9]+}', $this->url)) {
                throw new \InvalidArgumentException('The source URL '.$this->url.' is invalid, ssh URLs should have a port number after ":".'."\n".'Use ssh://git@example.com:22/path or just git@example.com:path if you do not want to provide a password or custom port.');
            }

            $gitUtil = new GitUtil($this->io, $this->config, $this->process, $fs);
            if (!$gitUtil->syncMirror($this->url, $this->repoDir)) {
                $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', '-', $cacheUrl));
    }

    /**
     * {@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_match('{^\* +(\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 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_match('{^([a-f0-9]{40}) refs/tags/(\S+?)(\^\{\})?$}', $tag, $match)) {
                    $this->tags[$match[2]] = $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_match('{^ *[^/]+/HEAD }', $branch)) {
                    if (preg_match('{^(?:\* )? *(\S+) *([a-f0-9]+)(?: .*)?$}', $branch, $match)) {
                        $branches[$match[1]] = $match[2];
                    }
                }
            }

            $this->branches = $branches;
        }

        return $this->branches;
    }

    /**
     * {@inheritDoc}
     */
    public static function supports(IOInterface $io, Config $config, $url, $deep = false)
    {
        if (preg_match('#(^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;
        }

        $process = new ProcessExecutor($io);

        return $process->execute('git ls-remote --heads ' . ProcessExecutor::escape($url), $output) === 0;
    }
}
<?php

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

namespace Composer\Repository\Vcs;

use Composer\Config;
use Composer\IO\IOInterface;

/**
 * @author Per Bernhardt <plb@webfactory.de>
 */
class HgBitbucketDriver extends BitbucketDriver
{
    /**
     * {@inheritDoc}
     */
    public function getRootIdentifier()
    {
        if ($this->fallbackDriver) {
            return $this->fallbackDriver->getRootIdentifier();
        }

        if (null === $this->rootIdentifier) {
            if (! $this->getRepoData()) {
                return $this->fallbackDriver->getRootIdentifier();
            }

            if ($this->vcsType !== 'hg') {
                throw new \RuntimeException(
                    $this->url.' does not appear to be a mercurial repository, use '.
                    $this->cloneHttpsUrl.' if this is a git bitbucket repository'
                );
            }

            $mainBranchData = $this->getMainBranchData();
            $this->rootIdentifier = !empty($mainBranchData['name']) ? $mainBranchData['name'] : 'default';
        }

        return $this->rootIdentifier;
    }

    /**
     * {@inheritDoc}
     */
    public static function supports(IOInterface $io, Config $config, $url, $deep = false)
    {
        if (!preg_match('#^https?://bitbucket\.org/([^/]+)/([^/]+)/?$#i', $url)) {
            return false;
        }

        if (!extension_loaded('openssl')) {
            $io->writeError('Skipping Bitbucket hg driver for '.$url.' because the OpenSSL PHP extension is missing.', true, IOInterface::VERBOSE);

            return false;
        }

        return true;
    }

    /**
     * {@inheritdoc}
     */
    protected function setupFallbackDriver($url)
    {
        $this->fallbackDriver = new HgDriver(
            array('url' => $url),
            $this->io,
            $this->config,
            $this->process,
            $this->remoteFilesystem
        );
        $this->fallbackDriver->initialize();
    }

    /**
     * {@inheritdoc}
     */
    protected function generateSshUrl()
    {
        return 'ssh://hg@' . $this->originUrl . '/' . $this->owner.'/'.$this->repository;
    }
}
<?php

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

namespace Composer\Repository\Vcs;

use Composer\Cache;
use Composer\Config;
use Composer\Json\JsonFile;
use Composer\Util\ProcessExecutor;
use Composer\Util\Filesystem;
use Composer\Util\Svn as SvnUtil;
use Composer\IO\IOInterface;
use Composer\Downloader\TransportException;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Till Klampaeckel <till@php.net>
 */
class SvnDriver extends VcsDriver
{
    /**
     * @var Cache
     */
    protected $cache;
    protected $baseUrl;
    protected $tags;
    protected $branches;
    protected $rootIdentifier;
    protected $infoCache = array();

    protected $trunkPath = 'trunk';
    protected $branchesPath = 'branches';
    protected $tagsPath = 'tags';
    protected $packagePath = '';
    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->url, 0, $pos);
        }

        $this->cache = new Cache($this->io, $this->config->get('cache-repo-dir').'/'.preg_replace('{[^a-z0-9.]}i', '-', $this->baseUrl));

        $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_match('{@\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_match('{^Last Changed Date: ([^(]+)}', $line, $match)) {
                return new \DateTime($match[1], new \DateTimeZone('UTC'));
            }
        }

        return null;
    }

    /**
     * {@inheritDoc}
     */
    public function getTags()
    {
        if (null === $this->tags) {
            $this->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_match('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) {
                            if (isset($match[1]) && isset($match[2]) && $match[2] !== './') {
                                $this->tags[rtrim($match[2], '/')] = $this->buildIdentifier(
                                    '/' . $this->tagsPath . '/' . $match[2],
                                    $match[1]
                                );
                            }
                        }
                    }
                }
            }
        }

        return $this->tags;
    }

    /**
     * {@inheritDoc}
     */
    public function getBranches()
    {
        if (null === $this->branches) {
            $this->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_match('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) {
                        if (isset($match[1]) && isset($match[2]) && $match[2] === './') {
                            $this->branches['trunk'] = $this->buildIdentifier(
                                '/' . $this->trunkPath,
                                $match[1]
                            );
                            $this->rootIdentifier = $this->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_match('{^\s*(\S+).*?(\S+)\s*$}', $line, $match)) {
                            if (isset($match[1]) && isset($match[2]) && $match[2] !== './') {
                                $this->branches[rtrim($match[2], '/')] = $this->buildIdentifier(
                                    '/' . $this->branchesPath . '/' . $match[2],
                                    $match[1]
                                );
                            }
                        }
                    }
                }
            }
        }

        return $this->branches;
    }

    /**
     * {@inheritDoc}
     */
    public static function supports(IOInterface $io, Config $config, $url, $deep = false)
    {
        $url = self::normalizeUrl($url);
        if (preg_match('#(^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;
        }

        $processExecutor = new ProcessExecutor($io);

        $exit = $processExecutor->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($processExecutor->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($processExecutor->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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository\Vcs;

use Composer\Config;
use Composer\Cache;
use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
use Composer\Downloader\TransportException;
use Composer\Util\RemoteFilesystem;
use Composer\Util\GitLab;

/**
 * Driver for GitLab API, use the Git driver for local checkouts.
 *
 * @author Henrik Bjørnskov <henrik@bjrnskov.dk>
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class GitLabDriver extends VcsDriver
{
    private $scheme;
    private $namespace;
    private $repository;

    /**
     * @var array Project data returned by GitLab API
     */
    private $project;

    /**
     * @var array Keeps commits returned by GitLab API
     */
    private $commits = array();

    /**
     * @var array List of tag => reference
     */
    private $tags;

    /**
     * @var array List of branch => reference
     */
    private $branches;

    /**
     * Git Driver
     *
     * @var GitDriver
     */
    protected $gitDriver;

    /**
     * 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_match(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 = $this->determineOrigin($configuredDomains, $guessedDomain, $urlParts, $match['port']);

        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->fetchProject();
    }

    /**
     * Updates the RemoteFilesystem instance.
     * Mainly useful for tests.
     *
     * @internal
     */
    public function setRemoteFilesystem(RemoteFilesystem $remoteFilesystem)
    {
        $this->remoteFilesystem = $remoteFilesystem;
    }

    /**
     * {@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_match('{[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);
        } 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();
    }

    /**
     * {@inheritDoc}
     */
    public function getRepositoryUrl()
    {
        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 {
            $data = JsonFile::parseJson($this->getContents($resource), $resource);

            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();
            } else {
                $resource = false;
            }
        } while ($resource);

        return $references;
    }

    protected function fetchProject()
    {
        // we need to fetch the default branch from the api
        $resource = $this->getApiUrl();
        $this->project = JsonFile::parseJson($this->getContents($resource, true), $resource);
        if (isset($this->project['visibility'])) {
            $this->isPrivate = $this->project['visibility'] !== 'public';
        } else {
            // client is not authendicated, therefore repository has to be public
            $this->isPrivate = false;
        }
    }

    protected function attemptCloneFallback()
    {
        try {
            if ($this->isPrivate === false) {
                $url = $this->generatePublicUrl();
            } else {
                $url = $this->generateSshUrl();
            }

            // 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;
        } 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';
    }

    protected function generatePublicUrl()
    {
        return $this->scheme . '://' . $this->originUrl . '/'.$this->namespace.'/'.$this->repository.'.git';
    }

    protected function setupGitDriver($url)
    {
        $this->gitDriver = new GitDriver(
            array('url' => $url),
            $this->io,
            $this->config,
            $this->process,
            $this->remoteFilesystem
        );
        $this->gitDriver->initialize();
    }

    /**
     * {@inheritDoc}
     */
    protected function getContents($url, $fetchingRepoData = false)
    {
        try {
            $res = parent::getContents($url);

            if ($fetchingRepoData) {
                $json = JsonFile::parseJson($res, $url);

                // force auth as the unauthenticated version of the API is broken
                if (!isset($json['default_branch'])) {
                    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 $res;
        } catch (TransportException $e) {
            $gitLabUtil = new GitLab($this->io, $this->config, $this->process, $this->remoteFilesystem);

            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()) {
                        return $this->attemptCloneFallback();
                    }
                    $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) {
                        return $this->attemptCloneFallback();
                    }

                    throw $e;

                default:
                    throw $e;
            }
        }
    }

    /**
     * Uses the config `gitlab-domains` to see if the driver supports the url for the
     * repository given.
     *
     * {@inheritDoc}
     */
    public static function supports(IOInterface $io, Config $config, $url, $deep = false)
    {
        if (!preg_match(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.', true, IOInterface::VERBOSE);

            return false;
        }

        return true;
    }

    private function getNextPage()
    {
        $headers = $this->remoteFilesystem->getLastHeaders();
        foreach ($headers as $header) {
            if (preg_match('{^link:\s*(.+?)\s*$}i', $header, $match)) {
                $links = explode(',', $match[1]);
                foreach ($links as $link) {
                    if (preg_match('{<(.+?)>; *rel="next"}', $link, $match)) {
                        return $match[1];
                    }
                }
            }
        }
    }

    /**
     * @param  array       $configuredDomains
     * @param  string      $guessedDomain
     * @param  array       $urlParts
     * @return bool|string
     */
    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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository\Vcs;

use Composer\Config;
use Composer\Downloader\TransportException;
use Composer\Json\JsonFile;
use Composer\Cache;
use Composer\IO\IOInterface;
use Composer\Util\GitHub;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class GitHubDriver extends VcsDriver
{
    protected $cache;
    protected $owner;
    protected $repository;
    protected $tags;
    protected $branches;
    protected $rootIdentifier;
    protected $repoData;
    protected $hasIssues;
    protected $infoCache = array();
    protected $isPrivate = false;

    /**
     * Git Driver
     *
     * @var GitDriver
     */
    protected $gitDriver;

    /**
     * {@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);

        if ( $this->config->get('use-github-api') === false || (isset($this->repoConfig['no-api']) && $this->repoConfig['no-api'] ) ){
            $this->setupGitDriver($this->url);

            return;
        }

        $this->fetchRootIdentifier();
    }

    public function getRepositoryUrl()
    {
        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';
    }

    /**
     * {@inheritDoc}
     */
    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)) {
                return $this->infoCache[$identifier] = JsonFile::parseJson($res);
            }

            $composer = $this->getBaseComposerInformation($identifier);

            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 ($this->shouldCache($identifier)) {
                $this->cache->write($identifier, json_encode($composer));
            }

            $this->infoCache[$identifier] = $composer;
        }

        return $this->infoCache[$identifier];
    }

    /**
     * {@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 = JsonFile::parseJson($this->getContents($resource));
        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 = JsonFile::parseJson($this->getContents($resource), $resource);

        return new \DateTime($commit['commit']['committer']['date']);
    }

    /**
     * {@inheritDoc}
     */
    public function getTags()
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getTags();
        }
        if (null === $this->tags) {
            $this->tags = array();
            $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/tags?per_page=100';

            do {
                $tagsData = JsonFile::parseJson($this->getContents($resource), $resource);
                foreach ($tagsData as $tag) {
                    $this->tags[$tag['name']] = $tag['commit']['sha'];
                }

                $resource = $this->getNextPage();
            } while ($resource);
        }

        return $this->tags;
    }

    /**
     * {@inheritDoc}
     */
    public function getBranches()
    {
        if ($this->gitDriver) {
            return $this->gitDriver->getBranches();
        }
        if (null === $this->branches) {
            $this->branches = array();
            $resource = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository.'/git/refs/heads?per_page=100';

            $branchBlacklist = array('gh-pages');

            do {
                $branchData = JsonFile::parseJson($this->getContents($resource), $resource);
                foreach ($branchData as $branch) {
                    $name = substr($branch['ref'], 11);
                    if (!in_array($name, $branchBlacklist)) {
                        $this->branches[$name] = $branch['object']['sha'];
                    }
                }

                $resource = $this->getNextPage();
            } while ($resource);
        }

        return $this->branches;
    }

    /**
     * {@inheritDoc}
     */
    public static function supports(IOInterface $io, Config $config, $url, $deep = false)
    {
        if (!preg_match('#^((?: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.', true, IOInterface::VERBOSE);

            return false;
        }

        return true;
    }

    /**
     * Gives back the loaded <github-api>/repos/<owner>/<repo> result
     *
     * @return array|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}
     */
    protected function getContents($url, $fetchingRepoData = false)
    {
        try {
            return parent::getContents($url);
        } catch (TransportException $e) {
            $gitHubUtil = new GitHub($this->io, $this->config, $this->process, $this->remoteFilesystem);

            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()) {
                        return $this->attemptCloneFallback();
                    }

                    $scopesIssued = array();
                    $scopesNeeded = array();
                    if ($headers = $e->getHeaders()) {
                        if ($scopes = $this->remoteFilesystem->findHeaderValue($headers, 'X-OAuth-Scopes')) {
                            $scopesIssued = explode(' ', $scopes);
                        }
                        if ($scopes = $this->remoteFilesystem->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) {
                        return $this->attemptCloneFallback();
                    }

                    $rateLimited = $gitHubUtil->isRateLimited($e->getHeaders());

                    if (!$this->io->hasAuthentication($this->originUrl)) {
                        if (!$this->io->isInteractive()) {
                            $this->io->writeError('<error>GitHub API limit exhausted. Failed to get metadata for the '.$this->url.' repository, try running in interactive mode so that you can enter your GitHub credentials to increase the API limit</error>');
                            throw $e;
                        }

                        $gitHubUtil->authorizeOAuthInteractively($this->originUrl, 'API limit exhausted. Enter your GitHub credentials to get a larger API limit (<info>'.$this->url.'</info>)');

                        return parent::getContents($url);
                    }

                    if ($rateLimited) {
                        $rateLimit = $gitHubUtil->getRateLimit($e->getHeaders());
                        $this->io->writeError(sprintf(
                            '<error>GitHub API limit (%d calls/hr) is exhausted. You are already authorized so you have to wait until %s before doing more requests</error>',
                            $rateLimit['limit'],
                            $rateLimit['reset']
                        ));
                    }

                    throw $e;

                default:
                    throw $e;
            }
        }
    }

    /**
     * Fetch root identifier from GitHub
     *
     * @throws TransportException
     */
    protected function fetchRootIdentifier()
    {
        if ($this->repoData) {
            return;
        }

        $repoDataUrl = $this->getApiUrl() . '/repos/'.$this->owner.'/'.$this->repository;

        $this->repoData = JsonFile::parseJson($this->getContents($repoDataUrl, true), $repoDataUrl);
        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']);
    }

    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;
        } catch (\RuntimeException $e) {
            $this->gitDriver = null;

            $this->io->writeError('<error>Failed to clone the '.$this->generateSshUrl().' repository, try running in interactive mode so that you can enter your GitHub credentials</error>');
            throw $e;
        }
    }

    protected function setupGitDriver($url)
    {
        $this->gitDriver = new GitDriver(
            array('url' => $url),
            $this->io,
            $this->config,
            $this->process,
            $this->remoteFilesystem
        );
        $this->gitDriver->initialize();
    }

    protected function getNextPage()
    {
        $headers = $this->remoteFilesystem->getLastHeaders();
        foreach ($headers as $header) {
            if (preg_match('{^link:\s*(.+?)\s*$}i', $header, $match)) {
                $links = explode(',', $match[1]);
                foreach ($links as $link) {
                    if (preg_match('{<(.+?)>; *rel="next"}', $link, $match)) {
                        return $match[1];
                    }
                }
            }
        }
    }
}
<?php

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

namespace Composer\Repository\Vcs;

use Composer\Cache;
use Composer\Downloader\TransportException;
use Composer\Config;
use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
use Composer\Util\ProcessExecutor;
use Composer\Util\RemoteFilesystem;
use Composer\Util\Filesystem;

/**
 * A driver implementation for driver with authentication interaction.
 *
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 */
abstract class VcsDriver implements VcsDriverInterface
{
    /** @var string */
    protected $url;
    /** @var string */
    protected $originUrl;
    /** @var array */
    protected $repoConfig;
    /** @var IOInterface */
    protected $io;
    /** @var Config */
    protected $config;
    /** @var ProcessExecutor */
    protected $process;
    /** @var RemoteFilesystem */
    protected $remoteFilesystem;
    /** @var array */
    protected $infoCache = array();
    /** @var Cache */
    protected $cache;

    /**
     * Constructor.
     *
     * @param array            $repoConfig       The repository configuration
     * @param IOInterface      $io               The IO instance
     * @param Config           $config           The composer configuration
     * @param ProcessExecutor  $process          Process instance, injectable for mocking
     * @param RemoteFilesystem $remoteFilesystem Remote Filesystem, injectable for mocking
     */
    final public function __construct(array $repoConfig, IOInterface $io, Config $config, ProcessExecutor $process = null, RemoteFilesystem $remoteFilesystem = null)
    {
        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->process = $process ?: new ProcessExecutor($io);
        $this->remoteFilesystem = $remoteFilesystem ?: Factory::createRemoteFilesystem($this->io, $config);
    }

    /**
     * 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_match('{[a-f0-9]{40}}i', $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($identifier, json_encode($composer));
            }

            $this->infoCache[$identifier] = $composer;
        }

        return $this->infoCache[$identifier];
    }

    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 mixed The result
     */
    protected function getContents($url)
    {
        $options = isset($this->repoConfig['options']) ? $this->repoConfig['options'] : array();

        return $this->remoteFilesystem->getContents($this->originUrl, $url, false, $options);
    }

    /**
     * {@inheritDoc}
     */
    public function cleanup()
    {
        return;
    }
}
<?php

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

namespace Composer\Repository\Vcs;

use Composer\Cache;
use Composer\Config;
use Composer\Util\ProcessExecutor;
use Composer\Util\Filesystem;
use Composer\IO\IOInterface;

/**
 * @author BohwaZ <http://bohwaz.net/>
 */
class FossilDriver extends VcsDriver
{
    protected $tags;
    protected $branches;
    protected $rootIdentifier;
    protected $repoFile;
    protected $checkoutDir;
    protected $infoCache = array();

    /**
     * {@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($this->config->get('cache-repo-dir')) || !Cache::isUsable($this->config->get('cache-vcs-dir'))) {
                throw new \RuntimeException('FossilDriver requires a usable cache directory, and it looks like you set it to be disabled');
            }

            $localName = preg_replace('{[^a-z0-9]}i', '-', $this->url);
            $this->repoFile = $this->config->get('cache-repo-dir') . '/' . $localName . '.fossil';
            $this->checkoutDir = $this->config->get('cache-vcs-dir') . '/' . $localName . '/';

            $this->updateLocalRepo();
        }

        $this->getTags();
        $this->getBranches();
    }

    /**
     * Check that fossil can be invoked via command line.
     */
    protected function checkFossil()
    {
        if (0 !== $this->process->execute('fossil version', $ignoredOutput)) {
            throw new \RuntimeException("fossil was not found, check that it is installed and in your PATH env.\n\n" . $this->process->getErrorOutput());
        }
    }

    /**
     * Clone or update existing local fossil repository.
     */
    protected function updateLocalRepo()
    {
        $fs = new Filesystem();
        $fs->ensureDirectoryExists($this->checkoutDir);

        if (!is_writable(dirname($this->checkoutDir))) {
            throw new \RuntimeException('Can not clone '.$this->url.' to access package information. The "'.$this->checkoutDir.'" directory is not writable by the current user.');
        }

        // update the repo if it is a valid fossil repository
        if (is_file($this->repoFile) && is_dir($this->checkoutDir) && 0 === $this->process->execute('fossil info', $output, $this->checkoutDir)) {
            if (0 !== $this->process->execute('fossil pull', $output, $this->checkoutDir)) {
                $this->io->writeError('<error>Failed to update '.$this->url.', package information from this repository may be outdated ('.$this->process->getErrorOutput().')</error>');
            }
        } else {
            // clean up directory and do a fresh clone into it
            $fs->removeDirectory($this->checkoutDir);
            $fs->remove($this->repoFile);

            $fs->ensureDirectoryExists($this->checkoutDir);

            if (0 !== $this->process->execute(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 (0 !== $this->process->execute(sprintf('fossil open %s --nested', 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($ckout, $date, $message) = 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();
            $bookmarks = 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 $io, Config $config, $url, $deep = false)
    {
        if (preg_match('#(^(?:https?|ssh)://(?:[^@]@)?(?:chiselapp\.com|fossil\.))#i', $url)) {
            return true;
        }

        if (preg_match('!/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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository\Vcs;

use Composer\Config;
use Composer\Cache;
use Composer\IO\IOInterface;
use Composer\Util\ProcessExecutor;
use Composer\Util\Perforce;

/**
 * @author Matt Whittom <Matt.Whittom@veteransunited.com>
 */
class PerforceDriver extends VcsDriver
{
    protected $depot;
    protected $branch;
    /** @var Perforce */
    protected $perforce;

    /**
     * {@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();

        return true;
    }

    private function initPerforce($repoConfig)
    {
        if (!empty($this->perforce)) {
            return;
        }

        if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) {
            throw new \RuntimeException('PerforceDriver requires a usable cache directory, and it looks like you set it to be disabled');
        }

        $repoDir = $this->config->get('cache-vcs-dir') . '/' . $this->depot;
        $this->perforce = Perforce::create($repoConfig, $this->getUrl(), $repoDir, $this->process, $this->io);
    }

    /**
     * {@inheritdoc}
     */
    public function getFileContent($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)
    {
        $source = array(
            'type' => 'perforce',
            'url' => $this->repoConfig['url'],
            'reference' => $identifier,
            'p4user' => $this->perforce->getUser(),
        );

        return $source;
    }

    /**
     * {@inheritDoc}
     */
    public function getUrl()
    {
        return $this->url;
    }

    /**
     * {@inheritDoc}
     */
    public function hasComposerFile($identifier)
    {
        $composerInfo = $this->perforce->getComposerInformation('//' . $this->depot . '/' . $identifier);
        $composerInfoIdentifier = $identifier;

        return !empty($composerInfo);
    }

    /**
     * {@inheritDoc}
     */
    public function getContents($url)
    {
        return false;
    }

    /**
     * {@inheritDoc}
     */
    public static function supports(IOInterface $io, Config $config, $url, $deep = false)
    {
        if ($deep || preg_match('#\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;
    }

    public function getDepot()
    {
        return $this->depot;
    }

    public function getBranch()
    {
        return $this->branch;
    }
}
<?php

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

namespace Composer\Repository\Vcs;

use Composer\Config;
use Composer\Cache;
use Composer\Util\Hg as HgUtils;
use Composer\Util\ProcessExecutor;
use Composer\Util\Filesystem;
use Composer\IO\IOInterface;

/**
 * @author Per Bernhardt <plb@webfactory.de>
 */
class HgDriver extends VcsDriver
{
    protected $tags;
    protected $branches;
    protected $rootIdentifier;
    protected $repoDir;
    protected $infoCache = array();

    /**
     * {@inheritDoc}
     */
    public function initialize()
    {
        if (Filesystem::isLocalPath($this->url)) {
            $this->repoDir = $this->url;
        } else {
            if (!Cache::isUsable($this->config->get('cache-vcs-dir'))) {
                throw new \RuntimeException('HgDriver requires a usable cache directory, and it looks like you set it to be disabled');
            }

            $cacheDir = $this->config->get('cache-vcs-dir');
            $this->repoDir = $cacheDir . '/' . preg_replace('{[^a-z0-9]}i', '-', $this->url) . '/';

            $fs = new Filesystem();
            $fs->ensureDirectoryExists($cacheDir);

            if (!is_writable(dirname($this->repoDir))) {
                throw new \RuntimeException('Can not clone '.$this->url.' to access package information. The "'.$cacheDir.'" directory is not writable by the current user.');
            }

            // Ensure we are allowed to use this URL by config
            $this->config->prohibitUrlByConfig($this->url, $this->io);

            $hgUtils = new HgUtils($this->io, $this->config, $this->process);

            // update the repo if it is a valid hg repository
            if (is_dir($this->repoDir) && 0 === $this->process->execute('hg summary', $output, $this->repoDir)) {
                if (0 !== $this->process->execute('hg pull', $output, $this->repoDir)) {
                    $this->io->writeError('<error>Failed to update '.$this->url.', package information from this repository may be outdated ('.$this->process->getErrorOutput().')</error>');
                }
            } else {
                // clean up directory and do a fresh clone into it
                $fs->removeDirectory($this->repoDir);

                $repoDir = $this->repoDir;
                $command = function ($url) use ($repoDir) {
                    return sprintf('hg clone --noupdate %s %s', ProcessExecutor::escape($url), ProcessExecutor::escape($repoDir));
                };

                $hgUtils->runCommand($command, $this->url, null);
            }
        }

        $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;
        }

        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_match('(^([^\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_match('(^([^\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_match('(^(?:[\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 $io, Config $config, $url, $deep = false)
    {
        if (preg_match('#(^(?: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;
        }

        $processExecutor = new ProcessExecutor($io);
        $exit = $processExecutor->execute(sprintf('hg identify %s', ProcessExecutor::escape($url)), $ignored);

        return $exit === 0;
    }
}
<?php

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

namespace Composer\Repository;

use Composer\Package\Loader\ArrayLoader;
use Composer\Package\PackageInterface;
use Composer\Package\AliasPackage;
use Composer\Package\Version\VersionParser;
use Composer\DependencyResolver\Pool;
use Composer\Json\JsonFile;
use Composer\Cache;
use Composer\Config;
use Composer\Composer;
use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\Util\RemoteFilesystem;
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;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class ComposerRepository extends ArrayRepository implements ConfigurableRepositoryInterface
{
    protected $config;
    protected $repoConfig;
    protected $options;
    protected $url;
    protected $baseUrl;
    protected $io;
    protected $rfs;
    protected $cache;
    protected $notifyUrl;
    protected $searchUrl;
    protected $hasProviders = false;
    protected $providersUrl;
    protected $lazyProvidersUrl;
    protected $providerListing;
    protected $providers = array();
    protected $providersByUid = array();
    protected $loader;
    protected $rootAliases;
    protected $allowSslDowngrade = false;
    protected $eventDispatcher;
    protected $sourceMirrors;
    protected $distMirrors;
    private $degradedMode = false;
    private $rootData;
    private $hasPartialPackages;
    private $partialPackagesByName;

    public function __construct(array $repoConfig, IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, RemoteFilesystem $rfs = null)
    {
        parent::__construct();
        if (!preg_match('{^[\w.]+\??://}', $repoConfig['url'])) {
            // assume http as the default protocol
            $repoConfig['url'] = 'http://'.$repoConfig['url'];
        }
        $repoConfig['url'] = rtrim($repoConfig['url'], '/');

        if ('https?' === substr($repoConfig['url'], 0, 6)) {
            $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->config = $config;
        $this->options = $repoConfig['options'];
        $this->url = $repoConfig['url'];

        // force url for packagist.org to repo.packagist.org
        if (preg_match('{^(?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', '-', $this->url), 'a-z0-9.$');
        $this->loader = new ArrayLoader();
        if ($rfs && $this->options) {
            $rfs = clone $rfs;
            $rfs->setOptions($this->options);
        }
        $this->rfs = $rfs ?: Factory::createRemoteFilesystem($this->io, $this->config, $this->options);
        $this->eventDispatcher = $eventDispatcher;
        $this->repoConfig = $repoConfig;
    }

    public function getRepoConfig()
    {
        return $this->repoConfig;
    }

    public function setRootAliases(array $rootAliases)
    {
        $this->rootAliases = $rootAliases;
    }

    /**
     * {@inheritDoc}
     */
    public function findPackage($name, $constraint)
    {
        if (!$this->hasProviders()) {
            return parent::findPackage($name, $constraint);
        }

        $name = strtolower($name);
        if (!$constraint instanceof ConstraintInterface) {
            $versionParser = new VersionParser();
            $constraint = $versionParser->parseConstraints($constraint);
        }

        foreach ($this->getProviderNames() as $providerName) {
            if ($name === $providerName) {
                $packages = $this->whatProvides(new Pool('dev'), $providerName);
                foreach ($packages as $package) {
                    if ($name === $package->getName()) {
                        $pkgConstraint = new Constraint('==', $package->getVersion());
                        if ($constraint->matches($pkgConstraint)) {
                            return $package;
                        }
                    }
                }
                break;
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public function findPackages($name, $constraint = null)
    {
        if (!$this->hasProviders()) {
            return parent::findPackages($name, $constraint);
        }
        // normalize name
        $name = strtolower($name);

        if (null !== $constraint && !$constraint instanceof ConstraintInterface) {
            $versionParser = new VersionParser();
            $constraint = $versionParser->parseConstraints($constraint);
        }

        $packages = array();

        foreach ($this->getProviderNames() as $providerName) {
            if ($name === $providerName) {
                $candidates = $this->whatProvides(new Pool('dev'), $providerName);
                foreach ($candidates as $package) {
                    if ($name === $package->getName()) {
                        $pkgConstraint = new Constraint('==', $package->getVersion());
                        if (null === $constraint || $constraint->matches($pkgConstraint)) {
                            $packages[] = $package;
                        }
                    }
                }
                break;
            }
        }

        return $packages;
    }

    public function getPackages()
    {
        if ($this->hasProviders()) {
            throw new \LogicException('Composer repositories that have providers can not load the complete list of packages, use getProviderNames instead.');
        }

        return parent::getPackages();
    }

    /**
     * {@inheritDoc}
     */
    public function search($query, $mode = 0, $type = null)
    {
        $this->loadRootServerFile();

        if ($this->searchUrl && $mode === self::SEARCH_FULLTEXT) {
            $url = str_replace(array('%query%', '%type%'), array($query, $type), $this->searchUrl);

            $origin = RemoteFilesystem::getOrigin($url);
            $json = $this->rfs->getContents($origin, $url, false);
            $search = JsonFile::parseJson($json, $url);

            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'])) {
                    $results[] = $result;
                }
            }

            return $results;
        }

        if ($this->hasProviders()) {
            $results = array();
            $regex = '{(?:'.implode('|', preg_split('{\s+}', $query)).')}i';

            foreach ($this->getProviderNames() as $name) {
                if (preg_match($regex, $name)) {
                    $results[] = array('name' => $name);
                }
            }

            return $results;
        }

        return parent::search($query, $mode);
    }

    public 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();
    }

    protected function configurePackageTransportOptions(PackageInterface $package)
    {
        foreach ($package->getDistUrls() as $url) {
            if (strpos($url, $this->baseUrl) === 0) {
                $package->setTransportOptions($this->options);

                return;
            }
        }
    }

    public function hasProviders()
    {
        $this->loadRootServerFile();

        return $this->hasProviders;
    }

    public function resetPackageIds()
    {
        foreach ($this->providersByUid as $package) {
            if ($package instanceof AliasPackage) {
                $package->getAliasOf()->setId(-1);
            }
            $package->setId(-1);
        }
    }

    /**
     * @param  Pool        $pool
     * @param  string      $name          package name
     * @param  bool        $bypassFilters If set to true, this bypasses the stability filtering, and forces a recompute without cache
     * @return array|mixed
     */
    public function whatProvides(Pool $pool, $name, $bypassFilters = false)
    {
        if (isset($this->providers[$name]) && !$bypassFilters) {
            return $this->providers[$name];
        }

        if ($this->hasPartialPackages && null === $this->partialPackagesByName) {
            $this->initializePartialPackages();
        }

        if (!$this->hasPartialPackages || !isset($this->partialPackagesByName[$name])) {
            // skip platform packages, root package and composer-plugin-api
            if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $name) || '__root__' === $name || 'composer-plugin-api' === $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 ($cacheKey) {
                if (!$useLastModifiedCheck && $hash && $this->cache->sha256($cacheKey) === $hash) {
                    $packages = json_decode($this->cache->read($cacheKey), true);
                } elseif ($useLastModifiedCheck) {
                    if ($contents = $this->cache->read($cacheKey)) {
                        $contents = json_decode($contents, true);
                        if (isset($contents['last-modified'])) {
                            $response = $this->fetchFileIfLastModified($url, $cacheKey, $contents['last-modified']);
                            if (true === $response) {
                                $packages = $contents;
                            } elseif ($response) {
                                $packages = $response;
                            }
                        }
                    }
                }
            }

            if (!$packages) {
                try {
                    $packages = $this->fetchFile($url, $cacheKey, $hash, $useLastModifiedCheck);
                } catch (TransportException $e) {
                    // 404s are acceptable for lazy provider repos
                    if ($e->getStatusCode() === 404 && $this->lazyProvidersUrl) {
                        $packages = array('packages' => array());
                    } else {
                        throw $e;
                    }
                }
            }

            $loadingPartialPackage = false;
        } else {
            $packages = array('packages' => array('versions' => $this->partialPackagesByName[$name]));
            $loadingPartialPackage = true;
        }

        $this->providers[$name] = array();
        foreach ($packages['packages'] as $versions) {
            foreach ($versions as $version) {
                if (!$loadingPartialPackage && $this->hasPartialPackages && isset($this->partialPackagesByName[$version['name']])) {
                    continue;
                }

                // avoid loading the same objects twice
                if (isset($this->providersByUid[$version['uid']])) {
                    // skip if already assigned
                    if (!isset($this->providers[$name][$version['uid']])) {
                        // expand alias in two packages
                        if ($this->providersByUid[$version['uid']] instanceof AliasPackage) {
                            $this->providers[$name][$version['uid']] = $this->providersByUid[$version['uid']]->getAliasOf();
                            $this->providers[$name][$version['uid'].'-alias'] = $this->providersByUid[$version['uid']];
                        } else {
                            $this->providers[$name][$version['uid']] = $this->providersByUid[$version['uid']];
                        }
                        // check for root aliases
                        if (isset($this->providersByUid[$version['uid'].'-root'])) {
                            $this->providers[$name][$version['uid'].'-root'] = $this->providersByUid[$version['uid'].'-root'];
                        }
                    }
                } else {
                    if (!$bypassFilters && !$pool->isPackageAcceptable(strtolower($version['name']), VersionParser::parseStability($version['version']))) {
                        continue;
                    }

                    // load acceptable packages in the providers
                    $package = $this->createPackage($version, 'Composer\Package\CompletePackage');
                    $package->setRepository($this);

                    if ($package instanceof AliasPackage) {
                        $aliased = $package->getAliasOf();
                        $aliased->setRepository($this);

                        $this->providers[$name][$version['uid']] = $aliased;
                        $this->providers[$name][$version['uid'].'-alias'] = $package;

                        // override provider with its alias so it can be expanded in the if block above
                        $this->providersByUid[$version['uid']] = $package;
                    } else {
                        $this->providers[$name][$version['uid']] = $package;
                        $this->providersByUid[$version['uid']] = $package;
                    }

                    // handle root package aliases
                    unset($rootAliasData);

                    if (isset($this->rootAliases[$package->getName()][$package->getVersion()])) {
                        $rootAliasData = $this->rootAliases[$package->getName()][$package->getVersion()];
                    } elseif ($package instanceof AliasPackage && isset($this->rootAliases[$package->getName()][$package->getAliasOf()->getVersion()])) {
                        $rootAliasData = $this->rootAliases[$package->getName()][$package->getAliasOf()->getVersion()];
                    }

                    if (isset($rootAliasData)) {
                        $alias = $this->createAliasPackage($package, $rootAliasData['alias_normalized'], $rootAliasData['alias']);
                        $alias->setRepository($this);

                        $this->providers[$name][$version['uid'].'-root'] = $alias;
                        $this->providersByUid[$version['uid'].'-root'] = $alias;
                    }
                }
            }
        }

        $result = $this->providers[$name];

        // clean up the cache because otherwise using this puts the repo in an inconsistent state with a polluted unfiltered cache
        // which is likely not an issue but might cause hard to track behaviors depending on how the repo is used
        if ($bypassFilters) {
            foreach ($this->providers[$name] as $uid => $provider) {
                unset($this->providersByUid[$uid]);
            }
            unset($this->providers[$name]);
        }

        return $result;
    }

    /**
     * {@inheritDoc}
     */
    protected function initialize()
    {
        parent::initialize();

        $repoData = $this->loadDataFromServer();

        foreach ($repoData as $package) {
            $this->addPackage($this->createPackage($package, 'Composer\Package\CompletePackage'));
        }
    }

    /**
     * Adds a new package to the repository
     *
     * @param PackageInterface $package
     */
    public function addPackage(PackageInterface $package)
    {
        parent::addPackage($package);
        $this->configurePackageTransportOptions($package);
    }

    protected function loadRootServerFile()
    {
        if (null !== $this->rootData) {
            return $this->rootData;
        }

        if (!extension_loaded('openssl') && 'https' === substr($this->url, 0, 5)) {
            throw new \RuntimeException('You must enable the openssl extension in your php.ini to load information from '.$this->url);
        }

        $jsonUrlParts = parse_url($this->url);

        if (isset($jsonUrlParts['path']) && false !== strpos($jsonUrlParts['path'], '.json')) {
            $jsonUrl = $this->url;
        } else {
            $jsonUrl = $this->url . '/packages.json';
        }

        $data = $this->fetchFile($jsonUrl, 'packages.json');

        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']);
        }

        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['providers']) || !empty($data['providers-includes'])) {
            $this->hasProviders = true;
        }

        // force values for packagist
        if (preg_match('{^https?://repo\.packagist\.org/?$}i', $this->url) && !empty($this->repoConfig['force-lazy-providers'])) {
            $this->url = 'https://repo.packagist.org';
            $this->baseUrl = 'https://repo.packagist.org';
            $this->lazyProvidersUrl = $this->canonicalizeUrl('https://repo.packagist.org/p/%package%.json');
            $this->providersUrl = null;
        } elseif (!empty($this->repoConfig['force-lazy-providers'])) {
            $this->lazyProvidersUrl = $this->canonicalizeUrl('/p/%package%.json');
            $this->providersUrl = null;
        }

        return $this->rootData = $data;
    }

    protected function canonicalizeUrl($url)
    {
        if ('/' === $url[0]) {
            if (preg_match('{^[^:]++://[^/]*+}', $this->url, $matches)) {
                return $matches[0] . $url;
            }

            return $this->url;
        }

        return $url;
    }

    protected function loadDataFromServer()
    {
        $data = $this->loadRootServerFile();

        return $this->loadIncludes($data);
    }

    protected 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);
            }
        }
    }

    protected function loadIncludes($data)
    {
        $packages = array();

        // legacy repo handling
        if (!isset($data['packages']) && !isset($data['includes'])) {
            foreach ($data as $pkg) {
                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 ($this->cache->sha1($include) === $metadata['sha1']) {
                    $includedData = json_decode($this->cache->read($include), true);
                } else {
                    $includedData = $this->fetchFile($include);
                }
                $packages = array_merge($packages, $this->loadIncludes($includedData));
            }
        }

        return $packages;
    }

    protected function createPackage(array $data, $class = 'Composer\Package\CompletePackage')
    {
        try {
            if (!isset($data['notification-url'])) {
                $data['notification-url'] = $this->notifyUrl;
            }

            $package = $this->loader->load($data, $class);
            if (isset($this->sourceMirrors[$package->getSourceType()])) {
                $package->setSourceMirrors($this->sourceMirrors[$package->getSourceType()]);
            }
            $package->setDistMirrors($this->distMirrors);
            $this->configurePackageTransportOptions($package);

            return $package;
        } catch (\Exception $e) {
            throw new \RuntimeException('Could not load package '.(isset($data['name']) ? $data['name'] : json_encode($data)).' in '.$this->url.': ['.get_class($e).'] '.$e->getMessage(), 0, $e);
        }
    }

    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_match('{^https?://.*}i', $filename)) {
            $filename = substr($filename, 0, $pos) . '%24' . substr($filename, $pos + 1);
        }

        $retries = 3;
        while ($retries--) {
            try {
                $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->rfs, $filename);
                if ($this->eventDispatcher) {
                    $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent);
                }

                $origin = RemoteFilesystem::getOrigin($filename);
                $rfs = $preFileDownloadEvent->getRemoteFilesystem();

                $json = $rfs->getContents($origin, $filename, false);
                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) {
                        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.');
                }

                $data = JsonFile::parseJson($json, $filename);
                RemoteFilesystem::outputWarnings($this->io, $this->url, $data);

                if ($cacheKey) {
                    if ($storeLastModifiedTime) {
                        $lastModifiedDate = $rfs->findHeaderValue($rfs->getLastHeaders(), 'last-modified');
                        if ($lastModifiedDate) {
                            $data['last-modified'] = $lastModifiedDate;
                            $json = json_encode($data);
                        }
                    }
                    $this->cache->write($cacheKey, $json);
                }

                break;
            } catch (\Exception $e) {
                if ($e instanceof TransportException && $e->getStatusCode() === 404) {
                    throw $e;
                }

                if ($retries) {
                    usleep(100000);
                    continue;
                }

                if ($e instanceof RepositorySecurityException) {
                    throw $e;
                }

                if ($cacheKey && ($contents = $this->cache->read($cacheKey))) {
                    if (!$this->degradedMode) {
                        $this->io->writeError('<warning>'.$e->getMessage().'</warning>');
                        $this->io->writeError('<warning>'.$this->url.' could not be fully loaded, 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;
            }
        }

        return $data;
    }

    protected function fetchFileIfLastModified($filename, $cacheKey, $lastModifiedTime)
    {
        $retries = 3;
        while ($retries--) {
            try {
                $preFileDownloadEvent = new PreFileDownloadEvent(PluginEvents::PRE_FILE_DOWNLOAD, $this->rfs, $filename);
                if ($this->eventDispatcher) {
                    $this->eventDispatcher->dispatch($preFileDownloadEvent->getName(), $preFileDownloadEvent);
                }

                $origin = RemoteFilesystem::getOrigin($filename);
                $rfs = $preFileDownloadEvent->getRemoteFilesystem();
                $options = array('http' => array('header' => array('If-Modified-Since: '.$lastModifiedTime)));
                $json = $rfs->getContents($origin, $filename, false, $options);
                if ($json === '' && $rfs->findStatusCode($rfs->getLastHeaders()) === 304) {
                    return true;
                }

                $data = JsonFile::parseJson($json, $filename);
                RemoteFilesystem::outputWarnings($this->io, $this->url, $data);

                $lastModifiedDate = $rfs->findHeaderValue($rfs->getLastHeaders(), 'last-modified');
                if ($lastModifiedDate) {
                    $data['last-modified'] = $lastModifiedDate;
                    $json = json_encode($data);
                }
                $this->cache->write($cacheKey, $json);

                return $data;
            } catch (\Exception $e) {
                if ($e instanceof TransportException && $e->getStatusCode() === 404) {
                    throw $e;
                }

                if ($retries) {
                    usleep(100000);
                    continue;
                }

                if (!$this->degradedMode) {
                    $this->io->writeError('<warning>'.$e->getMessage().'</warning>');
                    $this->io->writeError('<warning>'.$this->url.' could not be fully loaded, package information was loaded from the local cache and may be out of date</warning>');
                }
                $this->degradedMode = true;

                return true;
            }
        }
    }

    /**
     * This initializes the packages key of a partial packages.json that contain some packages inlined + a providers-lazy-url
     *
     * This should only be called once
     */
    private function initializePartialPackages()
    {
        $rootData = $this->loadRootServerFile();

        $this->partialPackagesByName = array();
        foreach ($rootData['packages'] as $package => $versions) {
            $package = strtolower($package);
            foreach ($versions as $version) {
                $this->partialPackagesByName[$package][] = $version;
                if (!empty($version['provide']) && is_array($version['provide'])) {
                    foreach ($version['provide'] as $provided => $providedVersion) {
                        $this->partialPackagesByName[strtolower($provided)][] = $version;
                    }
                }
                if (!empty($version['replace']) && is_array($version['replace'])) {
                    foreach ($version['replace'] as $provided => $providedVersion) {
                        $this->partialPackagesByName[strtolower($provided)][] = $version;
                    }
                }
            }
        }

        // wipe rootData as it is fully consumed at this point and this saves some memory
        $this->rootData = true;
    }
}
<?php

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

namespace Composer\Repository;

use Composer\Json\JsonFile;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Dumper\ArrayDumper;

/**
 * Filesystem repository.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class FilesystemRepository extends WritableArrayRepository
{
    private $file;

    /**
     * Initializes filesystem repository.
     *
     * @param JsonFile $repositoryFile repository json file
     */
    public function __construct(JsonFile $repositoryFile)
    {
        parent::__construct();
        $this->file = $repositoryFile;
    }

    /**
     * Initializes repository (reads file, or remote address).
     */
    protected function initialize()
    {
        parent::initialize();

        if (!$this->file->exists()) {
            return;
        }

        try {
            $packages = $this->file->read();

            // forward compatibility for composer v2 installed.json
            if (isset($packages['packages'])) {
                $packages = $packages['packages'];
            }

            if (!is_array($packages)) {
                throw new \UnexpectedValueException('Could not parse package list from the repository');
            }
        } catch (\Exception $e) {
            throw new InvalidRepositoryException('Invalid repository data in '.$this->file->getPath().', packages could not be loaded: ['.get_class($e).'] '.$e->getMessage());
        }

        $loader = new ArrayLoader(null, true);
        foreach ($packages as $packageData) {
            $package = $loader->load($packageData);
            $this->addPackage($package);
        }
    }

    public function reload()
    {
        $this->packages = null;
        $this->initialize();
    }

    /**
     * Writes writable repository.
     */
    public function write()
    {
        $data = array();
        $dumper = new ArrayDumper();

        foreach ($this->getCanonicalPackages() as $package) {
            $data[] = $dumper->dump($package);
        }

        usort($data, function ($a, $b) {
            return strcmp($a['name'], $b['name']);
        });

        $this->file->write($data);
    }
}
<?php

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

namespace Composer\Repository;

/**
 * Thrown when a security problem, like a broken or missing signature
 *
 * @author Eric Daspet <edaspet@survol.fr>
 */
class RepositorySecurityException extends \Exception
{
}
<?php

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

namespace Composer\Repository;

use Composer\Package\CompletePackage;
use Composer\Package\PackageInterface;
use Composer\Package\Version\VersionParser;
use Composer\Plugin\PluginInterface;
use Composer\Util\ProcessExecutor;
use Composer\Util\Silencer;
use Composer\Util\Platform;
use Composer\XdebugHandler\XdebugHandler;
use Symfony\Component\Process\ExecutableFinder;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PlatformRepository extends ArrayRepository
{
    const PLATFORM_PACKAGE_REGEX = '{^(?:php(?:-64bit|-ipv6|-zts|-debug)?|hhvm|(?:ext|lib)-[a-z0-9](?:[_.-]?[a-z0-9]+)*|composer-plugin-api)$}iD';

    private $versionParser;

    /**
     * Defines overrides so that the platform can be mocked
     *
     * Should be an array of package name => version number mappings
     *
     * @var array
     */
    private $overrides = array();

    private $process;

    public function __construct(array $packages = array(), array $overrides = array(), ProcessExecutor $process = null)
    {
        $this->process = $process === null ? (new ProcessExecutor()) : $process;
        foreach ($overrides as $name => $version) {
            $this->overrides[strtolower($name)] = array('name' => $name, 'version' => $version);
        }
        parent::__construct($packages);
    }

    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 (!preg_match(self::PLATFORM_PACKAGE_REGEX, $override['name'])) {
                throw new \InvalidArgumentException('Invalid platform package name in config.platform: '.$override['name']);
            }

            $this->addOverriddenPackage($override);
        }

        $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);

        try {
            $prettyVersion = PHP_VERSION;
            $version = $this->versionParser->normalize($prettyVersion);
        } catch (\UnexpectedValueException $e) {
            $prettyVersion = preg_replace('#^([^~+-]+).*$#', '$1', PHP_VERSION);
            $version = $this->versionParser->normalize($prettyVersion);
        }

        $php = new CompletePackage('php', $version, $prettyVersion);
        $php->setDescription('The PHP interpreter');
        $this->addPackage($php);

        if (PHP_DEBUG) {
            $phpdebug = new CompletePackage('php-debug', $version, $prettyVersion);
            $phpdebug->setDescription('The PHP interpreter, with debugging symbols');
            $this->addPackage($phpdebug);
        }

        if (defined('PHP_ZTS') && PHP_ZTS) {
            $phpzts = new CompletePackage('php-zts', $version, $prettyVersion);
            $phpzts->setDescription('The PHP interpreter, with Zend Thread Safety');
            $this->addPackage($phpzts);
        }

        if (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 (defined('AF_INET6') || Silencer::call('inet_pton', '::') !== false) {
            $phpIpv6 = new CompletePackage('php-ipv6', $version, $prettyVersion);
            $phpIpv6->setDescription('The PHP interpreter, with IPv6 support');
            $this->addPackage($phpIpv6);
        }

        $loadedExtensions = get_loaded_extensions();

        // Extensions scanning
        foreach ($loadedExtensions as $name) {
            if (in_array($name, array('standard', 'Core'))) {
                continue;
            }

            $reflExt = new \ReflectionExtension($name);
            $prettyVersion = $reflExt->getVersion();
            $this->addExtension($name, $prettyVersion);
        }

        // Check for xdebug in a restarted process
        if (!in_array('xdebug', $loadedExtensions, true) && ($prettyVersion = XdebugHandler::getSkippedVersion())) {
            $this->addExtension('xdebug', $prettyVersion);
        }

        // Another quick loop, just for possible libraries
        // Doing it this way to know that functions or constants exist before
        // relying on them.
        foreach ($loadedExtensions as $name) {
            $prettyVersion = null;
            $description = 'The '.$name.' PHP library';
            switch ($name) {
                case 'curl':
                    $curlVersion = curl_version();
                    $prettyVersion = $curlVersion['version'];
                    break;

                case 'iconv':
                    $prettyVersion = ICONV_VERSION;
                    break;

                case 'intl':
                    $name = 'ICU';
                    if (defined('INTL_ICU_VERSION')) {
                        $prettyVersion = INTL_ICU_VERSION;
                    } else {
                        $reflector = new \ReflectionExtension('intl');

                        ob_start();
                        $reflector->info();
                        $output = ob_get_clean();

                        preg_match('/^ICU version => (.*)$/m', $output, $matches);
                        $prettyVersion = $matches[1];
                    }

                    break;

                case 'imagick':
                    $imagick = new \Imagick();
                    $imageMagickVersion = $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 ([\d.]+)(?:-(\d+))?/', $imageMagickVersion['versionString'], $matches);
                    if (isset($matches[2])) {
                        $prettyVersion = "{$matches[1]}.{$matches[2]}";
                    } else {
                        $prettyVersion = $matches[1];
                    }
                    break;

                case 'libxml':
                    $prettyVersion = LIBXML_DOTTED_VERSION;
                    break;

                case 'openssl':
                    $prettyVersion = preg_replace_callback('{^(?:OpenSSL|LibreSSL)?\s*([0-9.]+)([a-z]*).*}i', function ($match) {
                        if (empty($match[2])) {
                            return $match[1];
                        }

                        // OpenSSL versions add another letter when they reach Z.
                        // e.g. OpenSSL 0.9.8zh 3 Dec 2015

                        if (!preg_match('{^z*[a-z]$}', $match[2])) {
                            // 0.9.8abc is garbage
                            return 0;
                        }

                        $len = strlen($match[2]);
                        $patchVersion = ($len - 1) * 26; // All Z
                        $patchVersion += ord($match[2][$len - 1]) - 96;

                        return $match[1].'.'.$patchVersion;
                    }, OPENSSL_VERSION_TEXT);

                    $description = OPENSSL_VERSION_TEXT;
                    break;

                case 'pcre':
                    $prettyVersion = preg_replace('{^(\S+).*}', '$1', PCRE_VERSION);
                    break;

                case 'uuid':
                    $prettyVersion = phpversion('uuid');
                    break;

                case 'xsl':
                    $prettyVersion = LIBXSLT_DOTTED_VERSION;
                    break;

                default:
                    // None handled extensions have no special cases, skip
                    continue 2;
            }

            try {
                $version = $this->versionParser->normalize($prettyVersion);
            } catch (\UnexpectedValueException $e) {
                continue;
            }

            $lib = new CompletePackage('lib-'.$name, $version, $prettyVersion);
            $lib->setDescription($description);
            $this->addPackage($lib);
        }

        $hhvmVersion = defined('HHVM_VERSION') ? HHVM_VERSION : null;
        if ($hhvmVersion === null && !Platform::isWindows()) {
            $finder = new ExecutableFinder();
            $hhvm = $finder->find('hhvm');
            if ($hhvm !== null) {
                $exitCode = $this->process->execute(
                    ProcessExecutor::escape($hhvm).
                    ' --php -d hhvm.jit=0 -r "echo HHVM_VERSION;" 2>/dev/null',
                    $hhvmVersion
                );
                if ($exitCode !== 0) {
                    $hhvmVersion = null;
                }
            }
        }
        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)
    {
        // Skip if overridden
        if (isset($this->overrides[$package->getName()])) {
            $overrider = $this->findPackage($package->getName(), '*');
            if ($package->getVersion() === $overrider->getVersion()) {
                $actualText = 'same as actual';
            } else {
                $actualText = 'actual: '.$package->getPrettyVersion();
            }
            $overrider->setDescription($overrider->getDescription().' ('.$actualText.')');

            return;
        }

        // Skip if PHP is overridden and we are adding a php-* package
        if (isset($this->overrides['php']) && 0 === strpos($package->getName(), 'php-')) {
            $overrider = $this->addOverriddenPackage($this->overrides['php'], $package->getPrettyName());
            if ($package->getVersion() === $overrider->getVersion()) {
                $actualText = 'same as actual';
            } else {
                $actualText = 'actual: '.$package->getPrettyVersion();
            }
            $overrider->setDescription($overrider->getDescription().' ('.$actualText.')');

            return;
        }

        parent::addPackage($package);
    }

    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);

        return $package;
    }

    /**
     * Parses the version and adds a new package to the repository
     *
     * @param string      $name
     * @param null|string $prettyVersion
     */
    private function addExtension($name, $prettyVersion)
    {
        $extraDescription = null;

        try {
            $version = $this->versionParser->normalize($prettyVersion);
        } catch (\UnexpectedValueException $e) {
            $extraDescription = ' (actual version: '.$prettyVersion.')';
            if (preg_match('{^(\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);
        $this->addPackage($ext);
    }

    private function buildPackageName($name)
    {
        return 'ext-' . str_replace(' ', '-', $name);
    }
}
<?php

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

namespace Composer\Repository;

use Composer\Package\PackageInterface;

/**
 * Writable repository interface.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
interface WritableRepositoryInterface extends RepositoryInterface
{
    /**
     * Writes repository (f.e. to the disc).
     */
    public function write();

    /**
     * Adds package to the repository.
     *
     * @param PackageInterface $package package instance
     */
    public function addPackage(PackageInterface $package);

    /**
     * Removes package from the repository.
     *
     * @param PackageInterface $package package instance
     */
    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.
     */
    public function reload();
}
<?php

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

namespace Composer\Repository;

use Composer\Package\RootPackageInterface;
use Composer\Semver\Constraint\ConstraintInterface;
use Composer\Semver\Constraint\Constraint;
use Composer\Package\Link;

/**
 * Common ancestor class for generic repository functionality.
 *
 * @author Niels Keurentjes <niels.keurentjes@omines.com>
 */
abstract class BaseRepository implements RepositoryInterface
{
    /**
     * 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.
     */
    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) === !$invert)) {
                                // already displayed this node's dependencies, cutting short
                                if (in_array($link->getTarget(), $packagesInTree)) {
                                    $results[] = array($package, $link, false);
                                    continue;
                                }
                                $packagesInTree[] = $link->getTarget();
                                $dependents = $recurse ? $this->getDependents($link->getTarget(), null, false, true, $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, $link, false);
                                continue;
                            }
                            $packagesInTree[] = $link->getSource();
                            $dependents = $recurse ? $this->getDependents($link->getSource(), null, false, true, $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, $link, false);
                        }
                    }
                }
            }

            // When inverting, we need to check for conflicts of the needles' requirements against installed packages
            if ($invert && $constraint && in_array($package->getName(), $needles) && $constraint->matches(new Constraint('=', $package->getVersion()))) {
                foreach ($package->getRequires() as $link) {
                    if (preg_match(PlatformRepository::PLATFORM_PACKAGE_REGEX, $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(), null, 'requires', $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->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, $link, false);
                                        $results[] = array($rootPackage, $rootReq, false);
                                        continue 3;
                                    }
                                }
                                $results[] = array($package, $link, false);
                                $results[] = array($rootPackage, new Link($rootPackage->getName(), $link->getTarget(), null, 'does not require', 'but ' . $pkg->getPrettyVersion() . ' is installed'), false);
                            } else {
                                // no root so let's just print whatever we found
                                $results[] = array($package, $link, false);
                            }
                        }

                        continue 2;
                    }
                }
            }
        }

        ksort($results);

        return $results;
    }
}
<?php

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

namespace Composer\Repository;

use Composer\IO\IOInterface;
use Composer\Semver\VersionParser as SemverVersionParser;
use Composer\Package\Version\VersionParser;
use Composer\Repository\Pear\ChannelReader;
use Composer\Package\CompletePackage;
use Composer\Repository\Pear\ChannelInfo;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Package\Link;
use Composer\Semver\Constraint\Constraint;
use Composer\Util\RemoteFilesystem;
use Composer\Config;
use Composer\Factory;

/**
 * Builds list of package from PEAR channel.
 *
 * Packages read from channel are named as 'pear-{channelName}/{packageName}'
 * and has aliased as 'pear-{channelAlias}/{packageName}'
 *
 * @author Benjamin Eberlei <kontakt@beberlei.de>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PearRepository extends ArrayRepository implements ConfigurableRepositoryInterface
{
    private $url;
    private $io;
    private $rfs;
    private $versionParser;
    private $repoConfig;

    /** @var string vendor makes additional alias for each channel as {prefix}/{packagename}. It allows smoother
     * package transition to composer-like repositories.
     */
    private $vendorAlias;

    public function __construct(array $repoConfig, IOInterface $io, Config $config, EventDispatcher $dispatcher = null, RemoteFilesystem $rfs = null)
    {
        parent::__construct();
        if (!preg_match('{^https?://}', $repoConfig['url'])) {
            $repoConfig['url'] = 'http://'.$repoConfig['url'];
        }

        $urlBits = parse_url($repoConfig['url']);
        if (empty($urlBits['scheme']) || empty($urlBits['host'])) {
            throw new \UnexpectedValueException('Invalid url given for PEAR repository: '.$repoConfig['url']);
        }

        $this->url = rtrim($repoConfig['url'], '/');
        $this->io = $io;
        $this->rfs = $rfs ?: Factory::createRemoteFilesystem($this->io, $config);
        $this->vendorAlias = isset($repoConfig['vendor-alias']) ? $repoConfig['vendor-alias'] : null;
        $this->versionParser = new VersionParser();
        $this->repoConfig = $repoConfig;
    }

    public function getRepoConfig()
    {
        return $this->repoConfig;
    }

    protected function initialize()
    {
        parent::initialize();

        $this->io->writeError('Initializing PEAR repository '.$this->url);

        $reader = new ChannelReader($this->rfs);
        try {
            $channelInfo = $reader->read($this->url);
        } catch (\Exception $e) {
            $this->io->writeError('<warning>PEAR repository from '.$this->url.' could not be loaded. '.$e->getMessage().'</warning>');

            return;
        }
        $packages = $this->buildComposerPackages($channelInfo, $this->versionParser);
        foreach ($packages as $package) {
            $this->addPackage($package);
        }
    }

    /**
     * Builds CompletePackages from PEAR package definition data.
     *
     * @param  ChannelInfo         $channelInfo
     * @param  SemverVersionParser $versionParser
     * @return CompletePackage
     */
    private function buildComposerPackages(ChannelInfo $channelInfo, SemverVersionParser $versionParser)
    {
        $result = array();
        foreach ($channelInfo->getPackages() as $packageDefinition) {
            foreach ($packageDefinition->getReleases() as $version => $releaseInfo) {
                try {
                    $normalizedVersion = $versionParser->normalize($version);
                } catch (\UnexpectedValueException $e) {
                    $this->io->writeError('Could not load '.$packageDefinition->getPackageName().' '.$version.': '.$e->getMessage(), true, IOInterface::VERBOSE);
                    continue;
                }

                $composerPackageName = $this->buildComposerPackageName($packageDefinition->getChannelName(), $packageDefinition->getPackageName());

                // distribution url must be read from /r/{packageName}/{version}.xml::/r/g:text()
                // but this location is 'de-facto' standard
                $urlBits = parse_url($this->url);
                $scheme = (isset($urlBits['scheme']) && 'https' === $urlBits['scheme'] && extension_loaded('openssl')) ? 'https' : 'http';
                $distUrl = "{$scheme}://{$packageDefinition->getChannelName()}/get/{$packageDefinition->getPackageName()}-{$version}.tgz";

                $requires = array();
                $suggests = array();
                $conflicts = array();
                $replaces = array();

                // alias package only when its channel matches repository channel,
                // cause we've know only repository channel alias
                if ($channelInfo->getName() == $packageDefinition->getChannelName()) {
                    $composerPackageAlias = $this->buildComposerPackageName($channelInfo->getAlias(), $packageDefinition->getPackageName());
                    $aliasConstraint = new Constraint('==', $normalizedVersion);
                    $replaces[] = new Link($composerPackageName, $composerPackageAlias, $aliasConstraint, 'replaces', (string) $aliasConstraint);
                }

                // alias package with user-specified prefix. it makes private pear channels looks like composer's.
                if (!empty($this->vendorAlias)
                    && ($this->vendorAlias != 'pear-'.$channelInfo->getAlias() || $channelInfo->getName() != $packageDefinition->getChannelName())
                ) {
                    $composerPackageAlias = "{$this->vendorAlias}/{$packageDefinition->getPackageName()}";
                    $aliasConstraint = new Constraint('==', $normalizedVersion);
                    $replaces[] = new Link($composerPackageName, $composerPackageAlias, $aliasConstraint, 'replaces', (string) $aliasConstraint);
                }

                foreach ($releaseInfo->getDependencyInfo()->getRequires() as $dependencyConstraint) {
                    $dependencyPackageName = $this->buildComposerPackageName($dependencyConstraint->getChannelName(), $dependencyConstraint->getPackageName());
                    $constraint = $versionParser->parseConstraints($dependencyConstraint->getConstraint());
                    $link = new Link($composerPackageName, $dependencyPackageName, $constraint, $dependencyConstraint->getType(), $dependencyConstraint->getConstraint());
                    switch ($dependencyConstraint->getType()) {
                        case 'required':
                            $requires[] = $link;
                            break;
                        case 'conflicts':
                            $conflicts[] = $link;
                            break;
                        case 'replaces':
                            $replaces[] = $link;
                            break;
                    }
                }

                foreach ($releaseInfo->getDependencyInfo()->getOptionals() as $group => $dependencyConstraints) {
                    foreach ($dependencyConstraints as $dependencyConstraint) {
                        $dependencyPackageName = $this->buildComposerPackageName($dependencyConstraint->getChannelName(), $dependencyConstraint->getPackageName());
                        $suggests[$group.'-'.$dependencyPackageName] = $dependencyConstraint->getConstraint();
                    }
                }

                $package = new CompletePackage($composerPackageName, $normalizedVersion, $version);
                $package->setType('pear-library');
                $package->setDescription($packageDefinition->getDescription());
                $package->setLicense(array($packageDefinition->getLicense()));
                $package->setDistType('file');
                $package->setDistUrl($distUrl);
                $package->setAutoload(array('classmap' => array('')));
                $package->setIncludePaths(array('/'));
                $package->setRequires($requires);
                $package->setConflicts($conflicts);
                $package->setSuggests($suggests);
                $package->setReplaces($replaces);
                $result[] = $package;
            }
        }

        return $result;
    }

    private function buildComposerPackageName($channelName, $packageName)
    {
        if ('php' === $channelName) {
            return "php";
        }
        if ('ext' === $channelName) {
            return "ext-{$packageName}";
        }

        return "pear-{$channelName}/{$packageName}";
    }
}
<?php

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

namespace Composer\Repository;

/**
 * Configurable repository interface.
 *
 * @author Lukas Homza <lukashomz@gmail.com>
 */
interface ConfigurableRepositoryInterface
{
    public function getRepoConfig();
}
<?php

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

namespace Composer\Repository;

/**
 * Installed array repository.
 *
 * This is used for serving the RootPackage inside an in-memory InstalledRepository
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class InstalledArrayRepository extends WritableArrayRepository implements InstalledRepositoryInterface
{
}
<?php

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

namespace Composer\Repository;

use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Loader\ValidatingArrayLoader;

/**
 * Package repository.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class PackageRepository extends ArrayRepository
{
    private $config;

    /**
     * Initializes filesystem repository.
     *
     * @param array $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(null, true), false);
        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);
        }
    }
}
<?php

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

namespace Composer\Repository;

use Composer\IO\IOInterface;
use Composer\Config;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Package\PackageInterface;
use Composer\Util\RemoteFilesystem;

/**
 * Repositories manager.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author François Pluchino <francois.pluchino@opendisplay.com>
 */
class RepositoryManager
{
    private $localRepository;
    private $repositories = array();
    private $repositoryClasses = array();
    private $io;
    private $config;
    private $eventDispatcher;
    private $rfs;

    public function __construct(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, RemoteFilesystem $rfs = null)
    {
        $this->io = $io;
        $this->config = $config;
        $this->eventDispatcher = $eventDispatcher;
        $this->rfs = $rfs;
    }

    /**
     * Searches for a package by it's 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
     */
    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
     */
    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                     $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];

        $reflMethod = new \ReflectionMethod($class, '__construct');
        $params = $reflMethod->getParameters();
        if (isset($params[4]) && $params[4]->getClass() && $params[4]->getClass()->getName() === 'Composer\Util\RemoteFilesystem') {
            return new $class($config, $this->io, $this->config, $this->eventDispatcher, $this->rfs);
        }

        return new $class($config, $this->io, $this->config, $this->eventDispatcher);
    }

    /**
     * Stores repository class for a specific installation type.
     *
     * @param string $type  installation type
     * @param string $class class name of the repo implementation
     */
    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 WritableRepositoryInterface $repository repository instance
     */
    public function setLocalRepository(WritableRepositoryInterface $repository)
    {
        $this->localRepository = $repository;
    }

    /**
     * Returns local repository for the project.
     *
     * @return WritableRepositoryInterface
     */
    public function getLocalRepository()
    {
        return $this->localRepository;
    }
}
<?php

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

namespace Composer\Repository;

use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Json\JsonFile;
use Composer\Package\Loader\ArrayLoader;
use Composer\Package\Version\VersionGuesser;
use Composer\Package\Version\VersionParser;
use Composer\Util\Platform;
use Composer\Util\ProcessExecutor;

/**
 * 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 <samuel.roze@gmail.com>
 * @author Johann Reinke <johann.reinke@gmail.com>
 */
class PathRepository extends ArrayRepository implements ConfigurableRepositoryInterface
{
    /**
     * @var ArrayLoader
     */
    private $loader;

    /**
     * @var VersionGuesser
     */
    private $versionGuesser;

    /**
     * @var string
     */
    private $url;

    /**
     * @var array
     */
    private $repoConfig;

    /**
     * @var ProcessExecutor
     */
    private $process;

    /**
     * @var array
     */
    private $options;

    /**
     * Initializes path repository.
     *
     * @param array       $repoConfig
     * @param IOInterface $io
     * @param Config      $config
     */
    public function __construct(array $repoConfig, IOInterface $io, Config $config)
    {
        if (!isset($repoConfig['url'])) {
            throw new \RuntimeException('You must specify the `url` configuration for the path repository');
        }

        $this->loader = new ArrayLoader(null, true);
        $this->url = Platform::expandPath($repoConfig['url']);
        $this->process = new ProcessExecutor($io);
        $this->versionGuesser = new VersionGuesser($config, $this->process, new VersionParser());
        $this->repoConfig = $repoConfig;
        $this->options = isset($repoConfig['options']) ? $repoConfig['options'] : array();

        parent::__construct();
    }

    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();

        foreach ($this->getUrlMatches() 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;

            // carry over the root package version if this path repo is in the same git repository as root package
            if (!isset($package['version']) && ($rootVersion = getenv('COMPOSER_ROOT_VERSION'))) {
                if (
                    0 === $this->process->execute('git rev-parse HEAD', $ref1, $path)
                    && 0 === $this->process->execute('git rev-parse HEAD', $ref2)
                    && $ref1 === $ref2
                ) {
                    $package['version'] = $rootVersion;
                }
            }

            if (!isset($package['version'])) {
                $versionData = $this->versionGuesser->guessVersion($package, $path);
                if (is_array($versionData) && $versionData['pretty_version']) {
                    $package['version'] = $versionData['pretty_version'];
                } else {
                    $package['version'] = 'dev-master';
                }
            }

            $output = '';
            if (is_dir($path . DIRECTORY_SEPARATOR . '.git') && 0 === $this->process->execute('git log -n1 --pretty=%H', $output, $path)) {
                $package['dist']['reference'] = trim($output);
            }
            $package = $this->loader->load($package);
            $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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository\Pear;

/**
 * Read PEAR packages using REST 1.0 interface
 *
 * @author Alexey Prilipko <palex@farpost.com>
 */
class PackageDependencyParser
{
    /**
     * Builds dependency information. It detects used package.xml format.
     *
     * @param array $depArray
     * @return DependencyInfo
     */
    public function buildDependencyInfo($depArray)
    {
        if (!is_array($depArray)) {
            return new DependencyInfo(array(), array());
        }
        if (!$this->isHash($depArray)) {
            return new DependencyInfo($this->buildDependency10Info($depArray), array());
        }

        return $this->buildDependency20Info($depArray);
    }

    /**
     * Builds dependency information from package.xml 1.0 format
     *
     * https://pear.php.net/manual/en/guide.developers.package2.dependencies.php
     *
     * package.xml 1.0 format consists of array of
     * { type="php|os|sapi|ext|pkg" rel="has|not|eq|ge|gt|le|lt" optional="yes"
     *   channel="channelName" name="extName|packageName" }
     *
     * @param array $depArray Dependency data in package.xml 1.0 format
     * @return DependencyConstraint[]
     */
    private function buildDependency10Info($depArray)
    {
        static $dep10toOperatorMap = array('has' => '==', 'eq' => '==', 'ge' => '>=', 'gt' => '>', 'le' => '<=', 'lt' => '<', 'not' => '!=');

        $result = array();

        foreach ($depArray as $depItem) {
            if (empty($depItem['rel']) || !array_key_exists($depItem['rel'], $dep10toOperatorMap)) {
                // 'unknown rel type:' . $depItem['rel'];
                continue;
            }

            $depType = !empty($depItem['optional']) && 'yes' == $depItem['optional']
                ? 'optional'
                : 'required';
            $depType = 'not' == $depItem['rel']
                ? 'conflicts'
                : $depType;

            $depVersion = !empty($depItem['version']) ? $this->parseVersion($depItem['version']) : '*';

            // has & not are special operators that does not requires version
            $depVersionConstraint = ('has' == $depItem['rel'] || 'not' == $depItem['rel']) && '*' == $depVersion
                ? '*'
                : $dep10toOperatorMap[$depItem['rel']] . $depVersion;

            switch ($depItem['type']) {
                case 'php':
                    $depChannelName = 'php';
                    $depPackageName = '';
                    break;
                case 'pkg':
                    $depChannelName = !empty($depItem['channel']) ? $depItem['channel'] : 'pear.php.net';
                    $depPackageName = $depItem['name'];
                    break;
                case 'ext':
                    $depChannelName = 'ext';
                    $depPackageName = $depItem['name'];
                    break;
                case 'os':
                case 'sapi':
                    $depChannelName = '';
                    $depPackageName = '';
                break;
                default:
                    $depChannelName = '';
                    $depPackageName = '';
                    break;
            }

            if ('' != $depChannelName) {
                $result[] = new DependencyConstraint(
                    $depType,
                    $depVersionConstraint,
                    $depChannelName,
                    $depPackageName
                );
            }
        }

        return $result;
    }

    /**
     * Builds dependency information from package.xml 2.0 format
     *
     * @param array $depArray Dependency data in package.xml 1.0 format
     * @return DependencyInfo
     */
    private function buildDependency20Info($depArray)
    {
        $result = array();
        $optionals = array();
        $defaultOptionals = array();
        foreach ($depArray as $depType => $depTypeGroup) {
            if (!is_array($depTypeGroup)) {
                continue;
            }
            if ('required' == $depType || 'optional' == $depType) {
                foreach ($depTypeGroup as $depItemType => $depItem) {
                    switch ($depItemType) {
                        case 'php':
                            $result[] = new DependencyConstraint(
                                $depType,
                                $this->parse20VersionConstraint($depItem),
                                'php',
                                ''
                            );
                            break;
                        case 'package':
                            $deps = $this->buildDepPackageConstraints($depItem, $depType);
                            $result = array_merge($result, $deps);
                            break;
                        case 'extension':
                            $deps = $this->buildDepExtensionConstraints($depItem, $depType);
                            $result = array_merge($result, $deps);
                            break;
                        case 'subpackage':
                            $deps = $this->buildDepPackageConstraints($depItem, 'replaces');
                            $defaultOptionals += $deps;
                            break;
                        case 'os':
                        case 'pearinstaller':
                            break;
                        default:
                            break;
                    }
                }
            } elseif ('group' == $depType) {
                if ($this->isHash($depTypeGroup)) {
                    $depTypeGroup = array($depTypeGroup);
                }

                foreach ($depTypeGroup as $depItem) {
                    $groupName = $depItem['attribs']['name'];
                    if (!isset($optionals[$groupName])) {
                        $optionals[$groupName] = array();
                    }

                    if (isset($depItem['subpackage'])) {
                        $optionals[$groupName] += $this->buildDepPackageConstraints($depItem['subpackage'], 'replaces');
                    } else {
                        $result += $this->buildDepPackageConstraints($depItem['package'], 'optional');
                    }
                }
            }
        }

        if (count($defaultOptionals) > 0) {
            $optionals['*'] = $defaultOptionals;
        }

        return new DependencyInfo($result, $optionals);
    }

    /**
     * Builds dependency constraint of 'extension' type
     *
     * @param array $depItem dependency constraint or array of dependency constraints
     * @param string $depType target type of building constraint.
     * @return DependencyConstraint[]
     */
    private function buildDepExtensionConstraints($depItem, $depType)
    {
        if ($this->isHash($depItem)) {
            $depItem = array($depItem);
        }

        $result = array();
        foreach ($depItem as $subDepItem) {
            $depChannelName = 'ext';
            $depPackageName = $subDepItem['name'];
            $depVersionConstraint = $this->parse20VersionConstraint($subDepItem);

            $result[] = new DependencyConstraint(
                $depType,
                $depVersionConstraint,
                $depChannelName,
                $depPackageName
            );
        }

        return $result;
    }

    /**
     * Builds dependency constraint of 'package' type
     *
     * @param array $depItem dependency constraint or array of dependency constraints
     * @param string $depType target type of building constraint.
     * @return DependencyConstraint[]
     */
    private function buildDepPackageConstraints($depItem, $depType)
    {
        if ($this->isHash($depItem)) {
            $depItem = array($depItem);
        }

        $result = array();
        foreach ($depItem as $subDepItem) {
            if (!array_key_exists('channel', $subDepItem)) {
                $subDepItem['channel'] = $subDepItem['uri'];
            }
            $depChannelName = $subDepItem['channel'];
            $depPackageName = $subDepItem['name'];
            $depVersionConstraint = $this->parse20VersionConstraint($subDepItem);
            if (isset($subDepItem['conflicts'])) {
                $depType = 'conflicts';
            }

            $result[] = new DependencyConstraint(
                $depType,
                $depVersionConstraint,
                $depChannelName,
                $depPackageName
            );
        }

        return $result;
    }

    /**
     * Parses version constraint
     *
     * @param  array  $data array containing several 'min', 'max', 'has', 'exclude' and other keys.
     * @return string
     */
    private function parse20VersionConstraint(array $data)
    {
        static $dep20toOperatorMap = array('has' => '==', 'min' => '>=', 'max' => '<=', 'exclude' => '!=');

        $versions = array();
        $values = array_intersect_key($data, $dep20toOperatorMap);
        if (0 == count($values)) {
            return '*';
        }
        if (isset($values['min']) && isset($values['exclude']) && $data['min'] == $data['exclude']) {
            $versions[] = '>' . $this->parseVersion($values['min']);
        } elseif (isset($values['max']) && isset($values['exclude']) && $data['max'] == $data['exclude']) {
            $versions[] = '<' . $this->parseVersion($values['max']);
        } else {
            foreach ($values as $op => $version) {
                if ('exclude' == $op && is_array($version)) {
                    foreach ($version as $versionPart) {
                        $versions[] = $dep20toOperatorMap[$op] . $this->parseVersion($versionPart);
                    }
                } else {
                    $versions[] = $dep20toOperatorMap[$op] . $this->parseVersion($version);
                }
            }
        }

        return implode(',', $versions);
    }

    /**
     * Softened version parser
     *
     * @param string $version
     * @return null|string
     */
    private function parseVersion($version)
    {
        if (preg_match('{^v?(\d{1,3})(\.\d+)?(\.\d+)?(\.\d+)?}i', $version, $matches)) {
            $version = $matches[1]
                .(!empty($matches[2]) ? $matches[2] : '.0')
                .(!empty($matches[3]) ? $matches[3] : '.0')
                .(!empty($matches[4]) ? $matches[4] : '.0');

            return $version;
        }

        return null;
    }

    /**
     * Test if array is associative or hash type
     *
     * @param  array $array
     * @return bool
     */
    private function isHash(array $array)
    {
        return !array_key_exists(1, $array) && !array_key_exists(0, $array);
    }
}
<?php

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

namespace Composer\Repository\Pear;

use Composer\Downloader\TransportException;

/**
 * Read PEAR packages using REST 1.0 interface
 *
 * At version 1.0 package descriptions read from:
 *  {baseUrl}/p/packages.xml
 *  {baseUrl}/p/{package}/info.xml
 *  {baseUrl}/p/{package}/allreleases.xml
 *  {baseUrl}/p/{package}/deps.{version}.txt
 *
 * @author Alexey Prilipko <palex@farpost.com>
 */
class ChannelRest10Reader extends BaseChannelReader
{
    private $dependencyReader;

    public function __construct($rfs)
    {
        parent::__construct($rfs);

        $this->dependencyReader = new PackageDependencyParser();
    }

    /**
     * Reads package descriptions using PEAR Rest 1.0 interface
     *
     * @param string $baseUrl base Url interface
     *
     * @return PackageInfo[]
     */
    public function read($baseUrl)
    {
        return $this->readPackages($baseUrl);
    }

    /**
     * Read list of packages from
     *  {baseUrl}/p/packages.xml
     *
     * @param string $baseUrl
     * @return PackageInfo[]
     */
    private function readPackages($baseUrl)
    {
        $result = array();

        $xmlPath = '/p/packages.xml';
        $xml = $this->requestXml($baseUrl, $xmlPath);
        $xml->registerXPathNamespace('ns', self::ALL_PACKAGES_NS);
        foreach ($xml->xpath('ns:p') as $node) {
            $packageName = (string) $node;
            $packageInfo = $this->readPackage($baseUrl, $packageName);
            $result[] = $packageInfo;
        }

        return $result;
    }

    /**
     * Read package info from
     *  {baseUrl}/p/{package}/info.xml
     *
     * @param string $baseUrl
     * @param string $packageName
     * @return PackageInfo
     */
    private function readPackage($baseUrl, $packageName)
    {
        $xmlPath = '/p/' . strtolower($packageName) . '/info.xml';
        $xml = $this->requestXml($baseUrl, $xmlPath);
        $xml->registerXPathNamespace('ns', self::PACKAGE_INFO_NS);

        $channelName = (string) $xml->c;
        $packageName = (string) $xml->n;
        $license = (string) $xml->l;
        $shortDescription = (string) $xml->s;
        $description = (string) $xml->d;

        return new PackageInfo(
            $channelName,
            $packageName,
            $license,
            $shortDescription,
            $description,
            $this->readPackageReleases($baseUrl, $packageName)
        );
    }

    /**
     * Read package releases from
     *  {baseUrl}/p/{package}/allreleases.xml
     *
     * @param string $baseUrl
     * @param string $packageName
     * @throws \Composer\Downloader\TransportException|\Exception
     * @return ReleaseInfo[]                                      hash array with keys as version numbers
     */
    private function readPackageReleases($baseUrl, $packageName)
    {
        $result = array();

        try {
            $xmlPath = '/r/' . strtolower($packageName) . '/allreleases.xml';
            $xml = $this->requestXml($baseUrl, $xmlPath);
            $xml->registerXPathNamespace('ns', self::ALL_RELEASES_NS);
            foreach ($xml->xpath('ns:r') as $node) {
                $releaseVersion = (string) $node->v;
                $releaseStability = (string) $node->s;

                try {
                    $result[$releaseVersion] = new ReleaseInfo(
                        $releaseStability,
                        $this->readPackageReleaseDependencies($baseUrl, $packageName, $releaseVersion)
                    );
                } catch (TransportException $exception) {
                    if ($exception->getCode() != 404) {
                        throw $exception;
                    }
                }
            }
        } catch (TransportException $exception) {
            if ($exception->getCode() != 404) {
                throw $exception;
            }
        }

        return $result;
    }

    /**
     * Read package dependencies from
     *  {baseUrl}/p/{package}/deps.{version}.txt
     *
     * @param string $baseUrl
     * @param string $packageName
     * @param string $version
     * @return DependencyInfo[]
     */
    private function readPackageReleaseDependencies($baseUrl, $packageName, $version)
    {
        $dependencyReader = new PackageDependencyParser();

        $depthPath = '/r/' . strtolower($packageName) . '/deps.' . $version . '.txt';
        $content = $this->requestContent($baseUrl, $depthPath);
        $dependencyArray = unserialize($content);

        return $dependencyReader->buildDependencyInfo($dependencyArray);
    }
}
<?php

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

namespace Composer\Repository\Pear;

/**
 * PEAR package release dependency info
 *
 * @author Alexey Prilipko <palex@farpost.com>
 */
class DependencyConstraint
{
    private $type;
    private $constraint;
    private $channelName;
    private $packageName;

    /**
     * @param string $type
     * @param string $constraint
     * @param string $channelName
     * @param string $packageName
     */
    public function __construct($type, $constraint, $channelName, $packageName)
    {
        $this->type = $type;
        $this->constraint = $constraint;
        $this->channelName = $channelName;
        $this->packageName = $packageName;
    }

    public function getChannelName()
    {
        return $this->channelName;
    }

    public function getConstraint()
    {
        return $this->constraint;
    }

    public function getPackageName()
    {
        return $this->packageName;
    }

    public function getType()
    {
        return $this->type;
    }
}
<?php

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

namespace Composer\Repository\Pear;

/**
 * PEAR package release dependency info
 *
 * @author Alexey Prilipko <palex@farpost.com>
 */
class DependencyInfo
{
    private $requires;
    private $optionals;

    /**
     * @param DependencyConstraint[] $requires  list of requires/conflicts/replaces
     * @param array                  $optionals [groupName => DependencyConstraint[]] list of optional groups
     */
    public function __construct($requires, $optionals)
    {
        $this->requires = $requires;
        $this->optionals = $optionals;
    }

    /**
     * @return DependencyConstraint[] list of requires/conflicts/replaces
     */
    public function getRequires()
    {
        return $this->requires;
    }

    /**
     * @return array [groupName => DependencyConstraint[]] list of optional groups
     */
    public function getOptionals()
    {
        return $this->optionals;
    }
}
<?php

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

namespace Composer\Repository\Pear;

/**
 * PEAR channel info
 *
 * @author Alexey Prilipko <palex@farpost.com>
 */
class ChannelInfo
{
    private $name;
    private $alias;
    private $packages;

    /**
     * @param string        $name
     * @param string        $alias
     * @param PackageInfo[] $packages
     */
    public function __construct($name, $alias, array $packages)
    {
        $this->name = $name;
        $this->alias = $alias;
        $this->packages = $packages;
    }

    /**
     * Name of the channel
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Alias of the channel
     *
     * @return string
     */
    public function getAlias()
    {
        return $this->alias;
    }

    /**
     * List of channel packages
     *
     * @return PackageInfo[]
     */
    public function getPackages()
    {
        return $this->packages;
    }
}
<?php

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

namespace Composer\Repository\Pear;

use Composer\Util\RemoteFilesystem;

/**
 * Base PEAR Channel reader.
 *
 * Provides xml namespaces and red
 *
 * @author Alexey Prilipko <palex@farpost.com>
 */
abstract class BaseChannelReader
{
    /**
     * PEAR REST Interface namespaces
     */
    const CHANNEL_NS = 'http://pear.php.net/channel-1.0';
    const ALL_CATEGORIES_NS = 'http://pear.php.net/dtd/rest.allcategories';
    const CATEGORY_PACKAGES_INFO_NS = 'http://pear.php.net/dtd/rest.categorypackageinfo';
    const ALL_PACKAGES_NS = 'http://pear.php.net/dtd/rest.allpackages';
    const ALL_RELEASES_NS = 'http://pear.php.net/dtd/rest.allreleases';
    const PACKAGE_INFO_NS = 'http://pear.php.net/dtd/rest.package';

    /** @var RemoteFilesystem */
    private $rfs;

    protected function __construct(RemoteFilesystem $rfs)
    {
        $this->rfs = $rfs;
    }

    /**
     * Read content from remote filesystem.
     *
     * @param string $origin server
     * @param string $path   relative path to content
     * @throws \UnexpectedValueException
     * @return \SimpleXMLElement
     */
    protected function requestContent($origin, $path)
    {
        $url = rtrim($origin, '/') . '/' . ltrim($path, '/');
        $content = $this->rfs->getContents($origin, $url, false);
        if (!$content) {
            throw new \UnexpectedValueException('The PEAR channel at ' . $url . ' did not respond.');
        }

        return str_replace('http://pear.php.net/rest/', 'https://pear.php.net/rest/', $content);
    }

    /**
     * Read xml content from remote filesystem
     *
     * @param string $origin server
     * @param string $path   relative path to content
     * @throws \UnexpectedValueException
     * @return \SimpleXMLElement
     */
    protected function requestXml($origin, $path)
    {
        // http://components.ez.no/p/packages.xml is malformed. to read it we must ignore parsing errors.
        $xml = simplexml_load_string($this->requestContent($origin, $path), "SimpleXMLElement", LIBXML_NOERROR);

        if (false === $xml) {
            throw new \UnexpectedValueException(sprintf('The PEAR channel at ' . $origin . ' is broken. (Invalid XML at file `%s`)', $path));
        }

        return $xml;
    }
}
<?php

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

namespace Composer\Repository\Pear;

/**
 * Read PEAR packages using REST 1.1 interface
 *
 * At version 1.1 package descriptions read from:
 *  {baseUrl}/c/categories.xml
 *  {baseUrl}/c/{category}/packagesinfo.xml
 *
 * @author Alexey Prilipko <palex@farpost.com>
 */
class ChannelRest11Reader extends BaseChannelReader
{
    private $dependencyReader;

    public function __construct($rfs)
    {
        parent::__construct($rfs);

        $this->dependencyReader = new PackageDependencyParser();
    }

    /**
     * Reads package descriptions using PEAR Rest 1.1 interface
     *
     * @param string $baseUrl base Url interface
     *
     * @return PackageInfo[]
     */
    public function read($baseUrl)
    {
        return $this->readChannelPackages($baseUrl);
    }

    /**
     * Read list of channel categories from
     *  {baseUrl}/c/categories.xml
     *
     * @param string $baseUrl
     * @return PackageInfo[]
     */
    private function readChannelPackages($baseUrl)
    {
        $result = array();

        $xml = $this->requestXml($baseUrl, "/c/categories.xml");
        $xml->registerXPathNamespace('ns', self::ALL_CATEGORIES_NS);
        foreach ($xml->xpath('ns:c') as $node) {
            $categoryName = (string) $node;
            $categoryPackages = $this->readCategoryPackages($baseUrl, $categoryName);
            $result = array_merge($result, $categoryPackages);
        }

        return $result;
    }

    /**
     * Read packages from
     *  {baseUrl}/c/{category}/packagesinfo.xml
     *
     * @param string $baseUrl
     * @param string $categoryName
     * @return PackageInfo[]
     */
    private function readCategoryPackages($baseUrl, $categoryName)
    {
        $result = array();

        $categoryPath = '/c/'.urlencode($categoryName).'/packagesinfo.xml';
        $xml = $this->requestXml($baseUrl, $categoryPath);
        $xml->registerXPathNamespace('ns', self::CATEGORY_PACKAGES_INFO_NS);
        foreach ($xml->xpath('ns:pi') as $node) {
            $packageInfo = $this->parsePackage($node);
            $result[] = $packageInfo;
        }

        return $result;
    }

    /**
     * Parses package node.
     *
     * @param \SimpleXMLElement $packageInfo  xml element describing package
     * @return PackageInfo
     */
    private function parsePackage($packageInfo)
    {
        $packageInfo->registerXPathNamespace('ns', self::CATEGORY_PACKAGES_INFO_NS);
        $channelName = (string) $packageInfo->p->c;
        $packageName = (string) $packageInfo->p->n;
        $license = (string) $packageInfo->p->l;
        $shortDescription = (string) $packageInfo->p->s;
        $description = (string) $packageInfo->p->d;

        $dependencies = array();
        foreach ($packageInfo->xpath('ns:deps') as $node) {
            $dependencyVersion = (string) $node->v;
            $dependencyArray = unserialize((string) $node->d);

            $dependencyInfo = $this->dependencyReader->buildDependencyInfo($dependencyArray);

            $dependencies[$dependencyVersion] = $dependencyInfo;
        }

        $releases = array();
        $releasesInfo = $packageInfo->xpath('ns:a/ns:r');
        if ($releasesInfo) {
            foreach ($releasesInfo as $node) {
                $releaseVersion = (string) $node->v;
                $releaseStability = (string) $node->s;
                $releases[$releaseVersion] = new ReleaseInfo(
                    $releaseStability,
                    isset($dependencies[$releaseVersion]) ? $dependencies[$releaseVersion] : new DependencyInfo(array(), array())
                );
            }
        }

        return new PackageInfo(
            $channelName,
            $packageName,
            $license,
            $shortDescription,
            $description,
            $releases
        );
    }
}
<?php

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

namespace Composer\Repository\Pear;

/**
 * PEAR Package info
 *
 * @author Alexey Prilipko <palex@farpost.com>
 */
class PackageInfo
{
    private $channelName;
    private $packageName;
    private $license;
    private $shortDescription;
    private $description;
    private $releases;

    /**
     * @param string        $channelName
     * @param string        $packageName
     * @param string        $license
     * @param string        $shortDescription
     * @param string        $description
     * @param ReleaseInfo[] $releases         associative array maps release version to release info
     */
    public function __construct($channelName, $packageName, $license, $shortDescription, $description, $releases)
    {
        $this->channelName = $channelName;
        $this->packageName = $packageName;
        $this->license = $license;
        $this->shortDescription = $shortDescription;
        $this->description = $description;
        $this->releases = $releases;
    }

    /**
     * @return string the package channel name
     */
    public function getChannelName()
    {
        return $this->channelName;
    }

    /**
     * @return string the package name
     */
    public function getPackageName()
    {
        return $this->packageName;
    }

    /**
     * @return string the package description
     */
    public function getDescription()
    {
        return $this->description;
    }

    /**
     * @return string the package short description
     */
    public function getShortDescription()
    {
        return $this->shortDescription;
    }

    /**
     * @return string the package license
     */
    public function getLicense()
    {
        return $this->license;
    }

    /**
     * @return ReleaseInfo[]
     */
    public function getReleases()
    {
        return $this->releases;
    }
}
<?php

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

namespace Composer\Repository\Pear;

/**
 * PEAR package release info
 *
 * @author Alexey Prilipko <palex@farpost.com>
 */
class ReleaseInfo
{
    private $stability;
    private $dependencyInfo;

    /**
     * @param string         $stability
     * @param DependencyInfo $dependencyInfo
     */
    public function __construct($stability, $dependencyInfo)
    {
        $this->stability = $stability;
        $this->dependencyInfo = $dependencyInfo;
    }

    /**
     * @return DependencyInfo release dependencies
     */
    public function getDependencyInfo()
    {
        return $this->dependencyInfo;
    }

    /**
     * @return string release stability
     */
    public function getStability()
    {
        return $this->stability;
    }
}
<?php

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

namespace Composer\Repository\Pear;

use Composer\Util\RemoteFilesystem;

/**
 * PEAR Channel package reader.
 *
 * Reads channel packages info from and builds Package's
 *
 * @author Alexey Prilipko <palex@farpost.com>
 */
class ChannelReader extends BaseChannelReader
{
    /** @var array of ('xpath test' => 'rest implementation') */
    private $readerMap;

    public function __construct(RemoteFilesystem $rfs)
    {
        parent::__construct($rfs);

        $rest10reader = new ChannelRest10Reader($rfs);
        $rest11reader = new ChannelRest11Reader($rfs);

        $this->readerMap = array(
            'REST1.3' => $rest11reader,
            'REST1.2' => $rest11reader,
            'REST1.1' => $rest11reader,
            'REST1.0' => $rest10reader,
        );
    }

    /**
     * Reads PEAR channel through REST interface and builds list of packages
     *
     * @param string $url PEAR Channel url
     * @throws \UnexpectedValueException
     * @return ChannelInfo
     */
    public function read($url)
    {
        $xml = $this->requestXml($url, "/channel.xml");

        $channelName = (string) $xml->name;
        $channelAlias = (string) $xml->suggestedalias;

        $supportedVersions = array_keys($this->readerMap);
        $selectedRestVersion = $this->selectRestVersion($xml, $supportedVersions);
        if (!$selectedRestVersion) {
            throw new \UnexpectedValueException(sprintf('PEAR repository %s does not supports any of %s protocols.', $url, implode(', ', $supportedVersions)));
        }

        $reader = $this->readerMap[$selectedRestVersion['version']];
        $packageDefinitions = $reader->read($selectedRestVersion['baseUrl']);

        return new ChannelInfo($channelName, $channelAlias, $packageDefinitions);
    }

    /**
     * Reads channel supported REST interfaces and selects one of them
     *
     * @param \SimpleXMLElement $channelXml
     * @param string[] $supportedVersions supported PEAR REST protocols
     * @return array|null hash with selected version and baseUrl
     */
    private function selectRestVersion($channelXml, $supportedVersions)
    {
        $channelXml->registerXPathNamespace('ns', self::CHANNEL_NS);

        foreach ($supportedVersions as $version) {
            $xpathTest = "ns:servers/ns:*/ns:rest/ns:baseurl[@type='{$version}']";
            $testResult = $channelXml->xpath($xpathTest);

            foreach ($testResult as $result) {
                // Choose first https:// option.
                $result = (string) $result;
                if (preg_match('{^https://}i', $result)) {
                    return array('version' => $version, 'baseUrl' => $result);
                }
            }

            // Fallback to non-https if it does not exist.
            if (count($testResult) > 0) {
                return array('version' => $version, 'baseUrl' => (string) $testResult[0]);
            }
        }

        return null;
    }
}
<?php

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

namespace Composer\Repository;

use Composer\Factory;
use Composer\IO\IOInterface;
use Composer\Config;
use Composer\EventDispatcher\EventDispatcher;
use Composer\Util\RemoteFilesystem;
use Composer\Json\JsonFile;

/**
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class RepositoryFactory
{
    /**
     * @param  IOInterface $io
     * @param  Config      $config
     * @param  string      $repository
     * @param  bool        $allowFilesystem
     * @return array|mixed
     */
    public static function configFromString(IOInterface $io, Config $config, $repository, $allowFilesystem = false)
    {
        if (0 === strpos($repository, 'http')) {
            $repoConfig = array('type' => 'composer', 'url' => $repository);
        } elseif ("json" === pathinfo($repository, PATHINFO_EXTENSION)) {
            $json = new JsonFile($repository, Factory::createRemoteFilesystem($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 ('{' === substr($repository, 0, 1)) {
            // 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 $io, Config $config, $repository, $allowFilesystem = false)
    {
        $repoConfig = static::configFromString($io, $config, $repository, $allowFilesystem);

        return static::createRepo($io, $config, $repoConfig);
    }

    /**
     * @param  IOInterface         $io
     * @param  Config              $config
     * @param  array               $repoConfig
     * @return RepositoryInterface
     */
    public static function createRepo(IOInterface $io, Config $config, array $repoConfig)
    {
        $rm = static::manager($io, $config, null, Factory::createRemoteFilesystem($io, $config));
        $repos = static::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 = null, Config $config = null, RepositoryManager $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, $config, null, Factory::createRemoteFilesystem($io, $config));
        }

        return static::createRepos($rm, $config->getRepositories());
    }

    /**
     * @param  IOInterface       $io
     * @param  Config            $config
     * @param  EventDispatcher   $eventDispatcher
     * @param  RemoteFilesystem  $rfs
     * @return RepositoryManager
     */
    public static function manager(IOInterface $io, Config $config, EventDispatcher $eventDispatcher = null, RemoteFilesystem $rfs = null)
    {
        $rm = new RepositoryManager($io, $config, $eventDispatcher, $rfs);
        $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('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('hg-bitbucket', 'Composer\Repository\VcsRepository');
        $rm->setRepositoryClass('artifact', 'Composer\Repository\ArtifactRepository');
        $rm->setRepositoryClass('path', 'Composer\Repository\PathRepository');

        return $rm;
    }

    /**
     * @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 = is_int($index) && isset($repo['url']) ? preg_replace('{^https?://}i', '', $repo['url']) : $index;
            while (isset($repos[$name])) {
                $name .= '2';
            }
            if ($repo['type'] === 'filesystem') {
                $repos[$name] = new FilesystemRepository($repo['json']);
            } else {
                $repos[$name] = $rm->createRepository($repo['type'], $repo, $index);
            }
        }

        return $repos;
    }
}
<?php

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

namespace Composer\Repository;

use Composer\Package\AliasPackage;

/**
 * Writable array repository.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class WritableArrayRepository extends ArrayRepository implements WritableRepositoryInterface
{
    /**
     * {@inheritDoc}
     */
    public function write()
    {
    }

    /**
     * {@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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Repository;

use Composer\Package\PackageInterface;

/**
 * Repository interface.
 *
 * @author Nils Adermann <naderman@naderman.de>
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface RepositoryInterface extends \Countable
{
    const SEARCH_FULLTEXT = 0;
    const SEARCH_NAME = 1;

    /**
     * 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|\Composer\Semver\Constraint\ConstraintInterface $constraint package version or version constraint to match against
     *
     * @return PackageInterface|null
     */
    public function findPackage($name, $constraint);

    /**
     * Searches for all packages matching a name and optionally a version.
     *
     * @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 = null);

    /**
     * Returns list of registered packages.
     *
     * @return PackageInterface[]
     */
    public function getPackages();

    /**
     * Searches the repository for packages containing the query
     *
     * @param string $query search query
     * @param int    $mode  a set of SEARCH_* constants to search on, implementations should do a best effort only
     *
     * @return array[] an array of array('name' => '...', 'description' => '...')
     */
    public function search($query, $mode = 0);
}
<?php

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

namespace Composer\Repository;

/**
 * Exception thrown when a package repository is utterly broken
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class InvalidRepositoryException extends \Exception
{
}
<?php

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

namespace Composer\Repository;

/**
 * Installable repository interface.
 *
 * Just used to tag installed repositories so the base classes can act differently on Alias packages
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
interface InstalledRepositoryInterface extends WritableRepositoryInterface
{
}
<?php

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

function includeIfExists($file)
{
    return file_exists($file) ? include $file : false;
}

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 ComposerAutoloaderInit5925cbfbda4231e100bd9070d8ec30d5
{
    private static $loader;

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

    public static function getLoader()
    {
        if (null !== self::$loader) {
            return self::$loader;
        }

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

        $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
        if ($useStaticLoader) {
            require_once __DIR__ . '/autoload_static.php';

            call_user_func(\Composer\Autoload\ComposerStaticInit5925cbfbda4231e100bd9070d8ec30d5::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\ComposerStaticInit5925cbfbda4231e100bd9070d8ec30d5::$files;
        } else {
            $includeFiles = require __DIR__ . '/autoload_files.php';
        }
        foreach ($includeFiles as $fileIdentifier => $file) {
            composerRequire5925cbfbda4231e100bd9070d8ec30d5($fileIdentifier, $file);
        }

        return $loader;
    }
}

function composerRequire5925cbfbda4231e100bd9070d8ec30d5($fileIdentifier, $file)
{
    if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
        require $file;

        $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
    }
}
<?php

// autoload_psr4.php @generated by Composer

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

return array(
    'cweagans\\Composer\\' => array($vendorDir . '/cweagans/composer-patches/src'),
    'WP_CLI\\Maintenance\\' => array($baseDir . '/utils/maintenance'),
    '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\\Yaml\\' => array($vendorDir . '/symfony/yaml'),
    'Symfony\\Component\\Translation\\' => array($vendorDir . '/symfony/translation'),
    'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'),
    'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'),
    'Symfony\\Component\\Filesystem\\' => array($vendorDir . '/symfony/filesystem'),
    'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'),
    'Symfony\\Component\\DependencyInjection\\' => array($vendorDir . '/symfony/dependency-injection'),
    'Symfony\\Component\\Debug\\' => array($vendorDir . '/symfony/debug'),
    'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'),
    'Symfony\\Component\\Config\\' => array($vendorDir . '/symfony/config'),
    'Seld\\PharUtils\\' => array($vendorDir . '/seld/phar-utils/src'),
    'Seld\\JsonLint\\' => array($vendorDir . '/seld/jsonlint/src/Seld/JsonLint'),
    'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
    'Prophecy\\' => array($vendorDir . '/phpspec/prophecy/src/Prophecy'),
    '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'),
    'JakubOnderka\\PhpConsoleHighlighter\\' => array($vendorDir . '/jakub-onderka/php-console-highlighter/src'),
    'JakubOnderka\\PhpConsoleColor\\' => array($vendorDir . '/jakub-onderka/php-console-color/src'),
    'Gettext\\Languages\\' => array($vendorDir . '/gettext/languages/src'),
    'Gettext\\' => array($vendorDir . '/gettext/gettext/src'),
    'Doctrine\\Instantiator\\' => array($vendorDir . '/doctrine/instantiator/src/Doctrine/Instantiator'),
    'Composer\\XdebugHandler\\' => array($vendorDir . '/composer/xdebug-handler/src'),
    'Composer\\Semver\\' => array($vendorDir . '/composer/semver/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/checksum-command/src', $vendorDir . '/wp-cli/config-command/src', $vendorDir . '/wp-cli/core-command/src', $vendorDir . '/wp-cli/cron-command/src', $vendorDir . '/wp-cli/db-command/src', $vendorDir . '/wp-cli/entity-command/src', $vendorDir . '/wp-cli/eval-command/src', $vendorDir . '/wp-cli/export-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/scaffold-command/src', $vendorDir . '/wp-cli/search-replace-command/src', $vendorDir . '/wp-cli/server-command/src', $vendorDir . '/wp-cli/shell-command/src', $vendorDir . '/wp-cli/super-admin-command/src', $vendorDir . '/wp-cli/widget-command/src'),
);
<?php

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

namespace Composer\Autoload;

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

    // PSR-0
    private $prefixesPsr0 = array();
    private $fallbackDirsPsr0 = array();

    private $useIncludePath = false;
    private $classMap = array();
    private $classMapAuthoritative = false;
    private $missingClasses = array();
    private $apcuPrefix;

    public function getPrefixes()
    {
        if (!empty($this->prefixesPsr0)) {
            return call_user_func_array('array_merge', $this->prefixesPsr0);
        }

        return array();
    }

    public function getPrefixesPsr4()
    {
        return $this->prefixDirsPsr4;
    }

    public function getFallbackDirs()
    {
        return $this->fallbackDirsPsr0;
    }

    public function getFallbackDirsPsr4()
    {
        return $this->fallbackDirsPsr4;
    }

    public function getClassMap()
    {
        return $this->classMap;
    }

    /**
     * @param array $classMap Class to filename map
     */
    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 array|string $paths   The PSR-0 root directories
     * @param bool         $prepend Whether to prepend the directories
     */
    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 array|string $paths   The PSR-4 base directories
     * @param bool         $prepend Whether to prepend the directories
     *
     * @throws \InvalidArgumentException
     */
    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 array|string $paths  The PSR-0 base directories
     */
    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 array|string $paths  The PSR-4 base directories
     *
     * @throws \InvalidArgumentException
     */
    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
     */
    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
     */
    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
     */
    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
     */
    public function register($prepend = false)
    {
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
    }

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

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

            return true;
        }
    }

    /**
     * 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;
    }

    private function findFileWithExtension($class, $ext)
    {
        // PSR-4 lookup
        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

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

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

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

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

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

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

        return false;
    }
}

/**
 * Scope isolated include.
 *
 * Prevents access to $this/self from included files.
 */
function includeFile($file)
{
    include $file;
}
<?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
     */
    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
     */
    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|null
     */
    public function getLicenseByIdentifier($identifier)
    {
        $key = strtolower($identifier);

        if (!isset($this->licenses[$key])) {
            return;
        }

        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[] 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|null
     */
    public function getExceptionByIdentifier($identifier)
    {
        $key = strtolower($identifier);

        if (!isset($this->exceptions[$key])) {
            return;
        }

        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];
            }
        }
    }

    /**
     * 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 array|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';
    }

    private function loadLicenses()
    {
        if (null !== $this->licenses) {
            return;
        }

        $json = file_get_contents(self::getResourcesDir() . '/' . self::LICENSES_FILE);
        $this->licenses = array();

        foreach (json_decode($json, true) as $identifier => $license) {
            $this->licenses[strtolower($identifier)] = array($identifier, $license[0], $license[1], $license[2]);
        }
    }

    private function loadExceptions()
    {
        if (null !== $this->exceptions) {
            return;
        }

        $json = file_get_contents(self::getResourcesDir() . '/' . self::EXCEPTIONS_FILE);
        $this->exceptions = array();

        foreach (json_decode($json, true) as $identifier => $exception) {
            $this->exceptions[strtolower($identifier)] = array($identifier, $exception[0]);
        }
    }

    /**
     * @return string
     */
    private function getLicensesExpression()
    {
        if (null === $this->licensesExpression) {
            $licenses = array_map('preg_quote', array_keys($this->licenses));
            rsort($licenses);
            $licenses = implode('|', $licenses);
            $this->licensesExpression = $licenses;
        }

        return $this->licensesExpression;
    }

    /**
     * @return string
     */
    private function getExceptionsExpression()
    {
        if (null === $this->exceptionsExpression) {
            $exceptions = array_map('preg_quote', array_keys($this->exceptions));
            rsort($exceptions);
            $exceptions = implode('|', $exceptions);
            $this->exceptionsExpression = $exceptions;
        }

        return $this->exceptionsExpression;
    }

    /**
     * @param string $license
     *
     * @throws \RuntimeException
     *
     * @return bool
     */
    private function isValidLicenseString($license)
    {
        if (isset($this->licenses[strtolower($license)])) {
            return true;
        }

        $licenses = $this->getLicensesExpression();
        $exceptions = $this->getExceptionsExpression();

        $regex = <<<REGEX
{
(?(DEFINE)
    # idstring: 1*( ALPHA / DIGIT / - / . )
    (?<idstring>[\pL\pN.-]{1,})

    # license-id: taken from list
    (?<licenseid>${licenses})

    # license-exception-id: taken from list
    (?<licenseexceptionid>${exceptions})

    # license-ref: [DocumentRef-1*(idstring):]LicenseRef-1*(idstring)
    (?<licenseref>(?:DocumentRef-(?&idstring):)?LicenseRef-(?&idstring))

    # simple-expresssion: license-id / license-id+ / license-ref
    (?<simple_expression>(?&licenseid)\+? | (?&licenseid) | (?&licenseref))

    # compound-expression: 1*(
    #   simple-expression /
    #   simple-expression WITH license-exception-id /
    #   compound-expression AND compound-expression /
    #   compound-expression OR compound-expression
    # ) / ( compound-expression ) )
    (?<compound_head>
        (?&simple_expression) ( \s+ WITH \s+ (?&licenseexceptionid))?
            | \( \s* (?&compound_expression) \s* \)
    )
    (?<compound_expression>
        (?&compound_head) (?: \s+ (?:AND|OR) \s+ (?&compound_expression))?
    )

    # license-expression: 1*1(simple-expression / compound-expression)
    (?<license_expression>(?&compound_expression) | (?&simple_expression))
) # end of define

^(NONE | NOASSERTION | (?&license_expression))$
}xi
REGEX;

        $match = preg_match($regex, $license);

        if (0 === $match) {
            return false;
        }

        if (false === $match) {
            throw new \RuntimeException('Regex failed to compile/run.');
        }

        return true;
    }
}
<?php

/*
 * This file is part of composer/ca-bundle.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\CaBundle;

use Psr\Log\LoggerInterface;
use Symfony\Component\Process\PhpProcess;

/**
 * @author Chris Smith <chris@cs278.org>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class CaBundle
{
    private static $caPath;
    private static $caFileValidity = array();
    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 <me@evancoury.com>
     *
     * For the full copyright and license information, please see below:
     *
     * Copyright (c) 2013, Evan Coury
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without modification,
     * are permitted provided that the following conditions are met:
     *
     *     * Redistributions of source code must retain the above copyright notice,
     *       this list of conditions and the following disclaimer.
     *
     *     * Redistributions in binary form must reproduce the above copyright notice,
     *       this list of conditions and the following disclaimer in the documentation
     *       and/or other materials provided with the distribution.
     *
     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
     * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
     * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
     * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     *
     * @param  LoggerInterface $logger optional logger for information about which CA files were loaded
     * @return string          path to a CA bundle file or directory
     */
    public static function getSystemCaRootBundlePath(LoggerInterface $logger = null)
    {
        if (self::$caPath !== null) {
            return self::$caPath;
        }
        $caBundlePaths = array();


        // If SSL_CERT_FILE env variable points to a valid certificate/bundle, use that.
        // This mimics how OpenSSL uses the SSL_CERT_FILE env variable.
        $caBundlePaths[] = getenv('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[] = getenv('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
        );

        foreach($otherLocations as $location) {
            $otherLocations[] = dirname($location);
        }

        $caBundlePaths = array_merge($caBundlePaths, $otherLocations);

        foreach ($caBundlePaths as $caBundle) {
            if (self::caFileUsable($caBundle, $logger)) {
                return self::$caPath = $caBundle;
            }

            if (self::caDirUsable($caBundle)) {
                return self::$caPath = $caBundle;
            }
        }

        return self::$caPath = static::getBundledCaBundlePath(); // Bundled CA file, last resort
    }

    /**
     * Returns the path to the bundled CA file
     *
     * In case you don't want to trust the user or the system, you can use this directly
     *
     * @return string path to a CA bundle file
     */
    public static function getBundledCaBundlePath()
    {
        $caBundleFile = __DIR__.'/../res/cacert.pem';

        // cURL does not understand 'phar://' paths
        // see https://github.com/composer/ca-bundle/issues/10
        if (0 === strpos($caBundleFile, 'phar://')) {
            file_put_contents(
                $tempCaBundleFile = tempnam(sys_get_temp_dir(), 'openssl-ca-bundle-'),
                file_get_contents($caBundleFile)
            );

            register_shutdown_function(function() use ($tempCaBundleFile) {
                @unlink($tempCaBundleFile);
            });

            $caBundleFile = $tempCaBundleFile;
        }

        return $caBundleFile;
    }

    /**
     * Validates a CA file using opensl_x509_parse only if it is safe to use
     *
     * @param string          $filename
     * @param LoggerInterface $logger   optional logger for information about which CA files were loaded
     *
     * @return bool
     */
    public static function validateCaFile($filename, LoggerInterface $logger = null)
    {
        static $warned = false;

        if (isset(self::$caFileValidity[$filename])) {
            return self::$caFileValidity[$filename];
        }

        $contents = file_get_contents($filename);

        // 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);
        } else {
            $isValid = (bool) openssl_x509_parse($contents);
        }

        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 < 50600 && 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($regex, PHP_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 (
            count($output) === 3
            && $output[0] === sprintf('string(%d) "%s"', strlen(PHP_VERSION), PHP_VERSION)
            && $output[1] === 'string(27) "stefan.esser@sektioneins.de"'
            && $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
     */
    public static function reset()
    {
        self::$caFileValidity = array();
        self::$caPath = null;
        self::$useOpensslParse = null;
    }

    private static function caFileUsable($certFile, LoggerInterface $logger = null)
    {
        return $certFile && @is_file($certFile) && @is_readable($certFile) && static::validateCaFile($certFile, $logger);
    }

    private static function caDirUsable($certDir)
    {
        return $certDir && @is_dir($certDir) && @is_readable($certDir) && glob($certDir . '/*');
    }
}
<?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;

/**
 * @author John Stevenson <john-stevenson@blueyonder.co.uk>
 */
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;

    private static $inRestart;
    private static $name;
    private static $skipped;

    private $cli;
    private $colorOption;
    private $debug;
    private $envAllowXdebug;
    private $envOriginalInis;
    private $loaded;
    private $persistent;
    private $script;
    /** @var Status|null */
    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
     * @param string $colorOption Command-line long option to force color output
     * @throws \RuntimeException If a parameter is invalid
     */
    public function __construct($envPrefix, $colorOption = '')
    {
        if (!is_string($envPrefix) || empty($envPrefix) || !is_string($colorOption)) {
            throw new \RuntimeException('Invalid constructor parameter');
        }

        self::$name = strtoupper($envPrefix);
        $this->envAllowXdebug = self::$name.self::SUFFIX_ALLOW;
        $this->envOriginalInis = self::$name.self::SUFFIX_INIS;

        $this->colorOption = $colorOption;

        if (extension_loaded('xdebug')) {
            $ext = new \ReflectionExtension('xdebug');
            $this->loaded = $ext->getVersion() ?: 'unknown';
        }

        if ($this->cli = PHP_SAPI === 'cli') {
            $this->debug = 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.
     */
    public function check()
    {
        $this->notify(Status::CHECK, $this->loaded);
        $envArgs = explode('|', (string) getenv($this->envAllowXdebug));

        if (empty($envArgs[0]) && $this->requiresRestart((bool) $this->loaded)) {
            // Restart required
            $this->notify(Status::RESTART);

            if ($this->prepareRestart()) {
                $command = $this->getCommand();
                $this->notify(Status::RESTARTING, $command);
                $this->restart($command);
            }
            return;
        }

        if (self::RESTART_ID === $envArgs[0] && count($envArgs) === 5) {
            // Restarting, so unset environment variable and use saved values
            $this->notify(Status::RESTARTED);

            Process::setEnv($this->envAllowXdebug);
            self::$inRestart = true;

            if (!$this->loaded) {
                // Skipped version is only set if Xdebug is not loaded
                self::$skipped = $envArgs[1];
            }

            // Put restart settings in the environment
            $this->setEnvRestartSettings($envArgs);
            return;
        }

        $this->notify(Status::NORESTART);

        if ($settings = self::getRestartSettings()) {
            // 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 array
     */
    public static function getAllIniFiles()
    {
        if (!empty(self::$name)) {
            $env = getenv(self::$name.self::SUFFIX_INIS);

            if (false !== $env) {
                return explode(PATH_SEPARATOR, $env);
            }
        }

        $paths = array((string) php_ini_loaded_file());

        if ($scanned = php_ini_scanned_files()) {
            $paths = array_merge($paths, array_map('trim', explode(',', $scanned)));
        }

        return $paths;
    }

    /**
     * Returns an array of restart settings or null
     *
     * Settings will be available if the current process was restarted, or
     * called with the settings from an existing restart.
     *
     * @return array|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;
        }

        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 true if Xdebug is loaded, or as directed by an extending class
     *
     * @param bool $isLoaded Whether Xdebug is loaded
     *
     * @return bool
     */
    protected function requiresRestart($isLoaded)
    {
        return $isLoaded;
    }

    /**
     * Allows an extending class to access the tmpIni
     *
     * @param string $command
     */
    protected function restart($command)
    {
        $this->doRestart($command);
    }

    /**
     * Executes the restarted command then deletes the tmp ini
     *
     * @param string $command
     */
    private function doRestart($command)
    {
        passthru($command, $exitCode);
        $this->notify(Status::INFO, 'Restarted process exited '.$exitCode);

        if ($this->debug === '2') {
            $this->notify(Status::INFO, 'Temp ini saved: '.$this->tmpIni);
        } else {
            @unlink($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 = '';
        $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 ?: 'Unable to create temp ini file at: '.$tmpDir;
        } elseif (!$this->setEnvironment($scannedInis, $iniFiles)) {
            $error = 'Unable to set environment variables';
        }

        if ($error) {
            $this->notify(Status::ERROR, $error);
        }

        return empty($error);
    }

    /**
     * Returns true if the tmp ini file was written
     *
     * @param array $iniFiles All ini files used in the current process
     * @param string $tmpDir The system temporary directory
     * @param string $error Set by method if ini file cannot be read
     *
     * @return bool
     */
    private function writeTmpIni(array $iniFiles, $tmpDir, &$error)
    {
        if (!$this->tmpIni = @tempnam($tmpDir, '')) {
            return false;
        }

        // $iniFiles has at least one item and it may be empty
        if (empty($iniFiles[0])) {
            array_shift($iniFiles);
        }

        $content = '';
        $regex = '/^\s*(zend_extension\s*=.*xdebug.*)$/mi';

        foreach ($iniFiles as $file) {
            // Check for inaccessible ini files
            if (!$data = @file_get_contents($file)) {
                $error = 'Unable to read ini: '.$file;
                return false;
            }
            $content .= preg_replace($regex, ';$1', $data).PHP_EOL;
        }

        // Merge loaded settings into our ini content, if it is valid
        if ($config = parse_ini_string($content)) {
            $loaded = ini_get_all(null, 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 @file_put_contents($this->tmpIni, $content);
    }

    /**
     * Returns the restart command line
     *
     * @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);
        }

        if (defined('STDOUT') && Process::supportsColor(STDOUT)) {
            $args = Process::addColorOption($args, $this->colorOption);
        }

        $args = array_merge($php, array($this->script), $args);

        $cmd = Process::escape(array_shift($args), true, true);
        foreach ($args as $arg) {
            $cmd .= ' '.Process::escape($arg);
        }

        return $cmd;
    }

    /**
     * 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 array $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,
            $this->loaded,
            (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
     */
    private function notify($op, $data = null)
    {
        $this->statusWriter->report($op, $data);
    }

    /**
     * Returns default, changed and command-line ini settings
     *
     * @param array $loadedConfig All current ini settings
     * @param array $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
        $options = PHP_VERSION_ID >= 50306 ? DEBUG_BACKTRACE_IGNORE_ARGS : false;
        $trace = debug_backtrace($options);

        if (($main = end($trace)) && isset($main['file'])) {
            return file_exists($this->script = $main['file']);
        }

        return false;
    }

    /**
     * Adds restart settings to the environment
     *
     * @param string $envArgs
     */
    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_SETTINGS, implode('|', $settings));
    }

    /**
     * Syncs settings and the environment if called with existing settings
     *
     * @param array $settings
     */
    private function syncSettings(array $settings)
    {
        if (false === getenv($this->envOriginalInis)) {
            // Called by another app, so make original inis available
            Process::setEnv($this->envOriginalInis, implode(PATH_SEPARATOR, $settings['inis']));
        }

        self::$skipped = $settings['skipped'];
        $this->notify(Status::INFO, 'Process called with existing restart settings');
    }

    /**
     * Returns true if there are 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()
    {
        return !(getenv('PHP_INI_SCAN_DIR')
            && !PHP_CONFIG_FILE_SCAN_DIR
            && (PHP_VERSION_ID < 70113
            || PHP_VERSION_ID === 70200));
    }

    /**
     * Returns true if there are no known configuration issues
     *
     * @param string $info Set by method
     */
    private function checkConfiguration(&$info)
    {
        if (false !== strpos(ini_get('disable_functions'), 'passthru')) {
            $info = 'passthru function is disabled';
            return false;
        }

        if (extension_loaded('uopz') && !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;
            }
        }

        return true;
    }
}
<?php

/*
 * This file is part of composer/xdebug-handler.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\XdebugHandler;

/**
 * @author John Stevenson <john-stevenson@blueyonder.co.uk>
 */
class PhpConfig
{
    /**
     * Use the original PHP configuration
     *
     * @return array PHP cli options
     */
    public function useOriginal()
    {
        $this->getDataAndReset();
        return array();
    }

    /**
     * Use standard restart settings
     *
     * @return array PHP cli options
     */
    public function useStandard()
    {
        if ($data = $this->getDataAndReset()) {
            return array('-n', '-c', $data['tmpIni']);
        }

        return array();
    }

    /**
     * Use environment variables to persist settings
     *
     * @return array PHP cli options
     */
    public function usePersistent()
    {
        if ($data = $this->getDataAndReset()) {
            Process::setEnv('PHPRC', $data['tmpIni']);
            Process::setEnv('PHP_INI_SCAN_DIR', '');
        }

        return array();
    }

    /**
     * Returns restart data if available and resets the environment
     *
     * @return array|null
     */
    private function getDataAndReset()
    {
        if ($data = XdebugHandler::getRestartSettings()) {
            Process::setEnv('PHPRC', $data['phprc']);
            Process::setEnv('PHP_INI_SCAN_DIR', $data['scanDir']);
        }

        return $data;
    }
}
<?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 <john-stevenson@blueyonder.co.uk>
 * @internal
 */
class Status
{
    const ENV_RESTART = 'XDEBUG_HANDLER_RESTART';
    const CHECK = 'Check';
    const ERROR = 'Error';
    const INFO = 'Info';
    const NORESTART = 'NoRestart';
    const RESTART = 'Restart';
    const RESTARTING = 'Restarting';
    const RESTARTED = 'Restarted';

    private $debug;
    private $envAllowXdebug;
    private $loaded;
    private $logger;
    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 = $start ? round((microtime(true) - $start) * 1000) : 0;

        $this->envAllowXdebug = $envAllowXdebug;
        $this->debug = $debug && defined('STDERR');
    }

    /**
     * @param LoggerInterface $logger
     */
    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
     */
    public function report($op, $data)
    {
        if ($this->logger || $this->debug) {
            call_user_func(array($this, 'report'.$op), $data);
        }
    }

    /**
     * Outputs a status message
     *
     * @param string $text
     * @param string $level
     */
    private function output($text, $level = null)
    {
        if ($this->logger) {
            $this->logger->log($level ?: LogLevel::DEBUG, $text);
        }

        if ($this->debug) {
            fwrite(STDERR, sprintf('xdebug-handler[%d] %s', getmypid(), $text.PHP_EOL));
        }
    }

    private function reportCheck($loaded)
    {
        $this->loaded = $loaded;
        $this->output('Checking '.$this->envAllowXdebug);
    }

    private function reportError($error)
    {
        $this->output(sprintf('No restart (%s)', $error), LogLevel::WARNING);
    }

    private function reportInfo($info)
    {
        $this->output($info);
    }

    private function reportNoRestart()
    {
        $this->output($this->getLoadedMessage());

        if ($this->loaded) {
            $text = sprintf('No restart (%s)', $this->getEnvAllow());
            if (!getenv($this->envAllowXdebug)) {
                $text .= ' Allowed by application';
            }
            $this->output($text);
        }
    }

    private function reportRestart()
    {
        $this->output($this->getLoadedMessage());
        Process::setEnv(self::ENV_RESTART, (string) microtime(true));
    }

    private function reportRestarted()
    {
        $loaded = $this->getLoadedMessage();
        $text = sprintf('Restarted (%d ms). %s', $this->time, $loaded);
        $level = $this->loaded ? LogLevel::WARNING : null;
        $this->output($text, $level);
    }

    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 ? 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;

/**
 * Provides utility functions to prepare a child process command-line and set
 * environment variables in that process.
 *
 * @author John Stevenson <john-stevenson@blueyonder.co.uk>
 * @internal
 */
class Process
{
    /**
     * Returns an array of parameters, including a color option if required
     *
     * A color option is needed because child process output is piped.
     *
     * @param array $args The script parameters
     * @param string $colorOption The long option to force color output
     *
     * @return array
     */
    public static function addColorOption(array $args, $colorOption)
    {
        if (!$colorOption
            || in_array($colorOption, $args)
            || !preg_match('/^--([a-z]+$)|(^--[a-z]+=)/', $colorOption, $matches)) {
            return $args;
        }

        if (isset($matches[2])) {
            // Handle --color(s)= options
            if (false !== ($index = array_search($matches[2].'auto', $args))) {
                $args[$index] = $colorOption;
                return $args;
            } elseif (preg_grep('/^'.$matches[2].'/', $args)) {
                return $args;
            }
        } elseif (in_array('--no-'.$matches[1], $args)) {
            return $args;
        }

        // Check for NO_COLOR variable (https://no-color.org/)
        if (false !== getenv('NO_COLOR')) {
            return $args;
        }

        if (false !== ($index = array_search('--', $args))) {
            // Position option before double-dash delimiter
            array_splice($args, $index, 0, $colorOption);
        } else {
            $args[] = $colorOption;
        }

        return $args;
    }

    /**
     * Escapes a string to be used as a shell argument.
     *
     * From https://github.com/johnstevenson/winbox-args
     * MIT Licensed (c) John Stevenson <john-stevenson@blueyonder.co.uk>
     *
     * @param string $arg  The argument to be escaped
     * @param bool   $meta Additionally escape cmd.exe meta characters
     * @param bool $module The argument is the module to invoke
     *
     * @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_match('/%[^%]+%/', $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;
    }

    /**
     * Returns true if the output stream supports colors
     *
     * This is tricky on Windows, because Cygwin, Msys2 etc emulate pseudo
     * terminals via named pipes, so we can only check the environment.
     *
     * @param mixed $output A valid CLI output stream
     *
     * @return bool
     */
    public static function supportsColor($output)
    {
        if ('Hyper' === getenv('TERM_PROGRAM')) {
            return true;
        }

        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
            return (function_exists('sapi_windows_vt100_support')
                && sapi_windows_vt100_support($output))
                || false !== getenv('ANSICON')
                || 'ON' === getenv('ConEmuANSI')
                || 'xterm' === getenv('TERM');
        }

        if (function_exists('stream_isatty')) {
            return stream_isatty($output);
        }

        if (function_exists('posix_isatty')) {
            return posix_isatty($output);
        }

        $stat = fstat($output);
        // Check if formatted mode is S_IFCHR
        return $stat ? 0020000 === ($stat['mode'] & 0170000) : false;
    }

    /**
     * Makes putenv environment changes available in $_SERVER and $_ENV
     *
     * @param string $name
     * @param string|false $value A false value unsets the variable
     *
     * @return bool Whether the environment variable was set
     */
    public static function setEnv($name, $value = false)
    {
        $unset = false === $value;

        if (!putenv($unset ? $name : $name.'='.$value)) {
            return false;
        }

        if ($unset) {
            unset($_SERVER[$name]);
        } else {
            $_SERVER[$name] = $value;
        }

        // Update $_ENV if it is being used
        if (false !== stripos((string) ini_get('variables_order'), 'E')) {
            if ($unset) {
                unset($_ENV[$name]);
            } else {
                $_ENV[$name] = $value;
            }
        }

        return true;
    }
}
<?php

/*
 * This file is part of composer/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\EmptyConstraint;
use Composer\Semver\Constraint\MultiConstraint;
use Composer\Semver\Constraint\Constraint;

/**
 * Version parser.
 *
 * @author Jordi Boggiano <j.boggiano@seld.be>
 */
class VersionParser
{
    /**
     * Regex to match pre-release data (sort of).
     *
     * Due to backwards compatibility:
     *   - Instead of enforcing hyphen, an underscore, dot or nothing at all are also accepted.
     *   - Only stabilities as recognized by Composer are allowed to precede a numerical identifier.
     *   - Numerical-only pre-release identifiers are not supported, see tests.
     *
     *                        |--------------|
     * [major].[minor].[patch] -[pre-release] +[build-metadata]
     *
     * @var string
     */
    private static $modifierRegex = '[._-]?(?:(stable|beta|b|RC|alpha|a|patch|pl|p)((?:[.-]?\d+)*+)?)?([.-]?dev)?';

    /** @var array */
    private static $stabilities = array('stable', 'RC', 'beta', 'alpha', 'dev');

    /**
     * Returns the stability of a version.
     *
     * @param string $version
     *
     * @return string
     */
    public static function parseStability($version)
    {
        $version = preg_replace('{#.+$}i', '', $version);

        if ('dev-' === substr($version, 0, 4) || '-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);
        if (null === $fullVersion) {
            $fullVersion = $version;
        }

        // strip off aliasing
        if (preg_match('{^([^,\s]++) ++as ++([^,\s]++)$}', $version, $match)) {
            $version = $match[1];
        }

        // match master-like branches
        if (preg_match('{^(?:dev-)?(?:master|trunk|default)$}i', $version)) {
            return '9999999-dev';
        }

        // if requirement is branch-like, use full name
        if ('dev-' === strtolower(substr($version, 0, 4))) {
            return 'dev-' . substr($version, 4);
        }

        // strip off build metadata
        if (preg_match('{^([^,\s+]++)\+[^\s]++$}', $version, $match)) {
            $version = $match[1];
        }

        // match classical versioning
        if (preg_match('{^v?(\d{1,5})(\.\d++)?(\.\d++)?(\.\d++)?' . self::$modifierRegex . '$}i', $version, $matches)) {
            $version = $matches[1]
                . (!empty($matches[2]) ? $matches[2] : '.0')
                . (!empty($matches[3]) ? $matches[3] : '.0')
                . (!empty($matches[4]) ? $matches[4] : '.0');
            $index = 5;
        // match date(time) based versioning
        } elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3})?)' . 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]) . (!empty($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 {
                return $this->normalizeBranch($match[1]);
            } catch (\Exception $e) {
            }
        }

        $extraMessage = '';
        if (preg_match('{ +as +' . preg_quote($version) . '$}', $fullVersion)) {
            $extraMessage = ' in "' . $fullVersion . '", the alias must be an exact version';
        } elseif (preg_match('{^' . preg_quote($version) . ' +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 "' . $version . '"' . $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 (in_array($name, array('master', 'trunk', 'default'))) {
            return $this->normalize($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;
    }

    /**
     * Parses a constraint string into MultiConstraint and/or Constraint objects.
     *
     * @param string $constraints
     *
     * @return ConstraintInterface
     */
    public function parseConstraints($constraints)
    {
        $prettyConstraint = $constraints;

        if (preg_match('{^([^,\s]*?)@(' . implode('|', self::$stabilities) . ')$}i', $constraints, $match)) {
            $constraints = empty($match[1]) ? '*' : $match[1];
        }

        if (preg_match('{^(dev-[^,\s@]+?|[^,\s@]+?\.x-dev)#.+$}i', $constraints, $match)) {
            $constraints = $match[1];
        }

        $orConstraints = preg_split('{\s*\|\|?\s*}', trim($constraints));
        $orGroups = array();
        foreach ($orConstraints as $constraints) {
            $andConstraints = preg_split('{(?<!^|as|[=>< ,]) *(?<!-)[, ](?!-) *(?!,|as|$)}', $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 (1 === count($constraintObjects)) {
                $constraint = $constraintObjects[0];
            } else {
                $constraint = new MultiConstraint($constraintObjects);
            }

            $orGroups[] = $constraint;
        }

        if (1 === count($orGroups)) {
            $constraint = $orGroups[0];
        } elseif (2 === count($orGroups)
            // parse the two OR groups and if they are contiguous we collapse
            // them into one constraint
            && $orGroups[0] instanceof MultiConstraint
            && $orGroups[1] instanceof MultiConstraint
            && 2 === count($orGroups[0]->getConstraints())
            && 2 === count($orGroups[1]->getConstraints())
            && ($a = (string) $orGroups[0])
            && substr($a, 0, 3) === '[>=' && (false !== ($posA = strpos($a, '<', 4)))
            && ($b = (string) $orGroups[1])
            && substr($b, 0, 3) === '[>=' && (false !== ($posB = strpos($b, '<', 4)))
            && substr($a, $posA + 2, -1) === substr($b, 4, $posB - 5)
        ) {
            $constraint = new MultiConstraint(array(
                new Constraint('>=', substr($a, 4, $posA - 5)),
                new Constraint('<', substr($b, $posB + 2, -1)),
            ));
        } else {
            $constraint = new MultiConstraint($orGroups, false);
        }

        $constraint->setPrettyString($prettyConstraint);

        return $constraint;
    }

    /**
     * @param string $constraint
     *
     * @throws \UnexpectedValueException
     *
     * @return array
     */
    private function parseConstraint($constraint)
    {
        if (preg_match('{^([^,\s]+?)@(' . implode('|', self::$stabilities) . ')$}i', $constraint, $match)) {
            $constraint = $match[1];
            if ($match[2] !== 'stable') {
                $stabilityModifier = $match[2];
            }
        }

        if (preg_match('{^v?[xX*](\.[xX*])*$}i', $constraint)) {
            return array(new EmptyConstraint());
        }

        $versionRegex = 'v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.(\d++))?' . self::$modifierRegex . '(?:\+[^\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 (substr($constraint, 0, 2) === '~>') {
                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;
            }

            // Calculate the stability suffix
            $stabilitySuffix = '';
            if (empty($matches[5]) && empty($matches[7])) {
                $stabilitySuffix .= '-dev';
            }

            $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1));
            $lowerBound = new Constraint('>=', $lowVersion);

            // For upper bound, we increment the position of one more significance,
            // but highPosition = 0 would be illegal
            $highPosition = max(1, $position - 1);
            $highVersion = $this->manipulateVersionString($matches, $highPosition, 1) . '-dev';
            $upperBound = new Constraint('<', $highVersion);

            return array(
                $lowerBound,
                $upperBound,
            );
        }

        // Caret Range
        //
        // Allows changes that do not modify the left-most non-zero digit in the [major, minor, patch] tuple.
        // In other words, this allows patch and minor updates for versions 1.0.0 and above, patch updates for
        // versions 0.X >=0.1.0, and no updates for versions 0.0.X
        if (preg_match('{^\^' . $versionRegex . '($)}i', $constraint, $matches)) {
            // Work out which position in the version we are operating at
            if ('0' !== $matches[1] || '' === $matches[2] || null === $matches[2]) {
                $position = 1;
            } elseif ('0' !== $matches[2] || '' === $matches[3] || null === $matches[3]) {
                $position = 2;
            } else {
                $position = 3;
            }

            // Calculate the stability suffix
            $stabilitySuffix = '';
            if (empty($matches[5]) && empty($matches[7])) {
                $stabilitySuffix .= '-dev';
            }

            $lowVersion = $this->normalize(substr($constraint . $stabilitySuffix, 1));
            $lowerBound = new Constraint('>=', $lowVersion);

            // For upper bound, we increment the position of one more significance,
            // but highPosition = 0 would be illegal
            $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev';
            $upperBound = new Constraint('<', $highVersion);

            return array(
                $lowerBound,
                $upperBound,
            );
        }

        // X Range
        //
        // Any of X, x, or * may be used to "stand in" for one of the numeric values in the [major, minor, patch] tuple.
        // A partial version range is treated as an X-Range, so the special character is in fact optional.
        if (preg_match('{^v?(\d++)(?:\.(\d++))?(?:\.(\d++))?(?:\.[xX*])++$}', $constraint, $matches)) {
            if (isset($matches[3]) && '' !== $matches[3] && null !== $matches[3]) {
                $position = 3;
            } elseif (isset($matches[2]) && '' !== $matches[2] && null !== $matches[2]) {
                $position = 2;
            } else {
                $position = 1;
            }

            $lowVersion = $this->manipulateVersionString($matches, $position) . '-dev';
            $highVersion = $this->manipulateVersionString($matches, $position, 1) . '-dev';

            if ($lowVersion === '0.0.0.0-dev') {
                return array(new Constraint('<', $highVersion));
            }

            return array(
                new Constraint('>=', $lowVersion),
                new Constraint('<', $highVersion),
            );
        }

        // Hyphen Range
        //
        // Specifies an inclusive set. If a partial version is provided as the first version in the inclusive range,
        // then the missing pieces are replaced with zeroes. If a partial version is provided as the second version in
        // the inclusive range, then all versions that start with the supplied parts of the tuple are accepted, but
        // nothing that would be greater than the provided tuple parts.
        if (preg_match('{^(?P<from>' . $versionRegex . ') +- +(?P<to>' . $versionRegex . ')($)}i', $constraint, $matches)) {
            // Calculate the stability suffix
            $lowStabilitySuffix = '';
            if (empty($matches[6]) && empty($matches[8])) {
                $lowStabilitySuffix = '-dev';
            }

            $lowVersion = $this->normalize($matches['from']);
            $lowerBound = new Constraint('>=', $lowVersion . $lowStabilitySuffix);

            $empty = function ($x) {
                return ($x === 0 || $x === '0') ? false : empty($x);
            };

            if ((!$empty($matches[11]) && !$empty($matches[12])) || !empty($matches[14]) || !empty($matches[16])) {
                $highVersion = $this->normalize($matches['to']);
                $upperBound = new Constraint('<=', $highVersion);
            } else {
                $highMatch = array('', $matches[10], $matches[11], $matches[12], $matches[13]);
                $highVersion = $this->manipulateVersionString($highMatch, $empty($matches[11]) ? 1 : 2, 1) . '-dev';
                $upperBound = new Constraint('<', $highVersion);
            }

            return array(
                $lowerBound,
                $upperBound,
            );
        }

        // Basic Comparators
        if (preg_match('{^(<>|!=|>=?|<=?|==?)?\s*(.*)}', $constraint, $matches)) {
            try {
                $version = $this->normalize($matches[2]);

                if (!empty($stabilityModifier) && $this->parseStability($version) === 'stable') {
                    $version .= '-' . $stabilityModifier;
                } elseif ('<' === $matches[1] || '>=' === $matches[1]) {
                    if (!preg_match('/-' . self::$modifierRegex . '$/', strtolower($matches[2]))) {
                        if (substr($matches[2], 0, 4) !== 'dev-') {
                            $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 The new version
     */
    private function manipulateVersionString($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;
                    }
                }
            }
        }

        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 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));
        $constraints = $versionParser->parseConstraints($constraints);

        return $constraints->matches($provider);
    }

    /**
     * Return all versions that satisfy given constraints.
     *
     * @param array $versions
     * @param string $constraints
     *
     * @return array
     */
    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 array $versions
     *
     * @return array
     */
    public static function sort(array $versions)
    {
        return self::usort($versions, self::SORT_ASC);
    }

    /**
     * Sort given array of versions in reverse.
     *
     * @param array $versions
     *
     * @return array
     */
    public static function rsort(array $versions)
    {
        return self::usort($versions, self::SORT_DESC);
    }

    /**
     * @param array $versions
     * @param int $direction
     *
     * @return array
     */
    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) {
            $normalized[] = array($versionParser->normalize($version), $key);
        }

        usort($normalized, function (array $left, array $right) use ($direction) {
            if ($left[0] === $right[0]) {
                return 0;
            }

            if (Comparator::lessThan($left[0], $right[0])) {
                return -$direction;
            }

            return $direction;
        });

        // Recreate input array, using the original indexes which are now in sorted order.
        $sorted = array();
        foreach ($normalized as $item) {
            $sorted[] = $versions[$item[1]];
        }

        return $sorted;
    }
}
<?php

/*
 * This file is part of composer/semver.
 *
 * (c) Composer <https://github.com/composer>
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Composer\Semver\Constraint;

/**
 * Defines a constraint.
 */
class Constraint implements ConstraintInterface
{
    /* operator integer values */
    const OP_EQ = 0;
    const OP_LT = 1;
    const OP_LE = 2;
    const OP_GT = 3;
    const OP_GE = 4;
    const OP_NE = 5;

    /**
     * Operator to integer translation table.
     *
     * @var array
     */
    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
     */
    private static $transOpInt = array(
        self::OP_EQ => '==',
        self::OP_LT => '<',
        self::OP_LE => '<=',
        self::OP_GT => '>',
        self::OP_GE => '>=',
        self::OP_NE => '!=',
    );

    /** @var string */
    protected $operator;

    /** @var string */
    protected $version;

    /** @var string */
    protected $prettyString;

    /**
     * @param ConstraintInterface $provider
     *
     * @return bool
     */
    public function matches(ConstraintInterface $provider)
    {
        if ($provider instanceof $this) {
            return $this->matchSpecific($provider);
        }

        // turn matching around to find a match
        return $provider->matches($this);
    }

    /**
     * @param string $prettyString
     */
    public function setPrettyString($prettyString)
    {
        $this->prettyString = $prettyString;
    }

    /**
     * @return string
     */
    public function getPrettyString()
    {
        if ($this->prettyString) {
            return $this->prettyString;
        }

        return $this->__toString();
    }

    /**
     * Get all supported comparison operators.
     *
     * @return array
     */
    public static function getSupportedOperators()
    {
        return array_keys(self::$transOpStr);
    }

    /**
     * Sets operator and version to compare with.
     *
     * @param string $operator
     * @param string $version
     *
     * @throws \InvalidArgumentException if invalid operator is given.
     */
    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;
    }

    /**
     * @param string $a
     * @param string $b
     * @param string $operator
     * @param bool $compareBranches
     *
     * @throws \InvalidArgumentException if invalid operator is given.
     *
     * @return bool
     */
    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 = 'dev-' === substr($a, 0, 4);
        $bIsBranch = 'dev-' === substr($b, 0, 4);

        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);
    }

    /**
     * @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) {
            return !$isEqualOp && !$isProviderEqualOp
                || $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 true;
        }

        if ($this->versionCompare($provider->version, $this->version, self::$transOpInt[$this->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
            if ($provider->version === $this->version
                && self::$transOpInt[$provider->operator] === $providerNoEqualOp
                && self::$transOpInt[$this->operator] !== $noEqualOp) {
                return false;
            }

            return true;
        }

        return false;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return self::$transOpInt[$this->operator] . ' ' . $this->version;
    }
}
<?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;

interface ConstraintInterface
{
    /**
     * @param ConstraintInterface $provider
     *
     * @return bool
     */
    public function matches(ConstraintInterface $provider);

    /**
     * @return string
     */
    public function getPrettyString();

    /**
     * @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;

trigger_error('The ' . __NAMESPACE__ . '\AbstractConstraint abstract class is deprecated, there is no replacement for it, it will be removed in the next major version.', E_USER_DEPRECATED);

/**
 * Base constraint class.
 */
abstract class AbstractConstraint implements ConstraintInterface
{
    /** @var string */
    protected $prettyString;

    /**
     * @param ConstraintInterface $provider
     *
     * @return bool
     */
    public function matches(ConstraintInterface $provider)
    {
        if ($provider instanceof $this) {
            // see note at bottom of this class declaration
            return $this->matchSpecific($provider);
        }

        // turn matching around to find a match
        return $provider->matches($this);
    }

    /**
     * @param string $prettyString
     */
    public function setPrettyString($prettyString)
    {
        $this->prettyString = $prettyString;
    }

    /**
     * @return string
     */
    public function getPrettyString()
    {
        if ($this->prettyString) {
            return $this->prettyString;
        }

        return $this->__toString();
    }

    // implementations must implement a method of this format:
    // not declared abstract here because type hinting violates parameter coherence (TODO right word?)
    // public function matchSpecific(<SpecificConstraintType> $provider);
}
<?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.
 */
class EmptyConstraint implements ConstraintInterface
{
    /** @var string */
    protected $prettyString;

    /**
     * @param ConstraintInterface $provider
     *
     * @return bool
     */
    public function matches(ConstraintInterface $provider)
    {
        return true;
    }

    /**
     * @param $prettyString
     */
    public function setPrettyString($prettyString)
    {
        $this->prettyString = $prettyString;
    }

    /**
     * @return string
     */
    public function getPrettyString()
    {
        if ($this->prettyString) {
            return $this->prettyString;
        }

        return $this->__toString();
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return '[]';
    }
}
<?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[] */
    protected $constraints;

    /** @var string */
    protected $prettyString;

    /** @var bool */
    protected $conjunctive;

    /**
     * @param ConstraintInterface[] $constraints A set of constraints
     * @param bool $conjunctive Whether the constraints should be treated as conjunctive or disjunctive
     */
    public function __construct(array $constraints, $conjunctive = true)
    {
        $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;
    }

    /**
     * @param ConstraintInterface $provider
     *
     * @return bool
     */
    public function matches(ConstraintInterface $provider)
    {
        if (false === $this->conjunctive) {
            foreach ($this->constraints as $constraint) {
                if ($constraint->matches($provider)) {
                    return true;
                }
            }

            return false;
        }

        foreach ($this->constraints as $constraint) {
            if (!$constraint->matches($provider)) {
                return false;
            }
        }

        return true;
    }

    /**
     * @param string $prettyString
     */
    public function setPrettyString($prettyString)
    {
        $this->prettyString = $prettyString;
    }

    /**
     * @return string
     */
    public function getPrettyString()
    {
        if ($this->prettyString) {
            return $this->prettyString;
        }

        return $this->__toString();
    }

    /**
     * @return string
     */
    public function __toString()
    {
        $constraints = array();
        foreach ($this->constraints as $constraint) {
            $constraints[] = (string) $constraint;
        }

        return '[' . implode($this->conjunctive ? ' ' : ' || ', $constraints) . ']';
    }
}
<?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
     */
    public static function compare($version1, $operator, $version2)
    {
        $constraint = new Constraint($operator, $version2);

        return $constraint->matches(new Constraint('==', $version1));
    }
}
<?php

// autoload_classmap.php @generated by Composer

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

return array(
    'ExampleClass' => $vendorDir . '/jakub-onderka/php-parallel-lint/tests/skip-on-5.3/class.php',
    'ExampleTrait' => $vendorDir . '/jakub-onderka/php-parallel-lint/tests/skip-on-5.3/trait.php',
    'JakubOnderka\\PhpParallelLint\\ArrayIterator' => $vendorDir . '/jakub-onderka/php-parallel-lint/src/Settings.php',
    'JakubOnderka\\PhpParallelLint\\Blame' => $vendorDir . '/jakub-onderka/php-parallel-lint/src/Error.php',
    'JakubOnderka\\PhpParallelLint\\ConsoleWriter' => $vendorDir . '/jakub-onderka/php-parallel-lint/src/Output.php',
    'JakubOnderka\\PhpParallelLint\\Error' => $vendorDir . '/jakub-onderka/php-parallel-lint/src/Error.php',
    'JakubOnderka\\PhpParallelLint\\ErrorFormatter' => $vendorDir . '/jakub-onderka/php-parallel-lint/src/ErrorFormatter.php',
    'JakubOnderka\\PhpParallelLint\\Exception' => $vendorDir . '/jakub-onderka/php-parallel-lint/src/exceptions.php',
    'JakubOnderka\\PhpParallelLint\\FileWriter' => $vendorDir . '/jakub-onderka/php-parallel-lint/src/Output.php',
    'JakubOnderka\\PhpParallelLint\\IWriter' => $vendorDir . '/jakub-onderka/php-parallel-lint/src/Output.php',
    'JakubOnderka\\PhpParallelLint\\InvalidArgumentException' => $vendorDir . '/jakub-onderka/php-parallel-lint/src/exceptions.php',
    'JakubOnderka\\PhpParallelLint\\JsonOutput' => $vendorDir . '/jakub-onderka/php-parallel-lint/src/Output.php',
    'JakubOnderka\\PhpParallelLint\\Manager' => $vendorDir . '/jakub-onderka/php-parallel-lint/src/Manager.php',
    'JakubOnderka\\PhpParallelLint\\MultipleWriter' => $vendorDir . '/jakub-onderka/php-parallel-lint/src/Output.php',
    'JakubOnderka\\PhpParallelLint\\NotExistsPathException' => $vendorDir . '/jakub-onderka/php-parallel-lint/src/exceptions.php',
    'JakubOnderka\\PhpParallelLint\\NullWriter' => $vendorDir . '/jakub-onderka/php-parallel-lint/src/Output.php',
    'JakubOnderka\\PhpParallelLint\\Output' => $vendorDir . '/jakub-onderka/php-parallel-lint/src/Output.php',
    'JakubOnderka\\PhpParallelLint\\ParallelLint' => $vendorDir . '/jakub-onderka/php-parallel-lint/src/ParallelLint.php',
    'JakubOnderka\\PhpParallelLint\\Process\\GitBlameProcess' => $vendorDir . '/jakub-onderka/php-parallel-lint/src/Process/GitBlameProcess.php',
    'JakubOnderka\\PhpParallelLint\\Process\\LintProcess' => $vendorDir . '/jakub-onderka/php-parallel-lint/src/Process/LintProcess.php',
    'JakubOnderka\\PhpParallelLint\\Process\\PhpExecutable' => $vendorDir . '/jakub-onderka/php-parallel-lint/src/Process/PhpExecutable.php',
    'JakubOnderka\\PhpParallelLint\\Process\\PhpProcess' => $vendorDir . '/jakub-onderka/php-parallel-lint/src/Process/PhpProcess.php',
    'JakubOnderka\\PhpParallelLint\\Process\\Process' => $vendorDir . '/jakub-onderka/php-parallel-lint/src/Process/Process.php',
    'JakubOnderka\\PhpParallelLint\\Process\\SkipLintProcess' => $vendorDir . '/jakub-onderka/php-parallel-lint/src/Process/SkipLintProcess.php',
    'JakubOnderka\\PhpParallelLint\\RecursiveDirectoryFilterIterator' => $vendorDir . '/jakub-onderka/php-parallel-lint/src/Manager.php',
    'JakubOnderka\\PhpParallelLint\\Result' => $vendorDir . '/jakub-onderka/php-parallel-lint/src/Result.php',
    'JakubOnderka\\PhpParallelLint\\RunTimeException' => $vendorDir . '/jakub-onderka/php-parallel-lint/src/exceptions.php',
    'JakubOnderka\\PhpParallelLint\\Settings' => $vendorDir . '/jakub-onderka/php-parallel-lint/src/Settings.php',
    'JakubOnderka\\PhpParallelLint\\SyntaxError' => $vendorDir . '/jakub-onderka/php-parallel-lint/src/Error.php',
    'JakubOnderka\\PhpParallelLint\\TextOutput' => $vendorDir . '/jakub-onderka/php-parallel-lint/src/Output.php',
    'JakubOnderka\\PhpParallelLint\\TextOutputColored' => $vendorDir . '/jakub-onderka/php-parallel-lint/src/Output.php',
    'JsonSerializable' => $vendorDir . '/jakub-onderka/php-parallel-lint/src/JsonSerializable.php',
    'SebastianBergmann\\Comparator\\ArrayComparator' => $vendorDir . '/sebastian/comparator/src/ArrayComparator.php',
    'SebastianBergmann\\Comparator\\Comparator' => $vendorDir . '/sebastian/comparator/src/Comparator.php',
    'SebastianBergmann\\Comparator\\ComparisonFailure' => $vendorDir . '/sebastian/comparator/src/ComparisonFailure.php',
    'SebastianBergmann\\Comparator\\DOMNodeComparator' => $vendorDir . '/sebastian/comparator/src/DOMNodeComparator.php',
    'SebastianBergmann\\Comparator\\DateTimeComparator' => $vendorDir . '/sebastian/comparator/src/DateTimeComparator.php',
    'SebastianBergmann\\Comparator\\DoubleComparator' => $vendorDir . '/sebastian/comparator/src/DoubleComparator.php',
    'SebastianBergmann\\Comparator\\ExceptionComparator' => $vendorDir . '/sebastian/comparator/src/ExceptionComparator.php',
    'SebastianBergmann\\Comparator\\Factory' => $vendorDir . '/sebastian/comparator/src/Factory.php',
    'SebastianBergmann\\Comparator\\MockObjectComparator' => $vendorDir . '/sebastian/comparator/src/MockObjectComparator.php',
    'SebastianBergmann\\Comparator\\NumericComparator' => $vendorDir . '/sebastian/comparator/src/NumericComparator.php',
    'SebastianBergmann\\Comparator\\ObjectComparator' => $vendorDir . '/sebastian/comparator/src/ObjectComparator.php',
    'SebastianBergmann\\Comparator\\ResourceComparator' => $vendorDir . '/sebastian/comparator/src/ResourceComparator.php',
    'SebastianBergmann\\Comparator\\ScalarComparator' => $vendorDir . '/sebastian/comparator/src/ScalarComparator.php',
    'SebastianBergmann\\Comparator\\SplObjectStorageComparator' => $vendorDir . '/sebastian/comparator/src/SplObjectStorageComparator.php',
    'SebastianBergmann\\Comparator\\TypeComparator' => $vendorDir . '/sebastian/comparator/src/TypeComparator.php',
    'SebastianBergmann\\Diff\\Chunk' => $vendorDir . '/sebastian/diff/src/Chunk.php',
    'SebastianBergmann\\Diff\\Diff' => $vendorDir . '/sebastian/diff/src/Diff.php',
    'SebastianBergmann\\Diff\\Differ' => $vendorDir . '/sebastian/diff/src/Differ.php',
    'SebastianBergmann\\Diff\\LCS\\LongestCommonSubsequence' => $vendorDir . '/sebastian/diff/src/LCS/LongestCommonSubsequence.php',
    'SebastianBergmann\\Diff\\LCS\\MemoryEfficientImplementation' => $vendorDir . '/sebastian/diff/src/LCS/MemoryEfficientLongestCommonSubsequenceImplementation.php',
    'SebastianBergmann\\Diff\\LCS\\TimeEfficientImplementation' => $vendorDir . '/sebastian/diff/src/LCS/TimeEfficientLongestCommonSubsequenceImplementation.php',
    'SebastianBergmann\\Diff\\Line' => $vendorDir . '/sebastian/diff/src/Line.php',
    'SebastianBergmann\\Diff\\Parser' => $vendorDir . '/sebastian/diff/src/Parser.php',
    'SebastianBergmann\\Environment\\Console' => $vendorDir . '/sebastian/environment/src/Console.php',
    'SebastianBergmann\\Environment\\Runtime' => $vendorDir . '/sebastian/environment/src/Runtime.php',
    'SebastianBergmann\\Exporter\\Exporter' => $vendorDir . '/sebastian/exporter/src/Exporter.php',
    'SebastianBergmann\\GlobalState\\Blacklist' => $vendorDir . '/sebastian/global-state/src/Blacklist.php',
    'SebastianBergmann\\GlobalState\\CodeExporter' => $vendorDir . '/sebastian/global-state/src/CodeExporter.php',
    'SebastianBergmann\\GlobalState\\Exception' => $vendorDir . '/sebastian/global-state/src/Exception.php',
    'SebastianBergmann\\GlobalState\\Restorer' => $vendorDir . '/sebastian/global-state/src/Restorer.php',
    'SebastianBergmann\\GlobalState\\RuntimeException' => $vendorDir . '/sebastian/global-state/src/RuntimeException.php',
    'SebastianBergmann\\GlobalState\\Snapshot' => $vendorDir . '/sebastian/global-state/src/Snapshot.php',
    'SebastianBergmann\\RecursionContext\\Context' => $vendorDir . '/sebastian/recursion-context/src/Context.php',
    'SebastianBergmann\\RecursionContext\\Exception' => $vendorDir . '/sebastian/recursion-context/src/Exception.php',
    'SebastianBergmann\\RecursionContext\\InvalidArgumentException' => $vendorDir . '/sebastian/recursion-context/src/InvalidArgumentException.php',
    'SebastianBergmann\\Version' => $vendorDir . '/sebastian/version/src/Version.php',
);
<?php

// autoload_namespaces.php @generated by Composer

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

return array(
    'phpDocumentor' => array($vendorDir . '/phpdocumentor/reflection-docblock/src'),
    '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'),
    'Behat\\Gherkin' => array($vendorDir . '/behat/gherkin/src'),
    'Behat\\Behat' => array($vendorDir . '/behat/behat/src'),
);
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Expression;

@trigger_error('The '.__NAMESPACE__.'\ValueInterface interface is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
interface ValueInterface
{
    /**
     * Renders string representation of expression.
     *
     * @return string
     */
    public function render();

    /**
     * Renders string representation of pattern.
     *
     * @return string
     */
    public function renderPattern();

    /**
     * Returns value case sensitivity.
     *
     * @return bool
     */
    public function isCaseSensitive();

    /**
     * Returns expression type.
     *
     * @return int
     */
    public function getType();

    /**
     * @param string $expr
     *
     * @return $this
     */
    public function prepend($expr);

    /**
     * @param string $expr
     *
     * @return $this
     */
    public function append($expr);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Expression;

@trigger_error('The '.__NAMESPACE__.'\Expression class is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class Expression implements ValueInterface
{
    const TYPE_REGEX = 1;
    const TYPE_GLOB = 2;

    /**
     * @var ValueInterface
     */
    private $value;

    /**
     * @param string $expr
     *
     * @return self
     */
    public static function create($expr)
    {
        return new self($expr);
    }

    /**
     * @param string $expr
     */
    public function __construct($expr)
    {
        try {
            $this->value = Regex::create($expr);
        } catch (\InvalidArgumentException $e) {
            $this->value = new Glob($expr);
        }
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return $this->render();
    }

    /**
     * {@inheritdoc}
     */
    public function render()
    {
        return $this->value->render();
    }

    /**
     * {@inheritdoc}
     */
    public function renderPattern()
    {
        return $this->value->renderPattern();
    }

    /**
     * @return bool
     */
    public function isCaseSensitive()
    {
        return $this->value->isCaseSensitive();
    }

    /**
     * @return int
     */
    public function getType()
    {
        return $this->value->getType();
    }

    /**
     * {@inheritdoc}
     */
    public function prepend($expr)
    {
        $this->value->prepend($expr);

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function append($expr)
    {
        $this->value->append($expr);

        return $this;
    }

    /**
     * @return bool
     */
    public function isRegex()
    {
        return self::TYPE_REGEX === $this->value->getType();
    }

    /**
     * @return bool
     */
    public function isGlob()
    {
        return self::TYPE_GLOB === $this->value->getType();
    }

    /**
     * @return Glob
     *
     * @throws \LogicException
     */
    public function getGlob()
    {
        if (self::TYPE_GLOB !== $this->value->getType()) {
            throw new \LogicException('Regex can\'t be transformed to glob.');
        }

        return $this->value;
    }

    /**
     * @return Regex
     */
    public function getRegex()
    {
        return self::TYPE_REGEX === $this->value->getType() ? $this->value : $this->value->toRegex();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Expression;

@trigger_error('The '.__NAMESPACE__.'\Regex class is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class Regex implements ValueInterface
{
    const START_FLAG = '^';
    const END_FLAG = '$';
    const BOUNDARY = '~';
    const JOKER = '.*';
    const ESCAPING = '\\';

    /**
     * @var string
     */
    private $pattern;

    /**
     * @var string
     */
    private $options;

    /**
     * @var bool
     */
    private $startFlag;

    /**
     * @var bool
     */
    private $endFlag;

    /**
     * @var bool
     */
    private $startJoker;

    /**
     * @var bool
     */
    private $endJoker;

    /**
     * @param string $expr
     *
     * @return self
     *
     * @throws \InvalidArgumentException
     */
    public static function create($expr)
    {
        if (preg_match('/^(.{3,}?)([imsxuADU]*)$/', $expr, $m)) {
            $start = substr($m[1], 0, 1);
            $end = substr($m[1], -1);

            if (
                ($start === $end && !preg_match('/[*?[:alnum:] \\\\]/', $start))
                || ('{' === $start && '}' === $end)
                || ('(' === $start && ')' === $end)
            ) {
                return new self(substr($m[1], 1, -1), $m[2], $end);
            }
        }

        throw new \InvalidArgumentException('Given expression is not a regex.');
    }

    /**
     * @param string $pattern
     * @param string $options
     * @param string $delimiter
     */
    public function __construct($pattern, $options = '', $delimiter = null)
    {
        if (null !== $delimiter) {
            // removes delimiter escaping
            $pattern = str_replace('\\'.$delimiter, $delimiter, $pattern);
        }

        $this->parsePattern($pattern);
        $this->options = $options;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return $this->render();
    }

    /**
     * {@inheritdoc}
     */
    public function render()
    {
        return self::BOUNDARY
            .$this->renderPattern()
            .self::BOUNDARY
            .$this->options;
    }

    /**
     * {@inheritdoc}
     */
    public function renderPattern()
    {
        return ($this->startFlag ? self::START_FLAG : '')
            .($this->startJoker ? self::JOKER : '')
            .str_replace(self::BOUNDARY, '\\'.self::BOUNDARY, $this->pattern)
            .($this->endJoker ? self::JOKER : '')
            .($this->endFlag ? self::END_FLAG : '');
    }

    /**
     * {@inheritdoc}
     */
    public function isCaseSensitive()
    {
        return !$this->hasOption('i');
    }

    /**
     * {@inheritdoc}
     */
    public function getType()
    {
        return Expression::TYPE_REGEX;
    }

    /**
     * {@inheritdoc}
     */
    public function prepend($expr)
    {
        $this->pattern = $expr.$this->pattern;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function append($expr)
    {
        $this->pattern .= $expr;

        return $this;
    }

    /**
     * @param string $option
     *
     * @return bool
     */
    public function hasOption($option)
    {
        return false !== strpos($this->options, $option);
    }

    /**
     * @param string $option
     *
     * @return $this
     */
    public function addOption($option)
    {
        if (!$this->hasOption($option)) {
            $this->options .= $option;
        }

        return $this;
    }

    /**
     * @param string $option
     *
     * @return $this
     */
    public function removeOption($option)
    {
        $this->options = str_replace($option, '', $this->options);

        return $this;
    }

    /**
     * @param bool $startFlag
     *
     * @return $this
     */
    public function setStartFlag($startFlag)
    {
        $this->startFlag = $startFlag;

        return $this;
    }

    /**
     * @return bool
     */
    public function hasStartFlag()
    {
        return $this->startFlag;
    }

    /**
     * @param bool $endFlag
     *
     * @return $this
     */
    public function setEndFlag($endFlag)
    {
        $this->endFlag = (bool) $endFlag;

        return $this;
    }

    /**
     * @return bool
     */
    public function hasEndFlag()
    {
        return $this->endFlag;
    }

    /**
     * @param bool $startJoker
     *
     * @return $this
     */
    public function setStartJoker($startJoker)
    {
        $this->startJoker = $startJoker;

        return $this;
    }

    /**
     * @return bool
     */
    public function hasStartJoker()
    {
        return $this->startJoker;
    }

    /**
     * @param bool $endJoker
     *
     * @return $this
     */
    public function setEndJoker($endJoker)
    {
        $this->endJoker = (bool) $endJoker;

        return $this;
    }

    /**
     * @return bool
     */
    public function hasEndJoker()
    {
        return $this->endJoker;
    }

    /**
     * @return $this
     */
    public function replaceJokers($replacement)
    {
        $replace = function ($subject) use ($replacement) {
            $subject = $subject[0];
            $replace = 0 === substr_count($subject, '\\') % 2;

            return $replace ? str_replace('.', $replacement, $subject) : $subject;
        };

        $this->pattern = preg_replace_callback('~[\\\\]*\\.~', $replace, $this->pattern);

        return $this;
    }

    /**
     * @param string $pattern
     */
    private function parsePattern($pattern)
    {
        if ($this->startFlag = self::START_FLAG === substr($pattern, 0, 1)) {
            $pattern = substr($pattern, 1);
        }

        if ($this->startJoker = self::JOKER === substr($pattern, 0, 2)) {
            $pattern = substr($pattern, 2);
        }

        if ($this->endFlag = (self::END_FLAG === substr($pattern, -1) && self::ESCAPING !== substr($pattern, -2, -1))) {
            $pattern = substr($pattern, 0, -1);
        }

        if ($this->endJoker = (self::JOKER === substr($pattern, -2) && self::ESCAPING !== substr($pattern, -3, -2))) {
            $pattern = substr($pattern, 0, -2);
        }

        $this->pattern = $pattern;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Expression;

@trigger_error('The '.__NAMESPACE__.'\Glob class is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

use Symfony\Component\Finder\Glob as FinderGlob;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class Glob implements ValueInterface
{
    private $pattern;

    /**
     * @param string $pattern
     */
    public function __construct($pattern)
    {
        $this->pattern = $pattern;
    }

    /**
     * {@inheritdoc}
     */
    public function render()
    {
        return $this->pattern;
    }

    /**
     * {@inheritdoc}
     */
    public function renderPattern()
    {
        return $this->pattern;
    }

    /**
     * {@inheritdoc}
     */
    public function getType()
    {
        return Expression::TYPE_GLOB;
    }

    /**
     * {@inheritdoc}
     */
    public function isCaseSensitive()
    {
        return true;
    }

    /**
     * {@inheritdoc}
     */
    public function prepend($expr)
    {
        $this->pattern = $expr.$this->pattern;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function append($expr)
    {
        $this->pattern .= $expr;

        return $this;
    }

    /**
     * Tests if glob is expandable ("*.{a,b}" syntax).
     *
     * @return bool
     */
    public function isExpandable()
    {
        return false !== strpos($this->pattern, '{')
            && false !== strpos($this->pattern, '}');
    }

    /**
     * @param bool $strictLeadingDot
     * @param bool $strictWildcardSlash
     *
     * @return Regex
     */
    public function toRegex($strictLeadingDot = true, $strictWildcardSlash = true)
    {
        $regex = FinderGlob::toRegex($this->pattern, $strictLeadingDot, $strictWildcardSlash, '');

        return new Regex($regex);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Comparator;

/**
 * DateCompare compiles date comparisons.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class DateComparator extends Comparator
{
    /**
     * @param string $test A comparison string
     *
     * @throws \InvalidArgumentException If the test is not understood
     */
    public function __construct($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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Comparator;

/**
 * Comparator.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
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, array('>', '<', '>=', '<=', '==', '!='))) {
            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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Comparator;

/**
 * NumberComparator compiles a simple comparison to an anonymous
 * subroutine, which you can call with a value to be tested again.
 *
 * Now this would be very pointless, if NumberCompare didn't understand
 * magnitudes.
 *
 * The target value may use magnitudes of kilobytes (k, ki),
 * megabytes (m, mi), or gigabytes (g, gi).  Those suffixed
 * with an i use the appropriate 2**n version in accordance with the
 * IEC standard: http://physics.nist.gov/cuu/Units/binary.html
 *
 * Based on the Perl Number::Compare module.
 *
 * @author    Fabien Potencier <fabien@symfony.com> PHP port
 * @author    Richard Clamp <richardc@unixbeard.net> Perl version
 * @copyright 2004-2005 Fabien Potencier <fabien@symfony.com>
 * @copyright 2002 Richard Clamp <richardc@unixbeard.net>
 *
 * @see http://physics.nist.gov/cuu/Units/binary.html
 */
class NumberComparator extends Comparator
{
    /**
     * @param string|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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder;

use Symfony\Component\Finder\Adapter\AdapterInterface;
use Symfony\Component\Finder\Adapter\BsdFindAdapter;
use Symfony\Component\Finder\Adapter\GnuFindAdapter;
use Symfony\Component\Finder\Adapter\PhpAdapter;
use Symfony\Component\Finder\Comparator\DateComparator;
use Symfony\Component\Finder\Comparator\NumberComparator;
use Symfony\Component\Finder\Exception\ExceptionInterface;
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 easy chaining:
 *
 *     $finder = Finder::create()->files()->name('*.php')->in(__DIR__);
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Finder implements \IteratorAggregate, \Countable
{
    const IGNORE_VCS_FILES = 1;
    const IGNORE_DOT_FILES = 2;

    private $mode = 0;
    private $names = array();
    private $notNames = array();
    private $exclude = array();
    private $filters = array();
    private $depths = array();
    private $sizes = array();
    private $followLinks = false;
    private $sort = false;
    private $ignore = 0;
    private $dirs = array();
    private $dates = array();
    private $iterators = array();
    private $contains = array();
    private $notContains = array();
    private $adapters = null;
    private $paths = array();
    private $notPaths = array();
    private $ignoreUnreadableDirs = false;

    private static $vcsPatterns = array('.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();
    }

    /**
     * Registers a finder engine implementation.
     *
     * @param AdapterInterface $adapter  An adapter instance
     * @param int              $priority Highest is selected first
     *
     * @return $this
     *
     * @deprecated since 2.8, to be removed in 3.0.
     */
    public function addAdapter(AdapterInterface $adapter, $priority = 0)
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

        $this->initDefaultAdapters();

        $this->adapters[$adapter->getName()] = array(
            'adapter' => $adapter,
            'priority' => $priority,
            'selected' => false,
        );

        return $this->sortAdapters();
    }

    /**
     * Sets the selected adapter to the best one according to the current platform the code is run on.
     *
     * @return $this
     *
     * @deprecated since 2.8, to be removed in 3.0.
     */
    public function useBestAdapter()
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

        $this->initDefaultAdapters();

        $this->resetAdapterSelection();

        return $this->sortAdapters();
    }

    /**
     * Selects the adapter to use.
     *
     * @param string $name
     *
     * @return $this
     *
     * @throws \InvalidArgumentException
     *
     * @deprecated since 2.8, to be removed in 3.0.
     */
    public function setAdapter($name)
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

        $this->initDefaultAdapters();

        if (!isset($this->adapters[$name])) {
            throw new \InvalidArgumentException(sprintf('Adapter "%s" does not exist.', $name));
        }

        $this->resetAdapterSelection();
        $this->adapters[$name]['selected'] = true;

        return $this->sortAdapters();
    }

    /**
     * Removes all adapters registered in the finder.
     *
     * @return $this
     *
     * @deprecated since 2.8, to be removed in 3.0.
     */
    public function removeAdapters()
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

        $this->adapters = array();

        return $this;
    }

    /**
     * Returns registered adapters ordered by priority without extra information.
     *
     * @return AdapterInterface[]
     *
     * @deprecated since 2.8, to be removed in 3.0.
     */
    public function getAdapters()
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

        $this->initDefaultAdapters();

        return array_values(array_map(function (array $adapter) {
            return $adapter['adapter'];
        }, $this->adapters));
    }

    /**
     * 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|array $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 = array();

        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)) {
                $resolvedDirs = array_merge($resolvedDirs, array_map(array($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 (0 === \count($this->dirs) && 0 === \count($this->iterators)) {
            throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.');
        }

        if (1 === \count($this->dirs) && 0 === \count($this->iterators)) {
            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;
    }

    /**
     * Counts all the results collected by the iterators.
     *
     * @return int
     */
    public function count()
    {
        return iterator_count($this->getIterator());
    }

    /**
     * @return $this
     */
    private function sortAdapters()
    {
        uasort($this->adapters, function (array $a, array $b) {
            if ($a['selected'] || $b['selected']) {
                return $a['selected'] ? -1 : 1;
            }

            return $a['priority'] > $b['priority'] ? -1 : 1;
        });

        return $this;
    }

    /**
     * @param string $dir
     *
     * @return \Iterator
     */
    private function searchInDirectory($dir)
    {
        if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) {
            $this->exclude = array_merge($this->exclude, self::$vcsPatterns);
        }

        if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) {
            $this->notPaths[] = '#(^|/)\..+(/|$)#';
        }

        if ($this->adapters) {
            foreach ($this->adapters as $adapter) {
                if ($adapter['adapter']->isSupported()) {
                    try {
                        return $this
                            ->buildAdapter($adapter['adapter'])
                            ->searchInDirectory($dir);
                    } catch (ExceptionInterface $e) {
                    }
                }
            }
        }

        $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 ($this->exclude) {
            $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude);
        }

        $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST);

        if ($minDepth > 0 || $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 || $this->notPaths) {
            $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $this->notPaths);
        }

        if ($this->sort) {
            $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort);
            $iterator = $iteratorAggregate->getIterator();
        }

        return $iterator;
    }

    /**
     * @return AdapterInterface
     */
    private function buildAdapter(AdapterInterface $adapter)
    {
        return $adapter
            ->setFollowLinks($this->followLinks)
            ->setDepths($this->depths)
            ->setMode($this->mode)
            ->setExclude($this->exclude)
            ->setNames($this->names)
            ->setNotNames($this->notNames)
            ->setContains($this->contains)
            ->setNotContains($this->notContains)
            ->setSizes($this->sizes)
            ->setDates($this->dates)
            ->setFilters($this->filters)
            ->setSort($this->sort)
            ->setPath($this->paths)
            ->setNotPath($this->notPaths)
            ->ignoreUnreadableDirs($this->ignoreUnreadableDirs);
    }

    /**
     * Unselects all adapters.
     */
    private function resetAdapterSelection()
    {
        $this->adapters = array_map(function (array $properties) {
            $properties['selected'] = false;

            return $properties;
        }, $this->adapters);
    }

    private function initDefaultAdapters()
    {
        if (null === $this->adapters) {
            $this->adapters = array();
            $this
                ->addAdapter(new GnuFindAdapter())
                ->addAdapter(new BsdFindAdapter())
                ->addAdapter(new PhpAdapter(), -50)
                ->setAdapter('php')
            ;
        }
    }

    /**
     * Normalizes given directory names by removing trailing slashes.
     *
     * @param string $dir
     *
     * @return string
     */
    private function normalizeDir($dir)
    {
        return rtrim($dir, '/'.\DIRECTORY_SEPARATOR);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder;

/**
 * Extends \SplFileInfo to support relative paths.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class SplFileInfo extends \SplFileInfo
{
    private $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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Shell;

@trigger_error('The '.__NAMESPACE__.'\Shell class is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @deprecated since 2.8, to be removed in 3.0.
 */
class Shell
{
    const TYPE_UNIX = 1;
    const TYPE_DARWIN = 2;
    const TYPE_CYGWIN = 3;
    const TYPE_WINDOWS = 4;
    const TYPE_BSD = 5;

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

    /**
     * Returns guessed OS type.
     *
     * @return int
     */
    public function getType()
    {
        if (null === $this->type) {
            $this->type = $this->guessType();
        }

        return $this->type;
    }

    /**
     * Tests if a command is available.
     *
     * @param string $command
     *
     * @return bool
     */
    public function testCommand($command)
    {
        if (!\function_exists('exec')) {
            return false;
        }

        // todo: find a better way (command could not be available)
        $testCommand = 'which ';
        if (self::TYPE_WINDOWS === $this->type) {
            $testCommand = 'where ';
        }

        $command = escapeshellcmd($command);

        exec($testCommand.$command, $output, $code);

        return 0 === $code && \count($output) > 0;
    }

    /**
     * Guesses OS type.
     *
     * @return int
     */
    private function guessType()
    {
        $os = strtolower(PHP_OS);

        if (false !== strpos($os, 'cygwin')) {
            return self::TYPE_CYGWIN;
        }

        if (false !== strpos($os, 'darwin')) {
            return self::TYPE_DARWIN;
        }

        if (false !== strpos($os, 'bsd')) {
            return self::TYPE_BSD;
        }

        if (0 === strpos($os, 'win')) {
            return self::TYPE_WINDOWS;
        }

        return self::TYPE_UNIX;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Shell;

@trigger_error('The '.__NAMESPACE__.'\Command class is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @deprecated since 2.8, to be removed in 3.0.
 */
class Command
{
    private $parent;
    private $bits = array();
    private $labels = array();

    /**
     * @var \Closure|null
     */
    private $errorHandler;

    public function __construct(Command $parent = null)
    {
        $this->parent = $parent;
    }

    /**
     * Returns command as string.
     *
     * @return string
     */
    public function __toString()
    {
        return $this->join();
    }

    /**
     * Creates a new Command instance.
     *
     * @return self
     */
    public static function create(Command $parent = null)
    {
        return new self($parent);
    }

    /**
     * Escapes special chars from input.
     *
     * @param string $input A string to escape
     *
     * @return string The escaped string
     */
    public static function escape($input)
    {
        return escapeshellcmd($input);
    }

    /**
     * Quotes input.
     *
     * @param string $input An argument string
     *
     * @return string The quoted string
     */
    public static function quote($input)
    {
        return escapeshellarg($input);
    }

    /**
     * Appends a string or a Command instance.
     *
     * @param string|Command $bit
     *
     * @return $this
     */
    public function add($bit)
    {
        $this->bits[] = $bit;

        return $this;
    }

    /**
     * Prepends a string or a command instance.
     *
     * @param string|Command $bit
     *
     * @return $this
     */
    public function top($bit)
    {
        array_unshift($this->bits, $bit);

        foreach ($this->labels as $label => $index) {
            ++$this->labels[$label];
        }

        return $this;
    }

    /**
     * Appends an argument, will be quoted.
     *
     * @param string $arg
     *
     * @return $this
     */
    public function arg($arg)
    {
        $this->bits[] = self::quote($arg);

        return $this;
    }

    /**
     * Appends escaped special command chars.
     *
     * @param string $esc
     *
     * @return $this
     */
    public function cmd($esc)
    {
        $this->bits[] = self::escape($esc);

        return $this;
    }

    /**
     * Inserts a labeled command to feed later.
     *
     * @param string $label The unique label
     *
     * @return self|string
     *
     * @throws \RuntimeException If label already exists
     */
    public function ins($label)
    {
        if (isset($this->labels[$label])) {
            throw new \RuntimeException(sprintf('Label "%s" already exists.', $label));
        }

        $this->bits[] = self::create($this);
        $this->labels[$label] = \count($this->bits) - 1;

        return $this->bits[$this->labels[$label]];
    }

    /**
     * Retrieves a previously labeled command.
     *
     * @param string $label
     *
     * @return self|string
     *
     * @throws \RuntimeException
     */
    public function get($label)
    {
        if (!isset($this->labels[$label])) {
            throw new \RuntimeException(sprintf('Label "%s" does not exist.', $label));
        }

        return $this->bits[$this->labels[$label]];
    }

    /**
     * Returns parent command (if any).
     *
     * @return self
     *
     * @throws \RuntimeException If command has no parent
     */
    public function end()
    {
        if (null === $this->parent) {
            throw new \RuntimeException('Calling end on root command doesn\'t make sense.');
        }

        return $this->parent;
    }

    /**
     * Counts bits stored in command.
     *
     * @return int The bits count
     */
    public function length()
    {
        return \count($this->bits);
    }

    /**
     * @return $this
     */
    public function setErrorHandler(\Closure $errorHandler)
    {
        $this->errorHandler = $errorHandler;

        return $this;
    }

    /**
     * @return \Closure|null
     */
    public function getErrorHandler()
    {
        return $this->errorHandler;
    }

    /**
     * Executes current command.
     *
     * @return array The command result
     *
     * @throws \RuntimeException
     */
    public function execute()
    {
        if (null === $errorHandler = $this->errorHandler) {
            exec($this->join(), $output);
        } else {
            $process = proc_open($this->join(), array(0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w')), $pipes);
            $output = preg_split('~(\r\n|\r|\n)~', stream_get_contents($pipes[1]), -1, PREG_SPLIT_NO_EMPTY);

            if ($error = stream_get_contents($pipes[2])) {
                $errorHandler($error);
            }

            proc_close($process);
        }

        return $output ?: array();
    }

    /**
     * Joins bits.
     *
     * @return string
     */
    public function join()
    {
        return implode(' ', array_filter(
            array_map(function ($bit) {
                return $bit instanceof Command ? $bit->join() : ($bit ?: null);
            }, $this->bits),
            function ($bit) { return null !== $bit; }
        ));
    }

    /**
     * Insert a string or a Command instance before the bit at given position $index (index starts from 0).
     *
     * @param string|Command $bit
     * @param int            $index
     *
     * @return $this
     */
    public function addAtIndex($bit, $index)
    {
        array_splice($this->bits, $index, 0, $bit instanceof self ? array($bit) : $bit);

        return $this;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Adapter;

@trigger_error('The '.__NAMESPACE__.'\AbstractAdapter class is deprecated since Symfony 2.8 and will be removed in 3.0. Use directly the Finder class instead.', E_USER_DEPRECATED);

/**
 * Interface for finder engine implementations.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @deprecated since 2.8, to be removed in 3.0. Use Finder instead.
 */
abstract class AbstractAdapter implements AdapterInterface
{
    protected $followLinks = false;
    protected $mode = 0;
    protected $minDepth = 0;
    protected $maxDepth = PHP_INT_MAX;
    protected $exclude = array();
    protected $names = array();
    protected $notNames = array();
    protected $contains = array();
    protected $notContains = array();
    protected $sizes = array();
    protected $dates = array();
    protected $filters = array();
    protected $sort = false;
    protected $paths = array();
    protected $notPaths = array();
    protected $ignoreUnreadableDirs = false;

    private static $areSupported = array();

    /**
     * {@inheritdoc}
     */
    public function isSupported()
    {
        $name = $this->getName();

        if (!array_key_exists($name, self::$areSupported)) {
            self::$areSupported[$name] = $this->canBeUsed();
        }

        return self::$areSupported[$name];
    }

    /**
     * {@inheritdoc}
     */
    public function setFollowLinks($followLinks)
    {
        $this->followLinks = $followLinks;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setMode($mode)
    {
        $this->mode = $mode;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setDepths(array $depths)
    {
        $this->minDepth = 0;
        $this->maxDepth = PHP_INT_MAX;

        foreach ($depths as $comparator) {
            switch ($comparator->getOperator()) {
                case '>':
                    $this->minDepth = $comparator->getTarget() + 1;
                    break;
                case '>=':
                    $this->minDepth = $comparator->getTarget();
                    break;
                case '<':
                    $this->maxDepth = $comparator->getTarget() - 1;
                    break;
                case '<=':
                    $this->maxDepth = $comparator->getTarget();
                    break;
                default:
                    $this->minDepth = $this->maxDepth = $comparator->getTarget();
            }
        }

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setExclude(array $exclude)
    {
        $this->exclude = $exclude;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setNames(array $names)
    {
        $this->names = $names;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setNotNames(array $notNames)
    {
        $this->notNames = $notNames;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setContains(array $contains)
    {
        $this->contains = $contains;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setNotContains(array $notContains)
    {
        $this->notContains = $notContains;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setSizes(array $sizes)
    {
        $this->sizes = $sizes;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setDates(array $dates)
    {
        $this->dates = $dates;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setFilters(array $filters)
    {
        $this->filters = $filters;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setSort($sort)
    {
        $this->sort = $sort;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setPath(array $paths)
    {
        $this->paths = $paths;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function setNotPath(array $notPaths)
    {
        $this->notPaths = $notPaths;

        return $this;
    }

    /**
     * {@inheritdoc}
     */
    public function ignoreUnreadableDirs($ignore = true)
    {
        $this->ignoreUnreadableDirs = (bool) $ignore;

        return $this;
    }

    /**
     * Returns whether the adapter is supported in the current environment.
     *
     * This method should be implemented in all adapters. Do not implement
     * isSupported in the adapters as the generic implementation provides a cache
     * layer.
     *
     * @see isSupported()
     *
     * @return bool Whether the adapter is supported
     */
    abstract protected function canBeUsed();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Adapter;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @deprecated since 2.8, to be removed in 3.0.
 */
interface AdapterInterface
{
    /**
     * @param bool $followLinks
     *
     * @return $this
     */
    public function setFollowLinks($followLinks);

    /**
     * @param int $mode
     *
     * @return $this
     */
    public function setMode($mode);

    /**
     * @return $this
     */
    public function setExclude(array $exclude);

    /**
     * @return $this
     */
    public function setDepths(array $depths);

    /**
     * @return $this
     */
    public function setNames(array $names);

    /**
     * @return $this
     */
    public function setNotNames(array $notNames);

    /**
     * @return $this
     */
    public function setContains(array $contains);

    /**
     * @return $this
     */
    public function setNotContains(array $notContains);

    /**
     * @return $this
     */
    public function setSizes(array $sizes);

    /**
     * @return $this
     */
    public function setDates(array $dates);

    /**
     * @return $this
     */
    public function setFilters(array $filters);

    /**
     * @param \Closure|int $sort
     *
     * @return $this
     */
    public function setSort($sort);

    /**
     * @return $this
     */
    public function setPath(array $paths);

    /**
     * @return $this
     */
    public function setNotPath(array $notPaths);

    /**
     * @param bool $ignore
     *
     * @return $this
     */
    public function ignoreUnreadableDirs($ignore = true);

    /**
     * @param string $dir
     *
     * @return \Iterator Result iterator
     */
    public function searchInDirectory($dir);

    /**
     * Tests adapter support for current platform.
     *
     * @return bool
     */
    public function isSupported();

    /**
     * Returns adapter name.
     *
     * @return string
     */
    public function getName();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Adapter;

@trigger_error('The '.__NAMESPACE__.'\BsdFindAdapter class is deprecated since Symfony 2.8 and will be removed in 3.0. Use directly the Finder class instead.', E_USER_DEPRECATED);

use Symfony\Component\Finder\Expression\Expression;
use Symfony\Component\Finder\Iterator\SortableIterator;
use Symfony\Component\Finder\Shell\Command;
use Symfony\Component\Finder\Shell\Shell;

/**
 * Shell engine implementation using BSD find command.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @deprecated since 2.8, to be removed in 3.0. Use Finder instead.
 */
class BsdFindAdapter extends AbstractFindAdapter
{
    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'bsd_find';
    }

    /**
     * {@inheritdoc}
     */
    protected function canBeUsed()
    {
        return \in_array($this->shell->getType(), array(Shell::TYPE_BSD, Shell::TYPE_DARWIN)) && parent::canBeUsed();
    }

    /**
     * {@inheritdoc}
     */
    protected function buildFormatSorting(Command $command, $sort)
    {
        switch ($sort) {
            case SortableIterator::SORT_BY_NAME:
                $command->ins('sort')->add('| sort');

                return;
            case SortableIterator::SORT_BY_TYPE:
                $format = '%HT';
                break;
            case SortableIterator::SORT_BY_ACCESSED_TIME:
                $format = '%a';
                break;
            case SortableIterator::SORT_BY_CHANGED_TIME:
                $format = '%c';
                break;
            case SortableIterator::SORT_BY_MODIFIED_TIME:
                $format = '%m';
                break;
            default:
                throw new \InvalidArgumentException(sprintf('Unknown sort options: %s.', $sort));
        }

        $command
            ->add('-print0 | xargs -0 stat -f')
            ->arg($format.'%t%N')
            ->add('| sort | cut -f 2');
    }

    /**
     * {@inheritdoc}
     */
    protected function buildFindCommand(Command $command, $dir)
    {
        parent::buildFindCommand($command, $dir)->addAtIndex('-E', 1);

        return $command;
    }

    /**
     * {@inheritdoc}
     */
    protected function buildContentFiltering(Command $command, array $contains, $not = false)
    {
        foreach ($contains as $contain) {
            $expr = Expression::create($contain);

            // todo: avoid forking process for each $pattern by using multiple -e options
            $command
                ->add('| grep -v \'^$\'')
                ->add('| xargs -I{} grep -I')
                ->add($expr->isCaseSensitive() ? null : '-i')
                ->add($not ? '-L' : '-l')
                ->add('-Ee')->arg($expr->renderPattern())
                ->add('{}')
            ;
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Adapter;

@trigger_error('The '.__NAMESPACE__.'\AbstractFindAdapter class is deprecated since Symfony 2.8 and will be removed in 3.0. Use directly the Finder class instead.', E_USER_DEPRECATED);

use Symfony\Component\Finder\Comparator\DateComparator;
use Symfony\Component\Finder\Comparator\NumberComparator;
use Symfony\Component\Finder\Exception\AccessDeniedException;
use Symfony\Component\Finder\Expression\Expression;
use Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Shell\Command;
use Symfony\Component\Finder\Shell\Shell;

/**
 * Shell engine implementation using GNU find command.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @deprecated since 2.8, to be removed in 3.0. Use Finder instead.
 */
abstract class AbstractFindAdapter extends AbstractAdapter
{
    protected $shell;

    public function __construct()
    {
        $this->shell = new Shell();
    }

    /**
     * {@inheritdoc}
     */
    public function searchInDirectory($dir)
    {
        // having "/../" in path make find fail
        $dir = realpath($dir);

        // searching directories containing or not containing strings leads to no result
        if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode && ($this->contains || $this->notContains)) {
            return new Iterator\FilePathsIterator(array(), $dir);
        }

        $command = Command::create();
        $find = $this->buildFindCommand($command, $dir);

        if ($this->followLinks) {
            $find->add('-follow');
        }

        $find->add('-mindepth')->add($this->minDepth + 1);

        if (PHP_INT_MAX !== $this->maxDepth) {
            $find->add('-maxdepth')->add($this->maxDepth + 1);
        }

        if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode) {
            $find->add('-type d');
        } elseif (Iterator\FileTypeFilterIterator::ONLY_FILES === $this->mode) {
            $find->add('-type f');
        }

        $this->buildNamesFiltering($find, $this->names);
        $this->buildNamesFiltering($find, $this->notNames, true);
        $this->buildPathsFiltering($find, $dir, $this->paths);
        $this->buildPathsFiltering($find, $dir, $this->notPaths, true);
        $this->buildSizesFiltering($find, $this->sizes);
        $this->buildDatesFiltering($find, $this->dates);

        $useGrep = $this->shell->testCommand('grep') && $this->shell->testCommand('xargs');
        $useSort = \is_int($this->sort) && $this->shell->testCommand('sort') && $this->shell->testCommand('cut');

        if ($useGrep && ($this->contains || $this->notContains)) {
            $grep = $command->ins('grep');
            $this->buildContentFiltering($grep, $this->contains);
            $this->buildContentFiltering($grep, $this->notContains, true);
        }

        if ($useSort) {
            $this->buildSorting($command, $this->sort);
        }

        $command->setErrorHandler(
            $this->ignoreUnreadableDirs
                // If directory is unreadable and finder is set to ignore it, `stderr` is ignored.
                ? function ($stderr) { }
                : function ($stderr) { throw new AccessDeniedException($stderr); }
        );

        $paths = $this->shell->testCommand('uniq') ? $command->add('| uniq')->execute() : array_unique($command->execute());
        $iterator = new Iterator\FilePathsIterator($paths, $dir);

        if ($this->exclude) {
            $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude);
        }

        if (!$useGrep && ($this->contains || $this->notContains)) {
            $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains);
        }

        if ($this->filters) {
            $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters);
        }

        if (!$useSort && $this->sort) {
            $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort);
            $iterator = $iteratorAggregate->getIterator();
        }

        return $iterator;
    }

    /**
     * {@inheritdoc}
     */
    protected function canBeUsed()
    {
        return $this->shell->testCommand('find');
    }

    /**
     * @param Command $command
     * @param string  $dir
     *
     * @return Command
     */
    protected function buildFindCommand(Command $command, $dir)
    {
        return $command
            ->ins('find')
            ->add('find ')
            ->arg($dir)
            ->add('-noleaf'); // the -noleaf option is required for filesystems that don't follow the '.' and '..' conventions
    }

    /**
     * @param Command  $command
     * @param string[] $names
     * @param bool     $not
     */
    private function buildNamesFiltering(Command $command, array $names, $not = false)
    {
        if (0 === \count($names)) {
            return;
        }

        $command->add($not ? '-not' : null)->cmd('(');

        foreach ($names as $i => $name) {
            $expr = Expression::create($name);

            // Find does not support expandable globs ("*.{a,b}" syntax).
            if ($expr->isGlob() && $expr->getGlob()->isExpandable()) {
                $expr = Expression::create($expr->getGlob()->toRegex(false));
            }

            // Fixes 'not search' and 'full path matching' regex problems.
            // - Jokers '.' are replaced by [^/].
            // - We add '[^/]*' before and after regex (if no ^|$ flags are present).
            if ($expr->isRegex()) {
                $regex = $expr->getRegex();
                $regex->prepend($regex->hasStartFlag() ? '/' : '/[^/]*')
                    ->setStartFlag(false)
                    ->setStartJoker(true)
                    ->replaceJokers('[^/]');
                if (!$regex->hasEndFlag() || $regex->hasEndJoker()) {
                    $regex->setEndJoker(false)->append('[^/]*');
                }
            }

            $command
                ->add($i > 0 ? '-or' : null)
                ->add($expr->isRegex()
                    ? ($expr->isCaseSensitive() ? '-regex' : '-iregex')
                    : ($expr->isCaseSensitive() ? '-name' : '-iname')
                )
                ->arg($expr->renderPattern());
        }

        $command->cmd(')');
    }

    /**
     * @param Command  $command
     * @param string   $dir
     * @param string[] $paths
     * @param bool     $not
     */
    private function buildPathsFiltering(Command $command, $dir, array $paths, $not = false)
    {
        if (0 === \count($paths)) {
            return;
        }

        $command->add($not ? '-not' : null)->cmd('(');

        foreach ($paths as $i => $path) {
            $expr = Expression::create($path);

            // Find does not support expandable globs ("*.{a,b}" syntax).
            if ($expr->isGlob() && $expr->getGlob()->isExpandable()) {
                $expr = Expression::create($expr->getGlob()->toRegex(false));
            }

            // Fixes 'not search' regex problems.
            if ($expr->isRegex()) {
                $regex = $expr->getRegex();
                $regex->prepend($regex->hasStartFlag() ? preg_quote($dir).\DIRECTORY_SEPARATOR : '.*')->setEndJoker(!$regex->hasEndFlag());
            } else {
                $expr->prepend('*')->append('*');
            }

            $command
                ->add($i > 0 ? '-or' : null)
                ->add($expr->isRegex()
                    ? ($expr->isCaseSensitive() ? '-regex' : '-iregex')
                    : ($expr->isCaseSensitive() ? '-path' : '-ipath')
                )
                ->arg($expr->renderPattern());
        }

        $command->cmd(')');
    }

    /**
     * @param Command            $command
     * @param NumberComparator[] $sizes
     */
    private function buildSizesFiltering(Command $command, array $sizes)
    {
        foreach ($sizes as $i => $size) {
            $command->add($i > 0 ? '-and' : null);

            switch ($size->getOperator()) {
                case '<=':
                    $command->add('-size -'.($size->getTarget() + 1).'c');
                    break;
                case '>=':
                    $command->add('-size +'.($size->getTarget() - 1).'c');
                    break;
                case '>':
                    $command->add('-size +'.$size->getTarget().'c');
                    break;
                case '!=':
                    $command->add('-size -'.$size->getTarget().'c');
                    $command->add('-size +'.$size->getTarget().'c');
                    break;
                case '<':
                default:
                    $command->add('-size -'.$size->getTarget().'c');
            }
        }
    }

    /**
     * @param Command          $command
     * @param DateComparator[] $dates
     */
    private function buildDatesFiltering(Command $command, array $dates)
    {
        foreach ($dates as $i => $date) {
            $command->add($i > 0 ? '-and' : null);

            $mins = (int) round((time() - $date->getTarget()) / 60);

            if (0 > $mins) {
                // mtime is in the future
                $command->add(' -mmin -0');
                // we will have no result so we don't need to continue
                return;
            }

            switch ($date->getOperator()) {
                case '<=':
                    $command->add('-mmin +'.($mins - 1));
                    break;
                case '>=':
                    $command->add('-mmin -'.($mins + 1));
                    break;
                case '>':
                    $command->add('-mmin -'.$mins);
                    break;
                case '!=':
                    $command->add('-mmin +'.$mins.' -or -mmin -'.$mins);
                    break;
                case '<':
                default:
                    $command->add('-mmin +'.$mins);
            }
        }
    }

    /**
     * @param Command $command
     * @param string  $sort
     *
     * @throws \InvalidArgumentException
     */
    private function buildSorting(Command $command, $sort)
    {
        $this->buildFormatSorting($command, $sort);
    }

    /**
     * @param Command $command
     * @param string  $sort
     */
    abstract protected function buildFormatSorting(Command $command, $sort);

    /**
     * @param Command $command
     * @param array   $contains
     * @param bool    $not
     */
    abstract protected function buildContentFiltering(Command $command, array $contains, $not = false);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Adapter;

@trigger_error('The '.__NAMESPACE__.'\GnuFindAdapter class is deprecated since Symfony 2.8 and will be removed in 3.0. Use directly the Finder class instead.', E_USER_DEPRECATED);

use Symfony\Component\Finder\Expression\Expression;
use Symfony\Component\Finder\Iterator\SortableIterator;
use Symfony\Component\Finder\Shell\Command;
use Symfony\Component\Finder\Shell\Shell;

/**
 * Shell engine implementation using GNU find command.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @deprecated since 2.8, to be removed in 3.0. Use Finder instead.
 */
class GnuFindAdapter extends AbstractFindAdapter
{
    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'gnu_find';
    }

    /**
     * {@inheritdoc}
     */
    protected function buildFormatSorting(Command $command, $sort)
    {
        switch ($sort) {
            case SortableIterator::SORT_BY_NAME:
                $command->ins('sort')->add('| sort');

                return;
            case SortableIterator::SORT_BY_TYPE:
                $format = '%y';
                break;
            case SortableIterator::SORT_BY_ACCESSED_TIME:
                $format = '%A@';
                break;
            case SortableIterator::SORT_BY_CHANGED_TIME:
                $format = '%C@';
                break;
            case SortableIterator::SORT_BY_MODIFIED_TIME:
                $format = '%T@';
                break;
            default:
                throw new \InvalidArgumentException(sprintf('Unknown sort options: %s.', $sort));
        }

        $command
            ->get('find')
            ->add('-printf')
            ->arg($format.' %h/%f\\n')
            ->add('| sort | cut')
            ->arg('-d ')
            ->arg('-f2-')
        ;
    }

    /**
     * {@inheritdoc}
     */
    protected function canBeUsed()
    {
        return Shell::TYPE_UNIX === $this->shell->getType() && parent::canBeUsed();
    }

    /**
     * {@inheritdoc}
     */
    protected function buildFindCommand(Command $command, $dir)
    {
        return parent::buildFindCommand($command, $dir)->add('-regextype posix-extended');
    }

    /**
     * {@inheritdoc}
     */
    protected function buildContentFiltering(Command $command, array $contains, $not = false)
    {
        foreach ($contains as $contain) {
            $expr = Expression::create($contain);

            // todo: avoid forking process for each $pattern by using multiple -e options
            $command
                ->add('| xargs -I{} -r grep -I')
                ->add($expr->isCaseSensitive() ? null : '-i')
                ->add($not ? '-L' : '-l')
                ->add('-Ee')->arg($expr->renderPattern())
                ->add('{}')
            ;
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Adapter;

@trigger_error('The '.__NAMESPACE__.'\PhpAdapter class is deprecated since Symfony 2.8 and will be removed in 3.0. Use directly the Finder class instead.', E_USER_DEPRECATED);

use Symfony\Component\Finder\Iterator;

/**
 * PHP finder engine implementation.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @deprecated since 2.8, to be removed in 3.0. Use Finder instead.
 */
class PhpAdapter extends AbstractAdapter
{
    /**
     * {@inheritdoc}
     */
    public function searchInDirectory($dir)
    {
        $flags = \RecursiveDirectoryIterator::SKIP_DOTS;

        if ($this->followLinks) {
            $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS;
        }

        $iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs);

        if ($this->exclude) {
            $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude);
        }

        $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST);

        if ($this->minDepth > 0 || $this->maxDepth < PHP_INT_MAX) {
            $iterator = new Iterator\DepthRangeFilterIterator($iterator, $this->minDepth, $this->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 || $this->notPaths) {
            $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $this->notPaths);
        }

        if ($this->sort) {
            $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort);
            $iterator = $iteratorAggregate->getIterator();
        }

        return $iterator;
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'php';
    }

    /**
     * {@inheritdoc}
     */
    protected function canBeUsed()
    {
        return true;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

use Symfony\Component\Finder\Glob;

/**
 * FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string).
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * FilecontentFilterIterator filters files by their contents using patterns (regexps or strings).
 *
 * @author Fabien Potencier  <fabien@symfony.com>
 * @author Włodzimierz Gajda <gajdaw@gajdaw.pl>
 */
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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * DepthRangeFilterIterator limits the directory depth.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
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 ? -1 : $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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

@trigger_error('The '.__NAMESPACE__.'\FilePathsIterator class is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

use Symfony\Component\Finder\SplFileInfo;

/**
 * Iterate over shell command result.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @deprecated since 2.8, to be removed in 3.0.
 */
class FilePathsIterator extends \ArrayIterator
{
    /**
     * @var string
     */
    private $baseDir;

    /**
     * @var int
     */
    private $baseDirLength;

    /**
     * @var string
     */
    private $subPath;

    /**
     * @var string
     */
    private $subPathname;

    /**
     * @var SplFileInfo
     */
    private $current;

    /**
     * @param array  $paths   List of paths returned by shell command
     * @param string $baseDir Base dir for relative path building
     */
    public function __construct(array $paths, $baseDir)
    {
        $this->baseDir = $baseDir;
        $this->baseDirLength = \strlen($baseDir);

        parent::__construct($paths);
    }

    /**
     * @param string $name
     * @param array  $arguments
     *
     * @return mixed
     */
    public function __call($name, array $arguments)
    {
        return \call_user_func_array(array($this->current(), $name), $arguments);
    }

    /**
     * Return an instance of SplFileInfo with support for relative paths.
     *
     * @return SplFileInfo File information
     */
    public function current()
    {
        return $this->current;
    }

    /**
     * @return string
     */
    public function key()
    {
        return $this->current->getPathname();
    }

    public function next()
    {
        parent::next();
        $this->buildProperties();
    }

    public function rewind()
    {
        parent::rewind();
        $this->buildProperties();
    }

    /**
     * @return string
     */
    public function getSubPath()
    {
        return $this->subPath;
    }

    /**
     * @return string
     */
    public function getSubPathname()
    {
        return $this->subPathname;
    }

    private function buildProperties()
    {
        $absolutePath = parent::current();

        if ($this->baseDir === substr($absolutePath, 0, $this->baseDirLength)) {
            $this->subPathname = ltrim(substr($absolutePath, $this->baseDirLength), '/\\');
            $dir = \dirname($this->subPathname);
            $this->subPath = '.' === $dir ? '' : $dir;
        } else {
            $this->subPath = $this->subPathname = '';
        }

        $this->current = new SplFileInfo(parent::current(), $this->subPath, $this->subPathname);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * ExcludeDirectoryFilterIterator filters out directories.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ExcludeDirectoryFilterIterator extends FilterIterator implements \RecursiveIterator
{
    private $iterator;
    private $isRecursive;
    private $excludedDirs = array();
    private $excludedPattern;

    /**
     * @param \Iterator $iterator    The Iterator to filter
     * @param array     $directories An array of directories to exclude
     */
    public function __construct(\Iterator $iterator, array $directories)
    {
        $this->iterator = $iterator;
        $this->isRecursive = $iterator instanceof \RecursiveIterator;
        $patterns = array();
        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(), array());
        $children->excludedDirs = $this->excludedDirs;
        $children->excludedPattern = $this->excludedPattern;

        return $children;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * FileTypeFilterIterator only keeps files, directories, or both.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * SortableIterator applies a sort on a given Iterator.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
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 = function ($a, $b) {
                return strcmp($a->getRealpath() ?: $a->getPathname(), $b->getRealpath() ?: $b->getPathname());
            };
        } elseif (self::SORT_BY_TYPE === $sort) {
            $this->sort = 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 = function ($a, $b) {
                return $a->getATime() - $b->getATime();
            };
        } elseif (self::SORT_BY_CHANGED_TIME === $sort) {
            $this->sort = function ($a, $b) {
                return $a->getCTime() - $b->getCTime();
            };
        } elseif (self::SORT_BY_MODIFIED_TIME === $sort) {
            $this->sort = 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->iterator, true);
        uasort($array, $this->sort);

        return new \ArrayIterator($array);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

use Symfony\Component\Finder\Exception\AccessDeniedException;
use Symfony\Component\Finder\SplFileInfo;

/**
 * Extends the \RecursiveDirectoryIterator to support relative paths.
 *
 * @author Victor Berchet <victor@suumit.com>
 */
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 = (string) $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();

        return new SplFileInfo($this->rootPath.$this->directorySeparator.$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(array());
            } 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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * PathFilterIterator filters files by path patterns (e.g. some/special/dir).
 *
 * @author Fabien Potencier  <fabien@symfony.com>
 * @author Włodzimierz Gajda <gajdaw@gajdaw.pl>
 */
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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * CustomFilterIterator filters files by applying anonymous functions.
 *
 * The anonymous function receives a \SplFileInfo and must return false
 * to remove files.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class CustomFilterIterator extends FilterIterator
{
    private $filters = array();

    /**
     * @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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * 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
 */
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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

/**
 * MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings).
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
abstract class MultiplePcreFilterIterator extends FilterIterator
{
    protected $matchRegexps = array();
    protected $noMatchRegexps = array();

    /**
     * @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], 0, 1);
            $end = substr($m[1], -1);

            if ($start === $end) {
                return !preg_match('/[*?[:alnum:] \\\\]/', $start);
            }

            foreach (array(array('{', '}'), array('(', ')'), array('[', ']'), array('<', '>')) 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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

use Symfony\Component\Finder\Comparator\DateComparator;

/**
 * DateRangeFilterIterator filters out files that are not in the given date range (last modified dates).
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class DateRangeFilterIterator extends FilterIterator
{
    private $comparators = array();

    /**
     * @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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Iterator;

use Symfony\Component\Finder\Comparator\NumberComparator;

/**
 * SizeRangeFilterIterator filters out files that are not in the given size range.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class SizeRangeFilterIterator extends FilterIterator
{
    private $comparators = array();

    /**
     * @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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Exception;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
interface ExceptionInterface
{
    /**
     * @return \Symfony\Component\Finder\Adapter\AdapterInterface
     */
    public function getAdapter();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Exception;

/**
 * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
 */
class AccessDeniedException extends \UnexpectedValueException
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Exception;

@trigger_error('The '.__NAMESPACE__.'\OperationNotPermitedException class is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @deprecated since 2.8, to be removed in 3.0.
 */
class OperationNotPermitedException extends AdapterFailureException
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Exception;

@trigger_error('The '.__NAMESPACE__.'\AdapterFailureException class is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

use Symfony\Component\Finder\Adapter\AdapterInterface;

/**
 * Base exception for all adapter failures.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @deprecated since 2.8, to be removed in 3.0.
 */
class AdapterFailureException extends \RuntimeException implements ExceptionInterface
{
    private $adapter;

    /**
     * @param AdapterInterface $adapter
     * @param string|null      $message
     * @param \Exception|null  $previous
     */
    public function __construct(AdapterInterface $adapter, $message = null, \Exception $previous = null)
    {
        $this->adapter = $adapter;
        parent::__construct($message ?: 'Search failed with "'.$adapter->getName().'" adapter.', $previous);
    }

    /**
     * {@inheritdoc}
     */
    public function getAdapter()
    {
        return $this->adapter;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Finder\Exception;

@trigger_error('The '.__NAMESPACE__.'\ShellCommandFailureException class is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

use Symfony\Component\Finder\Adapter\AdapterInterface;
use Symfony\Component\Finder\Shell\Command;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @deprecated since 2.8, to be removed in 3.0.
 */
class ShellCommandFailureException extends AdapterFailureException
{
    private $command;

    public function __construct(AdapterInterface $adapter, Command $command, \Exception $previous = null)
    {
        $this->command = $command;
        parent::__construct($adapter, 'Shell command failed: "'.$command->join().'".', $previous);
    }

    /**
     * @return Command
     */
    public function getCommand()
    {
        return $this->command;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\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 (array('foo.bar', 'foo.baz', 'foo', 'bar') as $t)
 *     {
 *         if (/$regex/) echo "matched: $car\n";
 *     }
 *
 * Glob implements glob(3) style matching that can be used to match
 * against text, rather than fetching names from a filesystem.
 *
 * Based on the Perl Text::Glob module.
 *
 * @author Fabien Potencier <fabien@symfony.com> PHP port
 * @author     Richard Clamp <richardc@unixbeard.net> Perl version
 * @copyright  2004-2005 Fabien Potencier <fabien@symfony.com>
 * @copyright  2002 Richard Clamp <richardc@unixbeard.net>
 */
class Glob
{
    /**
     * Returns a regexp which is the equivalent of the glob pattern.
     *
     * @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) {
                if ($strictLeadingDot && '.' !== $car) {
                    $regex .= '(?=[^\.])';
                }

                $firstByte = false;
            }

            if ('/' === $car) {
                $firstByte = true;
            }

            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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Polyfill\Ctype;

/**
 * Ctype implementation through regex.
 *
 * @internal
 *
 * @author Gert de Pagter <BackEndTea@gmail.com>
 */
final class Ctype
{
    /**
     * Returns TRUE if every character in text is either a letter or a digit, FALSE otherwise.
     *
     * @see https://php.net/ctype-alnum
     *
     * @param 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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use Symfony\Polyfill\Ctype as p;

if (!function_exists('ctype_alnum')) {
    function ctype_alnum($text) { return p\Ctype::ctype_alnum($text); }
    function ctype_alpha($text) { return p\Ctype::ctype_alpha($text); }
    function ctype_cntrl($text) { return p\Ctype::ctype_cntrl($text); }
    function ctype_digit($text) { return p\Ctype::ctype_digit($text); }
    function ctype_graph($text) { return p\Ctype::ctype_graph($text); }
    function ctype_lower($text) { return p\Ctype::ctype_lower($text); }
    function ctype_print($text) { return p\Ctype::ctype_print($text); }
    function ctype_punct($text) { return p\Ctype::ctype_punct($text); }
    function ctype_space($text) { return p\Ctype::ctype_space($text); }
    function ctype_upper($text) { return p\Ctype::ctype_upper($text); }
    function ctype_xdigit($text) { return p\Ctype::ctype_xdigit($text); }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Polyfill\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 <p@tchwork.com>
 *
 * @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($s, ENT_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($s, 2, self::$internalEncoding);
    }

    public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null)
    {
        trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', E_USER_WARNING);
    }

    public static function mb_decode_numericentity($s, $convmap, $encoding = null)
    {
        if (null !== $s && !\is_scalar($s) && !(\is_object($s) && \method_exists($s, '__toString'))) {
            trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', E_USER_WARNING);

            return null;
        }

        if (!\is_array($convmap) || !$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" ? 1 : $ulenMask[$s[$i] & "\xF0"];
            $uchr = substr($s, $i, $ulen);
            $i += $ulen;
            $c = self::mb_ord($uchr);

            for ($j = 0; $j < $cnt; $j += 4) {
                if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) {
                    $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3];
                    $result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';';
                    continue 2;
                }
            }
            $result .= $uchr;
        }

        if (null === $encoding) {
            return $result;
        }

        return iconv('UTF-8', $encoding.'//IGNORE', $result);
    }

    public static function mb_convert_case($s, $mode, $encoding = null)
    {
        $s = (string) $s;
        if ('' === $s) {
            return '';
        }

        $encoding = self::getEncoding($encoding);

        if ('UTF-8' === $encoding) {
            $encoding = null;
            if (!preg_match('//u', $s)) {
                $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
            }
        } else {
            $s = iconv($encoding, 'UTF-8//IGNORE', $s);
        }

        if (MB_CASE_TITLE == $mode) {
            static $titleRegexp = null;
            if (null === $titleRegexp) {
                $titleRegexp = self::getData('titleCaseRegexp');
            }
            $s = preg_replace_callback($titleRegexp, 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" ? 1 : $ulenMask[$s[$i] & "\xF0"];
                $uchr = substr($s, $i, $ulen);
                $i += $ulen;

                if (isset($map[$uchr])) {
                    $uchr = $map[$uchr];
                    $nlen = \strlen($uchr);

                    if ($nlen == $ulen) {
                        $nlen = $i;
                        do {
                            $s[--$nlen] = $uchr[--$ulen];
                        } while ($ulen);
                    } else {
                        $s = substr_replace($s, $uchr, $i - $ulen, $ulen);
                        $len += $nlen - $ulen;
                        $i += $nlen - $ulen;
                    }
                }
            }
        }

        if (null === $encoding) {
            return $s;
        }

        return iconv('UTF-8', $encoding.'//IGNORE', $s);
    }

    public static function mb_internal_encoding($encoding = null)
    {
        if (null === $encoding) {
            return self::$internalEncoding;
        }

        $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 (0 === strncmp($enc, 'ISO-8859-', 9)) {
                        return $enc;
                    }
            }
        }

        return false;
    }

    public static function mb_detect_order($encodingList = null)
    {
        if (null === $encodingList) {
            return self::$encodingList;
        }

        if (!\is_array($encodingList)) {
            $encodingList = array_map('trim', explode(',', $encodingList));
        }
        $encodingList = array_map('strtoupper', $encodingList);

        foreach ($encodingList as $enc) {
            switch ($enc) {
                default:
                    if (strncmp($enc, 'ISO-8859-', 9)) {
                        return false;
                    }
                    // no break
                case 'ASCII':
                case 'UTF8':
                case 'UTF-8':
            }
        }

        self::$encodingList = $encodingList;

        return true;
    }

    public static function mb_strlen($s, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);
        if ('CP850' === $encoding || 'ASCII' === $encoding) {
            return \strlen($s);
        }

        return @iconv_strlen($s, $encoding);
    }

    public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);
        if ('CP850' === $encoding || 'ASCII' === $encoding) {
            return strpos($haystack, $needle, $offset);
        }

        $needle = (string) $needle;
        if ('' === $needle) {
            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) {
                $haystack = self::mb_substr($haystack, 0, $offset, $encoding);
                $offset = 0;
            } else {
                $haystack = self::mb_substr($haystack, $offset, 2147483647, $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 < 1) {
            trigger_error('The length of each segment must be greater than zero', E_USER_WARNING);

            return false;
        }

        if (null === $encoding) {
            $encoding = mb_internal_encoding();
        }

        $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($s, MB_CASE_LOWER, $encoding);
    }

    public static function mb_strtoupper($s, $encoding = null)
    {
        return self::mb_convert_case($s, MB_CASE_UPPER, $encoding);
    }

    public static function mb_substitute_character($c = null)
    {
        if (0 === 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, $start, null === $length ? 2147483647 : $length);
        }

        if ($start < 0) {
            $start = iconv_strlen($s, $encoding) + $start;
            if ($start < 0) {
                $start = 0;
            }
        }

        if (null === $length) {
            $length = 2147483647;
        } elseif ($length < 0) {
            $length = iconv_strlen($s, $encoding) + $length - $start;
            if ($length < 0) {
                return '';
            }
        }

        return (string) iconv_substr($s, $start, $length, $encoding);
    }

    public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null)
    {
        $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding);
        $needle = self::mb_convert_case($needle, self::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, $needle, 0, $encoding);

        return self::getSubpart($pos, $part, $haystack, $encoding);
    }

    public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null)
    {
        $encoding = self::getEncoding($encoding);
        if ('CP850' === $encoding || 'ASCII' === $encoding) {
            return strrchr($haystack, $needle, $part);
        }
        $needle = self::mb_substr($needle, 0, 1, $encoding);
        $pos = iconv_strrpos($haystack, $needle, $encoding);

        return self::getSubpart($pos, $part, $haystack, $encoding);
    }

    public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null)
    {
        $needle = self::mb_substr($needle, 0, 1, $encoding);
        $pos = self::mb_strripos($haystack, $needle, $encoding);

        return self::getSubpart($pos, $part, $haystack, $encoding);
    }

    public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null)
    {
        $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding);
        $needle = self::mb_convert_case($needle, self::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($haystack, 0, $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 >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
        } else {
            $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
        }

        if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
            $s = mb_convert_encoding($s, $encoding, 'UTF-8');
        }

        return $s;
    }

    public static function mb_ord($s, $encoding = null)
    {
        if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
            $s = mb_convert_encoding($s, 'UTF-8', $encoding);
        }

        if (1 === \strlen($s)) {
            return \ord($s);
        }

        $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0;
        if (0xF0 <= $code) {
            return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80;
        }
        if (0xE0 <= $code) {
            return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80;
        }
        if (0xC0 <= $code) {
            return (($code - 0xC0) << 6) + $s[2] - 0x80;
        }

        return $code;
    }

    private static function getSubpart($pos, $part, $haystack, $encoding)
    {
        if (false === $pos) {
            return false;
        }
        if ($part) {
            return self::mb_substr($haystack, 0, $pos, $encoding);
        }

        return self::mb_substr($haystack, $pos, null, $encoding);
    }

    private static function html_encoding_callback(array $m)
    {
        $i = 1;
        $entities = '';
        $m = unpack('C*', htmlentities($m[0], ENT_COMPAT, 'UTF-8'));

        while (isset($m[$i])) {
            if (0x80 > $m[$i]) {
                $entities .= \chr($m[$i++]);
                continue;
            }
            if (0xF0 <= $m[$i]) {
                $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
            } elseif (0xE0 <= $m[$i]) {
                $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
            } else {
                $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80;
            }

            $entities .= '&#'.$c.';';
        }

        return $entities;
    }

    private static function title_case(array $s)
    {
        return self::mb_convert_case($s[1], MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], MB_CASE_LOWER, 'UTF-8');
    }

    private static function getData($file)
    {
        if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) {
            return require $file;
        }

        return false;
    }

    private static function getEncoding($encoding)
    {
        if (null === $encoding) {
            return self::$internalEncoding;
        }

        $encoding = strtoupper($encoding);

        if ('8BIT' === $encoding || 'BINARY' === $encoding) {
            return 'CP850';
        }
        if ('UTF8' === $encoding) {
            return 'UTF-8';
        }

        return $encoding;
    }
}
<?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

// 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',
  'Ĵ' => 'ĵ',
  'Ķ' => 'ķ',
  'Ĺ' => 'ĺ',
  'Ļ' => 'ļ',
  'Ľ' => 'ľ',
  'Ŀ' => 'ŀ',
  'Ł' => 'ł',
  'Ń' => 'ń',
  'Ņ' => 'ņ',
  'Ň' => 'ň',
  'Ŋ' => 'ŋ',
  'Ō' => 'ō',
  'Ŏ' => 'ŏ',
  'Ő' => 'ő',
  'Œ' => 'œ',
  'Ŕ' => 'ŕ',
  'Ŗ' => 'ŗ',
  'Ř' => 'ř',
  'Ś' => 'ś',
  'Ŝ' => 'ŝ',
  'Ş' => 'ş',
  'Š' => 'š',
  'Ţ' => 'ţ',
  'Ť' => 'ť',
  'Ŧ' => 'ŧ',
  'Ũ' => 'ũ',
  'Ū' => 'ū',
  'Ŭ' => 'ŭ',
  'Ů' => 'ů',
  'Ű' => 'ű',
  'Ų' => 'ų',
  'Ŵ' => 'ŵ',
  'Ŷ' => 'ŷ',
  'Ÿ' => 'ÿ',
  'Ź' => 'ź',
  'Ż' => 'ż',
  'Ž' => 'ž',
  'Ɓ' => 'ɓ',
  'Ƃ' => 'ƃ',
  'Ƅ' => 'ƅ',
  'Ɔ' => 'ɔ',
  'Ƈ' => 'ƈ',
  'Ɖ' => 'ɖ',
  'Ɗ' => 'ɗ',
  'Ƌ' => 'ƌ',
  'Ǝ' => 'ǝ',
  'Ə' => 'ə',
  'Ɛ' => 'ɛ',
  'Ƒ' => 'ƒ',
  'Ɠ' => 'ɠ',
  'Ɣ' => 'ɣ',
  'Ɩ' => 'ɩ',
  'Ɨ' => 'ɨ',
  'Ƙ' => 'ƙ',
  'Ɯ' => 'ɯ',
  'Ɲ' => 'ɲ',
  'Ɵ' => 'ɵ',
  'Ơ' => 'ơ',
  'Ƣ' => 'ƣ',
  'Ƥ' => 'ƥ',
  'Ʀ' => 'ʀ',
  'Ƨ' => 'ƨ',
  'Ʃ' => 'ʃ',
  'Ƭ' => 'ƭ',
  'Ʈ' => 'ʈ',
  'Ư' => 'ư',
  'Ʊ' => 'ʊ',
  'Ʋ' => 'ʋ',
  'Ƴ' => 'ƴ',
  'Ƶ' => 'ƶ',
  'Ʒ' => 'ʒ',
  'Ƹ' => 'ƹ',
  'Ƽ' => 'ƽ',
  '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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use Symfony\Polyfill\Mbstring as p;

if (!function_exists('mb_strlen')) {
    define('MB_CASE_UPPER', 0);
    define('MB_CASE_LOWER', 1);
    define('MB_CASE_TITLE', 2);

    function mb_convert_encoding($s, $to, $from = null) { return p\Mbstring::mb_convert_encoding($s, $to, $from); }
    function mb_decode_mimeheader($s) { return p\Mbstring::mb_decode_mimeheader($s); }
    function mb_encode_mimeheader($s, $charset = null, $transferEnc = null, $lf = null, $indent = null) { return p\Mbstring::mb_encode_mimeheader($s, $charset, $transferEnc, $lf, $indent); }
    function mb_decode_numericentity($s, $convmap, $enc = null) { return p\Mbstring::mb_decode_numericentity($s, $convmap, $enc); }
    function mb_encode_numericentity($s, $convmap, $enc = null, $is_hex = false) { return p\Mbstring::mb_encode_numericentity($s, $convmap, $enc, $is_hex); }
    function mb_convert_case($s, $mode, $enc = null) { return p\Mbstring::mb_convert_case($s, $mode, $enc); }
    function mb_internal_encoding($enc = null) { return p\Mbstring::mb_internal_encoding($enc); }
    function mb_language($lang = null) { return p\Mbstring::mb_language($lang); }
    function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); }
    function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); }
    function mb_check_encoding($var = null, $encoding = null) { return p\Mbstring::mb_check_encoding($var, $encoding); }
    function mb_detect_encoding($str, $encodingList = null, $strict = false) { return p\Mbstring::mb_detect_encoding($str, $encodingList, $strict); }
    function mb_detect_order($encodingList = null) { return p\Mbstring::mb_detect_order($encodingList); }
    function mb_parse_str($s, &$result = array()) { parse_str($s, $result); }
    function mb_strlen($s, $enc = null) { return p\Mbstring::mb_strlen($s, $enc); }
    function mb_strpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strpos($s, $needle, $offset, $enc); }
    function mb_strtolower($s, $enc = null) { return p\Mbstring::mb_strtolower($s, $enc); }
    function mb_strtoupper($s, $enc = null) { return p\Mbstring::mb_strtoupper($s, $enc); }
    function mb_substitute_character($char = null) { return p\Mbstring::mb_substitute_character($char); }
    function mb_substr($s, $start, $length = 2147483647, $enc = null) { return p\Mbstring::mb_substr($s, $start, $length, $enc); }
    function mb_stripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_stripos($s, $needle, $offset, $enc); }
    function mb_stristr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_stristr($s, $needle, $part, $enc); }
    function mb_strrchr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrchr($s, $needle, $part, $enc); }
    function mb_strrichr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrichr($s, $needle, $part, $enc); }
    function mb_strripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strripos($s, $needle, $offset, $enc); }
    function mb_strrpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strrpos($s, $needle, $offset, $enc); }
    function mb_strstr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strstr($s, $needle, $part, $enc); }
    function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); }
    function mb_http_output($enc = null) { return p\Mbstring::mb_http_output($enc); }
    function mb_strwidth($s, $enc = null) { return p\Mbstring::mb_strwidth($s, $enc); }
    function mb_substr_count($haystack, $needle, $enc = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $enc); }
    function mb_output_handler($contents, $status) { return p\Mbstring::mb_output_handler($contents, $status); }
    function mb_http_input($type = '') { return p\Mbstring::mb_http_input($type); }
    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_chr')) {
    function mb_ord($s, $enc = null) { return p\Mbstring::mb_ord($s, $enc); }
    function mb_chr($code, $enc = null) { return p\Mbstring::mb_chr($code, $enc); }
    function mb_scrub($s, $enc = null) { $enc = null === $enc ? mb_internal_encoding() : $enc; return mb_convert_encoding($s, $enc, $enc); }
}

if (!function_exists('mb_str_split')) {
    function mb_str_split($string, $split_length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $split_length, $encoding); }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process;

/**
 * An executable finder specifically designed for the PHP executable.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class PhpExecutableFinder
{
    private $executableFinder;

    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 (\defined('PHP_BINARY') && PHP_BINARY && \in_array(\PHP_SAPI, array('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 = array(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 = array();

        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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process;

/**
 * Generic executable finder.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class ExecutableFinder
{
    private $suffixes = array('.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 $default   The default to return if no executable is found
     * @param array  $extraDirs Additional dirs to check into
     *
     * @return string The executable path or default value
     */
    public function find($name, $default = null, array $extraDirs = array())
    {
        if (ini_get('open_basedir')) {
            $searchPath = explode(PATH_SEPARATOR, ini_get('open_basedir'));
            $dirs = array();
            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_SEPARATOR, getenv('PATH') ?: getenv('Path')),
                $extraDirs
            );
        }

        $suffixes = array('');
        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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process;

use Symfony\Component\Process\Exception\RuntimeException;

/**
 * PhpProcess runs a PHP script in an independent process.
 *
 *     $p = new PhpProcess('<?php echo "foo"; ?>');
 *     $p->run();
 *     print $p->getOutput()."\n";
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class PhpProcess extends Process
{
    /**
     * @param string      $script  The PHP script to run (as a string)
     * @param string|null $cwd     The working directory or null to use the working dir of the current PHP process
     * @param array|null  $env     The environment variables or null to use the same environment as the current PHP process
     * @param int         $timeout The timeout in seconds
     * @param array       $options An array of options for proc_open
     */
    public function __construct($script, $cwd = null, array $env = null, $timeout = 60, array $options = array())
    {
        $executableFinder = new PhpExecutableFinder();
        if (false === $php = $executableFinder->find()) {
            $php = null;
        }
        if ('phpdbg' === \PHP_SAPI) {
            $file = tempnam(sys_get_temp_dir(), 'dbg');
            file_put_contents($file, $script);
            register_shutdown_function('unlink', $file);
            $php .= ' '.ProcessUtils::escapeArgument($file);
            $script = null;
        }
        if ('\\' !== \DIRECTORY_SEPARATOR && null !== $php) {
            // exec is mandatory to deal with sending a signal to the process
            // see https://github.com/symfony/symfony/issues/5030 about prepending
            // command with exec
            $php = 'exec '.$php;
        }

        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($callback = null)
    {
        if (null === $this->getCommandLine()) {
            throw new RuntimeException('Unable to find the PHP executable.');
        }

        parent::start($callback);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process;

use Symfony\Component\Process\Exception\InvalidArgumentException;
use Symfony\Component\Process\Exception\LogicException;

/**
 * @author Kris Wallsmith <kris@symfony.com>
 */
class ProcessBuilder
{
    private $arguments;
    private $cwd;
    private $env = array();
    private $input;
    private $timeout = 60;
    private $options = array();
    private $inheritEnv = true;
    private $prefix = array();
    private $outputDisabled = false;

    /**
     * @param string[] $arguments An array of arguments
     */
    public function __construct(array $arguments = array())
    {
        $this->arguments = $arguments;
    }

    /**
     * Creates a process builder instance.
     *
     * @param string[] $arguments An array of arguments
     *
     * @return static
     */
    public static function create(array $arguments = array())
    {
        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 : array($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 mixed $input The input as a string
     *
     * @return $this
     *
     * @throws InvalidArgumentException In case the argument is invalid
     *
     * Passing an object as an input is deprecated since version 2.5 and will be removed in 3.0.
     */
    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 (0 === \count($this->prefix) && 0 === \count($this->arguments)) {
            throw new LogicException('You must add() command arguments before calling getProcess().');
        }

        $options = $this->options;

        $arguments = array_merge($this->prefix, $this->arguments);
        $script = implode(' ', array_map(array(__NAMESPACE__.'\\ProcessUtils', 'escapeArgument'), $arguments));

        if ($this->inheritEnv) {
            // include $_ENV for BC purposes
            $env = array_replace($_ENV, $_SERVER, $this->env);
        } else {
            $env = $this->env;
        }

        $process = new Process($script, $this->cwd, $env, $this->input, $this->timeout, $options);

        if ($this->outputDisabled) {
            $process->disableOutput();
        }

        return $process;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\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 <fabien@symfony.com>
 * @author Romain Neutron <imprec@gmail.com>
 */
class Process
{
    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;

    private $callback;
    private $commandline;
    private $cwd;
    private $env;
    private $input;
    private $starttime;
    private $lastOutputTime;
    private $timeout;
    private $idleTimeout;
    private $options;
    private $exitcode;
    private $fallbackStatus = array();
    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;
    private $pty;

    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 = array(
        0 => 'OK',
        1 => 'General error',
        2 => 'Misuse of shell builtins',

        126 => 'Invoked command cannot execute',
        127 => 'Command not found',
        128 => 'Invalid exit argument',

        // signals
        129 => 'Hangup',
        130 => 'Interrupt',
        131 => 'Quit and dump core',
        132 => 'Illegal instruction',
        133 => 'Trace/breakpoint trap',
        134 => 'Process aborted',
        135 => 'Bus error: "access to undefined portion of memory object"',
        136 => 'Floating point exception: "erroneous arithmetic operation"',
        137 => 'Kill (terminate immediately)',
        138 => 'User-defined 1',
        139 => 'Segmentation violation',
        140 => 'User-defined 2',
        141 => 'Write to pipe with no one reading',
        142 => 'Signal raised by alarm',
        143 => 'Termination (request to terminate)',
        // 144 - not defined
        145 => 'Child process terminated, stopped (or continued*)',
        146 => 'Continue if stopped',
        147 => 'Stop executing temporarily',
        148 => 'Terminal stop signal',
        149 => 'Background process attempting to read from tty ("in")',
        150 => 'Background process attempting to write to tty ("out")',
        151 => 'Urgent data available on socket',
        152 => 'CPU time limit exceeded',
        153 => 'File size limit exceeded',
        154 => 'Signal raised by timer counting virtual time: "virtual timer expired"',
        155 => 'Profiling timer expired',
        // 156 - not defined
        157 => 'Pollable event',
        // 158 - not defined
        159 => 'Bad syscall',
    );

    /**
     * @param string         $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 string|null    $input       The 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 = array())
    {
        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/bug.php?id=51800
        // @see : https://bugs.php.net/bug.php?id=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();
        $this->options = array_replace(array('suppress_errors' => true, 'binary_pipes' => true), $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
     */
    public function run($callback = null)
    {
        $this->start($callback);

        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.
     *
     * @param callable|null $callback
     *
     * @return self
     *
     * @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
     */
    public function mustRun($callback = null)
    {
        if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
            throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.');
        }

        if (0 !== $this->run($callback)) {
            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($callback = null)
    {
        if ($this->isRunning()) {
            throw new RuntimeException('Process is already running');
        }
        if ($this->outputDisabled && null !== $callback) {
            throw new LogicException('Output has been disabled, enable it to allow the use of a callback.');
        }

        $this->resetProcessData();
        $this->starttime = $this->lastOutputTime = microtime(true);
        $this->callback = $this->buildCallback($callback);
        $descriptors = $this->getDescriptors();

        $commandline = $this->commandline;

        if ('\\' === \DIRECTORY_SEPARATOR && $this->enhanceWindowsCompatibility) {
            $commandline = 'cmd /V:ON /E:ON /D /C "('.$commandline.')';
            foreach ($this->processPipes->getFiles() as $offset => $filename) {
                $commandline .= ' '.$offset.'>'.ProcessUtils::escapeArgument($filename);
            }
            $commandline .= '"';

            if (!isset($this->options['bypass_shell'])) {
                $this->options['bypass_shell'] = true;
            }
        } 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] = array('pipe', 'w');

            // See https://unix.stackexchange.com/questions/71205/background-process-pipe-input
            $commandline = '{ ('.$this->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');
        }

        $this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $this->env, $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 $this
     *
     * @throws RuntimeException When process can't be launched
     * @throws RuntimeException When process is already running
     *
     * @see start()
     */
    public function restart($callback = null)
    {
        if ($this->isRunning()) {
            throw new RuntimeException('Process is already running');
        }

        $process = clone $this;
        $process->start($callback);

        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($callback = null)
    {
        $this->requireProcessIsStarted(__FUNCTION__);

        $this->updateStatus(false);
        if (null !== $callback) {
            $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()) {
            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 http://www.php.net/manual/en/pcntl.constants.php)
     *
     * @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($signal, true);

        return $this;
    }

    /**
     * Disables fetching output and error output from the underlying process.
     *
     * @return $this
     *
     * @throws RuntimeException In case the process is already running
     * @throws LogicException   if an idle timeout is set
     */
    public function disableOutput()
    {
        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, -1, 0)) {
            return '';
        }

        return $ret;
    }

    /**
     * Returns the output incrementally.
     *
     * In comparison with the getOutput method which always return the whole
     * output, this one returns the new output since the last call.
     *
     * @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;
    }

    /**
     * Clears the process output.
     *
     * @return $this
     */
    public function clearOutput()
    {
        ftruncate($this->stdout, 0);
        fseek($this->stdout, 0);
        $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, -1, 0)) {
            return '';
        }

        return $ret;
    }

    /**
     * Returns the errorOutput incrementally.
     *
     * In comparison with the getErrorOutput method which always return the
     * whole error output, this one returns the new error output since the last
     * call.
     *
     * @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->stderr, 0);
        fseek($this->stderr, 0);
        $this->incrementalErrorOutputOffset = 0;

        return $this;
    }

    /**
     * Returns the exit code returned by the process.
     *
     * @return int|null The exit status code, null if the Process is not terminated
     *
     * @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;
        }

        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 0 === $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 || -1 === $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 The exit-code of the process
     */
    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(15, false);
            do {
                usleep(1000);
            } while ($this->isRunning() && microtime(true) < $timeoutMicro);

            if ($this->isRunning()) {
                // Avoid exception here: process is supposed to be running, but it might have stopped just
                // after this line. In any case, let's silently discard the error, we cannot do anything.
                $this->doSignal($signal ?: 9, false);
            }
        }

        if ($this->isRunning()) {
            if (isset($this->fallbackStatus['pid'])) {
                unset($this->fallbackStatus['pid']);

                return $this->stop(0, $signal);
            }
            $this->close();
        }

        return $this->exitcode;
    }

    /**
     * Adds a line to the STDOUT stream.
     *
     * @internal
     *
     * @param string $line The line to append
     */
    public function addOutput($line)
    {
        $this->lastOutputTime = microtime(true);

        fseek($this->stdout, 0, SEEK_END);
        fwrite($this->stdout, $line);
        fseek($this->stdout, $this->incrementalOutputOffset);
    }

    /**
     * Adds a line to the STDERR stream.
     *
     * @internal
     *
     * @param string $line The line to append
     */
    public function addErrorOutput($line)
    {
        $this->lastOutputTime = microtime(true);

        fseek($this->stderr, 0, 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 $this->commandline;
    }

    /**
     * Sets the command line to be executed.
     *
     * @param string $commandline The command to execute
     *
     * @return self The current Process instance
     */
    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).
     *
     * To disable the timeout, set this value to null.
     *
     * @param int|float|null $timeout The timeout in seconds
     *
     * @return self The current Process instance
     *
     * @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 self The current Process instance
     *
     * @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 self The current Process instance
     *
     * @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', array(array('file', '/dev/tty', 'r'), array('file', '/dev/tty', 'w'), array('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 self
     */
    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 self The current Process instance
     */
    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.
     *
     * That happens in PHP when 'argv' is registered into
     * the $_ENV array for instance.
     *
     * @param array $env The new environment variables
     *
     * @return self The current Process instance
     */
    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 = array();
        foreach ($env as $key => $value) {
            $this->env[$key] = (string) $value;
        }

        return $this;
    }

    /**
     * Gets the contents of STDIN.
     *
     * @return string|null The current contents
     *
     * @deprecated since version 2.5, to be removed in 3.0.
     *             Use setInput() instead.
     *             This method is deprecated in favor of getInput.
     */
    public function getStdin()
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.5 and will be removed in 3.0. Use the getInput() method instead.', E_USER_DEPRECATED);

        return $this->getInput();
    }

    /**
     * Gets the Process input.
     *
     * @return string|null The Process input
     */
    public function getInput()
    {
        return $this->input;
    }

    /**
     * Sets the contents of STDIN.
     *
     * @param string|null $stdin The new contents
     *
     * @return self The current Process instance
     *
     * @deprecated since version 2.5, to be removed in 3.0.
     *             Use setInput() instead.
     *
     * @throws LogicException           In case the process is running
     * @throws InvalidArgumentException In case the argument is invalid
     */
    public function setStdin($stdin)
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.5 and will be removed in 3.0. Use the setInput() method instead.', E_USER_DEPRECATED);

        return $this->setInput($stdin);
    }

    /**
     * Sets the input.
     *
     * This content will be passed to the underlying process standard input.
     *
     * @param mixed $input The content
     *
     * @return self The current Process instance
     *
     * @throws LogicException In case the process is running
     *
     * Passing an object as an input is deprecated since version 2.5 and will be removed in 3.0.
     */
    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
     */
    public function getOptions()
    {
        return $this->options;
    }

    /**
     * Sets the options for proc_open.
     *
     * @param array $options The new options
     *
     * @return self The current Process instance
     */
    public function setOptions(array $options)
    {
        $this->options = $options;

        return $this;
    }

    /**
     * Gets whether or not Windows compatibility is enabled.
     *
     * This is true by default.
     *
     * @return bool
     */
    public function getEnhanceWindowsCompatibility()
    {
        return $this->enhanceWindowsCompatibility;
    }

    /**
     * Sets whether or not Windows compatibility is enabled.
     *
     * @param bool $enhance
     *
     * @return self The current Process instance
     */
    public function setEnhanceWindowsCompatibility($enhance)
    {
        $this->enhanceWindowsCompatibility = (bool) $enhance;

        return $this;
    }

    /**
     * Returns whether sigchild compatibility mode is activated or not.
     *
     * @return bool
     */
    public function getEnhanceSigchildCompatibility()
    {
        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 self The current Process instance
     */
    public function setEnhanceSigchildCompatibility($enhance)
    {
        $this->enhanceSigchildCompatibility = (bool) $enhance;

        return $this;
    }

    /**
     * Performs a check between the timeout definition and the time the process started.
     *
     * In case you run a background process (with the start method), you should
     * trigger this method regularly to ensure the process timeout
     *
     * @throws ProcessTimedOutException In case the timeout was reached
     */
    public function checkTimeout()
    {
        if (self::STATUS_STARTED !== $this->status) {
            return;
        }

        if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) {
            $this->stop(0);

            throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL);
        }

        if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) {
            $this->stop(0);

            throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE);
        }
    }

    /**
     * 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', array(array('pty'), array('pty'), array('pty')), $pipes);
    }

    /**
     * Creates the descriptors needed by the proc_open.
     *
     * @return array
     */
    private function getDescriptors()
    {
        if ('\\' === \DIRECTORY_SEPARATOR) {
            $this->processPipes = WindowsPipes::create($this, $this->input);
        } else {
            $this->processPipes = UnixPipes::create($this, $this->input);
        }

        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($callback)
    {
        $that = $this;
        $out = self::OUT;
        $callback = function ($type, $data) use ($that, $callback, $out) {
            if ($out == $type) {
                $that->addOutput($data);
            } else {
                $that->addErrorOutput($data);
            }

            if (null !== $callback) {
                \call_user_func($callback, $type, $data);
            }
        };

        return $callback;
    }

    /**
     * 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
     *
     * @throws LogicException in case output has been disabled or process is not started
     */
    private function readPipesForOutput($caller)
    {
        if ($this->outputDisabled) {
            throw new LogicException('Output has been disabled.');
        }

        $this->requireProcessIsStarted($caller);

        $this->updateStatus(false);
    }

    /**
     * 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 (3 !== $type) {
                $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data);
            } elseif (!isset($this->fallbackStatus['signaled'])) {
                $this->fallbackStatus['exitcode'] = (int) $data;
            }
        }
    }

    /**
     * Closes process resource, closes file handles, sets the exitcode.
     *
     * @return int The exitcode
     */
    private function close()
    {
        $this->processPipes->close();
        if (\is_resource($this->process)) {
            proc_close($this->process);
        }
        $this->exitcode = $this->processInformation['exitcode'];
        $this->status = self::STATUS_TERMINATED;

        if (-1 === $this->exitcode) {
            if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) {
                // if process has been signaled, no exitcode but a valid termsig, apply Unix convention
                $this->exitcode = 128 + $this->processInformation['termsig'];
            } elseif ($this->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 = array();
        $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 http://www.php.net/manual/en/pcntl.constants.php)
     * @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), array(2 => array('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;
    }

    /**
     * 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));
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Pipes;

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

/**
 * WindowsPipes implementation uses temporary files as handles.
 *
 * @see https://bugs.php.net/bug.php?id=51800
 * @see https://bugs.php.net/bug.php?id=65650
 *
 * @author Romain Neutron <imprec@gmail.com>
 *
 * @internal
 */
class WindowsPipes extends AbstractPipes
{
    private $files = array();
    private $fileHandles = array();
    private $lockHandles = array();
    private $readBytes = array(
        Process::STDOUT => 0,
        Process::STDERR => 0,
    );
    private $disableOutput;

    public function __construct($disableOutput, $input)
    {
        $this->disableOutput = (bool) $disableOutput;

        if (!$this->disableOutput) {
            // 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/bug.php?id=51800
            $pipes = array(
                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')) {
                        restore_error_handler();
                        throw new RuntimeException(sprintf('A temporary file could not be opened to write the process output: %s', $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->disableOutput) {
            $nullstream = fopen('NUL', 'c');

            return array(
                array('pipe', 'r'),
                $nullstream,
                $nullstream,
            );
        }

        // We're not using pipe on Windows platform as it hangs (https://bugs.php.net/bug.php?id=51800)
        // We're not using file handles as it can produce corrupted output https://bugs.php.net/bug.php?id=65650
        // So we redirect output within the commandline and pass the nul device to the process
        return array(
            array('pipe', 'r'),
            array('file', 'NUL', 'w'),
            array('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 = array();

        if ($blocking) {
            if ($w) {
                @stream_select($r, $w, $e, 0, Process::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($fileHandle, 0);
                fclose($fileHandle);
                flock($this->lockHandles[$type], LOCK_UN);
                fclose($this->lockHandles[$type]);
                unset($this->fileHandles[$type], $this->lockHandles[$type]);
            }
        }

        return $read;
    }

    /**
     * {@inheritdoc}
     */
    public function areOpen()
    {
        return $this->pipes && $this->fileHandles;
    }

    /**
     * {@inheritdoc}
     */
    public function close()
    {
        parent::close();
        foreach ($this->fileHandles as $type => $handle) {
            ftruncate($handle, 0);
            fclose($handle);
            flock($this->lockHandles[$type], LOCK_UN);
            fclose($this->lockHandles[$type]);
        }
        $this->fileHandles = $this->lockHandles = array();
    }

    /**
     * Creates a new WindowsPipes instance.
     *
     * @param Process $process The process
     * @param $input
     *
     * @return static
     */
    public static function create(Process $process, $input)
    {
        return new static($process->isOutputDisabled(), $input);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Pipes;

/**
 * @author Romain Neutron <imprec@gmail.com>
 *
 * @internal
 */
abstract class AbstractPipes implements PipesInterface
{
    public $pipes = array();

    private $inputBuffer = '';
    private $input;
    private $blocked = true;
    private $lastError;

    /**
     * @param resource|null $input
     */
    public function __construct($input)
    {
        if (\is_resource($input)) {
            $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 = array();
    }

    /**
     * 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($pipe, 0);
        }
        if (null !== $this->input) {
            stream_set_blocking($this->input, 0);
        }

        $this->blocked = false;
    }

    /**
     * Writes input to stdin.
     */
    protected function write()
    {
        if (!isset($this->pipes[0])) {
            return;
        }
        $input = $this->input;
        $r = $e = array();
        $w = array($this->pipes[0]);

        // let's have a look if something changed in streams
        if (false === @stream_select($r, $w, $e, 0, 0)) {
            return;
        }

        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 array($this->pipes[0]);
                }
            }

            if ($input) {
                for (;;) {
                    $data = fread($input, self::CHUNK_SIZE);
                    if (!isset($data[0])) {
                        break;
                    }
                    $written = fwrite($stdin, $data);
                    $data = substr($data, $written);
                    if (isset($data[0])) {
                        $this->inputBuffer = $data;

                        return array($this->pipes[0]);
                    }
                }
                if (feof($input)) {
                    // no more data to read on input resource
                    // use an empty buffer in the next reads
                    $this->input = null;
                }
            }
        }

        // no input to read on resource, buffer is empty
        if (null === $this->input && !isset($this->inputBuffer[0])) {
            fclose($this->pipes[0]);
            unset($this->pipes[0]);
        } elseif (!$w) {
            return array($this->pipes[0]);
        }
    }

    /**
     * @internal
     */
    public function handleError($type, $msg)
    {
        $this->lastError = $msg;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Pipes;

use Symfony\Component\Process\Process;

/**
 * UnixPipes implementation uses unix pipes as handles.
 *
 * @author Romain Neutron <imprec@gmail.com>
 *
 * @internal
 */
class UnixPipes extends AbstractPipes
{
    private $ttyMode;
    private $ptyMode;
    private $disableOutput;

    public function __construct($ttyMode, $ptyMode, $input, $disableOutput)
    {
        $this->ttyMode = (bool) $ttyMode;
        $this->ptyMode = (bool) $ptyMode;
        $this->disableOutput = (bool) $disableOutput;

        parent::__construct($input);
    }

    public function __destruct()
    {
        $this->close();
    }

    /**
     * {@inheritdoc}
     */
    public function getDescriptors()
    {
        if ($this->disableOutput) {
            $nullstream = fopen('/dev/null', 'c');

            return array(
                array('pipe', 'r'),
                $nullstream,
                $nullstream,
            );
        }

        if ($this->ttyMode) {
            return array(
                array('file', '/dev/tty', 'r'),
                array('file', '/dev/tty', 'w'),
                array('file', '/dev/tty', 'w'),
            );
        }

        if ($this->ptyMode && Process::isPtySupported()) {
            return array(
                array('pty'),
                array('pty'),
                array('pty'),
            );
        }

        return array(
            array('pipe', 'r'),
            array('pipe', 'w'), // stdout
            array('pipe', 'w'), // stderr
        );
    }

    /**
     * {@inheritdoc}
     */
    public function getFiles()
    {
        return array();
    }

    /**
     * {@inheritdoc}
     */
    public function readAndWrite($blocking, $close = false)
    {
        $this->unblock();
        $w = $this->write();

        $read = $e = array();
        $r = $this->pipes;
        unset($r[0]);

        // let's have a look if something changed in streams
        set_error_handler(array($this, 'handleError'));
        if (($r || $w) && false === stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
            restore_error_handler();
            // if a system call has been interrupted, forget about it, let's try again
            // otherwise, an error occurred, let's reset pipes
            if (!$this->hasSystemCallBeenInterrupted()) {
                $this->pipes = array();
            }

            return $read;
        }
        restore_error_handler();

        foreach ($r as $pipe) {
            // prior PHP 5.4 the array passed to stream_select is modified and
            // lose key association, we have to find back the key
            $read[$type = array_search($pipe, $this->pipes, true)] = '';

            do {
                $data = fread($pipe, self::CHUNK_SIZE);
                $read[$type] .= $data;
            } while (isset($data[0]) && ($close || isset($data[self::CHUNK_SIZE - 1])));

            if (!isset($read[$type][0])) {
                unset($read[$type]);
            }

            if ($close && feof($pipe)) {
                fclose($pipe);
                unset($this->pipes[$type]);
            }
        }

        return $read;
    }

    /**
     * {@inheritdoc}
     */
    public function areOpen()
    {
        return (bool) $this->pipes;
    }

    /**
     * Creates a new UnixPipes instance.
     *
     * @param Process         $process
     * @param string|resource $input
     *
     * @return static
     */
    public static function create(Process $process, $input)
    {
        return new static($process->isTty(), $process->isPty(), $input, $process->isOutputDisabled());
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Pipes;

/**
 * PipesInterface manages descriptors and pipes for the use of proc_open.
 *
 * @author Romain Neutron <imprec@gmail.com>
 *
 * @internal
 */
interface PipesInterface
{
    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();

    /**
     * Closes file handles and pipes.
     */
    public function close();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process;

use Symfony\Component\Process\Exception\InvalidArgumentException;

/**
 * ProcessUtils is a bunch of utility methods.
 *
 * This class contains static methods only and is not meant to be instantiated.
 *
 * @author Martin Hasoň <martin.hason@gmail.com>
 */
class ProcessUtils
{
    /**
     * This class should not be instantiated.
     */
    private function __construct()
    {
    }

    /**
     * Escapes a string to be used as a shell argument.
     *
     * @param string $argument The argument that will be escaped
     *
     * @return string The escaped argument
     */
    public static function escapeArgument($argument)
    {
        //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/bug.php?id=43784
        //@see https://bugs.php.net/bug.php?id=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($part, 1, -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
     *
     * Passing an object as an input is deprecated since version 2.5 and will be removed in 3.0.
     */
    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;
            }
            // deprecated as of Symfony 2.5, to be removed in 3.0
            if (\is_object($input) && method_exists($input, '__toString')) {
                @trigger_error('Passing an object as an input is deprecated since Symfony 2.5 and will be removed in 3.0.', E_USER_DEPRECATED);

                return (string) $input;
            }

            throw new InvalidArgumentException(sprintf('%s only accepts strings or stream resources.', $caller));
        }

        return $input;
    }

    private static function isSurroundedBy($arg, $char)
    {
        return 2 < \strlen($arg) && $char === $arg[0] && $char === $arg[\strlen($arg) - 1];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Exception;

/**
 * LogicException for the Process Component.
 *
 * @author Romain Neutron <imprec@gmail.com>
 */
class LogicException extends \LogicException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Exception;

use Symfony\Component\Process\Process;

/**
 * Exception that is thrown when a process times out.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class ProcessTimedOutException extends RuntimeException
{
    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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Exception;

/**
 * Marker Interface for the Process Component.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
interface ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Exception;

/**
 * InvalidArgumentException for the Process Component.
 *
 * @author Romain Neutron <imprec@gmail.com>
 */
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Exception;

use Symfony\Component\Process\Process;

/**
 * Exception for failed processes.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class ProcessFailedException extends RuntimeException
{
    private $process;

    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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Exception;

/**
 * RuntimeException for the Process Component.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php

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 = array(
		'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 = array();
			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 );
		}
		$user_logins      = wp_list_pluck( $users, 'user_login' );
		$super_admins     = self::get_admins();
		$num_super_admins = count( $super_admins );

		foreach ( $user_logins as $user_login ) {
			if ( in_array( $user_login, $super_admins, true ) ) {
				WP_CLI::warning( "User '{$user_login}' already has super-admin capabilities." );
				continue;
			}

			$super_admins[] = $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 > 1 ? 'users' : 'user';
					WP_CLI::success( "Granted super-admin capabilities to {$successes} {$message}." );
				}
			} else {
				WP_CLI::error( 'Site options update failed.' );
			}
		}
	}

	/**
	 * 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, $_ ) {
		$super_admins = self::get_admins();
		if ( ! $super_admins ) {
			WP_CLI::error( 'No super admins to revoke super-admin privileges from.' );
		}

		$users             = $this->fetcher->get_many( $args );
		$user_logins       = $users ? array_values( array_unique( wp_list_pluck( $users, 'user_login' ) ) ) : array();
		$user_logins_count = count( $user_logins );

		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,
						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 > 1 ? '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 > 1 ? '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 );
	}

	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', array( 'admin' ) );
	}
}
<?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_fill( 0, count( $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
/**
 * 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_sections, true ) ) {
				$before_posts_xml .= $this->$section();
			}
		}

		return $before_posts_xml;
	}

	public function posts() {
		return new WP_Map_Iterator( $this->export->posts(), array( $this, 'post' ) );
	}

	public function after_posts() {
		return $this->footer();
	}

	public function header() {
		$oxymel           = new Oxymel();
		$charset          = $this->export->charset();
		$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(
				array(
					'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 $term_id => $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' );
		$action_output = ob_get_clean();
		return $action_output;
	}

	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
			->title( apply_filters( 'the_title_rss', $post->post_title ) ) // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WordPress native hook.
			->link( esc_url( apply_filters( 'the_permalink_rss', get_permalink() ) ) ) // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WordPress native hook.
			->pubDate( mysql2date( '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' ) )
			->guid( esc_url( get_the_guid() ), array( '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: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(
				array(
					'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_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->f ) {
			$this->close_current_file();
		}
		$file_path = $this->next_file_path();
		$this->f   = fopen( $file_path, 'w' );
		if ( ! $this->f ) {
			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->f ) {
			return;
		}
		$this->write( $this->after_posts_xml );
		fclose( $this->f );
	}

	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_Term_Exception extends RuntimeException {
}
<?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>]
	 * : 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',
			function( $file_path ) {
				WP_CLI::log( sprintf( '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_key( get_bloginfo( 'name' ) );
		if ( empty( $sitename ) ) {
			$sitename = 'site';
		}
		return str_replace( [ '{site}', '{date}', '{n}' ], [ $sitename, date( 'Y-m-d' ), '%03d' ], $filename_format );
	}

	public static function load_export_api() {
		require dirname( dirname( __FILE__ ) ) . '/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( 1 );
		}
	}

	private function check_dir( $path ) {
		if ( empty( $path ) ) {
			$path = getcwd();
		} elseif ( ! is_dir( $path ) ) {
			WP_CLI::error( sprintf( "The directory '%s' does not exist.", $path ) );
		} elseif ( ! is_writable( $path ) ) {
			WP_CLI::error( sprintf( "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::warning( sprintf( 'The start_date %s is invalid.', $date ) );
			return false;
		}
		$this->export_args['start_date'] = date( 'Y-m-d', $time );
		return true;
	}

	private function check_end_date( $date ) {
		if ( null === $date ) {
			return true;
		}

		$time = strtotime( $date );
		if ( ! empty( $date ) && ! $time ) {
			WP_CLI::warning( sprintf( 'The end_date %s is invalid.', $date ) );
			return false;
		}
		$this->export_args['end_date'] = date( 'Y-m-d', $time );
		return true;
	}

	private function check_post_type( $post_type ) {
		if ( null === $post_type || 'any' === $post_type ) {
			return true;
		}

		$post_type  = array_unique( array_filter( explode( ',', $post_type ) ) );
		$post_types = get_post_types();

		foreach ( $post_type as $type ) {
			if ( ! in_array( $type, $post_types, true ) ) {
				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_unique( array_filter( explode( ',', $post_type ) ) );
		$post_types = get_post_types();

		$keep_post_types = [];
		foreach ( $post_type as $type ) {
			if ( ! in_array( $type, $post_types, true ) ) {
				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_filter( array_unique( array_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 = intval( $start_id );

		// Post IDs must be greater than 0.
		if ( 0 >= $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( sprintf( '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::warning( sprintf( 'Could not find a matching author for %s. The following authors exist: %s', $author, implode( ', ', $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 <= 0 ) ) {
			WP_CLI::warning( sprintf( 'max_num_posts should be a positive integer.', $num ) );
			return false;
		}

		$this->export_args['max_num_posts'] = (int) $num;

		return true;
	}

	private function check_category( $category ) {
		if ( null === $category ) {
			return true;
		}

		$term = category_exists( $category );
		if ( empty( $term ) || is_wp_error( $term ) ) {
			WP_CLI::warning( sprintf( '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::warning( sprintf( 'Could not find a post_status matching %s. Here is a list of available stati: %s', $status, implode( ', ', array_keys( $stati ) ) ) );
			return false;
		}
		$this->export_args['status'] = $status;
		return true;
	}

	private function check_skip_comments( $skip ) {
		if ( null === $skip ) {
			return true;
		}

		if ( 0 !== (int) $skip && 1 !== (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( sprintf( 'max_file_size should be numeric.', $size ) );
			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 = array(
		'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 $xml_gen;

	private $wheres = array();
	private $joins  = array();

	private $author;
	private $category;

	public $missing_parents = false;

	public function __construct( $filters = array() ) {
		$this->filters  = wp_parse_args( $filters, self::$defaults );
		$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 = array(
			'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' ),
			'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;
		$authors    = array();
		$author_ids = $wpdb->get_col( "SELECT DISTINCT post_author FROM $wpdb->posts WHERE post_status != 'auto-draft'" );
		foreach ( (array) $author_ids as $author_id ) {
			$authors[] = get_userdata( $author_id );
		}
		$authors = array_filter( $authors );
		return $authors;
	}

	public function categories() {
		if ( $this->category ) {
			return array( $this->category );
		}
		if ( $this->filters['post_type'] ) {
			return array();
		}
		$categories = (array) get_categories( array( '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 array();
		}
		$tags = (array) get_tags( array( 'get' => 'all' ) );

		$this->check_for_orphaned_terms( $tags );

		return $tags;
	}

	public function custom_taxonomies_terms() {
		if ( $this->filters['post_type'] ) {
			return array();
		}
		$custom_taxonomies = get_taxonomies( array( '_builtin' => false ) );
		$custom_terms      = (array) get_terms( $custom_taxonomies, array( '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 ) ? 1 : 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_ids, self::QUERY_CHUNK );
		return new WP_Map_Iterator( $posts_iterator, array( $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() {
		global $wpdb;
		$post_types_filters = array( 'can_export' => true );
		if ( $this->filters['post_type'] ) {
			$post_types = $this->filters['post_type'];

			// Flatten single post types
			if ( is_array( $post_types ) && 1 === count( $post_types ) ) {
				$post_types = array_shift( $post_types );
			}
			$post_types_filters = array_merge( $post_types_filters, array( 'name' => $post_types ) );
		}

		// Multiple post types
		if ( isset( $post_types_filters['name'] ) && is_array( $post_types_filters['name'] ) ) {
			$post_types = array();
			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;
		$user = $this->find_user_from_any_object( $this->filters['author'] );
		if ( ! $user || is_wp_error( $user ) ) {
			return;
		}
		$this->author   = $user;
		$this->wheres[] = $wpdb->prepare( 'p.post_author = %d', $user->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 ) );
	}

	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 ) );
	}

	private function start_id_where() {
		global $wpdb;

		$start_id = absint( $this->filters['start_id'] );
		if ( 0 === $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'] > 0 ) {
			return "LIMIT {$this->filters['max_num_posts']}";
		} else {
			return '';
		}
	}

	private function include_attachment_ids( $post_ids ) {
		global $wpdb;
		if ( ! $post_ids ) {
			return array();
		}
		$attachment_ids = [];
		// phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition -- Assigment is part of the break condition.
		while ( $batch_of_post_ids = array_splice( $post_ids, 0, self::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 ( 0 === (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    = array();
		$have_parent = array();

		foreach ( $terms as $term ) {
			$term_ids[ $term->term_id ] = true;
			if ( 0 !== (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 array();
		}
		$terms = wp_get_object_terms( $post->ID, $taxonomies );
		$terms = $terms ? $terms : array();
		return $terms;
	}

	private static function get_meta_for_post( $post ) {
		global $wpdb;
		$meta_for_export = array();
		$meta_from_db    = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->postmeta WHERE post_id = %d", $post->ID ) );
		foreach ( $meta_from_db as $meta ) {
			// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Calling native WordPress hook.
			if ( apply_filters( 'wxr_export_skip_postmeta', false, $meta->meta_key, $meta ) ) {
				continue;
			}
			if ( in_array( $meta->meta_key, [ '_edit_lock', '_wp_attachment_metadata', '_wp_attached_file' ], true ) ) {
				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 array();
		}

		$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 ) {
			$meta          = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->commentmeta WHERE comment_id = %d", $comment->comment_ID ) );
			$meta          = $meta ? $meta : array();
			$comment->meta = $meta;
		}
		return $comments;
	}
}

<?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->f = fopen( $this->file_name, 'w' );
		if ( ! $this->f ) {
			throw new WP_Export_Exception( "WP Export: error opening {$this->file_name} for writing." );
		}

		try {
			parent::export();
		} catch ( WP_Export_Exception $e ) {
			throw $e;
		} catch ( WP_Export_Term_Exception $e ) {
			throw $e;
		}

		fclose( $this->f );
	}

	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_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_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_left, 0, $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_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


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' ), array( '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' ), array( '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

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

class WP_Iterator_Exception extends Exception {
}
<?php

class WP_Export_Exception extends RuntimeException {
}
<?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 ( ! seems_utf8( $text ) ) {
			$text = utf8_encode( $text );
		}
		return parent::cdata( $text );
	}
}

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

use Composer\Semver\Comparator;
use WP_CLI\Extractor;
use WP_CLI\Iterators\Table as TableIterator;
use WP_CLI\Utils;
use WP_CLI\Formatter;

/**
 * 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 --admin_email=info@example.com
 *     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.
	 *
	 * ## 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_writable( dirname( $download_dir ) ) ) {
				WP_CLI::error( "Insufficient permission to create directory '{$download_dir}'." );
			}

			WP_CLI::log( "Creating directory '{$download_dir}'." );
			if ( ! @mkdir( $download_dir, 0777, true /*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       = Utils\get_flag_value( $assoc_args, 'locale', 'en_US' );
		$skip_content = Utils\get_flag_value( $assoc_args, 'skip-content' );

		$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_array( strtolower( $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 {
			$offer = $this->get_download_offer( $locale );
			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,
			];

			$response = Utils\http_request( 'GET', $download_url, null, $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_code, 0, 2 ) ) {
				WP_CLI::error( "Couldn't access download URL (HTTP code {$response->status_code})." );
			}

			if ( 'nightly' !== $version ) {
				$md5_response = Utils\http_request( 'GET', $download_url . '.md5' );
				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 );
		}

		WP_CLI::success( 'WordPress downloaded.' );
	}

	private static function read( $url ) {
		$headers  = [ 'Accept' => 'application/json' ];
		$response = Utils\http_request( 'GET', $url, null, $headers, [ 'timeout' => 30 ] );
		if ( 200 === $response->status_code ) {
			return $response->body;
		} else {
			WP_CLI::error( "Couldn't fetch response from {$url} (HTTP code {$response->status_code})." );
		}
	}

	private function get_download_offer( $locale ) {
		$out = self::read( 'https://api.wordpress.org/core/version-check/1.7/?locale=' . $locale );
		$out = function_exists( 'wp_json_decode' )
			? wp_json_decode( $out )
			: json_decode( $out );

		$offer = $out->offers[0];

		if ( $offer->locale !== $locale ) {
			return false;
		}

		return $offer;
	}

	/**
	 * 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( 0 );
		}

		WP_CLI::halt( 1 );
	}

	/**
	 * 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 --admin_email=info@example.com
	 *     Success: WordPress installed successfully.
	 *
	 *     # Install WordPress without disclosing admin_password to bash history
	 *     $ wp core install --url=example.com --title=Example --admin_user=supervisor --admin_email=info@example.com --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="user@example.com"
	 *     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_password( 18 )
			: $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 ) ? 1 : (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', 1 );

		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_admins, true ) ) {
					$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( $domain, 0, $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_path, null, null, 6, 2048 );

		$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_path( dirname( __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.
	 * @return string|array String message on failure. An array of checksums on success.
	 */
	private static function get_core_checksums( $version, $locale ) {
		$query = http_build_query( compact( 'version', 'locale' ), null, '&' );
		$url   = "https://api.wordpress.org/core/checksums/1.0/?{$query}";

		$options = [ 'timeout' => 30 ];
		$headers = [ 'Accept' => 'application/json' ];

		$response = Utils\http_request( 'GET', $url, null, $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( $body, true );

		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.
	 *
	 * ## 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;
		$from_api = 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 ( \WP_CLI\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;

			$GLOBALS['wpcli_core_update_obj'] = $update;
			$result                           = Utils\get_upgrader( $upgrader )->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_exists( ABSPATH . 'wp-includes/version.php' ) ) {
					$wp_details = self::get_wp_details();
					$to_version = $wp_details['wp_version'];
				}

				$locale = Utils\get_flag_value( $assoc_args, 'locale', get_locale() );
				$this->cleanup_extra_files( $from_version, $to_version, $locale );

				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' ] );
				if ( 0 === (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( $locale, 0, 2 ) . '.';
		$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 );
	}

	private function cleanup_extra_files( $version_from, $version_to, $locale ) {
		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' );
		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' );
		if ( ! is_array( $new_checksums ) ) {
			WP_CLI::warning( "{$new_checksums} Please cleanup files manually." );
			return;
		}

		$files_to_remove = array_diff( array_keys( $old_checksums ), array_keys( $new_checksums ) );

		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 ( 0 === stripos( $file, 'wp-content' ) ) {
					continue;
				}

				if ( file_exists( ABSPATH . $file ) ) {
					unlink( ABSPATH . $file );
					WP_CLI::log( "File removed: {$file}" );
					$count++;
				}
			}

			if ( $count ) {
				WP_CLI::log( number_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

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 {

	/**
	 * 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 true.
	 * @return string|WP_Error The full path to the downloaded package file, or a WP_Error object.
	 */
	public function download_package( $package, $check_signatures = true ) {

		/**
		 * 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
		);
		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( $package, PATHINFO_FILENAME );
		$extension = pathinfo( $package, PATHINFO_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,
		];

		$this->skin->feedback( 'downloading_package', $package );

		/** @var Requests_Response|null $req */
		try {
			$response = Utils\http_request( 'GET', $package, null, $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 ( 0 !== 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

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' );
<?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 ) {
			Psy\Shell::debug();
		} else {
			$repl = new $class( 'wp> ' );
			$repl->start();
		}
	}
}
<?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_with( self::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 ( 0 < strlen( $out ) ) {
					$out = rtrim( $out, "\n" ) . "\n";
				}
				fwrite( STDOUT, $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 ( 0 < strlen( $out ) ) {
					echo rtrim( $out, "\n" ) . "\n";
				}
				echo '=> ';
				var_dump( $evl );
				fwrite( STDOUT, ob_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 = popen( self::create_prompt_cmd( $prompt, $this->history_file ), 'r' );

			$line = fgets( $fp );

			pclose( $fp );

			if ( ! $line ) {
				break;
			}

			$line = rtrim( $line, "\n" );

			if ( $line && '\\' === $line[ strlen( $line ) - 1 ] ) {
				$line = substr( $line, 0, -1 );
			} 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

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

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_quote( str_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_quote( str_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

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\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( $v, 0, 8 );
				}
			);
		}

		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_args, wp_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::warning( sprintf( "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_type, apply_filters( 'embed_cache_oembed_types', $post_types ), true ) ) {
			WP_CLI::warning( sprintf( "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;
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\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::log( json_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::warning( sprintf( "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, 3 );
		}

		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( $html, 0, 1 ) && Utils\get_flag_value( $assoc_args, 'do-shortcode' ) ) {
			$html = do_shortcode( $html, true );
		}

		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( $key, esc_html( $value ) );
			}
		}

		return $node->asXML();
	}
}
<?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::error( sprintf( 'Could not fetch composer.json (HTTP code %d)', $response->status_code ) );
		}
		$composer_json = json_decode( $response->body, true );
		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( $contributors, SORT_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 = trim( explode( ',', $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::error( sprintf( '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( 1 );
}

$contents = file_get_contents( $file );
$composer = json_decode( $contents );

if ( empty( $composer ) || ! is_object( $composer ) ) {
	echo 'Invalid composer.json for package.';
	exit( 1 );
}

if ( empty( $composer->autoload->files ) ) {
	echo 'composer.json must specify valid "autoload" => "files"';
	exit( 1 );
}

echo implode( PHP_EOL, $composer->autoload->files );
exit( 0 );
<?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;
		$position                  = Utils\get_flag_value( $args, 2, 1 ) - 1;
		$this->validate_sidebar( $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_id, null, $sidebar_id, null, $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'] - 1 : $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::warning( sprintf( "'%s' is already deactivated.", $widget_id ) );
				continue;
			}

			$this->move_sidebar_widget( $widget_id, $sidebar_id, 'wp_inactive_widgets', $sidebar_index, 0 );

			$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::warning( sprintf( 'Invalid sidebar: %s', $sidebar_id ) );
				$errors++;
				continue;
			}

			$widgets = $this->get_sidebar_widgets( $sidebar_id );
			if ( empty( $widgets ) ) {
				WP_CLI::warning( sprintf( "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, 0 );
				}
				WP_CLI::log( sprintf( "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, $widgets, true ) ) {
				$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, $widgets, true );
			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, 1 );
				array_splice( $widgets, $new_index, 0, $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( $widgets, 0, $new_index, true );
			$after                          = array_slice( $widgets, $new_index, count( $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

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

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

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

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_rules( Utils\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::warning( substr( $process_run->stderr, 9 ) );
		}
	}

	/**
	 * 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_structure, EP_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_base, EP_COMMENTS, true, true, true, false );
		$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( $permastruct, EP_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_list, explode( ',', $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::warning( sprintf( "Some rewrite rules may be missing because %s weren't loaded.", $skipped ) );
	}
}

WP_CLI::add_command( 'rewrite', 'Rewrite_Command' );
<?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_values( array_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_values( array_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_quote( trim( $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( $value, true );
	}

	/**
	 * 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_COMMENT, T_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_reverse( array_unique( array_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, $contents, LOCK_EX );

		if ( false === $result ) {
			throw new Exception( 'Failed to update the config file.' );
		}

		return true;
	}

}
<?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 = 1 ) {
	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 = '/' . ltrim( parse_url( urldecode( $_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, -1 ) !== '/' ) {
		header( "Location: $wpcli_server_path/" );
		exit;
	}

	if ( strpos( $wpcli_server_path, '.php' ) !== false ) {
		chdir( dirname( $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

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_null( WP_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_path( dirname( __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( STDIN, STDOUT, STDERR );

		// https://bugs.php.net/bug.php?id=60181
		$options = array();
		if ( Utils\is_windows() ) {
			$options['bypass_shell'] = true;
		}

		exit( proc_close( proc_open( $cmd, $descriptors, $pipes, null, null, $options ) ) );
	}
}
<?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_compare( PHP_VERSION, $min_version, '<' ) ) {
				WP_CLI::error( "The `wp server` command requires PHP {$min_version} or newer." );
			}
		},
	)
);
<?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\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_mode( true );
		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_mode( false );
		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() ? 0 : 1 );
	}

	/**
	 * 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
/**
   * Spyc -- A Simple PHP YAML Class
   * @version 0.6.2
   * @author Vlad Andersen <vlad.andersen@gmail.com>
   * @author Chris Wanstrath <chris@ozmm.org>
   * @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

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
/**
   * Spyc -- A Simple PHP YAML Class
   * @version 0.6.2
   * @author Vlad Andersen <vlad.andersen@gmail.com>
   * @author Chris Wanstrath <chris@ozmm.org>
   * @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($data, false, false, true);
  }
}
<?php namespace Mustangostang;
/**
   * Spyc -- A Simple PHP YAML Class
   * @version 0.6.2
   * @author Vlad Andersen <vlad.andersen@gmail.com>
   * @author Chris Wanstrath <chris@ozmm.org>
   * @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($key, self::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, "!") === 0 ||
      substr ($value, -1, 1) == ':')
    ) {
      $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(0, count($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, '"') === 0 && strrpos($line, '"') == (strlen($line)-1) || strpos($line, "'") === 0 && strrpos($line, "'") == (strlen($line)-1)) {
        $line = substr($line, 1, -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 !== 0 && 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, $words, true);
  }

  private function isFalseWord($value) {
    $words = self::getTranslations(array('false', 'off', 'no', 'n'));
    return in_array($value, $words, true);
  }

  private function isNullWord($value) {
    $words = self::getTranslations(array('null', '~'));
    return in_array($value, $words, true);
  }

  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, -1, 1);

    $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 ($value, 1, -1), array ('\'\'' => '\'', '\\\''=> '\''));
      return strtr(substr ($value, 1, -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 ($value, 1, -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 ($value, 1, -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 ($value, 0) === $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'), $inline, 1);
    }

    // Check for mappings
    while (preg_match('/{([^\[\]{}]+)}/U',$inline,$matchmaps)) {
      $maps[] = $matchmaps[0];
      $inline = preg_replace('/{([^\[\]{}]+)}/U', ('YAMLMap' . (count($maps) - 1) . 's'), $inline, 1);
    }

    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],$value, 1);
          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/', '', $value, 1);
          $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 == 0 && !$this->_containsGroupAlias && !$this->_containsGroupAnchor) { // Shortcut for root-level values.
      if ($key || $key === '' || $key === '0') {
        $this->result[$key] = $value;
      } else {
        $this->result[] = $value; end ($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[] = $value; end ($_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, -1, 1) == ']') 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($_, -1 * 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 <= $lastIndentInParentPath) array_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($line, 0, 2) != '- ') 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($line, 0, 2) == '- ' && substr ($line, -1, 1) == ':');
  }

  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, -1, 1) == ':');
  }

  private function isPlainArray ($line) {
    return ($line[0] == '[' && substr ($line, -1, 1) == ']');
  }

  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 ($group, 1);
    if ($group[0] == '*') $this->_containsGroupAlias = substr ($group, 1);
    //print_r ($this->path);
  }

  private function stripGroup ($line, $group) {
    $line = trim(str_replace($group, '', $line));
    return $line;
  }
}
<?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

/**
 * 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( $args, 2, 0 );

		$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 ( 0 === $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 > 0 ) {
			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 > 0 ) {
			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_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

use WP_CLI\Utils;

/**
 * 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.
	 *
	 * ## 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       = '';
		$wp_local_package = '';

		if ( ! empty( $assoc_args['version'] ) ) {
			$wp_version = $assoc_args['version'];
		}

		if ( ! empty( $assoc_args['locale'] ) ) {
			$wp_local_package = $assoc_args['locale'];
		}

		if ( empty( $wp_version ) ) {
			$details    = self::get_wp_details();
			$wp_version = $details['wp_version'];

			if ( empty( $wp_local_package ) ) {
				$wp_local_package = $details['wp_local_package'];
			}
		}

		$checksums = self::get_core_checksums(
			$wp_version,
			! empty( $wp_local_package ) ? $wp_local_package : 'en_US'
		);

		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( $file, 0, 10 ) ) {
				continue;
			}

			if ( ! file_exists( ABSPATH . $file ) ) {
				WP_CLI::warning( "File doesn't exist: {$file}" );
				$has_errors = true;
				continue;
			}

			$md5_file = md5_file( ABSPATH . $file );
			if ( $md5_file !== $checksum ) {
				WP_CLI::warning( "File doesn't verify against checksum: {$file}" );
				$has_errors = true;
			}
		}

		$core_checksums_files = array_filter( array_keys( $checksums ), [ $this, 'filter_file' ] );
		$core_files           = $this->get_files( ABSPATH );
		$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 ( 0 === strpos( $filepath, 'wp-admin/' )
			|| 0 === strpos( $filepath, 'wp-includes/' )
			|| 1 === 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_path, null, null, 6, 2048 );

		$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, "'" );
	}

	/**
	 * 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.
	 * @return bool|array False on failure. An array of checksums on success.
	 */
	private static function get_core_checksums( $version, $locale ) {
		$query = http_build_query( compact( 'version', 'locale' ), null, '&' );
		$url   = "https://api.wordpress.org/core/checksums/1.0/?{$query}";

		$options = [ 'timeout' => 30 ];

		$headers  = [ 'Accept' => 'application/json' ];
		$response = Utils\http_request( 'GET', $url, null, $headers, $options );

		if ( ! $response->success || 200 !== (int) $response->status_code ) {
			return false;
		}

		$body = trim( $response->body );
		$body = json_decode( $body, true );

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

		return $body['checksums'];
	}

}
<?php

use WP_CLI\Fetchers;
use WP_CLI\Formatter;
use WP_CLI\Utils;

/**
 * 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
	 * ---
	 *
	 * ## 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     = Utils\get_flag_value( $assoc_args, 'all', false );
		$strict  = Utils\get_flag_value( $assoc_args, 'strict', 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;
			}

			$checksums = $this->get_plugin_checksums( $plugin->name, $version );

			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, $files, true ) ) {
					$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_checksum( dirname( $plugin->file ) . '/' . $file, $checksums[ $file ] );
				if ( true !== $result ) {
					$this->add_error( $plugin->name, $file, is_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  = count( array_unique( array_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 checksums for the given version of plugin.
	 *
	 * @param string $version Version string to query.
	 * @param string $plugin  plugin string to query.
	 *
	 * @return bool|array False on failure. An array of checksums on success.
	 */
	private function get_plugin_checksums( $plugin, $version ) {
		$url = str_replace(
			array(
				'{slug}',
				'{version}',
			),
			array(
				$plugin,
				$version,
			),
			$this->url_template
		);

		$options = array(
			'timeout' => 30,
		);

		$headers  = array(
			'Accept' => 'application/json',
		);
		$response = Utils\http_request( 'GET', $url, null, $headers, $options );

		if ( ! $response->success || 200 !== $response->status_code ) {
			return false;
		}

		$body = trim( $response->body );
		$body = json_decode( $body, true );

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

		return $body['files'];
	}

	/**
	 * 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_files( trailingslashit( $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_array( strtolower( $file ), $this->get_soft_change_files(), true );
	}
}
<?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 RecursiveDirectoryIterator(
					$path,
					RecursiveDirectoryIterator::SKIP_DOTS
				),
				RecursiveIteratorIterator::CHILD_FIRST
			);
			foreach ( $files as $file_info ) {
				$pathname = self::normalize_directory_separators( substr( $file_info->getPathname(), strlen( $path ) ) );
				if ( $file_info->isFile() && $this->filter_file( $pathname ) ) {
					$filtered_files[] = $pathname;
				}
			}
		} 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

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\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_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' );

WP_CLI::add_command( 'i18n make-json', '\WP_CLI\I18n\MakeJsonCommand' );
<?php

namespace WP_CLI\I18n;

use Gettext\Generators\Po as PoGenerator;
use Gettext\Translations;

/**
 * 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 toString( Translations $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 ) : 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() ) {
				foreach ( $translation->getExtractedComments() as $comment ) {
					$lines[] = '#. ' . $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 ) === 1 ) {
			$lines[] = $name . ' ' . $newlines[0];
		} else {
			$lines[] = $name . ' ""';

			foreach ( $newlines as $line ) {
				$lines[] = $line;
			}
		}
	}
}
<?php

namespace WP_CLI\I18n;

use Gettext\Extractors\Po;
use Gettext\Merge;
use Gettext\Translation;
use Gettext\Translations;
use Symfony\Component\Finder\SplFileInfo;
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 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_audit = false;

	/**
	 * @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 blacklist for string extraction.
	 * Any string which is found on that blacklist 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.
	 *
	 * [--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.
	 *
	 * [--skip-js]
	 * : Skips JavaScript string extraction. Useful when this is done in another build step, e.g. through Babel.
	 *
	 * [--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 ( 1 === $translations_count ) {
			WP_CLI::debug( sprintf( 'Extracted %d string', $translations_count ), 'make-pot' );
		} else {
			WP_CLI::debug( sprintf( '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_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' );

		$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::debug( sprintf( '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::debug( sprintf( 'Destination: %s', $this->destination ), 'make-pot' );

		// Two is_dir() checks in case of a race condition.
		if ( ! is_dir( dirname( $this->destination ) )
			&& ! mkdir( dirname( $this->destination ), 0777, true )
			&& ! is_dir( dirname( $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::warning( sprintf( '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'
				);
			}
		}

		$this->exceptions = new Translations();

		if ( isset( $assoc_args['subtract'] ) ) {
			$exceptions = explode( ',', $assoc_args['subtract'] );

			$exceptions = array_filter(
				$exceptions,
				function ( $exception ) {
					if ( ! file_exists( $exception ) ) {
						WP_CLI::warning( sprintf( 'Invalid file provided to --subtract: %s', $exception ) );

						return false;
					}

					$exception_translations = new Translations();

					Po::fromFile( $exception, $exception_translations );
					$this->exceptions->mergeWith( $exception_translations );

					return true;
				}
			);

			if ( ! empty( $exceptions ) ) {
				WP_CLI::debug( sprintf( 'Ignoring any string already existing in: %s', implode( ',', $exceptions ) ), 'make-pot' );
			}
		}

		if ( isset( $assoc_args['include'] ) ) {
			$this->include = array_filter( explode( ',', $assoc_args['include'] ) );
			$this->include = array_map( [ $this, 'unslashit' ], $this->include );
			$this->include = array_unique( $this->include );

			WP_CLI::debug( sprintf( 'Only including the following files: %s', implode( ',', $this->include ) ), 'make-pot' );
		}

		if ( isset( $assoc_args['exclude'] ) ) {
			$this->exclude = array_filter( array_merge( $this->exclude, explode( ',', $assoc_args['exclude'] ) ) );
			$this->exclude = array_map( [ $this, 'unslashit' ], $this->exclude );
			$this->exclude = array_unique( $this->exclude );
		}

		WP_CLI::debug( sprintf( '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 ltrim( rtrim( trim( $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::debug( sprintf( '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::debug( sprintf( '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::debug( sprintf( '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',
					'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_translations, Merge::ADD | Merge::REMOVE );
		}

		PotGenerator::setCommentBeforeHeaders( $this->get_file_comment() );

		$this->set_default_headers( $translations );

		// POT files have no Language header.
		$translations->deleteHeader( Translations::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->addExtractedComment( sprintf( '%s of the theme', $header ) );
			} else {
				$translation->addExtractedComment( sprintf( '%s of the plugin', $header ) );
			}

			$translations[] = $translation;
		}

		try {
			$options = [
				// Extract 'Template Name' headers in theme files.
				'wpExtractTemplates' => isset( $this->main_file_data['Theme Name'] ),
				'include'            => $this->include,
				'exclude'            => $this->exclude,
				'extensions'         => [ 'php' ],
			];
			PhpCodeExtractor::fromDirectory( $this->source, $translations, $options );

			if ( ! $this->skip_js ) {
				JsCodeExtractor::fromDirectory(
					$this->source,
					$translations,
					[
						'include'    => $this->include,
						'exclude'    => $this->exclude,
						'extensions' => [ 'js', 'jsx' ],
					]
				);

				MapCodeExtractor::fromDirectory(
					$this->source,
					$translations,
					[
						'include'    => $this->include,
						'exclude'    => $this->exclude,
						'extensions' => [ 'map' ],
					]
				);
			}
		} catch ( \Exception $e ) {
			WP_CLI::error( $e->getMessage() );
		}

		foreach ( $this->exceptions as $translation ) {
			if ( $translations->find( $translation ) ) {
				unset( $translations[ $translation->getId() ] );
			}
		}

		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_match( self::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 string $comment */
						/** @var string $file_header */
						foreach ( $this->get_file_headers( $this->project_type ) as $file_header ) {
							if ( 0 === strpos( $comment, $file_header ) ) {
								return null;
							}
						}

						return $comment;
					}
				);

				$comments_count = count( $comments );

				if ( $comments_count > 1 ) {
					$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 = trim( preg_replace( '`^([\'"])(.*)\1$`Ds', '$2', $translation->getOriginal() ) );
			$non_placeholder_content = preg_replace( self::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_all( self::UNORDERED_SPRINTF_PLACEHOLDER_REGEX, $translation->getOriginal(), $unordered_matches );
			$unordered_matches       = $unordered_matches[0];

			if ( $unordered_matches_count >= 2 ) {
				$message = sprintf(
					'Multiple placeholders should be ordered. %s',
					$location
				);
				WP_CLI::warning( $message );
			}

			if ( $translation->hasPlural() ) {
				preg_match_all( self::SPRINTF_PLACEHOLDER_REGEX, $translation->getOriginal(), $single_placeholders );
				$single_placeholders = $single_placeholders[0];

				preg_match_all( self::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'] ) ) {
				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 <LL@li.org>' );
		$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( $fp, 8192 );

		// 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 trim( preg_replace( '/\s*(?:\*\/|\?>).*/', '', $str ) );
	}
}
<?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 toString( Translations $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 buildMessages( Translations $translations ) {
		$plural_forms      = $translations->getPluralForms();
		$number_of_plurals = is_array( $plural_forms ) ? ( $plural_forms[0] - 1 ) : null;
		$messages          = [];
		$context_glue      = chr( 4 );

		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->hasPluralTranslations( true ) ) {
				$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 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\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;

	/**
	 * 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];
		}

		$code = $this->code;
		// See https://github.com/mck89/peast/issues/7
		// Temporary workaround to fix dynamic imports. The τ is a greek letter.
		// This will trick the parser into thinking that it is a normal method call.
		$code = preg_replace( '/import(\\s*\\()/', 'imporτ$1', $code );

		$peast_options = [
			'sourceType' => Peast::SOURCE_TYPE_MODULE,
			'comments'   => false !== $this->extract_comments,
			'jsx'        => true,
		];
		$ast           = Peast::latest( $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'];

				/** @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() ) {
						$args[] = ''; // The value doesn't matter as it's unused.
					}

					if ( 'Literal' === $argument->getType() ) {
						/** @var Node\Literal $argument */
						$args[] = $argument->getValue();
					}
				}

				switch ( $functions[ $callee['name'] ] ) {
					case 'text_domain':
					case 'gettext':
						list( $original, $domain ) = array_pad( $args, 2, null );
						break;

					case 'text_context_domain':
						list( $original, $context, $domain ) = array_pad( $args, 3, null );
						break;

					case 'single_plural_number_domain':
						list( $original, $plural, $number, $domain ) = array_pad( $args, 4, null );
						break;

					case 'single_plural_number_context_domain':
						list( $original, $plural, $number, $context, $domain ) = array_pad( $args, 5, null );
						break;
				}

				if ( '' === (string) $original ) {
					return;
				}

				if ( $domain !== $translations->getDomain() && null !== $translations->getDomain() ) {
					return;
				}

				$translation = $translations->insert( $context, $original, $plural );
				$translation->addReference( $file, $node->getLocation()->getStart()->getLine() );

				/** @var Node\Comment $comment */
				foreach ( $all_comments as $comment ) {
					// Comments should be before the translation.
					if ( ! $this->commentPrecedesNode( $comment, $node ) ) {
						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() );
					}
				}

				if ( isset( $parsed_comment ) ) {
					$all_comments = [];
				}
			}
		);

		$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 resolveExpressionCallee( Node\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() ) {
				return [
					'name'     => $property->getValue(),
					'comments' => $callee->getCallee()->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 commentPrecedesNode( Node\Comment $comment, Node\Node $node ) {
		// Comments should be on the same or an earlier line than the translation.
		if ( $node->getLocation()->getStart()->getLine() - $comment->getLocation()->getEnd()->getLine() > 1 ) {
			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 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         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'.
	 * }
	 * @return null
	 */
	public static function fromFile( $file, Translations $translations, array $options = [] ) {
		foreach ( static::getFiles( $file ) as $f ) {
			// Make sure a relative file path is added as a comment.
			$options['file'] = ltrim( str_replace( static::$dir, '', Utils\normalize_path( $f ) ), '/' );

			$string = file_get_contents( $f );

			if ( ! $string ) {
				WP_CLI::debug(
					sprintf(
						'Could not load file %1s',
						$f
					)
				);

				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( $dir, Translations $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 calculateMatchScore( SplFileInfo $file, array $matchers = [] ) {
		if ( empty( $matchers ) ) {
			return 0;
		}

		if ( in_array( $file->getBasename(), $matchers, true ) ) {
			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_quote( str_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 ( 0 === $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 containsMatchingChildren( SplFileInfo $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, '*' ) &&
				0 === strpos( $path_or_file . '/', $root_relative_path )
			) {
				return true;
			}

			$base = current( explode( '*', $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 && 0 === strpos( $base, $root_relative_path ) ) ||
				( '' !== $base && 0 === 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( $dir, RecursiveDirectoryIterator::SKIP_DOTS | RecursiveDirectoryIterator::UNIX_PATHS ),
				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 ( 0 === $exclusion_score && $iterator->hasChildren() ) {
						return true;
					}

					if ( ( 0 === $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 );
					}

					return ( ( $inclusion_score >= $exclusion_score ) && $file->isFile() && in_array( $file->getExtension(), $extensions, true ) );
				}
			),
			RecursiveIteratorIterator::CHILD_FIRST
		);

		foreach ( $files as $file ) {
			/** @var SplFileInfo $file */
			if ( ! $file->isFile() || ! in_array( $file->getExtension(), $extensions, true ) ) {
				continue;
			}

			$filtered_files[] = Utils\normalize_path( $file->getPathname() );
		}

		sort( $filtered_files, SORT_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\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( $string, Translations $translations, array $options = [] ) {
		if ( ! array_key_exists( 'file', $options ) || substr( $options['file'], -7 ) !== '.js.map' ) {
			return;
		}
		$options['file'] = substr( $options['file'], 0, -7 ) . '.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']}" );

			$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()
				)
			);
		}
	}
}
<?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.
	 *
	 * [--pretty-print]
	 * : Pretty-print resulting JSON files.
	 *
	 * ## 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
	 *
	 * @when before_wp_load
	 *
	 * @throws WP_CLI\ExitException
	 */
	public function __invoke( $args, $assoc_args ) {
		$purge = Utils\get_flag_value( $assoc_args, 'purge', true );

		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];
		}

		// Two is_dir() checks in case of a race condition.
		if ( ! is_dir( $destination )
			&& ! mkdir( $destination, 0777, true )
			&& ! 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 );
				$result_count += count( $result );

				if ( $purge ) {
					$removed = $this->remove_js_strings_from_po_file( $file->getRealPath() );

					if ( ! $removed ) {
						WP_CLI::warning( sprintf( 'Could not update file %s', basename( $source ) ) );
					}
				}
			}
		}

		WP_CLI::success( sprintf( 'Created %d %s.', $result_count, Utils\pluralize( 'file', $result_count ) ) );
	}

	/**
	 * 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.
	 * @return array List of created JSON files.
	 */
	protected function make_json( $source_file, $destination ) {
		/** @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, - 7 ) === '.min.js' ) {
						return substr( $file, 0, - 7 ) . '.js';
					}

					if ( substr( $file, - 3 ) === '.js' ) {
						return $file;
					}

					return null;
				},
				$translation->getReferences()
			);

			$sources = array_unique( array_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;
	}

	/**
	 * 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::warning( sprintf( '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, - 3 ) !== '.js' ) {
					continue 2;
				}
			}

			unset( $translations[ $translation->getId() ] );
		}

		return PoGenerator::toFile( $translations, $source_file );
	}
}
<?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',
			'_e'              => 'text_domain',
			'esc_attr_e'      => 'text_domain',
			'esc_html_e'      => 'text_domain',
			'_x'              => 'text_context_domain',
			'_ex'             => 'text_context_domain',
			'esc_attr_x'      => 'text_context_domain',
			'esc_html_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( $string, Translations $translations, array $options = [] ) {
		WP_CLI::debug( "Parsing file {$options['file']}" );

		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()
				)
			);
		}
	}
}
<?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'];

		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( $args, 2, null );
					break;

				case 'text_context_domain':
					list( $original, $context, $domain ) = array_pad( $args, 3, null );
					break;

				case 'single_plural_number_domain':
					list( $original, $plural, $number, $domain ) = array_pad( $args, 4, null );
					break;

				case 'single_plural_number_context_domain':
					list( $original, $plural, $number, $context, $domain ) = array_pad( $args, 5, null );
					break;

				case 'single_plural_domain':
					list( $original, $plural, $domain ) = array_pad( $args, 3, null );
					break;

				case 'single_plural_context_domain':
					list( $original, $plural, $context, $domain ) = array_pad( $args, 4, null );
					break;

				default:
					// Should never happen.
					\WP_CLI::error( sprintf( "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 );
			$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 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( $string, Translations $translations, array $options = [] ) {
		WP_CLI::debug( "Parsing file {$options['file']}" );

		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()
				)
			);
		} catch ( Exception $exception ) {
			WP_CLI::debug(
				sprintf(
					'Could not parse file %1$s: %2$s',
					$options['file'],
					$exception->getMessage()
				)
			);
		}
	}

	/**
	 * @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

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

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_roles, true );
				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->name, ucwords( $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      = ( 1 === $restored_cap_count ) ? '%d capability' : '%d capabilities';
				$removed_text       = ( 1 === $removed_cap_count ) ? '%d capability' : '%d capabilities';
				$message            = "Restored {$restored_text} to and removed {$removed_text} from '%s' role.";
				WP_CLI::log( sprintf( $message, $restored_cap_count, $removed_cap_count, $role_key ) );
			} else {
				WP_CLI::log( "No changes necessary for '{$role_key}' role." );
			}
		}
		if ( $num_reset ) {
			if ( 1 === count( $args ) ) {
				WP_CLI::success( 'Role reset.' );
			} else {
				WP_CLI::success( "{$num_reset} of {$num_to_reset} roles reset." );
			}
		} else {
			if ( 1 === 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

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::line( implode( ',', 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

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

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::log( sprintf( 'Processing post #%d ("%s") (post_type: %s)', $post['post_id'], $post['post_title'], $post['post_type'] ) );
				WP_CLI::log( sprintf( '-- %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' ) );

				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 ( 0 === ( $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::log( sprintf( '-- Added comment #%d (%s of %s)', $comment_id, number_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 ( 0 === $lev ) {
				$closest  = $user->user_login;
				$shortest = 0;
				break;
			}

			if ( ( $lev <= $shortest || $shortest < 0 ) && $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_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\Process;
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 = array(
			'textdomain' => '',
			'dashicon'   => 'admin-post',
		);

		$templates = array(
			'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 = array(
			'textdomain' => '',
			'post_types' => "'post'",
		);

		if ( isset( $assoc_args['post_types'] ) ) {
			$assoc_args['post_types'] = $this->quote_comma_list_elements( $assoc_args['post_types'] );
		}

		$templates = array(
			'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 = array(
			'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           = \WP_CLI\Utils\get_flag_value( $assoc_args, 'force' );
			$files_written   = $this->create_files( array( $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 = array(
			'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 = array(
			'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 = array(
			"{$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 = array(
			'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             = \WP_CLI\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                                  = array();
		$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 ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'sassify' ) ) {
			$body['underscoresme_sass'] = 1;
		}

		if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'woocommerce' ) ) {
			$body['underscoresme_woocommerce'] = 1;
		}

		$tmpfname  = wp_tempnam( $url );
		$post_args = array(
			'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 = array(
				"{$theme_path}/{$theme_slug}/.editorconfig" => file_get_contents( self::get_template_path( '.editorconfig' ) ),
			);
			$this->create_files( $files_to_create, false );
			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 ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'activate' ) ) {
			WP_CLI::run_command( array( 'theme', 'activate', $theme_slug ) );
		} elseif ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'enable-network' ) ) {
			WP_CLI::run_command( array( 'theme', 'enable', $theme_slug ), array( '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, array( '.', '..' ), true ) ) {
			WP_CLI::error( "Invalid theme slug specified. The slug cannot be '.' or '..'." );
		}

		$defaults = array(
			'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( array( ' ', '-' ), '_', $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 = array(
			$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_contents( self::get_template_path( '.editorconfig' ) ),
		);
		$force           = \WP_CLI\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 ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'activate' ) ) {
			WP_CLI::run_command( array( 'theme', 'activate', $theme_slug ) );
		} elseif ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'enable-network' ) ) {
			WP_CLI::run_command( array( 'theme', 'enable', $theme_slug ), array( '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    = ucwords( str_replace( '-', ' ', $plugin_slug ) );
		$plugin_package = str_replace( ' ', '_', $plugin_name );

		if ( in_array( $plugin_slug, array( '.', '..' ), true ) ) {
			WP_CLI::error( "Invalid plugin slug specified. The slug cannot be '.' or '..'." );
		}

		$defaults = array(
			'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 = array(
			$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_contents( self::get_template_path( '.editorconfig' ) ),
		);
		$force           = \WP_CLI\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 ( ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'skip-tests' ) ) {
			$command_args = array(
				'dir'   => $plugin_dir,
				'ci'    => empty( $assoc_args['ci'] ) ? '' : $assoc_args['ci'],
				'force' => $force,
			);
			WP_CLI::run_command( array( 'scaffold', 'plugin-tests', $plugin_slug ), $command_args );
		}

		if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'activate' ) ) {
			WP_CLI::run_command( array( 'plugin', 'activate', $plugin_slug ) );
		} elseif ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'activate-network' ) ) {
			WP_CLI::run_command( array( 'plugin', 'activate', $plugin_slug ), array( '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, array( '.', '..' ), 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    = ucwords( str_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 = array();
		// 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 = array(
			"{$type}_slug"    => $slug,
			"{$type}_package" => $package,
		);

		$force           = \WP_CLI\Utils\get_flag_value( $assoc_args, 'force' );
		$files_to_create = array(
			"{$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 = array(
			'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             = \WP_CLI\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->copy( self::get_template_path( $file ), $file_name, true );
			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 = dirname( self::canonicalize_path( str_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_dir, WP_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_dir, WP_PLUGIN_DIR );
		}

		// Success.
		return null;
	}

	protected function create_files( $files_and_contents, $force ) {
		$wp_filesystem = $this->init_wp_filesystem();
		$wrote_files   = array();

		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->mkdir( dirname( $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, array( '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 = \WP_CLI\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 = array();

		foreach ( $defaults as $key => $value ) {
			$out[ $key ] = \WP_CLI\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_dir( WP_PLUGIN_DIR ) ) {
			wp_mkdir_p( WP_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 = array() ) {
		return Utils\mustache_render( dirname( dirname( __FILE__ ) ) . "/templates/{$template}", $data );
	}

	/**
	 * Gets the template path based on installation type.
	 */
	private static function get_template_path( $template ) {
		$command_root  = Utils\phar_safe_path( dirname( __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, -1 ) ) {
			$path .= '/';
		}

		$output = array();

		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

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

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_size, get_intermediate_image_sizes(), true ) ) {
			WP_CLI::error( sprintf( 'Unknown image size "%s".', $image_size ) );
		}

		if ( empty( $args ) ) {
			if ( $image_size ) {
				WP_CLI::confirm( sprintf( '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 ( 0 === $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 ( 0 === $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( $file, PHP_URL_HOST ) : parse_url( $file, PHP_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 = strtok( Utils\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_numeric( sanitize_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( $file, null );
				$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( $success, wp_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( 1 );
		}
	}

	/**
	 * 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 : -1;
			}
		);
		array_unshift(
			$sizes,
			array(
				'name'   => 'full',
				'width'  => '',
				'height' => '',
				'crop'   => 'N/A',
				'ratio'  => 'N/A',
			)
		);
		WP_CLI\Utils\format_items( $assoc_args['format'], $sizes, explode( ',', $assoc_args['fields'] ) );
	}

	private function get_ratio( $width, $height ) {
		if ( 0 === $height ) {
			return "0:{$width}";
		}

		if ( 0 === $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 ( 0 !== $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::warning( sprintf( '%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::warning( sprintf( '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 );

		// 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::warning( sprintf( '%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::warning( sprintf( '%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_diff( array_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 ) {

		// 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 ) 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 ) {
		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 ) ) {
			// 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_sizes, true ) ) {
				return array( $image_size );
			}
			return array();
		};

		foreach ( $image_size_filters as $name => $filter ) {
			add_filter( $name, $filter, PHP_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, $filter, PHP_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  = intval( get_option( "{$size}_size_w" ) );
		$height = intval( get_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 ( 0 === $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::success( sprintf( '%s of %s %s will be affected.', $successes, $count, Utils\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'] ) > 1 ) {
			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'] ) === 0 ) {
				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 = [ false, true ]; // $flip image along given axis [ horizontal, vertical ]
				break;
			case 3:
				$flip = [ true, true ];
				break;
			case 4:
				$flip = [ true, false ];
				break;
			case 5:
				$degree = -90;
				$rotate = true;
				$flip   = [ false, true ];
				break;
			case 6:
				$degree = -90;
				$rotate = true;
				break;
			case 7:
				$degree = 90;
				$rotate = true;
				$flip   = [ false, true ];
				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_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

use Composer\Composer;
use Composer\Config;
use Composer\Config\JsonConfigSource;
use Composer\DependencyResolver\Pool;
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 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.
 *
 * Installable packages are listed in the
 * [Package Index](http://wp-cli.org/package-index/).
 *
 * Learn how to create your own command from the
 * [Commands Cookbook](http://wp-cli.org/docs/commands-cookbook/)
 *
 * ## EXAMPLES
 *
 *     # List installed packages
 *     $ wp package list
 *     +-----------------------+------------------------------------------+---------+------------+
 *     | name                  | description                              | authors | version    |
 *     +-----------------------+------------------------------------------+---------+------------+
 *     | wp-cli/server-command | Start a development server for WordPress |         | dev-master |
 *     +-----------------------+------------------------------------------+---------+------------+
 *
 *     # Install the latest development version of the package
 *     $ wp package install wp-cli/server-command
 *     Installing package wp-cli/server-command (dev-master)
 *     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';

	private $pool = false;

	/**
	 * Default author data used while creating default WP-CLI packages composer.json.
	 *
	 * @var array
	 */
	private $author_data = [
		'name'  => 'WP-CLI',
		'email' => 'noreply@wpcli.org',
	];

	/**
	 * 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-master, 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-master, 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-master
	 */
	public function browse( $_, $assoc_args ) {
		$this->set_composer_auth_env_var();
		if ( empty( $assoc_args['format'] ) || 'table' === $assoc_args['format'] ) {
			WP_CLI::line( WP_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).
	 *
	 * ## EXAMPLES
	 *
	 *     # Install the latest development version from the package index.
	 *     $ wp package install wp-cli/server-command
	 *     Installing package wp-cli/server-command (dev-master)
	 *     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.
	 *
	 *     # Install the latest stable version.
	 *     $ wp package install wp-cli/server-command:@stable
	 *
	 *     # Install a package hosted at a git URL.
	 *     $ wp package install git@github.com:runcommand/hook.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;

		$this->set_composer_auth_env_var();
		$git_package = false;
		$dir_package = false;
		$version     = 'dev-master';
		if ( $this->is_git_repository( $package_name ) ) {
			$git_package = $package_name;
			preg_match( '#([^:\/]+\/[^\/]+)\.git#', $package_name, $matches );
			if ( ! empty( $matches[1] ) ) {
				$package_name = $this->check_git_package_name( $matches[1] );
			} 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_name, PATHINFO_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,
				];
				$response = Utils\http_request( 'GET', $package_name, null, [], $options );
				if ( 20 !== (int) substr( $response->status_code, 0, 2 ) ) {
					@unlink( $temp ); // @codingStandardsIgnoreLine
					WP_CLI::error( sprintf( "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          = rtrim( WP_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::error( sprintf( "Invalid package: shortened identifier '%s' not found.", $package_name ) );
			}
			if ( is_string( $package ) ) {
				if ( $this->is_git_repository( $package ) ) {
					$git_package = $package;
					if ( '@stable' === $version ) {
						$tag     = $this->get_github_latest_release_tag( $package_name );
						$version = $this->guess_version_constraint_from_tag( $tag );
					}
					$package_name = $this->check_git_package_name( $package_name, $version );
				}
			} 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();
			}
		}

		WP_CLI::log( sprintf( '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::log( sprintf( '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, $version, false /*sortPackages*/, true /*caseInsensitive*/ );
		$json_manipulator->addConfigSetting( 'secure-http', true );

		$package_args = [];
		if ( $git_package ) {
			WP_CLI::log( sprintf( '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::log( sprintf( '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->setUpdate( true ); // Installer class will only override composer.lock with this flag
		$install->setPreferSource( true ); // 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() );
		}
		WP_CLI::log( '---' );

		if ( 0 === $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-master |
	 *     +-----------------------+------------------------------------------+---------+------------+
	 *
	 * @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->setUpdate( true ); // Installer class will only override composer.lock with this flag
		$install->setPreferSource( true ); // 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( '---' );

		if ( 0 === $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.
	 *
	 * ## 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 ) {
		list( $package_name ) = $args;

		$this->set_composer_auth_env_var();
		$package = $this->get_installed_package_by_name( $package_name );
		if ( false === $package ) {
			WP_CLI::error( 'Package not installed.' );
		}
		$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::log( sprintf( 'Removing require statement from %s', $json_path ) );
		$manipulator = new JsonManipulator( $composer_backup );
		$manipulator->removeSubNode( 'require', $package_name, true /*caseInsensitive*/ );

		// Remove the 'repository' details from composer.json.
		WP_CLI::log( sprintf( 'Removing repository details from %s', $json_path ) );
		$manipulator->removeSubNode( 'repositories', $package_name, true /*caseInsensitive*/ );

		file_put_contents( $json_path, $manipulator->getContents() );
		$composer = $this->get_composer();

		// Set up the installer.
		$install = Installer::create( new NullIO(), $composer );
		$install->setUpdate( true ); // Installer class will only override composer.lock with this flag
		$install->setPreferSource( true ); // 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 ( 0 === $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
			chdir( pathinfo( $composer_path, PATHINFO_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.WP.TimezoneChange.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::error( sprintf( '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() ) );

			try {
				$package_index = new ComposerRepository( [ 'url' => self::PACKAGE_INDEX_URL ], new NullIO(), $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, $composer, null );
						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.
	 */
	private function get_package_by_shortened_identifier( $package_name ) {
		// 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;
			}
		}

		// Check if the package exists on Packagist.
		$url      = "https://repo.packagist.org/p/{$package_name}.json";
		$response = Utils\http_request( 'GET', $url );
		if ( 20 === (int) substr( $response->status_code, 0, 2 ) ) {
			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', $url, null /*data*/, $headers );
		if ( 20 === (int) substr( $response->status_code, 0, 2 ) ) {
			return $url;
		}

		return false;
	}

	/**
	 * Gets the installed community packages.
	 */
	private function get_installed_packages() {
		$composer = $this->get_composer();

		$repo                   = $composer->getRepositoryManager()->getLocalRepository();
		$existing               = json_decode( file_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_keys, true );
			// 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_keys, true ) ) {
				$installed_packages[] = $package;
			} elseif ( false !== $idx ) { // Legacy incorrect name check.
				WP_CLI::warning( sprintf( "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::error( sprintf( "Invalid package: composer.json file '%s' not found.", $composer_file ) );
		}
		$composer_data = json_decode( file_get_contents( $composer_file ), true );
		if ( null === $composer_data ) {
			WP_CLI::error( sprintf( "Invalid package: failed to parse composer.json file '%s' as json.", $composer_file ) );
		}
		if ( empty( $composer_data['name'] ) ) {
			WP_CLI::error( sprintf( "Invalid package: no name in composer.json file '%s'.", $composer_file ) );
		}
		$package_name = $composer_data['name'];
		$version      = 'dev-master';
		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\trailingslashit( getenv( 'WP_CLI_PACKAGES_DIR' ) ) . 'composer.json';
			} else {
				$composer_path = Utils\trailingslashit( Utils\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::error( sprintf( "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_path, PATHINFO_DIRNAME );
		if ( ! is_dir( $composer_dir ) ) {
			if ( ! @mkdir( $composer_dir, 0777, true ) ) { // @codingStandardsIgnoreLine
				$error = error_get_last();
				WP_CLI::error( sprintf( "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::error( sprintf( "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_package( PackageInterface $package, Composer $composer, $php_version, $minor_only = false ) {
		// find the latest version allowed in this pool
		$name             = $package->getPrettyName();
		$version_selector = new VersionSelector( $this->get_pool( $composer ) );
		$stability        = $composer->getPackage()->getMinimumStability();
		$flags            = $composer->getPackage()->getStabilityFlags();
		if ( isset( $flags[ $name ] ) ) {
			$stability = array_search( $flags[ $name ], BasePackage::$stabilities, true );
		}
		$best_stability = $stability;
		if ( $composer->getPackage()->getPreferStable() ) {
			$best_stability = $package->getStability();
		}
		$target_version = null;
		if ( 0 === strpos( $package->getVersion(), 'dev-' ) ) {
			$target_version = $package->getVersion();
		}
		if ( null === $target_version && $minor_only ) {
			$target_version = '^' . $package->getVersion();
		}
		return $version_selector->findBestCandidate( $name, $target_version, $php_version, $best_stability );
	}

	private function get_pool( Composer $composer ) {
		if ( ! $this->pool ) {
			$this->pool = new Pool( $composer->getPackage()->getMinimumStability(), $composer->getPackage()->getStabilityFlags() );
			$this->pool->addRepository( new CompositeRepository( $composer->getRepositoryManager()->getRepositories() ) );
		}
		return $this->pool;
	}

	/**
	 * 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' === strtolower( substr( $package, -4, 4 ) );
	}

	/**
	 * Checks that `$package_name` matches the name in the repo composer.json, and return corrected value if not.
	 *
	 * @string $package_name Package name to check.
	 * @string $version Optional. Package version. Default 'master'.
	 * @string Package name, possibly changed to match that in repo.
	 */
	private function check_git_package_name( $package_name, $version = '' ) {
		// 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';
		$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', $raw_content_url, null /*data*/, $headers );
		if ( 20 !== (int) substr( $response->status_code, 0, 2 ) ) {
			// 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->body, true );
		if ( null === $composer_content_as_array ) {
			WP_CLI::error( sprintf( "Failed to parse '%s' as json.", $raw_content_url ) );
		}
		if ( empty( $composer_content_as_array['name'] ) ) {
			WP_CLI::error( sprintf( "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::warning( sprintf( "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 + 1 );
		}

		// Strip any Composer 'dev-' prefix.
		if ( 0 === strncmp( $version, 'dev-', 4 ) ) {
			$version = substr( $version, 4 );
		}

		// 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 ) {
		$url      = "https://api.github.com/repos/{$package_name}/releases/latest";
		$response = Utils\http_request( 'GET', $url );
		if ( 20 !== (int) substr( $response->status_code, 0, 2 ) ) {
			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 ( 1 !== 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_auth, true /*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_phar( WP_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::error( sprintf( "Failed to read '%s': %s", $json_path, $error['message'] ) );
		}
		try {
			$composer_backup_decoded = $composer_json_obj->read();
		} catch ( Exception $e ) {
			WP_CLI::error( sprintf( "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(
			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 ) ) {
						fwrite( STDERR, $revert_fail_msg );
					} else {
						fwrite( STDERR, $revert_msg );
					}
				}
				$error_array = error_get_last();
				if ( false !== strpos( $error_array['message'], $memory_string ) ) {
					fwrite( STDERR, $memory_msg );
				}
			}
		);
	}
}
<?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 <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * 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 <j.boggiano@seld.be>
 */
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($links, true);
            $this->sortPackages($requirements);
            $links = $this->format($requirements);
        }

        $this->contents = $matches['start'] . $matches['property'] . $links . $matches['end'];

        return true;
    }

    /**
     * Sorts packages by importance (platform packages first, then PHP dependencies) and alphabetically.
     *
     * @link https://getcomposer.org/doc/02-libraries.md#platform-packages
     *
     * @param array $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($name, 0, 6) === 'extra.') {
            return $this->addSubNode('extra', substr($name, 6), $value);
        }

        return $this->addMainKey($name, $value);
    }

    public function removeProperty($name)
    {
        if (substr($name, 0, 6) === 'extra.') {
            return $this->removeSubNode('extra', substr($name, 6));
        }

        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('.', $name, 2);
        }

        // 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($value, 1) . $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($value, 1) . $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($value, 1) . $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($children, true)) {
            return false;
        }

        $subName = null;
        if (in_array($mainNode, array('config', 'extra')) && false !== strpos($name, '.')) {
            list($name, $subName) = explode('.', $name, 2);
        }

        // no node to remove
        if (!isset($decoded[$mainNode][$name]) || ($subName && !isset($decoded[$mainNode][$name][$subName]))) {
            return true;
        }

        // try and find a match for the subkey
        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 (1 !== $count) {
                    $childrenClean = preg_replace('{'.preg_quote($bestMatch).'\s*,?\s*}', '', $childrenClean, -1, $count); // WP_CLI: ditto.
                    if (1 !== $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($children, true);
                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($curVal, 0);
            }

            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

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

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 $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         = \WP_CLI\Utils\get_flag_value( $assoc_args, 'dry-run' );
		$php_only              = \WP_CLI\Utils\get_flag_value( $assoc_args, 'precise' );
		$this->recurse_objects = \WP_CLI\Utils\get_flag_value( $assoc_args, 'recurse-objects', true );
		$this->verbose         = \WP_CLI\Utils\get_flag_value( $assoc_args, 'verbose' );
		$this->format          = \WP_CLI\Utils\get_flag_value( $assoc_args, 'format' );
		$this->regex           = \WP_CLI\Utils\get_flag_value( $assoc_args, 'regex', false );

		if ( null !== $this->regex ) {
			$default_regex_delimiter = false;
			$this->regex_flags       = \WP_CLI\Utils\get_flag_value( $assoc_args, 'regex-flags', false );
			$this->regex_delimiter   = \WP_CLI\Utils\get_flag_value( $assoc_args, 'regex-delimiter', '' );
			if ( '' === $this->regex_delimiter ) {
				$this->regex_delimiter   = chr( 1 );
				$default_regex_delimiter = true;
			}
		}

		$regex_limit = \WP_CLI\Utils\get_flag_value( $assoc_args, 'regex-limit' );
		if ( null !== $regex_limit ) {
			if ( ! preg_match( '/^(?:[0-9]+|-1)$/', $regex_limit ) || 0 === (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( 1 );
			}
			$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, '' ) ) {
				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 );
			}
		}

		$this->skip_columns    = explode( ',', \WP_CLI\Utils\get_flag_value( $assoc_args, 'skip-columns' ) );
		$this->skip_tables     = explode( ',', \WP_CLI\Utils\get_flag_value( $assoc_args, 'skip-tables' ) );
		$this->include_columns = array_filter( explode( ',', \WP_CLI\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 = \WP_CLI\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::error( sprintf( 'Unable to open export file "%s" for writing: %s.', $assoc_args['export'], $error['message'] ) );
				}
			}
			$export_insert_size = WP_CLI\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 > 0 ) {
				$this->export_insert_size = $export_insert_size;
			}
			$php_only = true;
		}

		$log = \WP_CLI\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::error( sprintf( 'Unable to open log file "%s" for writing: %s.', $assoc_args['log'], $error['message'] ) );
				}
			}
			if ( $this->log_handle ) {
				$before_context = \WP_CLI\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 = \WP_CLI\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   = self::get_colors( $assoc_args, $default_log_colors );
				$this->log_encoding = 0 === strpos( $wpdb->charset, 'utf8' ) ? 'UTF-8' : false;
			}
		}

		$this->report = \WP_CLI\Utils\get_flag_value( $assoc_args, 'report', true );
		// Defaults to true if logging, else defaults to false.
		$this->report_changed_only = \WP_CLI\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 = \WP_CLI\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_columns, true ) ) {
					continue;
				}

				if ( in_array( $col, $this->skip_columns, true ) ) {
					continue;
				}

				if ( $this->verbose && 'count' !== $this->format ) {
					$this->start_time = microtime( true );
					WP_CLI::log( sprintf( '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 \cli\Table();
			$table->setHeaders( array( 'Table', 'Column', 'Replacements', 'Type' ) );
			$table->setRows( $report );
			$table->display();
		}

		if ( ! $this->dry_run ) {
			if ( ! empty( $assoc_args['export'] ) ) {
				$success_message = 1 === $total ? "Made 1 replacement and exported to {$assoc_args['export']}." : "Made {$total} replacements and exported to {$assoc_args['export']}.";
			} else {
				$success_message = 1 === $total ? 'Made 1 replacement.' : "Made $total replacements.";
				if ( $total && 'Default' !== WP_CLI\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 = ( 1 === $total ) ? '%d replacement to be made.' : '%d replacements to be made.';
			WP_CLI::success( sprintf( $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 \WP_CLI\SearchReplacer( $old, $new, $this->recurse_objects, $this->regex, $this->regex_flags, $this->regex_delimiter, false, $this->regex_limit );
		$col_counts = array_fill_keys( $all_columns, 0 );
		if ( $this->verbose && 'table' === $this->format ) {
			$this->start_time = microtime( true );
			WP_CLI::log( sprintf( 'Checking: %s', $table ) );
		}

		$rows = array();
		foreach ( new \WP_CLI\Iterators\Table( $args ) as $i => $row ) {
			$row_fields = array();
			foreach ( $all_columns as $col ) {
				$value = $row->$col;
				if ( $value && ! in_array( $col, $primary_keys, true ) && ! in_array( $col, $this->skip_columns, true ) ) {
					$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 = round( microtime( true ) - $this->start_time, 3 );
			WP_CLI::log( sprintf( '%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 = round( microtime( true ) - $this->start_time, 3 );
			WP_CLI::log( sprintf( '%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 \WP_CLI\SearchReplacer( $old, $new, $this->recurse_objects, $this->regex, $this->regex_flags, $this->regex_delimiter, null !== $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 ) . '%' );
		$primary_keys_sql = implode( ',', self::esc_sql_ident( $primary_keys ) );

		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- escaped through self::esc_sql_ident
		$rows = $wpdb->get_results( "SELECT {$primary_keys_sql} FROM {$table_sql} {$where}" );

		foreach ( $rows as $keys ) {
			$where_sql = '';
			foreach ( (array) $keys as $k => $v ) {
				if ( strlen( $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();
			}

			if ( $this->dry_run ) {
				$count++;
			} else {
				$where = array();
				foreach ( (array) $keys as $k => $v ) {
					$where[ $k ] = $v;
				}

				$count += $wpdb->update( $table, array( $col => $value ), $where );
			}
		}

		if ( $this->verbose && 'table' === $this->format ) {
			$time = round( microtime( true ) - $this->start_time, 3 );
			WP_CLI::log( sprintf( '%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_ident( array_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 ( ( 0 === $index % $export_insert_size && $index > 0 ) || $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( $sql, array_values( $values ) ) );
				} else {
					// 4.8.2 or less
					// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- verified inputs above
					$sql = $wpdb->prepare( $sql, array_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_escape( esc_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 = 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 = 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 = function ( $v ) {
			return substr( $v, 1 );
		};

		$color_codes = array_keys( \cli\Colors::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 = \WP_CLI\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 ) > 1 ? "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, $matches, PREG_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, $matches, PREG_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_data, null, true /*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,
			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}\\})/',
						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 = \cli\safe_substr( substr( $old_data, $last_old_offset, $old_offset - $last_old_offset ), -$this->log_before_context, null /*length*/, false /*is_width*/, $encoding );
				$new_before = \cli\safe_substr( substr( $new_data, $last_new_offset, $new_offset - $last_new_offset ), -$this->log_before_context, null /*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      = \cli\safe_substr( substr( $old_data, $old_end_offset ), 0, $this->log_after_context, false /*is_width*/, $encoding );
				$new_after      = \cli\safe_substr( substr( $new_data, $new_end_offset ), 0, $this->log_after_context, false /*is_width*/, $encoding );
				// To lessen context duplication in output, shorten the after context if it overlaps with the next match.
				if ( $i + 1 < $match_cnt && $old_end_offset + strlen( $old_after ) > $old_matches[0][ $i + 1 ][1] ) {
					$old_after       = substr( $old_after, 0, $old_matches[0][ $i + 1 ][1] - $old_end_offset );
					$new_after       = substr( $new_after, 0, $new_matches[0][ $i + 1 ][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 - 1 ] .= $old_log . $old_after;
				$new_bits[ $cnt - 1 ] .= $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

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 = -1 ) {
		$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 = intval( ini_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 ( 0 !== $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_data, true ) ) {
						return $data; // Avoid infinite loops when there's a cycle
					}
					// Add this data to the list of
					$visited_data[] = $data;
				}
			}

			$unserialized = @unserialize( $data );
			if ( is_string( $data ) && false !== $unserialized ) {
				$data = $this->run_recursively( $unserialized, true, $recursion_level + 1 );
			} 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( $value, false, $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_constants( true );
			$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

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::error( sprintf( '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 ( \WP_CLI\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 meta', 'User_Meta_Command' );
WP_CLI::add_command(
	'user session',
	'User_Session_Command',
	array(
		'before_invoke' => function() {
			if ( \WP_CLI\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

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_query, 10000 );

		$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" );
		}
	}

	/**
	 * 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', 0 );
	}

	/**
	 * 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, 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, comments, and terms" . $upload_message . '?', $assoc_args );

		$this->empty_posts();
		$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_details( trim( $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. Assignement 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_names, true ) ) {
				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_password( 12, false );
			$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', $id, true );
			}
		} else {
			WP_CLI::error( $id->get_error_message() );
		}

		if ( Utils\get_flag_value( $assoc_args, 'porcelain' ) ) {
			WP_CLI::line( $id );
		} else {
			$site_url = trailingslashit( get_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 = trailingslashit( get_site_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_args, null, 'site' );
			$formatter->display_items( $ids );
		} else {
			$formatter = new Formatter( $assoc_args, null, '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', 1 );
	}

	/**
	 * 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', 0 );
	}

	/**
	 * 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', 0 );
	}

	/**
	 * 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', 1 );
	}

	/**
	 * 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', 1 );
	}

	/**
	 * 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', 0 );
	}

	/**
	 * 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', 1 );
	}

	/**
	 * 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', 0 );
	}

	/**
	 * 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', 1 );
	}

	/**
	 * 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', 0 );
	}

	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\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
	 *     someone@example.com
	 *
	 *     # 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, 1 );
		$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 someone@example.com
	 *     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, 1 );
		$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( $key, get_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( 1 );
		}

		$key_path = array_map(
			function( $key ) {
				if ( is_numeric( $key ) && ( (string) intval( $key ) === $key ) ) {
					return (int) $key;
				}
					return $key;
			},
			array_slice( $args, 1 )
		);

		$traverser = new RecursiveDataStructureTraverser( $value );

		try {
			$value = $traverser->get( $key_path );
		} catch ( Exception $exception ) {
			die( 1 );
		}

		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, 2 )
		);

		if ( 'delete' === $action ) {
			$patch_value = null;
		} else {
			$stdin_value = EntityUtils::has_stdin()
				? trim( WP_CLI::get_value_from_arg_or_stdin( $args, -1 ) )
				: null;
			$patch_value = ! empty( $stdin_value )
				? WP_CLI::read_value( $stdin_value, $assoc_args )
				: WP_CLI::read_value( array_pop( $key_path ), $assoc_args );
		}

		/* Need to make a copy of $current_value here as it is modified by reference */
		$old_value     = sanitize_option( $key, get_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_escape( esc_sql( $old ) );
		}

		return $old;
	}
}
<?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 bob@example.com --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,supervisor@gmail.com,"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":"supervisor@gmail.com"}]
	 *
	 *     # 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 ) {
					return [ 'error', "Failed deleting user {$user_id}." ];
				}

				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 bob@example.com --role=author
	 *     Success: Created user 3.
	 *     Password: k9**&I4vNH(&
	 *
	 *     # Create user without showing password upon success
	 *     $ wp user create ann ann@example.com --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',
			strftime( '%F %T', current_time( 'timestamp' ) ) // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp
		);

		$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_password( 24 );
			$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 - 1 ) {
					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( $args, 1, get_option( 'default_role' ) );

		self::validate_role( $role );

		// Multisite
		if ( function_exists( 'add_user_to_blog' ) ) {
			add_user_to_blog( get_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->ID, get_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,bobjones@example.com,Bob Jones,contributor
	 *     newuser1,newuser1@example.com,New User,author
	 *     existinguser,existinguser@example.com,Existing User,administrator
	 *
	 * @subcommand import-csv
	 */
	public function import_csv( $args, $assoc_args ) {

		$blog_users = get_users();

		$filename = $args[0];

		if ( 0 === stripos( $filename, 'http://' ) || 0 === 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, 5 ], 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->setFlags( SplFileObject::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' => strftime( '%F %T', time() ),
				'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_login, wp_list_pluck( $blog_users, 'user_login' ), true ) && is_multisite() && $new_user['role'] ) {
					add_user_to_blog( get_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 ( 1 === $reset_user_count ) {
			WP_CLI::success( "Password reset for {$reset_user_count} user." );
		} elseif ( $reset_user_count > 1 ) {
			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_id, null, '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_id, true );
			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', $verb, count( $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.
	 *
	 * ## 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 ) {

		$user      = $this->fetcher->get_check( $args[0] );
		$user_pass = $args[1];

		if ( wp_check_password( $user_pass, $user->data->user_pass, $user->ID ) ) {
			WP_CLI::halt( 0 );
		} else {
			WP_CLI::halt( 1 );
		}
	}

}
<?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;

/**
 * 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 ) <= 0 ) {
			return [];
		}

		$query = $wpdb->prepare(
			"SELECT `post_type`, COUNT(*) AS `count`
			FROM $wpdb->posts
			WHERE `post_type` IN (" . implode( ',', array_fill( 0, count( $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, 0 ),
			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 ) > 0 && in_array( 'count', $fields, true ) ) {
			$counts = $this->get_counts( wp_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', $fields, true ) ) {
			$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\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\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 ) {

			$parent_menu_id = (int) get_post_meta( $arg, '_menu_item_menu_item_parent', true );
			$result         = wp_delete_post( $arg, true );
			if ( ! $result ) {
				WP_CLI::warning( "Couldn't delete menu item {$arg}." );
				$errors++;
			} elseif ( $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( $args, 1, 0 );

		$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 = ( 0 === $menu_item_obj->menu_order ) ? 1 : $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 {

			/**
			 * 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.' );
				}
			}
		}

	}

	protected function get_formatter( &$assoc_args ) {
		return new Formatter( $assoc_args, $this->obj_fields );
	}

}
<?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\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( 1 );
		}

		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, 1 );
		$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, 1 );
		$value = WP_CLI::read_value( $value, $assoc_args );

		$value     = sanitize_option( $key, $value );
		$old_value = sanitize_option( $key, get_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( 1 );
		}

		$key_path = array_map(
			function( $key ) {
				if ( is_numeric( $key ) && ( (string) intval( $key ) === $key ) ) {
					return (int) $key;
				}
					return $key;
			},
			array_slice( $args, 1 )
		);

		$traverser = new RecursiveDataStructureTraverser( $value );

		try {
			$value = $traverser->get( $key_path );
		} catch ( Exception $exception ) {
			die( 1 );
		}

		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, 2 )
		);

		if ( 'delete' === $action ) {
			$patch_value = null;
		} else {
			$stdin_value = EntityUtils::has_stdin()
				? trim( WP_CLI::get_value_from_arg_or_stdin( $args, -1 ) )
				: null;
			$patch_value = ! empty( $stdin_value )
				? WP_CLI::read_value( $stdin_value, $assoc_args )
				: WP_CLI::read_value( array_pop( $key_path ), $assoc_args );
		}

		/* Need to make a copy of $current_value here as it is modified by reference */
		$old_value     = sanitize_option( $key, get_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_escape( esc_sql( $old ) );
		}

		return $old;
	}
}
<?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\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

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 admin@example.com --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( $args, 1, null );
		$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 admin@example.com --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_sessions( WP_Session_Tokens $manager ) {
		// Make the private session data accessible to WP-CLI
		$get_sessions = new ReflectionMethod( $manager, 'get_sessions' );
		$get_sessions->setAccessible( true );
		$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_session( WP_Session_Tokens $manager, $token ) {
		$update_session = new ReflectionMethod( $manager, 'update_session' );
		$update_session->setAccessible( true );
		return $update_session->invoke( $manager, $token, null );
	}

	private function get_formatter( &$assoc_args ) {
		return new Formatter( $assoc_args, $this->fields );
	}

}
<?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( $params, true );
			}
		);
	}

	/**
	 * 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.
	 */
	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( $params, true );
			}
		);
	}

	/**
	 * 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/).
	 *
	 * ## 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     |
	 *     +----+--------------+-------------+---------------------+-------------+
	 *
	 * @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( $args, true );
			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 - 1 ) {
						echo ' ';
					}
				}
			}

			if ( 'progress' === $format ) {
				$notify->tick();
			}
		}
		if ( 'progress' === $format ) {
			$notify->finish();
		}
	}

	private function maybe_make_child() {
		// 50% chance of making child post.
		return ( wp_rand( 1, 2 ) === 1 );
	}

	private function maybe_reset_depth() {
		// 10% chance of reseting to root depth,
		return ( wp_rand( 1, 10 ) === 7 );
	}

	/**
	 * 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 1
	 *     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( 1 );
		}
	}
}
<?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\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'] > 0 && ! 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_by( Utils\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_by( Utils\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_invalidation( true );
		$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_by( Utils\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_type, true ) ) {
				$term_taxonomy_id = wp_set_object_terms( $post_id, $id['term_id'], $destination_taxonomy, true );

				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_rand( 1, 2 ) === 1 );
	}

	private function maybe_reset_depth() {
		// 10% chance of reseting to root depth.
		return ( wp_rand( 1, 10 ) === 7 );
	}

	private function get_formatter( &$assoc_args ) {
		return new Formatter( $assoc_args, $this->fields, 'term' );
	}
}
<?php

namespace WP_CLI\Fetchers;

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

	/**
	 * @var string $msg Error message to use when invalid data is provided
	 */
	protected $msg = 'Could not find the comment with ID %d.';

	/**
	 * Get a comment object by ID
	 *
	 * @param int $arg
	 * @return object|false
	 */
	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 post based on one of its attributes.
 */
class Post extends Base {

	/**
	 * @var string $msg Error message to use when invalid data is provided
	 */
	protected $msg = 'Could not find the post with ID %d.';

	/**
	 * Get a post object by ID
	 *
	 * @param int $arg
	 * @return WP_Post|false
	 */
	public function get( $arg ) {
		return get_post( $arg );
	}
}

<?php

namespace WP_CLI\Fetchers;

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

	/**
	 * @var string $msg Error message to use when invalid data is provided
	 */
	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 int     $site_id
	 * @return bool|array False if no site found with given id, array otherwise
	 */
	private function get_site( $site_id ) {
		global $wpdb;

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

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

		return false;
	}
}
<?php

namespace WP_CLI\Fetchers;

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

	/**
	 * @var string $msg Error message to use when invalid data is provided
	 */
	protected $msg = "Invalid user ID, email or login: '%s'";

	/**
	 * Get a user object by one of its identifying attributes
	 *
	 * @param mixed $id_email_or_login
	 * @return WP_User|false
	 */
	public function get( $id_email_or_login ) {

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

		return $user;
	}
}

<?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, $keys, true ) ) {
				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_key, true );

		if ( '' === $value ) {
			die( 1 );
		}

		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, 2 );
		$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, 2 );
		$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_key, true ), $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, 2 )
		);

		$value = $this->get_metadata( $object_id, $meta_key, true );

		$traverser = new RecursiveDataStructureTraverser( $value );

		try {
			$value = $traverser->get( $key_path );
		} catch ( Exception $exception ) {
			die( 1 );
		}

		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, 3 )
		);

		if ( 'delete' === $action ) {
			$patch_value = null;
		} else {
			$stdin_value = EntityUtils::has_stdin()
				? trim( WP_CLI::get_value_from_arg_or_stdin( $args, -1 ) )
				: null;
			$patch_value = ! empty( $stdin_value )
				? WP_CLI::read_value( $stdin_value, $assoc_args )
				: WP_CLI::read_value( array_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_key, true ), $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_value, false );
	}

	/**
	 * 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

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 ], $taxonomy, true );

				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 ) > 1 ? '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, $taxonomy, true );

		$label = count( $terms ) > 1 ? '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, $taxonomy, false );

		$label = count( $terms ) > 1 ? '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_names, true ) ) {
			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 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_counting( true );
		}

		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_counting( false );
		}

		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_counting( true );
		}

		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_counting( false );
		}

		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 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, 0 );
		fclose( $handle );

		return 1 === $streams;
	}
}
<?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

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;

/**
 * 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;
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 - 1 ) {
					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::error( sprintf( $failure, "comment {$comment_id}" ) );
		}
		WP_CLI::success( sprintf( $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, $status, true );

		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( $args, 0, 0 );

		$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::line( str_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\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;

/**
 * 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_compare( 3.7, '<' ) ) {
			// remove description for wp <= 3.7
			$this->fields = array_values( array_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 ) <= 0 ) {
			return [];
		}

		$query = $wpdb->prepare(
			"SELECT `taxonomy`, COUNT(*) AS `count`
			FROM $wpdb->term_taxonomy
			WHERE `taxonomy` IN (" . implode( ',', array_fill( 0, count( $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, 0 ),
			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 );

		if ( isset( $assoc_args['object_type'] ) ) {
			$assoc_args['object_type'] = array( $assoc_args['object_type'] );
		}

		$fields     = $formatter->fields;
		$taxonomies = get_taxonomies( $assoc_args, 'objects' );
		$counts     = [];

		if ( count( $taxonomies ) > 0 && in_array( 'count', $fields, true ) ) {
			$counts = $this->get_counts( wp_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', $fields, true ) ) {
			$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\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

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

/**
 * 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_keys( wp_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_translations, true ) ? '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, $available, true ) ) {
				\WP_CLI::halt( 1 );
			}
		}

		\WP_CLI::halt( 0 );
	}

	/**
	 * 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 ) < 2 ) {
			\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, $available, true ) ) {
				\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, $available, true ) ) {
					\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, $available, true ) ) {
				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_keys( wp_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

/**
 * 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_translations, true ) ? '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, $available, true ) ) {
				\WP_CLI::halt( 1 );
			}
		}

		\WP_CLI::halt( 0 );
	}

	/**
	 * 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 ) < 2 ) {
			\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, $available, true ) ) {
				\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, $available, true ) ) {
					\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, $available, true ) ) {
				\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

/**
 * 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'], $available, true ) ) {
					$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, $available, true ) ) {
			\WP_CLI::halt( 0 );
		} else {
			\WP_CLI::halt( 1 );
		}
	}

	/**
	 * 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 > 1 && in_array( true, $assoc_args, true ) ) {
			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, $available, true ) ) {
				\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, $available, true ) ) {
				WP_CLI::error( 'Language not installed.' );
			}

			$dir   = 'core' === $this->obj_type ? '' : "/$this->obj_type";
			$files = scandir( WP_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->delete( WP_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, $available, true ) ) {
			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

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 = count( array_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 > 0 ) {
			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 );
				$args['version'] = $plugin_data['Version'];
			} elseif ( 'themes' === $this->obj_type ) {
				$theme_data      = wp_get_theme( $slug );
				$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.
	 * @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 ) {

		/**
		 * 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 \WP_Upgrader  $this    The WP_Upgrader instance.
		 *
		 * @phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Using WP native hook.
		 */
		$reply = apply_filters( 'upgrader_pre_download', false, $package, $this );
		// 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( $package, PATHINFO_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( $package, 600 ); // 10 minutes ought to be enough for everybody.

		if ( is_wp_error( $temp ) ) {
			return $temp;
		}

		$cache->import( $cache_key, $temp );

		return $temp;
	}
}
<?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, $available, true ) ) {
			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

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 <dwarf@girsbrain.org>
 * @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->_format, compact('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 <dwarf@girsbrain.org>
 * @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->_format, compact('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 <dwarf@girsbrain.org>
 * @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 <dwarf@girsbrain.org>
 * @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->_raw, 2);
		} else if ($this->isShort) {
			$this->_argument = substr($this->_raw, 1);
		} 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 (0 == strncmp($this->_raw, '--', 2));
	}

	/**
	 * Returns true if the string matches the pattern for short arguments.
	 *
	 * @return bool
	 */
	public function isShort() {
		return !$this->isLong && (0 == 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 <dwarf@girsbrain.org>
 * @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) > 1 ? '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 <dwarf@girsbrain.org>
 * @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($help, "\n\n");
	}

	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 - 4 - $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($help, "\n");
	}

	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 <dwarf@girsbrain.org>
 * @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 ( 0 === $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($shellPipe, FILTER_VALIDATE_BOOLEAN);
		} 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_OS, 0, 3)) === '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    Ryan Sullivan <rsullivan@connectstudios.com>
 * @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 <dwarf@girsbrain.org>
 * @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__ .'] ' . $message, E_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 <dwarf@girsbrain.org>
 * @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->_formatMessage, compact('msg', 'percent'));

		$estimated = $this->formatTime($this->estimated());
		$elapsed   = str_pad($this->formatTime($this->elapsed()), strlen($estimated));
		$timing    = Streams::render($this->_formatTiming, compact('elapsed', 'estimated'));

		$size = Shell::columns();
		$size -= strlen($msg . $timing);
		if ( $size < 0 ) {
			$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->_format, compact('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 <dwarf@girsbrain.org>
 * @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 = explode( PHP_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 ' . $column, E_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() {
		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 ) == 1 || ( 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::$out, self::_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::out( str_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_merge( func_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_merge( func_get_args(), array( '' ) );
		$args[0] .= "\n";
		fwrite( static::$err, self::_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::hide( false );
			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::input( null, $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( $default, strtoupper( $default ), strtolower( $choice ) );
		// Seperate each choice with a forward-slash
		$choices = trim( join( '/', preg_split( '//', $choice ) ), '/' );

		while( true ) {
			$line = self::prompt( sprintf( '%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::$out, sprintf( '%s: ', $title ) );
			$line = self::input();

			if( is_numeric( $line ) ) {
				$line--;
				if( isset( $map[$line] ) ) {
					return array_search( $map[$line], $items );
				}

				if( $line < 0 || $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 <dwarf@girsbrain.org>
 * @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 & 1 ) ) {
			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 & 2 ) ) {
			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( $str, null, true /*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_all( get_unicode_regexs( 'm' ), $str, $dummy /*needed for PHP 5.3*/ );
		}
		if ( ! $test_safe_strlen || ( $test_safe_strlen & 4 ) ) {
			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 < 0 || ( $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 < 0 && -$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 & 1 ) ) {
			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 + 1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ) ) && ! preg_last_error() ) {
			$try = implode( '', array_slice( $try, $start, $length ) );
			if ( ! $test_safe_substr || ( $test_safe_substr & 2 ) ) {
				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( $str, null, true /*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 & 4 ) ) {
			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( $str, 0, max( (int) ( $length / 2 ), 1 ), '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 + 1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
			$cnt = min( count( $chars ), $length );
			$width = $length;

			for ( $length = 0; $length < $cnt && $width > 0; $length++ ) {
				$width -= preg_match( $eaw_regex, $chars[ $length ] ) ? 2 : 1;
			}
			// Round down to a minimum of 1.
			if ( $width < 0 && $length > 1 ) {
				$length--;
			}
			return join( '', array_slice( $chars, 0, $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 & 1 ) ) {
			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 & 2 ) ) {
			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( $string, null, true /*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 & 4 ) ) {
			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_compare( INTL_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 = substr( PCRE_VERSION, 0, strspn( PCRE_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 <dwarf@girsbrain.org>
 * @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 (0 == 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    James Logsdon <dwarf@girsbrain.org>
 * @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, 2 /*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($key, self::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 = 0 ) {
		if ( ! ( $keep & 1 ) ) {
			// 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 & 2 ) ) {
			// 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_strlen( self::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 ? 1 /*keep_tokens*/ : 0 ) : $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    Ryan Sullivan <rsullivan@connectstudios.com>
 * @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 <rsullivan@connectstudios.com>
 * @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 <rsullivan@connectstudios.com>
 * @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
// 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
/**
 * 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 <dwarf@girsbrain.org>
 * @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 % 60, 2, 0, STR_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
/**
 * 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 <dwarf@girsbrain.org>
 * @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 - 1 ) * 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 ) > 0 ) {
			$extra_rows = array_fill( 0, count( $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( $value, null, true /*strict*/ ) : false;
				$original_val_width = Colors::width( $value, self::isPreColorized( $col ), $encoding );
				if ( $col_width && $original_val_width > $col_width ) {
					$row[ $col ] = \cli\safe_substr( $value, 0, $col_width, true /*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( $value, 0, $col_width, true /*is_width*/, $encoding );
						$val_width = Colors::width( $extra_value, self::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'), $row, array_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_values, array_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    James Logsdon <dwarf@girsbrain.org>
 * @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 <dwarf@girsbrain.org>
 * @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 <dwarf@girsbrain.org>
 * @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
/**
 * 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(': ', $header, 2);
            \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->_prompt, false, null);

            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

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;
}

WP_CLI::add_command( 'plugin', 'Plugin_Command' );
WP_CLI::add_command( 'theme', 'Theme_Command' );
WP_CLI::add_command( 'theme mod', 'Theme_Mod_Command' );
<?php

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 {

	protected $item_type         = 'plugin';
	protected $upgrade_refresh   = 'wp_update_plugins';
	protected $upgrade_transient = 'update_plugins';

	protected $obj_fields = array(
		'name',
		'status',
		'update',
		'version',
	);

	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->file, false, false );
			}

			activate_plugin( $plugin->file, '', $network_wide );

			$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', $verb, count( $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->file, false, $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', $verb, count( $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 = untrailingslashit( WP_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::log( sprintf( 'Installing %s (%s)', html_entity_decode( $api->name, ENT_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.
	 *
	 * ## 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' => $update_info['new_version'],
				'update_package' => $update_info['package'],
				'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 ) <= 1 ) {
				continue;
			}
			foreach ( $files as $file ) {
				$items[ $file ]['name'] = str_replace( '.' . pathinfo( $file, PATHINFO_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
	 *
	 * ## 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_dir( WP_PLUGIN_DIR ) ) {
			wp_mkdir_p( WP_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_data( WP_PLUGIN_DIR . '/' . $file, false, false );

		$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 of --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( 0 );
		} else {
			WP_CLI::halt( 1 );
		}
	}

	/**
	 * Checks if a given plugin is active.
	 *
	 * Returns exit code 0 when active, 1 when not active.
	 *
	 * ## OPTIONS
	 *
	 * <plugin>
	 * : The plugin to check.
	 *
	 * ## 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( 1 );
		}

		$this->check_active( $plugin->file, $network_wide ) ? WP_CLI::halt( 0 ) : WP_CLI::halt( 1 );
	}

	/**
	 * 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 of --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
	 * ---
	 *
	 * ## 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_path( dirname( __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_basename( dirname( $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_join( WP_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 ) );
	}

	/**
	 * 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() );
	}

	/**
	 * 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.
	 * @return array Same as $args if not all, otherwise all slugs.
	 */
	private function check_optional_args_and_all( $args, $all, $verb = 'install' ) {
		if ( $all ) {
			$args = array_map(
				function( $file ) {
						return Utils\get_plugin_name( $file );
				},
				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;
	}
}
<?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_stylesheets, true ) ) {
			$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, $themes, true ) ) {
			return $target;
		}

		$suggestion = Utils\get_suggestion( $target, $themes );

		if ( '' !== $suggestion ) {
			return $suggestion;
		}

		return false;
	}
}

<?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;

/**
 * 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;

use Composer\Semver\Comparator;
use WP_CLI;
use WP_CLI\Utils;

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 1 * MINUTE_IN_SECONDS;
			},
			999
		);

		$this->fetcher = new WP_CLI\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:', $n, Utils\pluralize( $this->item_type, absint( $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::line( \WP_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_unique( wp_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_array( true, wp_list_pluck( $items, 'update' ), true ) ) {
			$legend_line[] = '%yU = Update Available%n';
		}

		\WP_CLI::line( 'Legend: ' . \WP_CLI::colorize( implode( ', ', $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( $slug, PATHINFO_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( $slug, PHP_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( $slug, PHP_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_dir, strrpos( $source, $source_dir ), strlen( $source_dir ) );

						if ( $GLOBALS['wp_filesystem']->move( $source, $new_path ) ) {
							WP_CLI::log( sprintf( "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', $filter, 10, 3 );
				}

				if ( $file_upgrader->install( $slug ) ) {
					$slug   = $file_upgrader->result['destination_name'];
					$result = true;
					if ( $filter ) {
						remove_filter( 'upgrader_source_selection', $filter, 10 );
					}
					$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 ) > 0 ) {
				$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 ) {
		$upgrader_class = $this->get_upgrader_class( Utils\get_flag_value( $assoc_args, 'force' ) );
		return Utils\get_upgrader( $upgrader_class );
	}

	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 \WP_CLI\Loggers\Quiet();
			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 ] );

		if ( 'plugin' === $this->item_type
			&& ( Utils\get_flag_value( $assoc_args, 'minor' )
			|| Utils\get_flag_value( $assoc_args, 'patch' ) ) ) {
			$type            = Utils\get_flag_value( $assoc_args, 'minor' ) ? 'minor' : 'patch';
			$items_to_update = self::get_minor_or_patch_updates( $items_to_update, $type );
		}

		$exclude = WP_CLI\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 ) ) {
				WP_CLI\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_filter, 999 );
			$result = $upgrader->bulk_upgrade( wp_list_pluck( $items_to_update, 'update_id' ) );
			remove_filter( 'site_transient_' . $this->upgrade_transient, $transient_filter, 999 );
		}

		// Let the user know the results.
		$num_to_update = count( $items_to_update );
		$num_updated   = count( array_filter( $result ) );

		if ( $num_to_update > 0 ) {
			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'] ] ? 'Updated' : 'Error',
					];
					if ( null === $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
		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 ( isset( $assoc_args[ $field ] )
					&& $assoc_args[ $field ] !== $item[ $field ] ) {
					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'
	 * @return array
	 */
	private function get_minor_or_patch_updates( $items, $type ) {
		foreach ( $items as $i => $item ) {
			$wporg_url = sprintf( 'https://api.wordpress.org/plugins/info/1.0/%s.json', $item['name'] );
			$response  = Utils\http_request( 'GET', $wporg_url );
			// Must not be hosted on wp.org
			if ( 20 !== absint( substr( $response->status_code, 0, 2 ) ) ) {
				unset( $items[ $i ] );
				continue;
			}
			$data = json_decode( $response->body, true );
			// 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::success( sprintf( '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 ( 0 !== 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

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 \WP_CLI\CommandWithUpgrade {

	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();
	}

	protected function get_status( $theme ) {
		if ( $this->is_active_theme( $theme ) ) {
			return 'active';
		} elseif ( $theme->get_stylesheet_directory() === get_template_directory() ) {
			return 'parent';
		} else {
			return 'inactive';
		}
	}

	/**
	 * 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." );
		}
	}

	private function is_active_theme( $theme ) {
		return $theme->get_stylesheet_directory() === get_stylesheet_directory();
	}

	/**
	 * 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::log( sprintf( 'Installing %s (%s)', html_entity_decode( $api->name, ENT_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() {
		$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' => $update_info['new_version'],
				'update_package' => $update_info['package'],
				'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'] = static::INVALID_VERSION_MESSAGE;
			}

			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;
	}

	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.
	 *
	 * ## 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.
	 *
	 * ## 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'] ) ) {
			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( 0 );
		} else {
			WP_CLI::halt( 1 );
		}
	}

	/**
	 * 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( 1 );
		}

		$this->is_active_theme( $theme ) ? WP_CLI::halt( 0 ) : WP_CLI::halt( 1 );
	}

	/**
	 * 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 ) {
				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
	 * ---
	 *
	 * ## 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 );
	}

	/**
	 * 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.
	 * @return array Same as $args if not all, otherwise all slugs.
	 */
	private function check_optional_args_and_all( $args, $all ) {
		if ( $all ) {
			$args = array_map(
				function( $item ) {
						return Utils\get_theme_name( $item );
				},
				array_keys( $this->get_all_items() )
			);
		}

		if ( empty( $args ) ) {
			if ( ! $all ) {
				WP_CLI::error( 'Please specify one or more themes, or use --all.' );
			}
			WP_CLI::success( 'No themes installed.' ); // Don't error if --all given for BC.
		}

		return $args;
	}

	/**
	 * Gets the template path based on installation type.
	 */
	private static function get_template_path( $template ) {
		$command_root  = Utils\phar_safe_path( dirname( __DIR__ ) );
		$template_path = "{$command_root}/templates/{$template}";

		if ( ! file_exists( $template_path ) ) {
			WP_CLI::error( "Couldn't find {$template}" );
		}

		return $template_path;
	}

	/**
	 * 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, '>' );

	}
}
<?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, $args, true ) ) {
				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 = ( 1 === $count ) ? '%d mod removed.' : '%d mods removed.';
		WP_CLI::success( sprintf( $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

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

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 === \WP_CLI\Utils\get_flag_value( $assoc_args, 'skip-wordpress' ) ) {
			WP_CLI::get_runner()->load_wordpress();
		}

		eval( $args[0] );
	}
}
<?php

class EvalFile_Command extends WP_CLI_Command {

	/**
	 * 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 === \WP_CLI\Utils\get_flag_value( $assoc_args, 'skip-wordpress' ) ) {
			WP_CLI::get_runner()->load_wordpress();
		}

		self::execute_eval( $file, $args );
	}

	private static function execute_eval( $file, $args ) {
		if ( '-' === $file ) {
			eval( '?>' . file_get_contents( 'php://stdin' ) );
		} else {
			$file_contents = file_get_contents( $file );

			// Check for and remove she-bang.
			if ( 0 === strpos( $file_contents, '#!' ) ) {
				$file_contents = preg_replace( '/^(#!.*)$/im', '', $file_contents );
			}

			$file = realpath( $file );
			$dir  = dirname( $file );

			// Replace __FILE__ and __DIR__ constants with value of $file or $dir.
			// 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/2/
			$file_contents = preg_replace_callback(
				'/(?>\'[^\']*?\')|(?>"[^"]*?")|(?<file>__FILE__)|(?<dir>__DIR__)/m',
				function ( $matches ) use ( $file, $dir ) {
					if ( ! empty( $matches['file'] ) ) {
						return "'{$file}'";
					}

					if ( ! empty( $matches['dir'] ) ) {
						return "'{$dir}'";
					}

					return $matches[0];
				},
				$file_contents
			);

			eval( '?>' . $file_contents );
		}
	}
}

<?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;

/**
 * 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 the next scheduled cron event
 *     $ 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>]
	 * : Associative args for the event.
	 *
	 * ## 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 associative arguments
	 *     $ wp cron event schedule cron_test '+1 hour' --foo=1 --bar=2
	 *     Success: Scheduled event with hook 'cron_test' for 2016-05-31 11:21:35 GMT.
	 */
	public function schedule( $args, $assoc_args ) {

		$hook       = $args[0];
		$next_run   = Utils\get_flag_value( $args, 1, 'now' );
		$recurrence = Utils\get_flag_value( $args, 2, false );

		if ( empty( $next_run ) ) {
			$timestamp = time();
		} elseif ( is_numeric( $next_run ) ) {
			$timestamp = absint( $next_run );
		} else {
			$timestamp = strtotime( $next_run );
		}

		if ( ! $timestamp ) {
			WP_CLI::error( sprintf( "'%s' is not a valid datetime.", $next_run ) );
		}

		if ( ! empty( $recurrence ) ) {

			$schedules = wp_get_schedules();

			if ( ! isset( $schedules[ $recurrence ] ) ) {
				WP_CLI::error( sprintf( "'%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::success( sprintf( "Scheduled event with hook '%s' for %s GMT.", $hook, date( self::$time_format, $timestamp ) ) );
		} 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, $hooks, true ) ) {
				WP_CLI::error( sprintf( "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, $args, true ) ) {
					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, $args, true ) ) {
					$due_events[] = $event;
				}
			}
			$events = $due_events;
		}

		$executed = 0;
		foreach ( $events as $event ) {
			$start  = microtime( true );
			$result = self::run_event( $event );
			$total  = round( microtime( true ) - $start, 3 );
			$executed++;
			WP_CLI::log( sprintf( "Executed the cron event '%s' in %ss.", $event->hook, $total ) );
		}

		$message = ( 1 === $executed ) ? 'Executed a total of %d cron event.' : 'Executed a total of %d cron events.';
		WP_CLI::success( sprintf( $message, $executed ) );
	}

	/**
	 * Executes an event immediately.
	 *
	 * @param stdClass $event The event
	 * @return bool Whether the event was successfully executed or not.
	 */
	protected static function run_event( stdClass $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 the next scheduled cron event for the given hook.
	 *
	 * ## OPTIONS
	 *
	 * <hook>
	 * : The hook name.
	 *
	 * ## EXAMPLES
	 *
	 *     # Delete the next scheduled cron event
	 *     $ 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::warning( sprintf( "Failed to the delete the cron event '%s'.", $hook ) );
				}
			}
		}

		if ( $deleted ) {
			$message = ( 1 === $deleted ) ? "Deleted the cron event '%2\$s'." : "Deleted %1\$d instances of the cron event '%2\$s'.";
			WP_CLI::success( sprintf( $message, $deleted, $hook ) );
		} else {
			WP_CLI::error( sprintf( "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_event( stdClass $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_event( stdClass $event ) {

		$event->next_run          = get_date_from_gmt( date( 'Y-m-d H:i:s', $event->time ), self::$time_format );
		$event->next_run_gmt      = date( self::$time_format, $event->time );
		$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 <= 0 ) {
			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( 0 ) !== $count ) {
				break;
			}
		}

		// set output var
		$output = sprintf( '%d %s', $count, Utils\pluralize( $name, absint( $count ) ) );

		// step two: the second chunk
		if ( $i + 1 < $j ) {
			$seconds2 = $chunks[ $i + 1 ][0];
			$name2    = $chunks[ $i + 1 ][1];

			$count2 = floor( ( $since - ( $seconds * $count ) ) / $seconds2 );
			if ( floatval( 0 ) !== $count2 ) {
				// add to output var
				$output .= ' ' . sprintf( '%d %s', $count2, Utils\pluralize( $name2, absint( $count2 ) ) );
			}
		}

		return $output;
	}

	private function get_formatter( &$assoc_args ) {
		return new \WP_CLI\Formatter( $assoc_args, $this->fields, 'event' );
	}

}
<?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::error( sprintf( '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::warning( sprintf( 'WP-Cron spawn succeeded but 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_compare( 4.0, '<' );
		$doing_wp_cron = sprintf( '%.22F', microtime( true ) );

		$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

/**
 * 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', $schedules, array_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

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

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 {

	/**
	 * 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.
	 *
	 * ## EXAMPLES
	 *
	 *     $ wp db create
	 *     Success: Database created.
	 */
	public function create( $_, $assoc_args ) {

		self::run_query( self::get_create_query(), self::get_dbuser_dbpass_args( $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.
	 *
	 * ## 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 );

		self::run_query( sprintf( 'DROP DATABASE `%s`', DB_NAME ), self::get_dbuser_dbpass_args( $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.
	 *
	 * ## 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 );

		$mysql_args = self::get_dbuser_dbpass_args( $assoc_args );

		self::run_query( sprintf( 'DROP DATABASE IF EXISTS `%s`', DB_NAME ), $mysql_args );
		self::run_query( self::get_create_query(), $mysql_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.
	 *
	 * ## 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
		);

		$mysql_args = self::get_dbuser_dbpass_args( $assoc_args );

		$tables = Utils\wp_get_table_names(
			array(),
			array( 'all-tables-with-prefix' => true )
		);

		foreach ( $tables as $table ) {
			self::run_query(
				sprintf(
					'DROP TABLE IF EXISTS `%s`.`%s`',
					DB_NAME,
					$table
				),
				$mysql_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).
	 *
	 * ## EXAMPLES
	 *
	 *     $ wp db check
	 *     Success: Database checked.
	 */
	public function check( $_, $assoc_args ) {
		$assoc_args['check'] = true;
		self::run(
			Utils\esc_cmd( '/usr/bin/env mysqlcheck --no-defaults %s', DB_NAME ),
			$assoc_args
		);

		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).
	 *
	 * ## EXAMPLES
	 *
	 *     $ wp db optimize
	 *     Success: Database optimized.
	 */
	public function optimize( $_, $assoc_args ) {
		$assoc_args['optimize'] = true;
		self::run(
			Utils\esc_cmd( '/usr/bin/env mysqlcheck --no-defaults %s', DB_NAME ),
			$assoc_args
		);

		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).
	 *
	 * ## EXAMPLES
	 *
	 *     $ wp db repair
	 *     Success: Database repaired.
	 */
	public function repair( $_, $assoc_args ) {
		$assoc_args['repair'] = true;
		self::run(
			Utils\esc_cmd( '/usr/bin/env mysqlcheck --no-defaults %s', DB_NAME ),
			$assoc_args
		);

		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).
	 *
	 * ## EXAMPLES
	 *
	 *     # Open MySQL console
	 *     $ wp db cli
	 *     mysql>
	 *
	 * @alias connect
	 */
	public function cli( $args, $assoc_args ) {
		if ( ! isset( $assoc_args['database'] ) ) {
			$assoc_args['database'] = DB_NAME;
		}

		self::run( '/usr/bin/env mysql --no-defaults --no-auto-rehash', $assoc_args );
	}

	/**
	 * 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).
	 *
	 * ## 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 ) {
		$assoc_args['database'] = DB_NAME;

		// The query might come from STDIN.
		if ( ! empty( $args ) ) {
			$assoc_args['execute'] = $args[0];
		}

		self::run( '/usr/bin/env mysql --no-defaults --no-auto-rehash', $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.
	 *
	 * ## 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.
	 *
	 * [--porcelain]
	 * : Output filename for the exported database.
	 *
	 * ## 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        = substr( md5( mt_rand() ), 0, 7 );
			$result_file = sprintf( '%s-%s-%s.sql', DB_NAME, date( '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;
		}

		$support_column_statistics = exec( 'mysqldump --help | grep "column-statistics"' );

		if ( $support_column_statistics ) {
			$command = '/usr/bin/env mysqldump --no-defaults --skip-column-statistics %s';
		} else {
			$command = '/usr/bin/env mysqldump --no-defaults %s';
		}

		$command_esc_args = array( 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[] = trim( DB_NAME . '.' . $table );
			}
		}

		$escaped_command = call_user_func_array( '\WP_CLI\Utils\esc_cmd', array_merge( array( $command ), $command_esc_args ) );

		// Remove parameters not needed for SQL run.
		unset( $assoc_args['porcelain'] );

		self::run( $escaped_command, $assoc_args );

		if ( $porcelain ) {
			WP_CLI::line( $result_file );
		} elseif ( ! $stdout ) {
			WP_CLI::success( sprintf( "Exported to '%s'.", $result_file ) );
		}
	}

	/**
	 * 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.
	 *
	 * ## 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 );
		}

		$mysql_args = array(
			'database' => DB_NAME,
		);
		$mysql_args = array_merge( self::get_dbuser_dbpass_args( $assoc_args ), $mysql_args );

		if ( '-' !== $result_file ) {
			if ( ! is_readable( $result_file ) ) {
				WP_CLI::error( sprintf( '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;';

			$mysql_args['execute'] = sprintf( $query, $result_file );
		} else {
			$result_file = 'STDIN';
		}
		// Check if any mysql option pass.
		$mysql_args = array_merge( $mysql_args, self::get_mysql_args( $assoc_args ) );

		self::run( '/usr/bin/env mysql --no-defaults --no-auto-rehash', $mysql_args );

		WP_CLI::success( sprintf( "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::line( implode( ',', $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.
	 *
	 * [--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   = array();
		$fields = array( '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[] = array(
					'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[] = array(
				'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 = floor( log( $row['Size'] ) / log( 1000 ) );
					$sizes    = array( '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 = pow( 1000, 4 );
						break;

					case 'GB':
						$divisor = pow( 1000, 3 );
						break;

					case 'MB':
						$divisor = pow( 1000, 2 );
						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 ) );

					$rows[ $index ]['Size'] = ceil( $row['Size'] / $divisor ) . ' ' . $size_format_display;
			}
		}

		if ( ! empty( $size_format ) && ! $tables && ! $format && ! $human_readable && true !== $all_tables && true !== $all_tables_with_prefix ) {
			WP_CLI::Line( filter_var( $rows[0]['Size'], FILTER_SANITIZE_NUMBER_INT ) );
		} else {
			// Display the rows.
			$args = array(
				'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 or 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.
	 *
	 * [--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:http://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:http://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 = microtime( true );

		$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( 1 );
				$default_regex_delimiter = true;
			}
		}

		$colors = self::get_colors(
			$assoc_args,
			array(
				'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      = array();

		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 ( 0 === strpos( $wpdb->charset, 'utf8' ) ) {
			$encoding = 'UTF-8';
		}

		$tables = Utils\wp_get_table_names( $args, $assoc_args );

		$start_search_time = microtime( true );

		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, $matches, PREG_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         = array();
							$col_encoding = $encoding;
							if ( ! $col_encoding && ( $before_context || $after_context ) && function_exists( 'mb_detect_encoding' ) ) {
								$col_encoding = mb_detect_encoding( $col_val, null, true /*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_substr( substr( $col_val, $last_offset, $offset - $last_offset ), -$before_context, null /*length*/, false /*is_width*/, $col_encoding );
								}
								if ( $after_context ) {
									$end_offset = $offset + strlen( $match );
									$after      = \cli\safe_substr( substr( $col_val, $end_offset ), 0, $after_context, false /*is_width*/, $col_encoding );
									// To lessen context duplication in output, shorten the after context if it overlaps with the next match.
									if ( $i + 1 < $match_cnt && $end_offset + strlen( $after ) > $matches[0][ $i + 1 ][1] ) {
										$after           = substr( $after, 0, $matches[0][ $i + 1 ][1] - $end_offset );
										$after_shortened = true;
										// On the next iteration, will append with no before context.
									}
								}
								if ( $append_next ) {
									$cnt               = count( $bits );
									$bits[ $cnt - 1 ] .= $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     = 1 === $match_count ? 'match' : 'matches';
			$table_str     = 1 === $table_count ? 'table' : 'tables';
			$column_str    = 1 === $column_count ? 'column' : 'columns';
			$row_str       = 1 === $row_count ? 'row' : 'rows';
			$skipped_str   = 1 === $skipped_count ? 'table skipped' : 'tables skipped';
			if ( 0 !== $skipped_count ) {
				$skipped_str .= ': ' . implode( ', ', $skipped );
			}
			$end_time    = microtime( true );
			$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( array( $args[0] ), array() );

		$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 = array( 'Field', 'Type', 'Null', 'Key', 'Default', 'Extra' );
		$formatter_args   = array(
			'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_ident( DB_NAME ) );
		if ( defined( 'DB_CHARSET' ) && constant( 'DB_CHARSET' ) ) {
			$create_query .= sprintf( ' DEFAULT CHARSET %s', self::esc_sql_ident( DB_CHARSET ) );
		}
		if ( defined( 'DB_COLLATE' ) && constant( 'DB_COLLATE' ) ) {
			$create_query .= sprintf( ' DEFAULT COLLATE %s', self::esc_sql_ident( DB_COLLATE ) );
		}
		return $create_query;
	}

	private static function run_query( $query, $assoc_args = array() ) {
		self::run( '/usr/bin/env mysql --no-defaults --no-auto-rehash', array_merge( $assoc_args, array( 'execute' => $query ) ) );
	}

	private static function run( $cmd, $assoc_args = array(), $descriptors = null ) {
		$required = array(
			'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( $assoc_args, $required );
		Utils\run_mysql_command( $cmd, $final_args, $descriptors );
	}

	/**
	 * 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 = array();
		$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    = array();
		$text_columns    = array();
		$all_columns     = array();
		$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 array( $primary_keys, $text_columns, $all_columns );
	}

	/**
	 * Determines whether a column is considered text or not.
	 *
	 * @param string Column type.
	 * @bool True if text column, false otherwise.
	 */
	private static function is_text_col( $type ) {
		foreach ( array( '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 = 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, 1 );
				},
				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 ] ? array( WP_CLI::colorize( $colors[ $color_col ] ), $color_reset ) : array( '', '' );
		}

		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 = array();

		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_options, true ) && ! empty( $mysql_option_value ) ) {
				$mysql_args[ $mysql_option_key ] = $mysql_option_value;
			}
		}

		return $mysql_args;
	}
}
<?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;

/**
 * Generates and reads the wp-config.php file.
 */
class Config_Command extends WP_CLI_Command {

	/**
	 * Retrieve the initiale 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.
	 *
	 * ## 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 ( ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'force' ) && Utils\locate_wp_config() ) {
			WP_CLI::error( "The 'wp-config.php' file already exists." );
		}

		$defaults   = [
			'dbhost'    => 'localhost',
			'dbpass'    => '',
			'dbprefix'  => 'wp_',
			'dbcharset' => 'utf8',
			'dbcollate' => '',
			'locale'    => self::get_initial_locale(),
		];
		$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, 'extra-php' ) === true ) {
			$assoc_args['extra-php'] = file_get_contents( 'php://stdin' );
		}

		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::read_(
					'https://api.wordpress.org/secret-key/1.1/salt/'
				);
			}
		}

		if ( Utils\wp_version_compare( '4.0', '<' ) ) {
			$assoc_args['add-wplang'] = true;
		} else {
			$assoc_args['add-wplang'] = false;
		}

		$command_root = Utils\phar_safe_path( dirname( __DIR__ ) );
		$out          = Utils\mustache_render( "{$command_root}/templates/wp-config.mustache", $assoc_args );

		$bytes_written = file_put_contents( ABSPATH . 'wp-config.php', $out );
		if ( ! $bytes_written ) {
			WP_CLI::error( "Could not create new 'wp-config.php' file." );
		} else {
			WP_CLI::success( "Generated 'wp-config.php' file." );
		}
	}

	/**
	 * Launches system editor to edit the wp-config.php file.
	 *
	 * ## 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() {
		$config_path = $this->get_config_path();
		$contents    = file_get_contents( $config_path );
		$r           = Utils\launch_editor_for_input( $contents, 'wp-config.php', 'php' );
		if ( false === $r ) {
			WP_CLI::warning( 'No changes made to wp-config.php.', 'Aborted' );
		} else {
			file_put_contents( $config_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() );
	}

	/**
	 * 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.
	 * ---
	 * default: table
	 * options:
	 *   - table
	 *   - csv
	 *   - json
	 *   - yaml
	 * ---
	 *
	 * [--strict]
	 * : Enforce strict matching when a filter is provided.
	 *
	 * ## 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();

		$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 = array(
			'name',
			'value',
			'type',
		);

		$defaults = array(
			'fields' => implode( ',', $default_fields ),
			'format' => 'table',
		);

		$assoc_args = array_merge( $defaults, $assoc_args );

		$values = self::get_wp_config_vars();

		if ( ! empty( $args ) ) {
			$values = $this->filter_values( $values, $args, $strict );
		}

		if ( empty( $values ) ) {
			WP_CLI::error( "No matching entries found in 'wp-config.php'." );
		}

		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.
	 * ---
	 * default: var_export
	 * options:
	 *   - var_export
	 *   - json
	 *   - yaml
	 * ---
	 *
	 * ## 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();

		list( $name ) = $args;
		$type         = Utils\get_flag_value( $assoc_args, 'type' );

		$value = $this->return_value( $name, $type, self::get_wp_config_vars() );
		WP_CLI::print_value( $value, $assoc_args );
	}

	/**
	 * Get the array of wp-config.php constants and variables.
	 *
	 * @return array
	 */
	private static function get_wp_config_vars() {
		$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_vars      = self::get_wp_config_diff( get_defined_vars(), $wp_cli_original_defined_vars, 'variable', array( 'wp_cli_original_defined_vars' ) );
		$wp_config_constants = self::get_wp_config_diff( get_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_diff( get_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!".
	 *
	 * [--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
	 * ---
	 *
	 * ## 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();

		list( $name, $value ) = $args;
		$type                 = Utils\get_flag_value( $assoc_args, 'type' );

		$options = array();

		$option_flags = array(
			'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.php' 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.php' 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.php' file." );
						}
						$adding = true;
					}
			}

			$config_transformer->update( $type, $name, $value, $options );

		} catch ( Exception $exception ) {
			WP_CLI::error( "Could not process the 'wp-config.php' transformation.\nReason: {$exception->getMessage()}" );
		}

		$raw = $options['raw'] ? 'raw ' : '';
		if ( $adding ) {
			$message = "Added the {$type} '{$name}' to the 'wp-config.php' file with the {$raw}value '{$value}'.";
		} else {
			$message = "Updated the {$type} '{$name}' in the 'wp-config.php' 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
	 * ---
	 *
	 * ## 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();

		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.php' 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.php' 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.php' file." );
					}
			}

			$config_transformer->remove( $type, $name );

		} catch ( Exception $exception ) {
			WP_CLI::error( "Could not process the 'wp-config.php' transformation.\nReason: {$exception->getMessage()}" );
		}

		WP_CLI::success( "Deleted the {$type} '{$name}' from the 'wp-config.php' 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
	 * ---
	 *
	 * ## 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();

		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.php' file. Use --type=<type> to disambiguate." );
					}
					if ( ! $has_constant && ! $has_variable ) {
						WP_CLI::halt( 1 );
					} else {
						WP_CLI::halt( 0 );
					}
					break;
				case 'constant':
				case 'variable':
					if ( ! $config_transformer->exists( $type, $name ) ) {
						WP_CLI::halt( 1 );
					}
					WP_CLI::halt( 0 );
			}
		} catch ( Exception $exception ) {
			WP_CLI::error( "Could not process the 'wp-config.php' transformation.\nReason: {$exception->getMessage()}" );
		}
	}

	/**
	 * Refreshes the salts defined in the wp-config.php file.
	 *
	 * ## OPTIONS
	 *
	 * ## EXAMPLES
	 *
	 *     # Get new salts for your wp-config.php file
	 *     $ wp config shuffle-salts
	 *     Success: Shuffled the salt keys.
	 *
	 * @subcommand shuffle-salts
	 * @when before_wp_load
	 */
	public function shuffle_salts( $args, $assoc_args ) {

		$constant_list = array(
			'AUTH_KEY',
			'SECURE_AUTH_KEY',
			'LOGGED_IN_KEY',
			'NONCE_KEY',
			'AUTH_SALT',
			'SECURE_AUTH_SALT',
			'LOGGED_IN_SALT',
			'NONCE_SALT',
		);

		try {
			foreach ( $constant_list as $key ) {
				$secret_keys[ $key ] = trim( self::unique_key() );
			}
		} catch ( Exception $ex ) {

			$remote_salts = self::read_( 'https://api.wordpress.org/secret-key/1.1/salt/' );
			$remote_salts = explode( "\n", $remote_salts );
			foreach ( $remote_salts as $k => $salt ) {
				if ( ! empty( $salt ) ) {
					$key                 = $constant_list[ $k ];
					$secret_keys[ $key ] = trim( substr( $salt, 28, 64 ) );
				}
			}
		}

		$path = $this->get_config_path();

		try {
			$config_transformer = new WPConfigTransformer( $path );
			foreach ( $secret_keys as $constant => $key ) {
				$config_transformer->update( 'constant', $constant, (string) $key );
			}
		} catch ( Exception $exception ) {
			WP_CLI::error( "Could not process the 'wp-config.php' 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 = array() ) {
		$result = array();
		foreach ( $list as $name => $val ) {
			if ( array_key_exists( $name, $previous_list ) || in_array( $name, $exclude_list, true ) ) {
				continue;
			}
			$out          = [];
			$out['name']  = $name;
			$out['value'] = $val;
			$out['type']  = $type;
			$result[]     = $out;
		}
		return $result;
	}

	private static function read_( $url ) {
		$headers  = array( 'Accept' => 'application/json' );
		$response = Utils\http_request( 'GET', $url, null, $headers, array( 'timeout' => 30 ) );
		if ( 200 === $response->status_code ) {
			return $response->body;
		} else {
			WP_CLI::error( "Couldn't fetch response from {$url} (HTTP code {$response->status_code})." );
		}
	}

	/**
	 * 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.php file then an error will be returned.
	 *
	 * @param string $name
	 * @param string $type
	 * @param array $values
	 *
	 * @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 ) {
		$results = array();
		foreach ( $values as $value ) {
			if ( $name === $value['name'] && ( 'all' === $type || $type === $value['type'] ) ) {
				$results[] = $value;
			}
		}

		if ( count( $results ) > 1 ) {
			WP_CLI::error( "Found both a constant and a variable '{$name}' in the 'wp-config.php' 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.php' file.\nDid you mean '{$candidate}'?" );
		}

		WP_CLI::error( "The {$type} '{$name}' is not defined in the 'wp-config.php' file." );
	}

	/**
	 * Generates a unique key/salt for the wp-config.php file.
	 *
	 * @throws Exception
	 *
	 * @return string
	 */
	private static function unique_key() {
		if ( ! function_exists( 'random_int' ) ) {
			throw new Exception( "'random_int' does not exist" );
		}

		$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_ []{}<>~`+=,.;:/?|';
		$key   = '';

		for ( $i = 0; $i < 64; $i++ ) {
			// phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.random_intFound -- Will be called only if function exists.
			$key .= substr( $chars, random_int( 0, strlen( $chars ) - 1 ), 1 );
		}

		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 = array();

		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.
	 *
	 * @return string Path to wp-config.php file.
	 */
	private function get_config_path() {
		$path = Utils\locate_wp_config();
		if ( ! $path ) {
			WP_CLI::error( "'wp-config.php' not found.\nEither create one manually or use `wp config create`." );
		}
		return $path;
	}

	/**
	 * 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(
			array( '\n', '\r', '\t' ),
			array( "\n", "\r", "\t" ),
			$separator
		);

		return $separator;
	}
}
<?php
require_once dirname( __FILE__ ) . '/Oxymel.php';

class OxymelTest extends PHPUnit_Framework_TestCase {
	function __construct() {
		$this->x = 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->x );
	}

	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
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 < 0 ) {
			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 =  0 === strpos( $name, 'open_' );
		$is_closing =  0 === 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 oxymel( Oxymel $other ) {
		foreach( $other->dom->childNodes as $child ) {
			$child = $this->dom->importNode( $child, true );
			$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 ( 0 !== $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->indent( str_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 < 0 ) {
			$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

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 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 array  $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 array  $context
     *
     * @return void
     */
    public function alert($message, array $context = array());

    /**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     *
     * @param string $message
     * @param array  $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 array  $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 array  $context
     *
     * @return void
     */
    public function warning($message, array $context = array());

    /**
     * Normal but significant events.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function notice($message, array $context = array());

    /**
     * Interesting events.
     *
     * Example: User logs in, SQL logs.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function info($message, array $context = array());

    /**
     * Detailed debug information.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function debug($message, array $context = array());

    /**
     * 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());
}
<?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 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);
    }
}
<?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

namespace Psr\Log;

/**
 * Basic Implementation of LoggerAwareInterface.
 */
trait LoggerAwareTrait
{
    /**
     * The logger instance.
     *
     * @var LoggerInterface
     */
    protected $logger;

    /**
     * Sets a logger.
     *
     * @param LoggerInterface $logger
     */
    public function setLogger(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }
}
<?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;

/**
 * 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

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

namespace Seld\PharUtils;

class Timestamps
{
    private $contents;

    /**
     * @param string $file path to the phar file to use
     */
    public function __construct($file)
    {
        $this->contents = file_get_contents($file);
    }

    /**
     * Updates each file's unix timestamps in the PHAR
     *
     * The PHAR signature can then be produced in a reproducible manner.
     *
     * @param int|DateTime|string $timestamp Date string or DateTime or unix timestamp to use
     */
    public function updateTimestamps($timestamp = null)
    {
        if ($timestamp instanceof \DateTime) {
            $timestamp = $timestamp->getTimestamp();
        } elseif (is_string($timestamp)) {
            $timestamp = strtotime($timestamp);
        } elseif (!is_int($timestamp)) {
            $timestamp = strtotime('1984-12-24T00:00:00Z');
        }

        // detect manifest offset / end of stub
        if (!preg_match('{__HALT_COMPILER\(\);(?: +\?>)?\r?\n}', $this->contents, $match, PREG_OFFSET_CAPTURE)) {
            throw new \RuntimeException('Could not detect the stub\'s end in the phar');
        }

        // set starting position and skip past manifest length
        $pos = $match[0][1] + strlen($match[0][0]);
        $stubEnd = $pos + $this->readUint($pos, 4);
        $pos += 4;

        $numFiles = $this->readUint($pos, 4);
        $pos += 4;

        // skip API version (YOLO)
        $pos += 2;

        // skip PHAR flags
        $pos += 4;

        $aliasLength = $this->readUint($pos, 4);
        $pos += 4 + $aliasLength;

        $metadataLength = $this->readUint($pos, 4);
        $pos += 4 + $metadataLength;

        while ($pos < $stubEnd) {
            $filenameLength = $this->readUint($pos, 4);
            $pos += 4 + $filenameLength;

            // skip filesize
            $pos += 4;

            // update timestamp to a fixed value
            $this->contents = substr_replace($this->contents, pack('L', $timestamp), $pos, 4);

            // skip timestamp, compressed file size, crc32 checksum and file flags
            $pos += 4*4;

            $metadataLength = $this->readUint($pos, 4);
            $pos += 4 + $metadataLength;

            $numFiles--;
        }

        if ($numFiles !== 0) {
            throw new \LogicException('All files were not processed, something must have gone wrong');
        }
    }

    /**
     * Saves the updated phar file, optionally with an updated signature.
     *
     * @param  string $path
     * @param  int $signatureAlgo One of Phar::MD5, Phar::SHA1, Phar::SHA256 or Phar::SHA512
     * @return bool
     */
    public function save($path, $signatureAlgo)
    {
        $pos = $this->determineSignatureBegin();

        $algos = array(
            \Phar::MD5 => 'md5',
            \Phar::SHA1 => 'sha1',
            \Phar::SHA256 => 'sha256',
            \Phar::SHA512 => 'sha512',
        );

        if (!isset($algos[$signatureAlgo])) {
            throw new \UnexpectedValueException('Invalid hash algorithm given: '.$signatureAlgo.' expected one of Phar::MD5, Phar::SHA1, Phar::SHA256 or Phar::SHA512');
        }
        $algo = $algos[$signatureAlgo];

        // re-sign phar
        //           signature
        $signature = hash($algo, substr($this->contents, 0, $pos), true)
            // sig type
            . pack('L', $signatureAlgo)
            // ohai Greg & Marcus
            . 'GBMB';

        $this->contents = substr($this->contents, 0, $pos) . $signature;

        return file_put_contents($path, $this->contents);
    }

    private function readUint($pos, $bytes)
    {
        $res = unpack("L", substr($this->contents, $pos, $bytes));

        return $res[1];
    }

    /**
     * Determine the beginning of the signature.
     *
     * @return int
     */
    private function determineSignatureBegin()
    {
        // detect signature position
        if (!preg_match('{__HALT_COMPILER\(\);(?: +\?>)?\r?\n}', $this->contents, $match, PREG_OFFSET_CAPTURE)) {
            throw new \RuntimeException('Could not detect the stub\'s end in the phar');
        }

        // set starting position and skip past manifest length
        $pos = $match[0][1] + strlen($match[0][0]);
        $stubEnd = $pos + $this->readUint($pos, 4);

        $pos += 4;
        $numFiles = $this->readUint($pos, 4);

        $pos += 4;

        // skip API version (YOLO)
        $pos += 2;

        // skip PHAR flags
        $pos += 4;

        $aliasLength = $this->readUint($pos, 4);
        $pos += 4 + $aliasLength;

        $metadataLength = $this->readUint($pos, 4);
        $pos += 4 + $metadataLength;

        $compressedSizes = 0;
        while ($pos < $stubEnd) {
            $filenameLength = $this->readUint($pos, 4);
            $pos += 4 + $filenameLength;

            // skip filesize and timestamp
            $pos += 2*4;

            $compressedSizes += $this->readUint($pos, 4);
            // skip compressed file size, crc32 checksum and file flags
            $pos += 3*4;

            $metadataLength = $this->readUint($pos, 4);
            $pos += 4 + $metadataLength;

            $numFiles--;
        }

        if ($numFiles !== 0) {
            throw new \LogicException('All files were not processed, something must have gone wrong');
        }

        return $pos + $compressedSizes;
    }
}
<?php

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

namespace Seld\JsonLint;

/**
 * Lexer class
 *
 * Ported from https://github.com/zaach/jsonlint
 */
class Lexer
{
    private $EOF = 1;
    private $rules = array(
        0 => '/^\s+/',
        1 => '/^-?([0-9]|[1-9][0-9]+)(\.[0-9]+)?([eE][+-]?[0-9]+)?\b/',
        2 => '{^"(?>\\\\["bfnrt/\\\\]|\\\\u[a-fA-F0-9]{4}|[^\0-\x1f\\\\"]++)*+"}',
        3 => '/^\{/',
        4 => '/^\}/',
        5 => '/^\[/',
        6 => '/^\]/',
        7 => '/^,/',
        8 => '/^:/',
        9 => '/^true\b/',
        10 => '/^false\b/',
        11 => '/^null\b/',
        12 => '/^$/',
        13 => '/^./',
    );

    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 $matched;

    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->yylineno = $this->yyleng = 0;
        $this->yytext = $this->matched = $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()
    {
        $past = substr($this->matched, 0, strlen($this->matched) - strlen($this->match));

        return (strlen($past) > 20 ? '...' : '') . substr($past, -20);
    }

    public function getUpcomingInput()
    {
        $next = $this->match;
        if (strlen($next) < 20) {
            $next .= substr($this->input, 0, 20 - strlen($next));
        }

        return substr($next, 0, 20) . (strlen($next) > 20 ? '...' : '');
    }

    protected function parseError($str, $hash)
    {
        throw new \Exception($str);
    }

    private function next()
    {
        if ($this->done) {
            return $this->EOF;
        }
        if ($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, $match)) {
                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]) - 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->input = substr($this->input, strlen($match[0]));
                $this->matched .= $match[0];
                $token = $this->performAction($rules[$i], $this->conditionStack[count($this->conditionStack)-1]);
                if ($token) {
                    return $token;
                }

                return new Undefined();
            }
        }

        if ($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;
           break;
        case 2:
            $this->yytext = substr($this->yytext, 1, $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 <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Seld\JsonLint;

class Undefined
{
}
<?php

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

namespace Seld\JsonLint;

class ParsingException extends \Exception
{
    protected $details;

    public function __construct($message, $details = array())
    {
        $this->details = $details;
        parent::__construct($message);
    }

    public function getDetails()
    {
        return $this->details;
    }
}
<?php

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

namespace Seld\JsonLint;

class DuplicateKeyException extends ParsingException
{
    public function __construct($message, $key, array $details = array())
    {
        $details['key'] = $key;
        parent::__construct($message, $details);
    }

    public function getKey()
    {
        return $this->details['key'];
    }
}
<?php

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

namespace Seld\JsonLint;
use stdClass;

/**
 * Parser class
 *
 * Example:
 *
 * $parser = new JsonParser();
 * // returns null if it's valid json, or an error object
 * $parser->lint($json);
 * // returns parsed json, like json_decode does, but slower, throws exceptions on failure.
 * $parser->parse($json);
 *
 * Ported from https://github.com/zaach/jsonlint
 */
class JsonParser
{
    const DETECT_KEY_CONFLICTS = 1;
    const ALLOW_DUPLICATE_KEYS = 2;
    const PARSE_TO_ASSOC = 4;

    private $lexer;

    private $flags;
    private $stack;
    private $vstack; // semantic value stack
    private $lstack; // location stack

    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,
    );

    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(3, 1),
        array(5, 1),
        array(7, 1),
        array(9, 1),
        array(9, 1),
        array(12, 2),
        array(13, 1),
        array(13, 1),
        array(13, 1),
        array(13, 1),
        array(13, 1),
        array(13, 1),
        array(15, 2),
        array(15, 3),
        array(20, 3),
        array(19, 1),
        array(19, 3),
        array(16, 2),
        array(16, 3),
        array(25, 1),
        array(25, 3)
    );

    private $table = array(array(3 => 5, 4 => array(1,12), 5 => 6, 6 => array(1,13), 7 => 3, 8 => array(1,9), 9 => 4, 10 => array(1,10), 11 => array(1,11), 12 => 1, 13 => 2, 15 => 7, 16 => 8, 17 => array(1,14), 23 => array(1,15)), array( 1 => 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( 3 => 20, 4 => array(1,12), 18 => array(1,17), 19 => 18, 20 => 19 ), array( 3 => 5, 4 => array(1,12), 5 => 6, 6 => array(1,13), 7 => 3, 8 => array(1,9), 9 => 4, 10 => array(1,10), 11 => array(1,11), 13 => 23, 15 => 7, 16 => 8, 17 => array(1,14), 23 => array(1,15), 24 => array(1,21), 25 => 22 ), array( 1 => 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( 3 => 20, 4 => array(1,12), 20 => 29 ), array( 3 => 5, 4 => array(1,12), 5 => 6, 6 => array(1,13), 7 => 3, 8 => array(1,9), 9 => 4, 10 => array(1,10), 11 => array(1,11), 13 => 30, 15 => 7, 16 => 8, 17 => array(1,14), 23 => array(1,15)), array( 14 => array(2,19), 18 => array(2,19), 22 => array(2,19), 24 => array(2,19)), array( 3 => 5, 4 => array(1,12), 5 => 6, 6 => array(1,13), 7 => 3, 8 => array(1,9), 9 => 4, 10 => array(1,10), 11 => array(1,11), 13 => 31, 15 => 7, 16 => 8, 17 => array(1,14), 23 => array(1,15)), 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(2, 6)
    );

    /**
     * @param  string                $input JSON string
     * @param  int                   $flags Bitmask of parse/lint options (see constants of this class)
     * @return null|ParsingException null if no error is found, a ParsingException containing all details otherwise
     */
    public function lint($input, $flags = 0)
    {
        try {
            $this->parse($input, $flags);
        } catch (ParsingException $e) {
            return $e;
        }
    }

    /**
     * @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->match, 0, 1), array('"', "'"))) {
                        $message = "Invalid string";
                        if ("'" === substr($this->lexer->match, 0, 1)) {
                            $message .= ", it appears you used single quotes instead of double quotes";
                        } elseif (preg_match('{".+?(\\\\[^"bfnrt/\\\\u])}', $this->lexer->getUpcomingInput(), $match)) {
                            $message .= ", it appears you have an unescaped backslash at: ".$match[1];
                        } elseif (preg_match('{"(?:[^"]+|\\\\")*$}m', $this->lexer->getUpcomingInput())) {
                            $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, $this->lstack);

                    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;
            }
        }

        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->stack, 0, - (2 * $n));
        $this->vstack = array_slice($this->vstack, 0, - $n);
        $this->lstack = array_slice($this->lstack, 0, - $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($input, 0, 3) === $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 Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Logger;

use Psr\Log\AbstractLogger;
use Psr\Log\InvalidArgumentException;
use Psr\Log\LogLevel;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * PSR-3 compliant console logger.
 *
 * @author Kévin Dunglas <dunglas@gmail.com>
 *
 * @see http://www.php-fig.org/psr/psr-3/
 */
class ConsoleLogger extends AbstractLogger
{
    const INFO = 'info';
    const ERROR = 'error';

    private $output;
    private $verbosityLevelMap = array(
        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 = array(
        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,
    );

    public function __construct(OutputInterface $output, array $verbosityLevelMap = array(), array $formatLevelMap = array())
    {
        $this->output = $output;
        $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap;
        $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap;
    }

    /**
     * {@inheritdoc}
     */
    public function log($level, $message, array $context = array())
    {
        if (!isset($this->verbosityLevelMap[$level])) {
            throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level));
        }

        // Write to the error output if necessary and available
        if (self::ERROR === $this->formatLevelMap[$level] && $this->output instanceof ConsoleOutputInterface) {
            $output = $this->output->getErrorOutput();
        } else {
            $output = $this->output;
        }

        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)));
        }
    }

    /**
     * Interpolates context values into the message placeholders.
     *
     * @author PHP Framework Interoperability Group
     *
     * @param string $message
     * @param array  $context
     *
     * @return string
     */
    private function interpolate($message, array $context)
    {
        // build a replacement array with braces around the context keys
        $replace = array();
        foreach ($context as $key => $val) {
            if (!\is_array($val) && (!\is_object($val) || method_exists($val, '__toString'))) {
                $replace[sprintf('{%s}', $key)] = $val;
            }
        }

        // interpolate replacement values into the message and return
        return strtr($message, $replace);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Style;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Decorates output to add console style guide helpers.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
abstract class OutputStyle implements OutputInterface, StyleInterface
{
    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();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Style;

use Symfony\Component\Console\Application;
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;

/**
 * Output decorator helpers for the Symfony Style Guide.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
class SymfonyStyle extends OutputStyle
{
    const MAX_LINE_LENGTH = 120;

    private $input;
    private $questionHelper;
    private $progressBar;
    private $lineLength;
    private $bufferedOutput;

    public function __construct(InputInterface $input, OutputInterface $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.
        $this->lineLength = min($this->getTerminalWidth() - (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
     */
    public function block($messages, $type = null, $style = null, $prefix = ' ', $padding = false)
    {
        $messages = \is_array($messages) ? array_values($messages) : array($messages);

        $this->autoPrependBlock();
        $this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding, true));
        $this->newLine();
    }

    /**
     * {@inheritdoc}
     */
    public function title($message)
    {
        $this->autoPrependBlock();
        $this->writeln(array(
            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(array(
            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) : array($message);
        foreach ($messages as $message) {
            $this->writeln(sprintf(' %s', $message));
        }
    }

    /**
     * Formats a command comment.
     *
     * @param string|array $message
     */
    public function comment($message)
    {
        $messages = \is_array($message) ? array_values($message) : array($message);

        $this->autoPrependBlock();
        $this->writeln($this->createBlock($messages, null, null, '<fg=default;bg=default> // </>'));
        $this->newLine();
    }

    /**
     * {@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 = $values[$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));
    }

    /**
     * @return ProgressBar
     */
    private function getProgressBar()
    {
        if (!$this->progressBar) {
            throw new RuntimeException('The ProgressBar is not started.');
        }

        return $this->progressBar;
    }

    private function getTerminalWidth()
    {
        $application = new Application();
        $dimensions = $application->getTerminalDimensions();

        return $dimensions[0] ?: self::MAX_LINE_LENGTH;
    }

    private function autoPrependBlock()
    {
        $chars = substr(str_replace(PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2);

        if (!isset($chars[0])) {
            return $this->newLine(); //empty history, so we should start with a new line.
        }
        //Prepend new line for each non LF chars (This means no blank line was output before)
        $this->newLine(2 - substr_count($chars, "\n"));
    }

    private function autoPrependText()
    {
        $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(array($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 = array();

        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($lines, explode(PHP_EOL, wordwrap($message, $this->lineLength - $prefixLength - $indentLength, PHP_EOL, true)));

            if (\count($messages) > 1 && $key < \count($messages) - 1) {
                $lines[] = '';
            }
        }

        $firstLineIndex = 0;
        if ($padding && $this->isDecorated()) {
            $firstLineIndex = 1;
            array_unshift($lines, '');
            $lines[] = '';
        }

        foreach ($lines as $i => &$line) {
            if (null !== $type) {
                $line = $firstLineIndex === $i ? $type.$line : $lineIndentation.$line;
            }

            $line = $prefix.$line;
            $line .= str_repeat(' ', $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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Style;

/**
 * Output style helpers.
 *
 * @author Kevin Bond <kevinbond@gmail.com>
 */
interface StyleInterface
{
    /**
     * Formats a command title.
     *
     * @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 array           $choices
     * @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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console;

/**
 * Contains all events dispatched by an Application.
 *
 * @author Francesco Levorato <git@flevour.net>
 */
final class ConsoleEvents
{
    /**
     * The COMMAND event allows you to attach listeners before any command is
     * executed by the console. It also allows you to modify the command, input and output
     * before they are handled to the command.
     *
     * The event listener method receives a Symfony\Component\Console\Event\ConsoleCommandEvent
     * instance.
     *
     * @Event
     */
    const COMMAND = 'console.command';

    /**
     * The TERMINATE event allows you to attach listeners after a command is
     * executed by the console.
     *
     * The event listener method receives a Symfony\Component\Console\Event\ConsoleTerminateEvent
     * instance.
     *
     * @Event
     */
    const TERMINATE = 'console.terminate';

    /**
     * The EXCEPTION event occurs when an uncaught exception appears.
     *
     * This event allows you to deal with the exception or
     * to modify the thrown exception. The event listener method receives
     * a Symfony\Component\Console\Event\ConsoleExceptionEvent
     * instance.
     *
     * @Event
     */
    const EXCEPTION = 'console.exception';
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\CommandNotFoundException;

/**
 * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
 *
 * @internal
 */
class ApplicationDescription
{
    const GLOBAL_NAMESPACE = '_global';

    private $application;
    private $namespace;

    /**
     * @var array
     */
    private $namespaces;

    /**
     * @var Command[]
     */
    private $commands;

    /**
     * @var Command[]
     */
    private $aliases;

    public function __construct(Application $application, $namespace = null)
    {
        $this->application = $application;
        $this->namespace = $namespace;
    }

    /**
     * @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 = array();
        $this->namespaces = array();

        $all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null);
        foreach ($this->sortCommands($all) as $namespace => $commands) {
            $names = array();

            /** @var Command $command */
            foreach ($commands as $name => $command) {
                if (!$command->getName()) {
                    continue;
                }

                if ($command->getName() === $name) {
                    $this->commands[$name] = $command;
                } else {
                    $this->aliases[$name] = $command;
                }

                $names[] = $name;
            }

            $this->namespaces[$namespace] = array('id' => $namespace, 'commands' => $names);
        }
    }

    /**
     * @return array
     */
    private function sortCommands(array $commands)
    {
        $namespacedCommands = array();
        $globalCommands = array();
        foreach ($commands as $name => $command) {
            $key = $this->application->extractNamespace($name, 1);
            if (!$key) {
                $globalCommands['_global'][$name] = $command;
            } else {
                $namespacedCommands[$key][$name] = $command;
            }
        }
        ksort($namespacedCommands);
        $namespacedCommands = array_merge($globalCommands, $namespacedCommands);

        foreach ($namespacedCommands as &$commandsSet) {
            ksort($commandsSet);
        }
        // unset reference to keep scope clear
        unset($commandsSet);

        return $namespacedCommands;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;

/**
 * Text descriptor.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @internal
 */
class TextDescriptor extends Descriptor
{
    /**
     * {@inheritdoc}
     */
    protected function describeInputArgument(InputArgument $argument, array $options = array())
    {
        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 = array())
    {
        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(array($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 = array())
    {
        $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions());
        foreach ($definition->getArguments() as $argument) {
            $totalWidth = max($totalWidth, Helper::strlen($argument->getName()));
        }

        if ($definition->getArguments()) {
            $this->writeText('<comment>Arguments:</comment>', $options);
            $this->writeText("\n");
            foreach ($definition->getArguments() as $argument) {
                $this->describeInputArgument($argument, array_merge($options, array('total_width' => $totalWidth)));
                $this->writeText("\n");
            }
        }

        if ($definition->getArguments() && $definition->getOptions()) {
            $this->writeText("\n");
        }

        if ($definition->getOptions()) {
            $laterOptions = array();

            $this->writeText('<comment>Options:</comment>', $options);
            foreach ($definition->getOptions() as $option) {
                if (\strlen($option->getShortcut()) > 1) {
                    $laterOptions[] = $option;
                    continue;
                }
                $this->writeText("\n");
                $this->describeInputOption($option, array_merge($options, array('total_width' => $totalWidth)));
            }
            foreach ($laterOptions as $option) {
                $this->writeText("\n");
                $this->describeInputOption($option, array_merge($options, array('total_width' => $totalWidth)));
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    protected function describeCommand(Command $command, array $options = array())
    {
        $command->getSynopsis(true);
        $command->getSynopsis(false);
        $command->mergeApplicationDefinition(false);

        $this->writeText('<comment>Usage:</comment>', $options);
        foreach (array_merge(array($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 = array())
    {
        $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");

            $width = $this->getColumnWidth($description->getCommands());

            if ($describedNamespace) {
                $this->writeText(sprintf('<comment>Available commands for the "%s" namespace:</comment>', $describedNamespace), $options);
            } else {
                $this->writeText('<comment>Available commands:</comment>', $options);
            }

            // add commands by namespace
            foreach ($description->getNamespaces() as $namespace) {
                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);
                    $this->writeText(sprintf('  <info>%s</info>%s%s', $name, str_repeat(' ', $spacingWidth), $description->getCommand($name)->getDescription()), $options);
                }
            }

            $this->writeText("\n");
        }
    }

    /**
     * {@inheritdoc}
     */
    private function writeText($content, array $options = array())
    {
        $this->write(
            isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content,
            isset($options['raw_output']) ? !$options['raw_output'] : true
        );
    }

    /**
     * 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);
                }
            }
        }

        if (\PHP_VERSION_ID < 50400) {
            return str_replace(array('\/', '\\\\'), array('/', '\\'), json_encode($default));
        }

        return str_replace('\\\\', '\\', json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
    }

    /**
     * @param Command[] $commands
     *
     * @return int
     */
    private function getColumnWidth(array $commands)
    {
        $widths = array();

        foreach ($commands as $command) {
            $widths[] = Helper::strlen($command->getName());
            foreach ($command->getAliases() as $alias) {
                $widths[] = Helper::strlen($alias);
            }
        }

        return max($widths) + 2;
    }

    /**
     * @param InputOption[] $options
     *
     * @return int
     */
    private function calculateTotalWidthForOptions(array $options)
    {
        $totalWidth = 0;
        foreach ($options as $option) {
            // "-" + shortcut + ", --" + name
            $nameLength = 1 + max(\strlen($option->getShortcut()), 1) + 4 + Helper::strlen($option->getName());

            if ($option->acceptValue()) {
                $valueLength = 1 + Helper::strlen($option->getName()); // = + value
                $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ]

                $nameLength += $valueLength;
            }
            $totalWidth = max($totalWidth, $nameLength);
        }

        return $totalWidth;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;

/**
 * JSON descriptor.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @internal
 */
class JsonDescriptor extends Descriptor
{
    /**
     * {@inheritdoc}
     */
    protected function describeInputArgument(InputArgument $argument, array $options = array())
    {
        $this->writeData($this->getInputArgumentData($argument), $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputOption(InputOption $option, array $options = array())
    {
        $this->writeData($this->getInputOptionData($option), $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputDefinition(InputDefinition $definition, array $options = array())
    {
        $this->writeData($this->getInputDefinitionData($definition), $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeCommand(Command $command, array $options = array())
    {
        $this->writeData($this->getCommandData($command), $options);
    }

    /**
     * {@inheritdoc}
     */
    protected function describeApplication(Application $application, array $options = array())
    {
        $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
        $description = new ApplicationDescription($application, $describedNamespace);
        $commands = array();

        foreach ($description->getCommands() as $command) {
            $commands[] = $this->getCommandData($command);
        }

        $data = $describedNamespace
            ? array('commands' => $commands, 'namespace' => $describedNamespace)
            : array('commands' => $commands, 'namespaces' => array_values($description->getNamespaces()));

        $this->writeData($data, $options);
    }

    /**
     * Writes data as json.
     *
     * @return array|string
     */
    private function writeData(array $data, array $options)
    {
        $this->write(json_encode($data, isset($options['json_encoding']) ? $options['json_encoding'] : 0));
    }

    /**
     * @return array
     */
    private function getInputArgumentData(InputArgument $argument)
    {
        return array(
            '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 array(
            '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 = array();
        foreach ($definition->getArguments() as $name => $argument) {
            $inputArguments[$name] = $this->getInputArgumentData($argument);
        }

        $inputOptions = array();
        foreach ($definition->getOptions() as $name => $option) {
            $inputOptions[$name] = $this->getInputOptionData($option);
        }

        return array('arguments' => $inputArguments, 'options' => $inputOptions);
    }

    /**
     * @return array
     */
    private function getCommandData(Command $command)
    {
        $command->getSynopsis();
        $command->mergeApplicationDefinition(false);

        return array(
            'name' => $command->getName(),
            'usage' => array_merge(array($command->getSynopsis()), $command->getUsages(), $command->getAliases()),
            'description' => $command->getDescription(),
            'help' => $command->getProcessedHelp(),
            'definition' => $this->getInputDefinitionData($command->getNativeDefinition()),
        );
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Output\OutputInterface;

/**
 * Descriptor interface.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
interface DescriptorInterface
{
    /**
     * Describes an object if supported.
     *
     * @param OutputInterface $output
     * @param object          $object
     * @param array           $options
     */
    public function describe(OutputInterface $output, $object, array $options = array());
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\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;

/**
 * Markdown descriptor.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @internal
 */
class MarkdownDescriptor extends Descriptor
{
    /**
     * {@inheritdoc}
     */
    protected function describeInputArgument(InputArgument $argument, array $options = array())
    {
        $this->write(
            '**'.$argument->getName().':**'."\n\n"
            .'* Name: '.($argument->getName() ?: '<none>')."\n"
            .'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n"
            .'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n"
            .'* Description: '.preg_replace('/\s*[\r\n]\s*/', "\n  ", $argument->getDescription() ?: '<none>')."\n"
            .'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`'
        );
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputOption(InputOption $option, array $options = array())
    {
        $this->write(
            '**'.$option->getName().':**'."\n\n"
            .'* Name: `--'.$option->getName().'`'."\n"
            .'* Shortcut: '.($option->getShortcut() ? '`-'.str_replace('|', '|-', $option->getShortcut()).'`' : '<none>')."\n"
            .'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n"
            .'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n"
            .'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n"
            .'* Description: '.preg_replace('/\s*[\r\n]\s*/', "\n  ", $option->getDescription() ?: '<none>')."\n"
            .'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`'
        );
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputDefinition(InputDefinition $definition, array $options = array())
    {
        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 = array())
    {
        $command->getSynopsis();
        $command->mergeApplicationDefinition(false);

        $this->write(
            $command->getName()."\n"
            .str_repeat('-', Helper::strlen($command->getName()))."\n\n"
            .'* Description: '.($command->getDescription() ?: '<none>')."\n"
            .'* Usage:'."\n\n"
            .array_reduce(array_merge(array($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 = array())
    {
        $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
        $description = new ApplicationDescription($application, $describedNamespace);

        $this->write($application->getName()."\n".str_repeat('=', Helper::strlen($application->getName())));

        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) {
                return '* '.$commandName;
            }, $namespace['commands'])));
        }

        foreach ($description->getCommands() as $command) {
            $this->write("\n\n");
            $this->write($this->describeCommand($command));
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;

/**
 * XML descriptor.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 *
 * @internal
 */
class XmlDescriptor extends Descriptor
{
    /**
     * @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->appendChild($usagesXML = $dom->createElement('usages'));

        foreach (array_merge(array($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 Application $application
     * @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, $namespace);

        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 = array())
    {
        $this->writeDocument($this->getInputArgumentDocument($argument));
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputOption(InputOption $option, array $options = array())
    {
        $this->writeDocument($this->getInputOptionDocument($option));
    }

    /**
     * {@inheritdoc}
     */
    protected function describeInputDefinition(InputDefinition $definition, array $options = array())
    {
        $this->writeDocument($this->getInputDefinitionDocument($definition));
    }

    /**
     * {@inheritdoc}
     */
    protected function describeCommand(Command $command, array $options = array())
    {
        $this->writeDocument($this->getCommandDocument($command));
    }

    /**
     * {@inheritdoc}
     */
    protected function describeApplication(Application $application, array $options = array())
    {
        $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($childNode, true));
        }
    }

    /**
     * Writes DOM document.
     *
     * @return \DOMDocument|string
     */
    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() ? 1 : 0);
        $objectXML->setAttribute('is_array', $argument->isArray() ? 1 : 0);
        $objectXML->appendChild($descriptionXML = $dom->createElement('description'));
        $descriptionXML->appendChild($dom->createTextNode($argument->getDescription()));

        $objectXML->appendChild($defaultsXML = $dom->createElement('defaults'));
        $defaults = \is_array($argument->getDefault()) ? $argument->getDefault() : (\is_bool($argument->getDefault()) ? array(var_export($argument->getDefault(), true)) : ($argument->getDefault() ? array($argument->getDefault()) : array()));
        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() ? 1 : 0);
        $objectXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0);
        $objectXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0);
        $objectXML->appendChild($descriptionXML = $dom->createElement('description'));
        $descriptionXML->appendChild($dom->createTextNode($option->getDescription()));

        if ($option->acceptValue()) {
            $defaults = \is_array($option->getDefault()) ? $option->getDefault() : (\is_bool($option->getDefault()) ? array(var_export($option->getDefault(), true)) : ($option->getDefault() ? array($option->getDefault()) : array()));
            $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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Descriptor;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
 *
 * @internal
 */
abstract class Descriptor implements DescriptorInterface
{
    /**
     * @var OutputInterface
     */
    private $output;

    /**
     * {@inheritdoc}
     */
    public function describe(OutputInterface $output, $object, array $options = array())
    {
        $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($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW);
    }

    /**
     * Describes an InputArgument instance.
     *
     * @return string|mixed
     */
    abstract protected function describeInputArgument(InputArgument $argument, array $options = array());

    /**
     * Describes an InputOption instance.
     *
     * @return string|mixed
     */
    abstract protected function describeInputOption(InputOption $option, array $options = array());

    /**
     * Describes an InputDefinition instance.
     *
     * @return string|mixed
     */
    abstract protected function describeInputDefinition(InputDefinition $definition, array $options = array());

    /**
     * Describes a Command instance.
     *
     * @return string|mixed
     */
    abstract protected function describeCommand(Command $command, array $options = array());

    /**
     * Describes an Application instance.
     *
     * @return string|mixed
     */
    abstract protected function describeApplication(Application $application, array $options = array());
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console;

use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Process\PhpExecutableFinder;
use Symfony\Component\Process\ProcessBuilder;

/**
 * A Shell wraps an Application to add shell capabilities to it.
 *
 * Support for history and completion only works with a PHP compiled
 * with readline support (either --with-readline or --with-libedit)
 *
 * @deprecated since version 2.8, to be removed in 3.0.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Martin Hasoň <martin.hason@gmail.com>
 */
class Shell
{
    private $application;
    private $history;
    private $output;
    private $hasReadline;
    private $processIsolation = false;

    /**
     * If there is no readline support for the current PHP executable
     * a \RuntimeException exception is thrown.
     */
    public function __construct(Application $application)
    {
        @trigger_error('The '.__CLASS__.' class is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED);

        $this->hasReadline = \function_exists('readline');
        $this->application = $application;
        $this->history = getenv('HOME').'/.history_'.$application->getName();
        $this->output = new ConsoleOutput();
    }

    /**
     * Runs the shell.
     */
    public function run()
    {
        $this->application->setAutoExit(false);
        $this->application->setCatchExceptions(true);

        if ($this->hasReadline) {
            readline_read_history($this->history);
            readline_completion_function(array($this, 'autocompleter'));
        }

        $this->output->writeln($this->getHeader());
        $php = null;
        if ($this->processIsolation) {
            $finder = new PhpExecutableFinder();
            $php = $finder->find();
            $this->output->writeln(<<<'EOF'
<info>Running with process isolation, you should consider this:</info>
  * each command is executed as separate process,
  * commands don't support interactivity, all params must be passed explicitly,
  * commands output is not colorized.

EOF
            );
        }

        while (true) {
            $command = $this->readline();

            if (false === $command) {
                $this->output->writeln("\n");

                break;
            }

            if ($this->hasReadline) {
                readline_add_history($command);
                readline_write_history($this->history);
            }

            if ($this->processIsolation) {
                $pb = new ProcessBuilder();

                $process = $pb
                    ->add($php)
                    ->add($_SERVER['argv'][0])
                    ->add($command)
                    ->inheritEnvironmentVariables(true)
                    ->getProcess()
                ;

                $output = $this->output;
                $process->run(function ($type, $data) use ($output) {
                    $output->writeln($data);
                });

                $ret = $process->getExitCode();
            } else {
                $ret = $this->application->run(new StringInput($command), $this->output);
            }

            if (0 !== $ret) {
                $this->output->writeln(sprintf('<error>The command terminated with an error status (%s)</error>', $ret));
            }
        }
    }

    /**
     * Returns the shell header.
     *
     * @return string The header string
     */
    protected function getHeader()
    {
        return <<<EOF

Welcome to the <info>{$this->application->getName()}</info> shell (<comment>{$this->application->getVersion()}</comment>).

At the prompt, type <comment>help</comment> for some help,
or <comment>list</comment> to get a list of available commands.

To exit the shell, type <comment>^D</comment>.

EOF;
    }

    /**
     * Renders a prompt.
     *
     * @return string The prompt
     */
    protected function getPrompt()
    {
        // using the formatter here is required when using readline
        return $this->output->getFormatter()->format($this->application->getName().' > ');
    }

    protected function getOutput()
    {
        return $this->output;
    }

    protected function getApplication()
    {
        return $this->application;
    }

    /**
     * Tries to return autocompletion for the current entered text.
     *
     * @param string $text The last segment of the entered text
     *
     * @return bool|array A list of guessed strings or true
     */
    private function autocompleter($text)
    {
        $info = readline_info();
        $text = substr($info['line_buffer'], 0, $info['end']);

        if ($info['point'] !== $info['end']) {
            return true;
        }

        // task name?
        if (false === strpos($text, ' ') || !$text) {
            return array_keys($this->application->all());
        }

        // options and arguments?
        try {
            $command = $this->application->find(substr($text, 0, strpos($text, ' ')));
        } catch (\Exception $e) {
            return true;
        }

        $list = array('--help');
        foreach ($command->getDefinition()->getOptions() as $option) {
            $list[] = '--'.$option->getName();
        }

        return $list;
    }

    /**
     * Reads a single line from standard input.
     *
     * @return string The single line from standard input
     */
    private function readline()
    {
        if ($this->hasReadline) {
            $line = readline($this->getPrompt());
        } else {
            $this->output->write($this->getPrompt());
            $line = fgets(STDIN, 1024);
            $line = (false === $line || '' === $line) ? false : rtrim($line);
        }

        return $line;
    }

    public function getProcessIsolation()
    {
        return $this->processIsolation;
    }

    public function setProcessIsolation($processIsolation)
    {
        $this->processIsolation = (bool) $processIsolation;

        if ($this->processIsolation && !class_exists('Symfony\\Component\\Process\\Process')) {
            throw new RuntimeException('Unable to isolate processes as the Symfony Process Component is not installed.');
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Question;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;

/**
 * Represents a Question.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Question
{
    private $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.
     *
     * @param callable|null $validator
     *
     * @return $this
     */
    public function setValidator($validator)
    {
        $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 < 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.
     *
     * @param callable $normalizer
     *
     * @return $this
     */
    public function setNormalizer($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
     */
    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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Question;

/**
 * Represents a yes/no question.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ConfirmationQuestion extends Question
{
    private $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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Question;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * Represents a choice question.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ChoiceQuestion extends Question
{
    private $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) {
            // Collapse all spaces.
            $selectedChoices = str_replace(' ', '', $selected);

            if ($multiselect) {
                // Check for a separated comma values
                if (!preg_match('/^[^,]+(?:,[^,]+)*$/', $selectedChoices, $matches)) {
                    throw new InvalidArgumentException(sprintf($errorMessage, $selected));
                }
                $selectedChoices = explode(',', $selectedChoices);
            } else {
                $selectedChoices = array($selected);
            }

            $multiselectChoices = array();
            foreach ($selectedChoices as $value) {
                $results = array();
                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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Tester;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
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 <fabien@symfony.com>
 */
class CommandTester
{
    private $command;
    private $input;
    private $output;
    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 = array())
    {
        // 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(array('command' => $this->command->getName()), $input);
        }

        $this->input = new ArrayInput($input);
        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)
    {
        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;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Tester;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputInterface;
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 <fabien@symfony.com>
 */
class ApplicationTester
{
    private $application;
    private $input;
    private $output;
    private $statusCode;

    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
     *
     * @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 = array())
    {
        $this->input = new ArrayInput($input);
        if (isset($options['interactive'])) {
            $this->input->setInteractive($options['interactive']);
        }

        $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']);
        }

        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 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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Command\HelpCommand;
use Symfony\Component\Console\Command\ListCommand;
use Symfony\Component\Console\Descriptor\TextDescriptor;
use Symfony\Component\Console\Descriptor\XmlDescriptor;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
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\DialogHelper;
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\ProgressHelper;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Helper\TableHelper;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputAwareInterface;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
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 <fabien@symfony.com>
 */
class Application
{
    private $commands = array();
    private $wantHelps = false;
    private $runningCommand;
    private $name;
    private $version;
    private $catchExceptions = true;
    private $autoExit = true;
    private $definition;
    private $helperSet;
    private $dispatcher;
    private $terminalDimensions;
    private $defaultCommand;
    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->defaultCommand = 'list';
    }

    public function setDispatcher(EventDispatcherInterface $dispatcher)
    {
        $this->dispatcher = $dispatcher;
    }

    /**
     * Runs the current application.
     *
     * @return int 0 if everything went fine, or an error code
     *
     * @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}.
     */
    public function run(InputInterface $input = null, OutputInterface $output = null)
    {
        if (null === $input) {
            $input = new ArgvInput();
        }

        if (null === $output) {
            $output = new ConsoleOutput();
        }

        $this->configureIO($input, $output);

        try {
            $e = null;
            $exitCode = $this->doRun($input, $output);
        } catch (\Exception $e) {
        }

        if (null !== $e) {
            if (!$this->catchExceptions) {
                throw $e;
            }

            if ($output instanceof ConsoleOutputInterface) {
                $this->renderException($e, $output->getErrorOutput());
            } else {
                $this->renderException($e, $output);
            }

            $exitCode = $this->getExitCodeForThrowable($e);
        }

        if ($this->autoExit) {
            if ($exitCode > 255) {
                $exitCode = 255;
            }

            exit($exitCode);
        }

        return $exitCode;
    }

    /**
     * Runs the current application.
     *
     * @return int 0 if everything went fine, or an error code
     */
    public function doRun(InputInterface $input, OutputInterface $output)
    {
        if (true === $input->hasParameterOption(array('--version', '-V'))) {
            $output->writeln($this->getLongVersion());

            return 0;
        }

        $name = $this->getCommandName($input);
        if (true === $input->hasParameterOption(array('--help', '-h'))) {
            if (!$name) {
                $name = 'help';
                $input = new ArrayInput(array('command' => 'help'));
            } else {
                $this->wantHelps = true;
            }
        }

        if (!$name) {
            $name = $this->defaultCommand;
            $definition = $this->getDefinition();
            $definition->setArguments(array_merge(
                $definition->getArguments(),
                array(
                    'command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name),
                )
            ));
        }

        $this->runningCommand = null;
        // the command name MUST be the first element of the input
        $command = $this->find($name);

        $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();
        }

        return $this->definition;
    }

    /**
     * Gets the help message.
     *
     * @return string A help message
     */
    public function getHelp()
    {
        return $this->getLongVersion();
    }

    /**
     * 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;
    }

    /**
     * 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('<info>%s</info> version <comment>%s</comment>', $this->getName(), $this->getVersion());
            }

            return sprintf('<info>%s</info>', $this->getName());
        }

        return '<info>Console Tool</info>';
    }

    /**
     * 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;
        }

        if (null === $command->getDefinition()) {
            throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', \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 (!isset($this->commands[$name])) {
            throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $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]);
    }

    /**
     * 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 = array();
        foreach ($this->all() as $command) {
            $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 (1 == \count($alternatives)) {
                    $message .= "\n\nDid you mean this?\n    ";
                } else {
                    $message .= "\n\nDid you mean one of these?\n    ";
                }

                $message .= implode("\n    ", $alternatives);
            }

            throw new CommandNotFoundException($message, $alternatives);
        }

        $exact = \in_array($namespace, $namespaces, true);
        if (\count($namespaces) > 1 && !$exact) {
            throw new CommandNotFoundException(sprintf('The namespace "%s" is ambiguous (%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 = array();
        $allCommands = array_keys($this->commands);
        $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name);
        $commands = preg_grep('{^'.$expr.'}', $allCommands);

        if (empty($commands) || \count(preg_grep('{^'.$expr.'$}', $commands)) < 1) {
            if (false !== $pos = strrpos($name, ':')) {
                // check if a namespace exists and contains commands
                $this->findNamespace(substr($name, 0, $pos));
            }

            $message = sprintf('Command "%s" is not defined.', $name);

            if ($alternatives = $this->findAlternatives($name, $allCommands)) {
                if (1 == \count($alternatives)) {
                    $message .= "\n\nDid you mean this?\n    ";
                } else {
                    $message .= "\n\nDid you mean one of these?\n    ";
                }
                $message .= implode("\n    ", $alternatives);
            }

            throw new CommandNotFoundException($message, $alternatives);
        }

        // filter out aliases for commands which are already on the list
        if (\count($commands) > 1) {
            $commandList = $this->commands;
            $commands = array_filter($commands, function ($nameOrAlias) use ($commandList, $commands, &$aliases) {
                $commandName = $commandList[$nameOrAlias]->getName();
                $aliases[$nameOrAlias] = $commandName;

                return $commandName === $nameOrAlias || !\in_array($commandName, $commands);
            });
        }

        $exact = \in_array($name, $commands, true) || isset($aliases[$name]);
        if (!$exact && \count($commands) > 1) {
            $suggestions = $this->getAbbreviationSuggestions(array_values($commands));

            throw new CommandNotFoundException(sprintf('Command "%s" is ambiguous (%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) {
            return $this->commands;
        }

        $commands = array();
        foreach ($this->commands as $name => $command) {
            if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) {
                $commands[$name] = $command;
            }
        }

        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 = array();
        foreach ($names as $name) {
            for ($len = \strlen($name); $len > 0; --$len) {
                $abbrev = substr($name, 0, $len);
                $abbrevs[$abbrev][] = $name;
            }
        }

        return $abbrevs;
    }

    /**
     * Returns a text representation of the Application.
     *
     * @param string $namespace An optional namespace name
     * @param bool   $raw       Whether to return raw command list
     *
     * @return string A string representing the Application
     *
     * @deprecated since version 2.3, to be removed in 3.0.
     */
    public function asText($namespace = null, $raw = false)
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.3 and will be removed in 3.0.', E_USER_DEPRECATED);

        $descriptor = new TextDescriptor();
        $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, !$raw);
        $descriptor->describe($output, $this, array('namespace' => $namespace, 'raw_output' => true));

        return $output->fetch();
    }

    /**
     * Returns an XML representation of the Application.
     *
     * @param string $namespace An optional namespace name
     * @param bool   $asDom     Whether to return a DOM or an XML string
     *
     * @return string|\DOMDocument An XML string representing the Application
     *
     * @deprecated since version 2.3, to be removed in 3.0.
     */
    public function asXml($namespace = null, $asDom = false)
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.3 and will be removed in 3.0.', E_USER_DEPRECATED);

        $descriptor = new XmlDescriptor();

        if ($asDom) {
            return $descriptor->getApplicationDocument($this, $namespace);
        }

        $output = new BufferedOutput();
        $descriptor->describe($output, $this, array('namespace' => $namespace));

        return $output->fetch();
    }

    /**
     * Renders a caught exception.
     */
    public function renderException($e, $output)
    {
        $output->writeln('', OutputInterface::VERBOSITY_QUIET);

        do {
            $title = sprintf('  [%s]  ', \get_class($e));

            $len = Helper::strlen($title);

            $width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : 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 > 1 << 31) {
                $width = 1 << 31;
            }
            $lines = array();
            foreach (preg_split('/\r?\n/', trim($e->getMessage())) 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[] = array($line, $lineLength);

                    $len = max($lineLength, $len);
                }
            }

            $messages = array();
            $messages[] = $emptyLine = sprintf('<error>%s</error>', str_repeat(' ', $len));
            $messages[] = sprintf('<error>%s%s</error>', $title, str_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($messages, OutputInterface::VERBOSITY_QUIET);

            if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
                $output->writeln('<comment>Exception trace:</comment>', OutputInterface::VERBOSITY_QUIET);

                // exception related properties
                $trace = $e->getTrace();
                array_unshift($trace, array(
                    'function' => '',
                    'file' => null !== $e->getFile() ? $e->getFile() : 'n/a',
                    'line' => null !== $e->getLine() ? $e->getLine() : 'n/a',
                    'args' => array(),
                ));

                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 = $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%s() at <info>%s:%s</info>', $class, $type, $function, $file, $line), OutputInterface::VERBOSITY_QUIET);
                }

                $output->writeln('', OutputInterface::VERBOSITY_QUIET);
            }
        } while ($e = $e->getPrevious());

        if (null !== $this->runningCommand) {
            $output->writeln(sprintf('<info>%s</info>', sprintf($this->runningCommand->getSynopsis(), $this->getName())), OutputInterface::VERBOSITY_QUIET);
            $output->writeln('', OutputInterface::VERBOSITY_QUIET);
        }
    }

    /**
     * Tries to figure out the terminal width in which this application runs.
     *
     * @return int|null
     */
    protected function getTerminalWidth()
    {
        $dimensions = $this->getTerminalDimensions();

        return $dimensions[0];
    }

    /**
     * Tries to figure out the terminal height in which this application runs.
     *
     * @return int|null
     */
    protected function getTerminalHeight()
    {
        $dimensions = $this->getTerminalDimensions();

        return $dimensions[1];
    }

    /**
     * Tries to figure out the terminal dimensions based on the current environment.
     *
     * @return array Array containing width and height
     */
    public function getTerminalDimensions()
    {
        if ($this->terminalDimensions) {
            return $this->terminalDimensions;
        }

        if ('\\' === \DIRECTORY_SEPARATOR) {
            // extract [w, H] from "wxh (WxH)"
            if (preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim(getenv('ANSICON')), $matches)) {
                return array((int) $matches[1], (int) $matches[2]);
            }
            // extract [w, h] from "wxh"
            if (preg_match('/^(\d+)x(\d+)$/', $this->getConsoleMode(), $matches)) {
                return array((int) $matches[1], (int) $matches[2]);
            }
        }

        if ($sttyString = $this->getSttyColumns()) {
            // extract [w, h] from "rows h; columns w;"
            if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) {
                return array((int) $matches[2], (int) $matches[1]);
            }
            // extract [w, h] from "; h rows; w columns"
            if (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) {
                return array((int) $matches[2], (int) $matches[1]);
            }
        }

        return array(null, null);
    }

    /**
     * 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
     */
    public function setTerminalDimensions($width, $height)
    {
        $this->terminalDimensions = array($width, $height);

        return $this;
    }

    /**
     * Configures the input and output instances based on the user arguments and options.
     */
    protected function configureIO(InputInterface $input, OutputInterface $output)
    {
        if (true === $input->hasParameterOption(array('--ansi'))) {
            $output->setDecorated(true);
        } elseif (true === $input->hasParameterOption(array('--no-ansi'))) {
            $output->setDecorated(false);
        }

        if (true === $input->hasParameterOption(array('--no-interaction', '-n'))) {
            $input->setInteractive(false);
        } elseif (\function_exists('posix_isatty') && $this->getHelperSet()->has('question')) {
            $inputStream = $this->getHelperSet()->get('question')->getInputStream();
            if (!@posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) {
                $input->setInteractive(false);
            }
        }

        if (true === $input->hasParameterOption(array('--quiet', '-q'))) {
            $output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
            $input->setInteractive(false);
        } else {
            if ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || 3 === $input->getParameterOption('--verbose')) {
                $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
            } elseif ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || 2 === $input->getParameterOption('--verbose')) {
                $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
            } elseif ($input->hasParameterOption('-v') || $input->hasParameterOption('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getParameterOption('--verbose')) {
                $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
            }
        }
    }

    /**
     * Runs the current command.
     *
     * If an event dispatcher has been attached to the application,
     * events are also dispatched during the life-cycle of the command.
     *
     * @return int 0 if everything went fine, or an error code
     */
    protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
    {
        foreach ($command->getHelperSet() as $helper) {
            if ($helper instanceof InputAwareInterface) {
                $helper->setInput($input);
            }
        }

        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) {
            $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();
            }

            $exitCode = $this->getExitCodeForThrowable($e);
        }

        $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 The command name
     */
    protected function getCommandName(InputInterface $input)
    {
        return $input->getFirstArgument();
    }

    /**
     * Gets the default input definition.
     *
     * @return InputDefinition An InputDefinition instance
     */
    protected function getDefaultInputDefinition()
    {
        return new InputDefinition(array(
            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 array(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(array(
            new FormatterHelper(),
            new DialogHelper(false),
            new ProgressHelper(false),
            new TableHelper(false),
            new DebugFormatterHelper(),
            new ProcessHelper(),
            new QuestionHelper(),
        ));
    }

    /**
     * Runs and parses stty -a if it's available, suppressing any error output.
     *
     * @return string
     */
    private function getSttyColumns()
    {
        if (!\function_exists('proc_open')) {
            return;
        }

        $descriptorspec = array(1 => array('pipe', 'w'), 2 => array('pipe', 'w'));
        $process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, array('suppress_errors' => true));
        if (\is_resource($process)) {
            $info = stream_get_contents($pipes[1]);
            fclose($pipes[1]);
            fclose($pipes[2]);
            proc_close($process);

            return $info;
        }
    }

    /**
     * Runs and parses mode CON if it's available, suppressing any error output.
     *
     * @return string|null <width>x<height> or null if it could not be parsed
     */
    private function getConsoleMode()
    {
        if (!\function_exists('proc_open')) {
            return;
        }

        $descriptorspec = array(1 => array('pipe', 'w'), 2 => array('pipe', 'w'));
        $process = proc_open('mode CON', $descriptorspec, $pipes, null, null, array('suppress_errors' => true));
        if (\is_resource($process)) {
            $info = stream_get_contents($pipes[1]);
            fclose($pipes[1]);
            fclose($pipes[2]);
            proc_close($process);

            if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
                return $matches[2].'x'.$matches[1];
            }
        }
    }

    /**
     * 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 sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], \count($abbrevs) > 2 ? sprintf(' and %d more', \count($abbrevs) - 2) : '');
    }

    /**
     * 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);
        array_pop($parts);

        return implode(':', null === $limit ? $parts : \array_slice($parts, 0, $limit));
    }

    /**
     * Finds alternative of $name among $collection,
     * if nothing is found in $collection, try in $abbrevs.
     *
     * @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 = array();

        $collectionParts = array();
        foreach ($collection as $item) {
            $collectionParts[$item] = explode(':', $item);
        }

        foreach (explode(':', $name) as $i => $subname) {
            foreach ($collectionParts as $collectionName => $parts) {
                $exists = isset($alternatives[$collectionName]);
                if (!isset($parts[$i]) && $exists) {
                    $alternatives[$collectionName] += $threshold;
                    continue;
                } elseif (!isset($parts[$i])) {
                    continue;
                }

                $lev = levenshtein($subname, $parts[$i]);
                if ($lev <= \strlen($subname) / 3 || '' !== $subname && 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) / 3 || false !== strpos($item, $name)) {
                $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
            }
        }

        $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; });
        asort($alternatives);

        return array_keys($alternatives);
    }

    /**
     * Sets the default Command name.
     *
     * @param string $commandName The Command name
     */
    public function setDefaultCommand($commandName)
    {
        $this->defaultCommand = $commandName;
    }

    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($string, null, true)) {
            return str_split($string, $width);
        }

        $utf8String = mb_convert_encoding($string, 'utf8', $encoding);
        $lines = array();
        $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 = array();

        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);
        }
    }

    /**
     * @param \Exception|\Throwable $throwable
     *
     * @return int
     */
    private function getExitCodeForThrowable($throwable)
    {
        $exitCode = $throwable->getCode();
        if (is_numeric($exitCode)) {
            $exitCode = (int) $exitCode;
            if (0 === $exitCode) {
                $exitCode = 1;
            }
        } else {
            $exitCode = 1;
        }

        return $exitCode;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * Formatter style class for defining styles.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class OutputFormatterStyle implements OutputFormatterStyleInterface
{
    private static $availableForegroundColors = array(
        'black' => array('set' => 30, 'unset' => 39),
        'red' => array('set' => 31, 'unset' => 39),
        'green' => array('set' => 32, 'unset' => 39),
        'yellow' => array('set' => 33, 'unset' => 39),
        'blue' => array('set' => 34, 'unset' => 39),
        'magenta' => array('set' => 35, 'unset' => 39),
        'cyan' => array('set' => 36, 'unset' => 39),
        'white' => array('set' => 37, 'unset' => 39),
        'default' => array('set' => 39, 'unset' => 39),
    );
    private static $availableBackgroundColors = array(
        'black' => array('set' => 40, 'unset' => 49),
        'red' => array('set' => 41, 'unset' => 49),
        'green' => array('set' => 42, 'unset' => 49),
        'yellow' => array('set' => 43, 'unset' => 49),
        'blue' => array('set' => 44, 'unset' => 49),
        'magenta' => array('set' => 45, 'unset' => 49),
        'cyan' => array('set' => 46, 'unset' => 49),
        'white' => array('set' => 47, 'unset' => 49),
        'default' => array('set' => 49, 'unset' => 49),
    );
    private static $availableOptions = array(
        'bold' => array('set' => 1, 'unset' => 22),
        'underscore' => array('set' => 4, 'unset' => 24),
        'blink' => array('set' => 5, 'unset' => 25),
        'reverse' => array('set' => 7, 'unset' => 27),
        'conceal' => array('set' => 8, 'unset' => 28),
    );

    private $foreground;
    private $background;
    private $options = array();

    /**
     * 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 = array())
    {
        if (null !== $foreground) {
            $this->setForeground($foreground);
        }
        if (null !== $background) {
            $this->setBackground($background);
        }
        if (\count($options)) {
            $this->setOptions($options);
        }
    }

    /**
     * Sets style foreground color.
     *
     * @param string|null $color The color name
     *
     * @throws InvalidArgumentException When the color name isn't defined
     */
    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)', $color, implode(', ', array_keys(static::$availableForegroundColors))));
        }

        $this->foreground = static::$availableForegroundColors[$color];
    }

    /**
     * Sets style background color.
     *
     * @param string|null $color The color name
     *
     * @throws InvalidArgumentException When the color name isn't defined
     */
    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)', $color, implode(', ', array_keys(static::$availableBackgroundColors))));
        }

        $this->background = static::$availableBackgroundColors[$color];
    }

    /**
     * Sets some specific style option.
     *
     * @param string $option The option name
     *
     * @throws InvalidArgumentException When the option name isn't defined
     */
    public function setOption($option)
    {
        if (!isset(static::$availableOptions[$option])) {
            throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions))));
        }

        if (!\in_array(static::$availableOptions[$option], $this->options)) {
            $this->options[] = static::$availableOptions[$option];
        }
    }

    /**
     * Unsets some specific style option.
     *
     * @param string $option The option name
     *
     * @throws InvalidArgumentException When the option name isn't defined
     */
    public function unsetOption($option)
    {
        if (!isset(static::$availableOptions[$option])) {
            throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', 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 = array();

        foreach ($options as $option) {
            $this->setOption($option);
        }
    }

    /**
     * Applies the style to a given text.
     *
     * @param string $text The text to style
     *
     * @return string
     */
    public function apply($text)
    {
        $setCodes = array();
        $unsetCodes = array();

        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 (0 === \count($setCodes)) {
            return $text;
        }

        return sprintf("\033[%sm%s\033[%sm", implode(';', $setCodes), $text, implode(';', $unsetCodes));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
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 = array();
    }

    /**
     * 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->styles, true) as $index => $stackedStyle) {
            if ($style->apply('') === $stackedStyle->apply('')) {
                $this->styles = \array_slice($this->styles, 0, $index);

                return $stackedStyle;
            }
        }

        throw new InvalidArgumentException('Incorrectly nested style tag found.');
    }

    /**
     * Computes current style with stacks top codes.
     *
     * @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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * Formatter class for console output.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
class OutputFormatter implements OutputFormatterInterface
{
    private $decorated;
    private $styles = array();
    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 = array())
    {
        $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($name, OutputFormatterStyleInterface $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 (0 != $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(strtolower($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, array("\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('/([^=]+)=([^;]+)(;|$)/', strtolower($string), $matches, PREG_SET_ORDER)) {
            return false;
        }

        $style = new OutputFormatterStyle();
        foreach ($matches as $match) {
            array_shift($match);

            if ('fg' == $match[0]) {
                $style->setForeground($match[1]);
            } elseif ('bg' == $match[0]) {
                $style->setBackground($match[1]);
            } else {
                try {
                    $style->setOption($match[1]);
                } catch (\InvalidArgumentException $e) {
                    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) > 0 ? $this->styleStack->getCurrent()->apply($text) : $text;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

/**
 * Formatter style interface for defining styles.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
interface OutputFormatterStyleInterface
{
    /**
     * Sets style foreground color.
     *
     * @param string $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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Formatter;

/**
 * Formatter interface for console output.
 *
 * @author Konstantin Kudryashov <ever.zet@gmail.com>
 */
interface OutputFormatterInterface
{
    /**
     * Sets the decorated flag.
     *
     * @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($name, OutputFormatterStyleInterface $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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

/**
 * Helps outputting debug information when running an external program from a command.
 *
 * An external program can be a Process, an HTTP request, or anything else.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class DebugFormatterHelper extends Helper
{
    private $colors = array('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default');
    private $started = array();
    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] = array('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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Descriptor\DescriptorInterface;
use Symfony\Component\Console\Descriptor\JsonDescriptor;
use Symfony\Component\Console\Descriptor\MarkdownDescriptor;
use Symfony\Component\Console\Descriptor\TextDescriptor;
use Symfony\Component\Console\Descriptor\XmlDescriptor;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * This class adds helper method to describe objects in various formats.
 *
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class DescriptorHelper extends Helper
{
    /**
     * @var DescriptorInterface[]
     */
    private $descriptors = array();

    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 OutputInterface $output
     * @param object          $object
     * @param array           $options
     *
     * @throws InvalidArgumentException when the given format is not supported
     */
    public function describe(OutputInterface $output, $object, array $options = array())
    {
        $options = array_merge(array(
            '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
     * @param DescriptorInterface $descriptor
     *
     * @return $this
     */
    public function register($format, DescriptorInterface $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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

/**
 * HelperInterface is the interface all helpers must implement.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
interface HelperInterface
{
    /**
     * Sets the helper set associated with this helper.
     */
    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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Input\InputAwareInterface;
use Symfony\Component\Console\Input\InputInterface;

/**
 * An implementation of InputAwareInterface for Helpers.
 *
 * @author Wouter J <waldio.webdesign@gmail.com>
 */
abstract class InputAwareHelper extends Helper implements InputAwareInterface
{
    protected $input;

    /**
     * {@inheritdoc}
     */
    public function setInput(InputInterface $input)
    {
        $this->input = $input;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * HelperSet represents a set of helpers to be used with a command.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class HelperSet implements \IteratorAggregate
{
    /**
     * @var Helper[]
     */
    private $helpers = array();
    private $command;

    /**
     * @param Helper[] $helpers An array of helper
     */
    public function __construct(array $helpers = array())
    {
        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));
        }

        if ('dialog' === $name && $this->helpers[$name] instanceof DialogHelper) {
            @trigger_error('"Symfony\Component\Console\Helper\DialogHelper" is deprecated since Symfony 2.5 and will be removed in 3.0. Use "Symfony\Component\Console\Helper\QuestionHelper" instead.', E_USER_DEPRECATED);
        } elseif ('progress' === $name && $this->helpers[$name] instanceof ProgressHelper) {
            @trigger_error('"Symfony\Component\Console\Helper\ProgressHelper" is deprecated since Symfony 2.5 and will be removed in 3.0. Use "Symfony\Component\Console\Helper\ProgressBar" instead.', E_USER_DEPRECATED);
        } elseif ('table' === $name && $this->helpers[$name] instanceof TableHelper) {
            @trigger_error('"Symfony\Component\Console\Helper\TableHelper" is deprecated since Symfony 2.5 and will be removed in 3.0. Use "Symfony\Component\Console\Helper\Table" instead.', E_USER_DEPRECATED);
        }

        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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * The Progress class provides helpers to display progress output.
 *
 * @author Chris Jones <leeked@gmail.com>
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @deprecated since version 2.5, to be removed in 3.0
 *             Use {@link ProgressBar} instead.
 */
class ProgressHelper extends Helper
{
    const FORMAT_QUIET = ' %percent%%';
    const FORMAT_NORMAL = ' %current%/%max% [%bar%] %percent%%';
    const FORMAT_VERBOSE = ' %current%/%max% [%bar%] %percent%% Elapsed: %elapsed%';
    const FORMAT_QUIET_NOMAX = ' %current%';
    const FORMAT_NORMAL_NOMAX = ' %current% [%bar%]';
    const FORMAT_VERBOSE_NOMAX = ' %current% [%bar%] Elapsed: %elapsed%';

    // options
    private $barWidth = 28;
    private $barChar = '=';
    private $emptyBarChar = '-';
    private $progressChar = '>';
    private $format = null;
    private $redrawFreq = 1;

    private $lastMessagesLength;
    private $barCharOriginal;

    /**
     * @var OutputInterface
     */
    private $output;

    /**
     * Current step.
     *
     * @var int
     */
    private $current;

    /**
     * Maximum number of steps.
     *
     * @var int
     */
    private $max;

    /**
     * Start time of the progress bar.
     *
     * @var int
     */
    private $startTime;

    /**
     * List of formatting variables.
     *
     * @var array
     */
    private $defaultFormatVars = array(
        'current',
        'max',
        'bar',
        'percent',
        'elapsed',
    );

    /**
     * Available formatting variables.
     *
     * @var array
     */
    private $formatVars;

    /**
     * Stored format part widths (used for padding).
     *
     * @var array
     */
    private $widths = array(
        'current' => 4,
        'max' => 4,
        'percent' => 3,
        'elapsed' => 6,
    );

    /**
     * Various time formats.
     *
     * @var array
     */
    private $timeFormats = array(
        array(0, '???'),
        array(2, '1 sec'),
        array(59, 'secs', 1),
        array(60, '1 min'),
        array(3600, 'mins', 60),
        array(5400, '1 hr'),
        array(86400, 'hrs', 3600),
        array(129600, '1 day'),
        array(604800, 'days', 86400),
    );

    public function __construct($triggerDeprecationError = true)
    {
        if ($triggerDeprecationError) {
            @trigger_error('The '.__CLASS__.' class is deprecated since Symfony 2.5 and will be removed in 3.0. Use the Symfony\Component\Console\Helper\ProgressBar class instead.', E_USER_DEPRECATED);
        }
    }

    /**
     * Sets the progress bar width.
     *
     * @param int $size The progress bar size
     */
    public function setBarWidth($size)
    {
        $this->barWidth = (int) $size;
    }

    /**
     * Sets the bar character.
     *
     * @param string $char A character
     */
    public function setBarCharacter($char)
    {
        $this->barChar = $char;
    }

    /**
     * Sets the empty bar character.
     *
     * @param string $char A character
     */
    public function setEmptyBarCharacter($char)
    {
        $this->emptyBarChar = $char;
    }

    /**
     * Sets the progress bar character.
     *
     * @param string $char A character
     */
    public function setProgressCharacter($char)
    {
        $this->progressChar = $char;
    }

    /**
     * Sets the progress bar format.
     *
     * @param string $format The format
     */
    public function setFormat($format)
    {
        $this->format = $format;
    }

    /**
     * Sets the redraw frequency.
     *
     * @param int $freq The frequency in steps
     */
    public function setRedrawFrequency($freq)
    {
        $this->redrawFreq = (int) $freq;
    }

    /**
     * Starts the progress output.
     *
     * @param OutputInterface $output An Output instance
     * @param int|null        $max    Maximum steps
     */
    public function start(OutputInterface $output, $max = null)
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        $this->startTime = time();
        $this->current = 0;
        $this->max = (int) $max;

        // Disabling output when it does not support ANSI codes as it would result in a broken display anyway.
        $this->output = $output->isDecorated() ? $output : new NullOutput();
        $this->lastMessagesLength = 0;
        $this->barCharOriginal = '';

        if (null === $this->format) {
            switch ($output->getVerbosity()) {
                case OutputInterface::VERBOSITY_QUIET:
                    $this->format = self::FORMAT_QUIET_NOMAX;
                    if ($this->max > 0) {
                        $this->format = self::FORMAT_QUIET;
                    }
                    break;
                case OutputInterface::VERBOSITY_VERBOSE:
                case OutputInterface::VERBOSITY_VERY_VERBOSE:
                case OutputInterface::VERBOSITY_DEBUG:
                    $this->format = self::FORMAT_VERBOSE_NOMAX;
                    if ($this->max > 0) {
                        $this->format = self::FORMAT_VERBOSE;
                    }
                    break;
                default:
                    $this->format = self::FORMAT_NORMAL_NOMAX;
                    if ($this->max > 0) {
                        $this->format = self::FORMAT_NORMAL;
                    }
                    break;
            }
        }

        $this->initialize();
    }

    /**
     * Advances the progress output X steps.
     *
     * @param int  $step   Number of steps to advance
     * @param bool $redraw Whether to redraw or not
     *
     * @throws LogicException
     */
    public function advance($step = 1, $redraw = false)
    {
        $this->setCurrent($this->current + $step, $redraw);
    }

    /**
     * Sets the current progress.
     *
     * @param int  $current The current progress
     * @param bool $redraw  Whether to redraw or not
     *
     * @throws LogicException
     */
    public function setCurrent($current, $redraw = false)
    {
        if (null === $this->startTime) {
            throw new LogicException('You must start the progress bar before calling setCurrent().');
        }

        $current = (int) $current;

        if ($current < $this->current) {
            throw new LogicException('You can\'t regress the progress bar');
        }

        if (0 === $this->current) {
            $redraw = true;
        }

        $prevPeriod = (int) ($this->current / $this->redrawFreq);

        $this->current = $current;

        $currPeriod = (int) ($this->current / $this->redrawFreq);
        if ($redraw || $prevPeriod !== $currPeriod || $this->max === $this->current) {
            $this->display();
        }
    }

    /**
     * Outputs the current progress string.
     *
     * @param bool $finish Forces the end result
     *
     * @throws LogicException
     */
    public function display($finish = false)
    {
        if (null === $this->startTime) {
            throw new LogicException('You must start the progress bar before calling display().');
        }

        $message = $this->format;
        foreach ($this->generate($finish) as $name => $value) {
            $message = str_replace("%{$name}%", $value, $message);
        }
        $this->overwrite($this->output, $message);
    }

    /**
     * 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()
    {
        $this->overwrite($this->output, '');
    }

    /**
     * Finishes the progress output.
     */
    public function finish()
    {
        if (null === $this->startTime) {
            throw new LogicException('You must start the progress bar before calling finish().');
        }

        if (null !== $this->startTime) {
            if (!$this->max) {
                $this->barChar = $this->barCharOriginal;
                $this->display(true);
            }
            $this->startTime = null;
            $this->output->writeln('');
            $this->output = null;
        }
    }

    /**
     * Initializes the progress helper.
     */
    private function initialize()
    {
        $this->formatVars = array();
        foreach ($this->defaultFormatVars as $var) {
            if (false !== strpos($this->format, "%{$var}%")) {
                $this->formatVars[$var] = true;
            }
        }

        if ($this->max > 0) {
            $this->widths['max'] = $this->strlen($this->max);
            $this->widths['current'] = $this->widths['max'];
        } else {
            $this->barCharOriginal = $this->barChar;
            $this->barChar = $this->emptyBarChar;
        }
    }

    /**
     * Generates the array map of format variables to values.
     *
     * @param bool $finish Forces the end result
     *
     * @return array Array of format vars and values
     */
    private function generate($finish = false)
    {
        $vars = array();
        $percent = 0;
        if ($this->max > 0) {
            $percent = (float) $this->current / $this->max;
        }

        if (isset($this->formatVars['bar'])) {
            if ($this->max > 0) {
                $completeBars = floor($percent * $this->barWidth);
            } else {
                if (!$finish) {
                    $completeBars = floor($this->current % $this->barWidth);
                } else {
                    $completeBars = $this->barWidth;
                }
            }

            $emptyBars = $this->barWidth - $completeBars - $this->strlen($this->progressChar);
            $bar = str_repeat($this->barChar, $completeBars);
            if ($completeBars < $this->barWidth) {
                $bar .= $this->progressChar;
                $bar .= str_repeat($this->emptyBarChar, $emptyBars);
            }

            $vars['bar'] = $bar;
        }

        if (isset($this->formatVars['elapsed'])) {
            $elapsed = time() - $this->startTime;
            $vars['elapsed'] = str_pad($this->humaneTime($elapsed), $this->widths['elapsed'], ' ', STR_PAD_LEFT);
        }

        if (isset($this->formatVars['current'])) {
            $vars['current'] = str_pad($this->current, $this->widths['current'], ' ', STR_PAD_LEFT);
        }

        if (isset($this->formatVars['max'])) {
            $vars['max'] = $this->max;
        }

        if (isset($this->formatVars['percent'])) {
            $vars['percent'] = str_pad(floor($percent * 100), $this->widths['percent'], ' ', STR_PAD_LEFT);
        }

        return $vars;
    }

    /**
     * Converts seconds into human-readable format.
     *
     * @param int $secs Number of seconds
     *
     * @return string Time in readable format
     */
    private function humaneTime($secs)
    {
        $text = '';
        foreach ($this->timeFormats as $format) {
            if ($secs < $format[0]) {
                if (2 == \count($format)) {
                    $text = $format[1];
                    break;
                } else {
                    $text = ceil($secs / $format[2]).' '.$format[1];
                    break;
                }
            }
        }

        return $text;
    }

    /**
     * Overwrites a previous message to the output.
     *
     * @param OutputInterface $output  An Output instance
     * @param string          $message The message
     */
    private function overwrite(OutputInterface $output, $message)
    {
        $length = $this->strlen($message);

        // append whitespace to match the last line's length
        if (null !== $this->lastMessagesLength && $this->lastMessagesLength > $length) {
            $message = str_pad($message, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT);
        }

        // carriage return
        $output->write("\x0D");
        $output->write($message);

        $this->lastMessagesLength = $this->strlen($message);
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'progress';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Output\NullOutput;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Provides helpers to display table output.
 *
 * @author Саша Стаменковић <umpirsky@gmail.com>
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @deprecated since version 2.5, to be removed in 3.0
 *             Use {@link Table} instead.
 */
class TableHelper extends Helper
{
    const LAYOUT_DEFAULT = 0;
    const LAYOUT_BORDERLESS = 1;
    const LAYOUT_COMPACT = 2;

    private $table;

    public function __construct($triggerDeprecationError = true)
    {
        if ($triggerDeprecationError) {
            @trigger_error('The '.__CLASS__.' class is deprecated since Symfony 2.5 and will be removed in 3.0. Use the Symfony\Component\Console\Helper\Table class instead.', E_USER_DEPRECATED);
        }

        $this->table = new Table(new NullOutput());
    }

    /**
     * Sets table layout type.
     *
     * @param int $layout self::LAYOUT_*
     *
     * @return $this
     *
     * @throws InvalidArgumentException when the table layout is not known
     */
    public function setLayout($layout)
    {
        switch ($layout) {
            case self::LAYOUT_BORDERLESS:
                $this->table->setStyle('borderless');
                break;

            case self::LAYOUT_COMPACT:
                $this->table->setStyle('compact');
                break;

            case self::LAYOUT_DEFAULT:
                $this->table->setStyle('default');
                break;

            default:
                throw new InvalidArgumentException(sprintf('Invalid table layout "%s".', $layout));
        }

        return $this;
    }

    public function setHeaders(array $headers)
    {
        $this->table->setHeaders($headers);

        return $this;
    }

    public function setRows(array $rows)
    {
        $this->table->setRows($rows);

        return $this;
    }

    public function addRows(array $rows)
    {
        $this->table->addRows($rows);

        return $this;
    }

    public function addRow(array $row)
    {
        $this->table->addRow($row);

        return $this;
    }

    public function setRow($column, array $row)
    {
        $this->table->setRow($column, $row);

        return $this;
    }

    /**
     * Sets padding character, used for cell padding.
     *
     * @param string $paddingChar
     *
     * @return $this
     */
    public function setPaddingChar($paddingChar)
    {
        $this->table->getStyle()->setPaddingChar($paddingChar);

        return $this;
    }

    /**
     * Sets horizontal border character.
     *
     * @param string $horizontalBorderChar
     *
     * @return $this
     */
    public function setHorizontalBorderChar($horizontalBorderChar)
    {
        $this->table->getStyle()->setHorizontalBorderChar($horizontalBorderChar);

        return $this;
    }

    /**
     * Sets vertical border character.
     *
     * @param string $verticalBorderChar
     *
     * @return $this
     */
    public function setVerticalBorderChar($verticalBorderChar)
    {
        $this->table->getStyle()->setVerticalBorderChar($verticalBorderChar);

        return $this;
    }

    /**
     * Sets crossing character.
     *
     * @param string $crossingChar
     *
     * @return $this
     */
    public function setCrossingChar($crossingChar)
    {
        $this->table->getStyle()->setCrossingChar($crossingChar);

        return $this;
    }

    /**
     * Sets header cell format.
     *
     * @param string $cellHeaderFormat
     *
     * @return $this
     */
    public function setCellHeaderFormat($cellHeaderFormat)
    {
        $this->table->getStyle()->setCellHeaderFormat($cellHeaderFormat);

        return $this;
    }

    /**
     * Sets row cell format.
     *
     * @param string $cellRowFormat
     *
     * @return $this
     */
    public function setCellRowFormat($cellRowFormat)
    {
        $this->table->getStyle()->setCellHeaderFormat($cellRowFormat);

        return $this;
    }

    /**
     * Sets row cell content format.
     *
     * @param string $cellRowContentFormat
     *
     * @return $this
     */
    public function setCellRowContentFormat($cellRowContentFormat)
    {
        $this->table->getStyle()->setCellRowContentFormat($cellRowContentFormat);

        return $this;
    }

    /**
     * Sets table border format.
     *
     * @param string $borderFormat
     *
     * @return $this
     */
    public function setBorderFormat($borderFormat)
    {
        $this->table->getStyle()->setBorderFormat($borderFormat);

        return $this;
    }

    /**
     * Sets cell padding type.
     *
     * @param int $padType STR_PAD_*
     *
     * @return $this
     */
    public function setPadType($padType)
    {
        $this->table->getStyle()->setPadType($padType);

        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(OutputInterface $output)
    {
        $p = new \ReflectionProperty($this->table, 'output');
        $p->setAccessible(true);
        $p->setValue($this->table, $output);

        $this->table->render();
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'table';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\ProcessBuilder;

/**
 * The ProcessHelper class provides helpers to run external processes.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
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, $callback = null, $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE)
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        $formatter = $this->getHelperSet()->get('debug_formatter');

        if (\is_array($cmd)) {
            $process = ProcessBuilder::create($cmd)->getProcess();
        } elseif ($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, $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 $output, Process $process, $callback = null)
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        $formatter = $this->getHelperSet()->get('debug_formatter');

        $that = $this;

        return function ($type, $buffer) use ($output, $process, $callback, $formatter, $that) {
            $output->write($formatter->progress(spl_object_hash($process), $that->escapeString($buffer), Process::ERR === $type));

            if (null !== $callback) {
                \call_user_func($callback, $type, $buffer);
            }
        };
    }

    /**
     * This method is public for PHP 5.3 compatibility, it should be private.
     *
     * @internal
     */
    public 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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

/**
 * Marks a row as being a separator.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class TableSeparator extends TableCell
{
    public function __construct(array $options = array())
    {
        parent::__construct('', $options);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
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 QuestionHelper class provides helpers to interact with the user.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class QuestionHelper extends Helper
{
    private $inputStream;
    private static $shell;
    private static $stty;

    /**
     * Asks a question to the user.
     *
     * @return mixed The user answer
     *
     * @throws RuntimeException If there is no data to read in the input stream
     */
    public function ask(InputInterface $input, OutputInterface $output, Question $question)
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        if (!$input->isInteractive()) {
            $default = $question->getDefault();

            if (null !== $default && $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 (!$question->getValidator()) {
            return $this->doAsk($output, $question);
        }

        $that = $this;

        $interviewer = function () use ($output, $question, $that) {
            return $that->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.
     *
     * @param resource $stream The input stream
     *
     * @throws InvalidArgumentException In case the stream is not a resource
     */
    public function setInputStream($stream)
    {
        if (!\is_resource($stream)) {
            throw new InvalidArgumentException('Input stream must be a valid resource.');
        }

        $this->inputStream = $stream;
    }

    /**
     * Returns the helper's input stream.
     *
     * @return resource
     */
    public function getInputStream()
    {
        return $this->inputStream;
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'question';
    }

    /**
     * Asks the question to the user.
     *
     * This method is public for PHP 5.3 compatibility, it should be private.
     *
     * @return bool|mixed|string|null
     *
     * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
     */
    public function doAsk(OutputInterface $output, Question $question)
    {
        $this->writePrompt($output, $question);

        $inputStream = $this->inputStream ?: STDIN;
        $autocomplete = $question->getAutocompleterValues();

        if (null === $autocomplete || !$this->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($inputStream, 4096);
                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($autocomplete, false)));
        }

        $ret = \strlen($ret) > 0 ? $ret : $question->getDefault();

        if ($normalizer = $question->getNormalizer()) {
            return $normalizer($ret);
        }

        return $ret;
    }

    /**
     * Outputs the question prompt.
     */
    protected function writePrompt(OutputInterface $output, Question $question)
    {
        $message = $question->getQuestion();

        if ($question instanceof ChoiceQuestion) {
            $maxWidth = max(array_map(array($this, 'strlen'), array_keys($question->getChoices())));

            $messages = (array) $question->getQuestion();
            foreach ($question->getChoices() as $key => $value) {
                $width = $maxWidth - $this->strlen($key);
                $messages[] = '  [<info>'.$key.str_repeat(' ', $width).'</info>] '.$value;
            }

            $output->writeln($messages);

            $message = $question->getPrompt();
        }

        $output->write($message);
    }

    /**
     * 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 OutputInterface $output
     * @param Question        $question
     * @param resource        $inputStream
     * @param array           $autocomplete
     *
     * @return string
     */
    private function autocomplete(OutputInterface $output, Question $question, $inputStream, array $autocomplete)
    {
        $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($inputStream, 1);

            // Backspace Character
            if ("\177" === $c) {
                if (0 === $numMatches && 0 !== $i) {
                    --$i;
                    // Move cursor backwards
                    $output->write("\033[1D");
                }

                if (0 === $i) {
                    $ofs = -1;
                    $matches = $autocomplete;
                    $numMatches = \count($matches);
                } else {
                    $numMatches = 0;
                }

                // Pop the last character off the end of our string
                $ret = substr($ret, 0, $i);
            } elseif ("\033" === $c) {
                // Did we read an escape sequence?
                $c .= fread($inputStream, 2);

                // A = Up Arrow. B = Down Arrow
                if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) {
                    if ('A' === $c[2] && -1 === $ofs) {
                        $ofs = 0;
                    }

                    if (0 === $numMatches) {
                        continue;
                    }

                    $ofs += ('A' === $c[2]) ? -1 : 1;
                    $ofs = ($numMatches + $ofs) % $numMatches;
                }
            } elseif (\ord($c) < 32) {
                if ("\t" === $c || "\n" === $c) {
                    if ($numMatches > 0 && -1 !== $ofs) {
                        $ret = $matches[$ofs];
                        // Echo out remaining chars for current match
                        $output->write(substr($ret, $i));
                        $i = \strlen($ret);
                    }

                    if ("\n" === $c) {
                        $output->write($c);
                        break;
                    }

                    $numMatches = 0;
                }

                continue;
            } else {
                $output->write($c);
                $ret .= $c;
                ++$i;

                $numMatches = 0;
                $ofs = 0;

                foreach ($autocomplete as $value) {
                    // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
                    if (0 === strpos($value, $ret)) {
                        $matches[$numMatches++] = $value;
                    }
                }
            }

            // Erase characters from cursor to end of line
            $output->write("\033[K");

            if ($numMatches > 0 && -1 !== $ofs) {
                // Save cursor position
                $output->write("\0337");
                // Write highlighted text
                $output->write('<hl>'.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $i)).'</hl>');
                // Restore cursor position
                $output->write("\0338");
            }
        }

        // Reset stty so it behaves normally again
        shell_exec(sprintf('stty %s', $sttyMode));

        return $ret;
    }

    /**
     * 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__, 0, 5)) {
                $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 ($this->hasSttyAvailable()) {
            $sttyMode = shell_exec('stty -g');

            shell_exec('stty -echo');
            $value = fgets($inputStream, 4096);
            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($interviewer, OutputInterface $output, Question $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 (array('bash', 'zsh', 'ksh', 'csh') as $sh) {
                if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) {
                    self::$shell = $sh;
                    break;
                }
            }
        }

        return self::$shell;
    }

    /**
     * Returns whether Stty is available or not.
     *
     * @return bool
     */
    private function hasSttyAvailable()
    {
        if (null !== self::$stty) {
            return self::$stty;
        }

        exec('stty 2>&1', $output, $exitcode);

        return self::$stty = 0 === $exitcode;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Provides helpers to display a table.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Саша Стаменковић <umpirsky@gmail.com>
 * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
 * @author Max Grigorian <maxakawizard@gmail.com>
 */
class Table
{
    /**
     * Table headers.
     */
    private $headers = array();

    /**
     * Table rows.
     */
    private $rows = array();

    /**
     * Column widths cache.
     */
    private $columnWidths = array();

    /**
     * Number of columns cache.
     *
     * @var int
     */
    private $numberOfColumns;

    /**
     * @var OutputInterface
     */
    private $output;

    /**
     * @var TableStyle
     */
    private $style;

    /**
     * @var array
     */
    private $columnStyles = array();

    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($name, TableStyle $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();
    }

    public function setHeaders(array $headers)
    {
        $headers = array_values($headers);
        if (!empty($headers) && !\is_array($headers[0])) {
            $headers = array($headers);
        }

        $this->headers = $headers;

        return $this;
    }

    public function setRows(array $rows)
    {
        $this->rows = array();

        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 (0 === $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->columnWidths[$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 array  $row
     * @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 array  $row
     * @param int    $column
     * @param string $cellFormat
     */
    private function renderCell(array $row, $column, $cellFormat)
    {
        $cell = isset($row[$column]) ? $row[$column] : '';
        $width = $this->columnWidths[$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->columnWidths[$nextColumn];
            }
        }

        // str_pad won't work properly with multi-byte strings, we need to fix the padding
        if (false !== $encoding = mb_detect_encoding($cell, null, true)) {
            $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($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $style->getPadType()));
    }

    /**
     * Calculate number of columns for this table.
     */
    private function calculateNumberOfColumns()
    {
        if (null !== $this->numberOfColumns) {
            return;
        }

        $columns = array(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 = array();
        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, array('colspan' => $cell->getColspan()));
                    }
                    if (0 === $lineKey) {
                        $rows[$rowKey][$column] = $line;
                    } else {
                        $unmergedRows[$rowKey][$lineKey][$column] = $line;
                    }
                }
            }
        }

        $tableRows = array();
        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 array $rows
     * @param int   $line
     *
     * @return array
     */
    private function fillNextRows(array $rows, $line)
    {
        $unmergedRows = array();
        foreach ($rows[$line] as $column => $cell) {
            if ($cell instanceof TableCell && $cell->getRowspan() > 1) {
                $nbLines = $cell->getRowspan() - 1;
                $lines = array($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], array('colspan' => $cell->getColspan()));
                    unset($lines[0]);
                }

                // create a two dimensional array (rowspan x colspan)
                $unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, array()), $unmergedRows);
                foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
                    $value = isset($lines[$unmergedRowKey - $line]) ? $lines[$unmergedRowKey - $line] : '';
                    $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, array('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], $cellKey, 0, array($cell));
                }
            } else {
                $row = $this->copyRow($rows, $unmergedRowKey - 1);
                foreach ($unmergedRow as $column => $cell) {
                    if (!empty($cell)) {
                        $row[$column] = $unmergedRow[$column];
                    }
                }
                array_splice($rows, $unmergedRowKey, 0, array($row));
            }
        }

        return $rows;
    }

    /**
     * fill cells for a row that contains colspan > 1.
     *
     * @return array
     */
    private function fillCells($row)
    {
        $newRow = array();
        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 array $rows
     * @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('', array('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($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1));
            }
        }

        return $columns;
    }

    /**
     * Calculates columns widths.
     *
     * @param array $rows
     */
    private function calculateColumnsWidth($rows)
    {
        for ($column = 0; $column < $this->numberOfColumns; ++$column) {
            $lengths = array();
            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($textContent, ceil($textLength / $cell->getColspan()));
                            foreach ($contentColumns as $position => $content) {
                                $row[$i + $position] = $content;
                            }
                        }
                    }
                }

                $lengths[] = $this->getCellWidth($row, $column);
            }

            $this->columnWidths[$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 array $row
     * @param int   $column
     *
     * @return int
     */
    private function getCellWidth(array $row, $column)
    {
        if (isset($row[$column])) {
            $cell = $row[$column];
            $cellWidth = Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell);

            return $cellWidth;
        }

        return 0;
    }

    /**
     * Called after rendering to cleanup cache data.
     */
    private function cleanup()
    {
        $this->columnWidths = array();
        $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 array(
            '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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Formatter\OutputFormatter;

/**
 * The Formatter class provides helpers to format messages.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class FormatterHelper extends Helper
{
    /**
     * Formats a message within a section.
     *
     * @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 = array($messages);
        }

        $len = 0;
        $lines = array();
        foreach ($messages as $message) {
            $message = OutputFormatter::escape($message);
            $lines[] = sprintf($large ? '  %s  ' : ' %s ', $message);
            $len = max($this->strlen($message) + ($large ? 4 : 2), $len);
        }

        $messages = $large ? array(str_repeat(' ', $len)) : array();
        for ($i = 0; isset($lines[$i]); ++$i) {
            $messages[] = $lines[$i].str_repeat(' ', $len - $this->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);
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'formatter';
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * The Dialog class provides helpers to interact with the user.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @deprecated since version 2.5, to be removed in 3.0.
 *             Use {@link \Symfony\Component\Console\Helper\QuestionHelper} instead.
 */
class DialogHelper extends InputAwareHelper
{
    private $inputStream;
    private static $shell;
    private static $stty;

    public function __construct($triggerDeprecationError = true)
    {
        if ($triggerDeprecationError) {
            @trigger_error('"Symfony\Component\Console\Helper\DialogHelper" is deprecated since Symfony 2.5 and will be removed in 3.0. Use "Symfony\Component\Console\Helper\QuestionHelper" instead.', E_USER_DEPRECATED);
        }
    }

    /**
     * Asks the user to select a value.
     *
     * @param OutputInterface $output       An Output instance
     * @param string|array    $question     The question to ask
     * @param array           $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
     *
     * @return int|string|array The selected value or values (the key of the choices array)
     *
     * @throws InvalidArgumentException
     */
    public function select(OutputInterface $output, $question, $choices, $default = null, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false)
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        $width = max(array_map('strlen', array_keys($choices)));

        $messages = (array) $question;
        foreach ($choices as $key => $value) {
            $messages[] = sprintf("  [<info>%-{$width}s</info>] %s", $key, $value);
        }

        $output->writeln($messages);

        $result = $this->askAndValidate($output, '> ', function ($picked) use ($choices, $errorMessage, $multiselect) {
            // Collapse all spaces.
            $selectedChoices = str_replace(' ', '', $picked);

            if ($multiselect) {
                // Check for a separated comma values
                if (!preg_match('/^[a-zA-Z0-9_-]+(?:,[a-zA-Z0-9_-]+)*$/', $selectedChoices, $matches)) {
                    throw new InvalidArgumentException(sprintf($errorMessage, $picked));
                }
                $selectedChoices = explode(',', $selectedChoices);
            } else {
                $selectedChoices = array($picked);
            }

            $multiselectChoices = array();

            foreach ($selectedChoices as $value) {
                if (empty($choices[$value])) {
                    throw new InvalidArgumentException(sprintf($errorMessage, $value));
                }
                $multiselectChoices[] = $value;
            }

            if ($multiselect) {
                return $multiselectChoices;
            }

            return $picked;
        }, $attempts, $default);

        return $result;
    }

    /**
     * Asks a question to the user.
     *
     * @param OutputInterface $output       An Output instance
     * @param string|array    $question     The question to ask
     * @param string          $default      The default answer if none is given by the user
     * @param array           $autocomplete List of values to autocomplete
     *
     * @return string The user answer
     *
     * @throws RuntimeException If there is no data to read in the input stream
     */
    public function ask(OutputInterface $output, $question, $default = null, array $autocomplete = null)
    {
        if ($this->input && !$this->input->isInteractive()) {
            return $default;
        }

        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        $output->write($question);

        $inputStream = $this->inputStream ?: STDIN;

        if (null === $autocomplete || !$this->hasSttyAvailable()) {
            $ret = fgets($inputStream, 4096);
            if (false === $ret) {
                throw new RuntimeException('Aborted');
            }
            $ret = trim($ret);
        } else {
            $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($inputStream, 1);

                // Backspace Character
                if ("\177" === $c) {
                    if (0 === $numMatches && 0 !== $i) {
                        --$i;
                        // Move cursor backwards
                        $output->write("\033[1D");
                    }

                    if (0 === $i) {
                        $ofs = -1;
                        $matches = $autocomplete;
                        $numMatches = \count($matches);
                    } else {
                        $numMatches = 0;
                    }

                    // Pop the last character off the end of our string
                    $ret = substr($ret, 0, $i);
                } elseif ("\033" === $c) {
                    // Did we read an escape sequence?
                    $c .= fread($inputStream, 2);

                    // A = Up Arrow. B = Down Arrow
                    if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) {
                        if ('A' === $c[2] && -1 === $ofs) {
                            $ofs = 0;
                        }

                        if (0 === $numMatches) {
                            continue;
                        }

                        $ofs += ('A' === $c[2]) ? -1 : 1;
                        $ofs = ($numMatches + $ofs) % $numMatches;
                    }
                } elseif (\ord($c) < 32) {
                    if ("\t" === $c || "\n" === $c) {
                        if ($numMatches > 0 && -1 !== $ofs) {
                            $ret = $matches[$ofs];
                            // Echo out remaining chars for current match
                            $output->write(substr($ret, $i));
                            $i = \strlen($ret);
                        }

                        if ("\n" === $c) {
                            $output->write($c);
                            break;
                        }

                        $numMatches = 0;
                    }

                    continue;
                } else {
                    $output->write($c);
                    $ret .= $c;
                    ++$i;

                    $numMatches = 0;
                    $ofs = 0;

                    foreach ($autocomplete as $value) {
                        // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
                        if (0 === strpos($value, $ret) && $i !== \strlen($value)) {
                            $matches[$numMatches++] = $value;
                        }
                    }
                }

                // Erase characters from cursor to end of line
                $output->write("\033[K");

                if ($numMatches > 0 && -1 !== $ofs) {
                    // Save cursor position
                    $output->write("\0337");
                    // Write highlighted text
                    $output->write('<hl>'.substr($matches[$ofs], $i).'</hl>');
                    // Restore cursor position
                    $output->write("\0338");
                }
            }

            // Reset stty so it behaves normally again
            shell_exec(sprintf('stty %s', $sttyMode));
        }

        return \strlen($ret) > 0 ? $ret : $default;
    }

    /**
     * Asks a confirmation to the user.
     *
     * The question will be asked until the user answers by nothing, yes, or no.
     *
     * @param OutputInterface $output   An Output instance
     * @param string|array    $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(OutputInterface $output, $question, $default = true)
    {
        $answer = 'z';
        while ($answer && !\in_array(strtolower($answer[0]), array('y', 'n'))) {
            $answer = $this->ask($output, $question);
        }

        if (false === $default) {
            return $answer && 'y' == strtolower($answer[0]);
        }

        return !$answer || 'y' == strtolower($answer[0]);
    }

    /**
     * Asks a question to the user, the response is hidden.
     *
     * @param OutputInterface $output   An Output instance
     * @param string|array    $question The question
     * @param bool            $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not
     *
     * @return string The answer
     *
     * @throws RuntimeException In case the fallback is deactivated and the response can not be hidden
     */
    public function askHiddenResponse(OutputInterface $output, $question, $fallback = true)
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        if ('\\' === \DIRECTORY_SEPARATOR) {
            $exe = __DIR__.'/../Resources/bin/hiddeninput.exe';

            // handle code running from a phar
            if ('phar:' === substr(__FILE__, 0, 5)) {
                $tmpExe = sys_get_temp_dir().'/hiddeninput.exe';
                copy($exe, $tmpExe);
                $exe = $tmpExe;
            }

            $output->write($question);
            $value = rtrim(shell_exec($exe));
            $output->writeln('');

            if (isset($tmpExe)) {
                unlink($tmpExe);
            }

            return $value;
        }

        if ($this->hasSttyAvailable()) {
            $output->write($question);

            $sttyMode = shell_exec('stty -g');

            shell_exec('stty -echo');
            $value = fgets($this->inputStream ?: STDIN, 4096);
            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()) {
            $output->write($question);
            $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;
        }

        if ($fallback) {
            return $this->ask($output, $question);
        }

        throw new RuntimeException('Unable to hide the response');
    }

    /**
     * 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 OutputInterface $output       An Output instance
     * @param string|array    $question     The question to ask
     * @param callable        $validator    A PHP callback
     * @param int|false       $attempts     Max number of times to ask before giving up (false by default, which means infinite)
     * @param string          $default      The default answer if none is given by the user
     * @param array           $autocomplete List of values to autocomplete
     *
     * @return mixed
     *
     * @throws \Exception When any of the validators return an error
     */
    public function askAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $default = null, array $autocomplete = null)
    {
        $that = $this;

        $interviewer = function () use ($output, $question, $default, $autocomplete, $that) {
            return $that->ask($output, $question, $default, $autocomplete);
        };

        return $this->validateAttempts($interviewer, $output, $validator, $attempts);
    }

    /**
     * Asks for a value, hide 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 OutputInterface $output    An Output instance
     * @param string|array    $question  The question to ask
     * @param callable        $validator A PHP callback
     * @param int|false       $attempts  Max number of times to ask before giving up (false by default, which means infinite)
     * @param bool            $fallback  In case the response can not be hidden, whether to fallback on non-hidden question or not
     *
     * @return string The response
     *
     * @throws \Exception       When any of the validators return an error
     * @throws RuntimeException In case the fallback is deactivated and the response can not be hidden
     */
    public function askHiddenResponseAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $fallback = true)
    {
        $that = $this;

        $interviewer = function () use ($output, $question, $fallback, $that) {
            return $that->askHiddenResponse($output, $question, $fallback);
        };

        return $this->validateAttempts($interviewer, $output, $validator, $attempts);
    }

    /**
     * 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 setInputStream($stream)
    {
        $this->inputStream = $stream;
    }

    /**
     * Returns the helper's input stream.
     *
     * @return resource|null The input stream or null if the default STDIN is used
     */
    public function getInputStream()
    {
        return $this->inputStream;
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return 'dialog';
    }

    /**
     * Return 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 (array('bash', 'zsh', 'ksh', 'csh') as $sh) {
                if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) {
                    self::$shell = $sh;
                    break;
                }
            }
        }

        return self::$shell;
    }

    private function hasSttyAvailable()
    {
        if (null !== self::$stty) {
            return self::$stty;
        }

        exec('stty 2>&1', $output, $exitcode);

        return self::$stty = 0 === $exitcode;
    }

    /**
     * Validate an attempt.
     *
     * @param callable        $interviewer A callable that will ask for a question and return the result
     * @param OutputInterface $output      An Output instance
     * @param callable        $validator   A PHP callback
     * @param int|false       $attempts    Max number of times to ask before giving up; false will ask infinitely
     *
     * @return string 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($interviewer, OutputInterface $output, $validator, $attempts)
    {
        if ($output instanceof ConsoleOutputInterface) {
            $output = $output->getErrorOutput();
        }

        $e = null;
        while (false === $attempts || $attempts--) {
            if (null !== $e) {
                $output->writeln($this->getHelperSet()->get('formatter')->formatBlock($e->getMessage(), 'error'));
            }

            try {
                return \call_user_func($validator, $interviewer());
            } catch (\Exception $e) {
            }
        }

        throw $e;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace 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 <kevinbond@gmail.com>
 */
class SymfonyQuestionHelper extends QuestionHelper
{
    /**
     * {@inheritdoc}
     */
    public function ask(InputInterface $input, OutputInterface $output, Question $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) && 0 === \strlen($value)) {
                    throw new LogicException('A value is required.');
                }
            }

            return $value;
        });

        return parent::ask($input, $output, $question);
    }

    /**
     * {@inheritdoc}
     */
    protected function writePrompt(OutputInterface $output, Question $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>]:', $text, OutputFormatter::escape(implode(', ', $default)));

                break;

            case $question instanceof ChoiceQuestion:
                $choices = $question->getChoices();
                $text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape(isset($choices[$default]) ? $choices[$default] : $default));

                break;

            default:
                $text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($default));
        }

        $output->writeln($text);

        if ($question instanceof ChoiceQuestion) {
            $width = max(array_map('strlen', array_keys($question->getChoices())));

            foreach ($question->getChoices() as $key => $value) {
                $output->writeln(sprintf("  [<comment>%-${width}s</comment>] %s", $key, $value));
            }
        }

        $output->write(' > ');
    }

    /**
     * {@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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * The ProgressBar provides helpers to display progress output.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Chris Jones <leeked@gmail.com>
 */
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 = array();
    private $overwrite = true;
    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);

        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)
    {
        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 progress bar step.
     *
     * @deprecated since version 2.6, to be removed in 3.0. Use {@link getProgress()} instead.
     *
     * @return int The progress bar step
     */
    public function getStep()
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the getProgress() method instead.', E_USER_DEPRECATED);

        return $this->getProgress();
    }

    /**
     * Gets the current step position.
     *
     * @return int The progress bar step
     */
    public function getProgress()
    {
        return $this->step;
    }

    /**
     * Gets the progress bar step width.
     *
     * @internal This method is public for PHP 5.3 compatibility, it should not be used.
     *
     * @return int The progress bar step width
     */
    public 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 = (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) $freq, 1);
    }

    /**
     * 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
     *
     * @throws LogicException
     */
    public function advance($step = 1)
    {
        $this->setProgress($this->step + $step);
    }

    /**
     * Sets the current progress.
     *
     * @deprecated since version 2.6, to be removed in 3.0. Use {@link setProgress()} instead.
     *
     * @param int $step The current progress
     *
     * @throws LogicException
     */
    public function setCurrent($step)
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the setProgress() method instead.', E_USER_DEPRECATED);

        $this->setProgress($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
     *
     * @throws LogicException
     */
    public function setProgress($step)
    {
        $step = (int) $step;
        if ($step < $this->step) {
            throw new LogicException('You can\'t regress the progress bar.');
        }

        if ($this->max && $step > $this->max) {
            $this->max = $step;
        }

        $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());
        }

        // these 3 variables can be removed in favor of using $this in the closure when support for PHP 5.3 will be dropped.
        $self = $this;
        $output = $this->output;
        $messages = $this->messages;
        $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) use ($self, $output, $messages) {
            if ($formatter = $self::getPlaceholderFormatterDefinition($matches[1])) {
                $text = \call_user_func($formatter, $self, $output);
            } elseif (isset($messages[$matches[1]])) {
                $text = $messages[$matches[1]];
            } else {
                return $matches[0];
            }

            if (isset($matches[2])) {
                $text = sprintf('%'.$matches[2], $text);
            }

            return $text;
        }, $this->format));
    }

    /**
     * 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) {
                // Move the cursor to the beginning of the line
                $this->output->write("\x0D");

                // Erase the line
                $this->output->write("\x1B[2K");

                // Erase previous lines
                if ($this->formatLineCount > 0) {
                    $this->output->write(str_repeat("\x1B[1A\x1B[2K", $this->formatLineCount));
                }
            }
        } elseif ($this->step > 0) {
            $this->output->writeln('');
        }

        $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 array(
            'bar' => function (ProgressBar $bar, OutputInterface $output) {
                $completeBars = floor($bar->getMaxSteps() > 0 ? $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 (ProgressBar $bar) {
                return Helper::formatTime(time() - $bar->getStartTime());
            },
            'remaining' => function (ProgressBar $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 (ProgressBar $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 (ProgressBar $bar) {
                return Helper::formatMemory(memory_get_usage(true));
            },
            'current' => function (ProgressBar $bar) {
                return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', STR_PAD_LEFT);
            },
            'max' => function (ProgressBar $bar) {
                return $bar->getMaxSteps();
            },
            'percent' => function (ProgressBar $bar) {
                return floor($bar->getProgressPercent() * 100);
            },
        );
    }

    private static function initFormats()
    {
        return array(
            '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%',
        );
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * Helper is the base class for all helper classes.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
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($string, null, true)) {
            return \strlen($string);
        }

        return mb_strwidth($string, $encoding);
    }

    public static function formatTime($secs)
    {
        static $timeFormats = array(
            array(0, '< 1 sec'),
            array(1, '1 sec'),
            array(2, 'secs', 1),
            array(60, '1 min'),
            array(120, 'mins', 60),
            array(3600, '1 hr'),
            array(7200, 'hrs', 3600),
            array(86400, '1 day'),
            array(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 (2 == \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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * @author Kevin Bond <kevinbond@gmail.com>
 */
class ProgressIndicator
{
    private $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 OutputInterface $output
     * @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 = array('-', '\\', '|', '/');
        }

        $indicatorValues = array_values($indicatorValues);

        if (2 > \count($indicatorValues)) {
            throw new InvalidArgumentException('Must have at least 2 indicator value characters.');
        }

        $this->format = self::getFormatDefinition($format);
        $this->indicatorChangeInterval = $indicatorChangeInterval;
        $this->indicatorValues = $indicatorValues;
        $this->startTime = time();
    }

    /**
     * Sets the current indicator message.
     *
     * @param string|null $message
     */
    public function setMessage($message)
    {
        $this->message = $message;

        $this->display();
    }

    /**
     * Gets the current indicator message.
     *
     * @return string|null
     *
     * @internal for PHP 5.3 compatibility
     */
    public function getMessage()
    {
        return $this->message;
    }

    /**
     * Gets the progress bar start time.
     *
     * @return int The progress bar start time
     *
     * @internal for PHP 5.3 compatibility
     */
    public function getStartTime()
    {
        return $this->startTime;
    }

    /**
     * Gets the current animated indicator character.
     *
     * @return string
     *
     * @internal for PHP 5.3 compatibility
     */
    public function getCurrentValue()
    {
        return $this->indicatorValues[$this->indicatorCurrent % \count($this->indicatorValues)];
    }

    /**
     * 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 array(
            'indicator' => function (ProgressIndicator $indicator) {
                return $indicator->getCurrentValue();
            },
            'message' => function (ProgressIndicator $indicator) {
                return $indicator->getMessage();
            },
            'elapsed' => function (ProgressIndicator $indicator) {
                return Helper::formatTime(time() - $indicator->getStartTime());
            },
            'memory' => function () {
                return Helper::formatMemory(memory_get_usage(true));
            },
        );
    }

    private static function initFormats()
    {
        return array(
            '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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;

/**
 * Defines the styles for a Table.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Саша Стаменковић <umpirsky@gmail.com>
 */
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, array(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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Helper;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
 */
class TableCell
{
    private $value;
    private $options = array(
        'rowspan' => 1,
        'colspan' => 1,
    );

    /**
     * @param string $value
     * @param array  $options
     */
    public function __construct($value = '', array $options = array())
    {
        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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

/**
 * ConsoleOutputInterface is the interface implemented by ConsoleOutput class.
 * This adds information about stderr output stream.
 *
 * @author Dariusz Górecki <darek.krk@gmail.com>
 */
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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * ConsoleOutput is the default class for all CLI output. It uses STDOUT.
 *
 * This class is a convenient wrapper around `StreamOutput`.
 *
 *     $output = new ConsoleOutput();
 *
 * This is equivalent to:
 *
 *     $output = new StreamOutput(fopen('php://stdout', 'w'));
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
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 = null, OutputFormatterInterface $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 = array(
            \function_exists('php_uname') ? php_uname('s') : '',
            getenv('OSTYPE'),
            PHP_OS,
        );

        return false !== stripos(implode(';', $checks), 'OS400');
    }

    /**
     * @return resource
     */
    private function openOutputStream()
    {
        $outputStream = $this->hasStdoutSupport() ? 'php://stdout' : 'php://output';

        return @fopen($outputStream, 'w') ?: fopen('php://output', 'w');
    }

    /**
     * @return resource
     */
    private function openErrorStream()
    {
        $errorStream = $this->hasStderrSupport() ? 'php://stderr' : 'php://output';

        return fopen($errorStream, 'w');
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * NullOutput suppresses all output.
 *
 *     $output = new NullOutput();
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Tobias Schultze <http://tobion.de>
 */
class NullOutput implements OutputInterface
{
    /**
     * {@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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * StreamOutput writes the output to a given stream.
 *
 * Usage:
 *
 *     $output = new StreamOutput(fopen('php://stdout', 'w'));
 *
 * As `StreamOutput` can use any stream, you can also use a file:
 *
 *     $output = new StreamOutput(fopen('/path/to/output.log', 'a', false));
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class StreamOutput extends Output
{
    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 = null, OutputFormatterInterface $formatter = null)
    {
        if (!\is_resource($stream) || 'stream' !== get_resource_type($stream)) {
            throw new InvalidArgumentException('The StreamOutput class needs a stream as its first argument.');
        }

        $this->stream = $stream;

        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;
        }

        if (false === @fwrite($this->stream, $message)) {
            // should never happen
            throw new RuntimeException('Unable to write output.');
        }

        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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * Base class for output classes.
 *
 * There are five levels of verbosity:
 *
 *  * normal: no option passed (normal output)
 *  * verbose: -v (more output)
 *  * very verbose: -vv (highly extended output)
 *  * debug: -vvv (all debug output)
 *  * quiet: -q (no output)
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
abstract class Output implements OutputInterface
{
    private $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 = false, OutputFormatterInterface $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($messages, true, $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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

/**
 * @author Jean-François Simon <contact@jfsimon.fr>
 */
class BufferedOutput extends Output
{
    private $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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Output;

use Symfony\Component\Console\Formatter\OutputFormatterInterface;

/**
 * OutputInterface is the interface implemented by all Output classes.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
interface OutputInterface
{
    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();

    /**
     * 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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Event;

/**
 * Allows to do things before the command is executed, like skipping the command or changing the input.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Event;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Allows to manipulate the exit code of a command after its execution.
 *
 * @author Francesco Levorato <git@flevour.net>
 */
class ConsoleTerminateEvent extends ConsoleEvent
{
    /**
     * The exit code of the command.
     *
     * @var int
     */
    private $exitCode;

    public function __construct(Command $command, InputInterface $input, OutputInterface $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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Event;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\EventDispatcher\Event;

/**
 * Allows to inspect input and output of a command.
 *
 * @author Francesco Levorato <git@flevour.net>
 */
class ConsoleEvent extends Event
{
    protected $command;

    private $input;
    private $output;

    public function __construct(Command $command, InputInterface $input, OutputInterface $output)
    {
        $this->command = $command;
        $this->input = $input;
        $this->output = $output;
    }

    /**
     * Gets the command that is executed.
     *
     * @return Command 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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Event;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Allows to handle exception thrown in a command.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ConsoleExceptionEvent extends ConsoleEvent
{
    private $exception;
    private $exitCode;

    public function __construct(Command $command, InputInterface $input, OutputInterface $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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

/**
 * InputAwareInterface should be implemented by classes that depends on the
 * Console Input.
 *
 * @author Wouter J <waldio.webdesign@gmail.com>
 */
interface InputAwareInterface
{
    /**
     * Sets the Console Input.
     */
    public function setInput(InputInterface $input);
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Descriptor\TextDescriptor;
use Symfony\Component\Console\Descriptor\XmlDescriptor;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Output\BufferedOutput;

/**
 * A InputDefinition represents a set of valid command line arguments and options.
 *
 * Usage:
 *
 *     $definition = new InputDefinition(array(
 *         new InputArgument('name', InputArgument::REQUIRED),
 *         new InputOption('foo', 'f', InputOption::VALUE_REQUIRED),
 *     ));
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
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 = array())
    {
        $this->setDefinition($definition);
    }

    /**
     * Sets the definition of the input.
     */
    public function setDefinition(array $definition)
    {
        $arguments = array();
        $options = array();
        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 = array())
    {
        $this->arguments = array();
        $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 = array())
    {
        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 = array();
        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 = array())
    {
        $this->options = array();
        $this->shortcuts = array();
        $this->addOptions($options);
    }

    /**
     * Adds an array of InputOption objects.
     *
     * @param InputOption[] $options An array of InputOption objects
     */
    public function addOptions($options = array())
    {
        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 = array();
        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
     */
    private 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 = array();

        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);
    }

    /**
     * Returns a textual representation of the InputDefinition.
     *
     * @return string A string representing the InputDefinition
     *
     * @deprecated since version 2.3, to be removed in 3.0.
     */
    public function asText()
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.3 and will be removed in 3.0.', E_USER_DEPRECATED);

        $descriptor = new TextDescriptor();
        $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true);
        $descriptor->describe($output, $this, array('raw_output' => true));

        return $output->fetch();
    }

    /**
     * Returns an XML representation of the InputDefinition.
     *
     * @param bool $asDom Whether to return a DOM or an XML string
     *
     * @return string|\DOMDocument An XML string representing the InputDefinition
     *
     * @deprecated since version 2.3, to be removed in 3.0.
     */
    public function asXml($asDom = false)
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.3 and will be removed in 3.0.', E_USER_DEPRECATED);

        $descriptor = new XmlDescriptor();

        if ($asDom) {
            return $descriptor->getInputDefinitionDocument($this);
        }

        $output = new BufferedOutput();
        $descriptor->describe($output, $this);

        return $output->fetch();
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;

/**
 * Input is the base class for all concrete Input classes.
 *
 * Three concrete classes are provided by default:
 *
 *  * `ArgvInput`: The input comes from the CLI arguments (argv)
 *  * `StringInput`: The input is provided as a string
 *  * `ArrayInput`: The input is provided as an array
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
abstract class Input implements InputInterface
{
    protected $definition;
    protected $options = array();
    protected $arguments = array();
    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 = array();
        $this->options = array();
        $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 isset($this->options[$name]) ? $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);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;

/**
 * Represents a command line argument.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
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 > 7 || $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 = array();
            } 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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\InvalidOptionException;

/**
 * ArrayInput represents an input provided as an array.
 *
 * Usage:
 *
 *     $input = new ArrayInput(array('name' => 'foo', '--bar' => 'foobar'));
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ArrayInput extends Input
{
    private $parameters;

    public function __construct(array $parameters, InputDefinition $definition = null)
    {
        $this->parameters = $parameters;

        parent::__construct($definition);
    }

    /**
     * {@inheritdoc}
     */
    public function getFirstArgument()
    {
        foreach ($this->parameters as $key => $value) {
            if ($key && '-' === $key[0]) {
                continue;
            }

            return $value;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function hasParameterOption($values)
    {
        $values = (array) $values;

        foreach ($this->parameters as $k => $v) {
            if (!\is_int($k)) {
                $v = $k;
            }

            if (\in_array($v, $values)) {
                return true;
            }
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function getParameterOption($values, $default = false)
    {
        $values = (array) $values;

        foreach ($this->parameters as $k => $v) {
            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 = array();
        foreach ($this->parameters as $param => $val) {
            if ($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(array($this, 'escapeToken'), $val)) : $this->escapeToken($val);
            }
        }

        return implode(' ', $params);
    }

    /**
     * {@inheritdoc}
     */
    protected function parse()
    {
        foreach ($this->parameters as $key => $value) {
            if (0 === strpos($key, '--')) {
                $this->addLongOption(substr($key, 2), $value);
            } elseif ('-' === $key[0]) {
                $this->addShortOption(substr($key, 1), $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));
            }

            $value = $option->isValueOptional() ? $option->getDefault() : 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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;

/**
 * StringInput represents an input provided as a string.
 *
 * Usage:
 *
 *     $input = new StringInput('foo --bar="foobar"');
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class StringInput extends ArgvInput
{
    const REGEX_STRING = '([^\s]+?)(?:\s|(?<!\\\\)"|(?<!\\\\)\'|$)';
    const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')';

    /**
     * @param string          $input      A string representing the parameters from the CLI
     * @param InputDefinition $definition A InputDefinition instance
     *
     * @deprecated The second argument is deprecated as it does not work (will be removed in 3.0), use 'bind' method instead
     */
    public function __construct($input, InputDefinition $definition = null)
    {
        if ($definition) {
            @trigger_error('The $definition argument of the '.__METHOD__.' method is deprecated and will be removed in 3.0. Set this parameter with the bind() method instead.', E_USER_DEPRECATED);
        }

        parent::__construct(array(), null);

        $this->setTokens($this->tokenize($input));

        if (null !== $definition) {
            $this->bind($definition);
        }
    }

    /**
     * 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 = array();
        $length = \strlen($input);
        $cursor = 0;
        while ($cursor < $length) {
            if (preg_match('/\s+/A', $input, $match, null, $cursor)) {
            } elseif (preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, null, $cursor)) {
                $tokens[] = $match[1].$match[2].stripcslashes(str_replace(array('"\'', '\'"', '\'\'', '""'), '', substr($match[3], 1, \strlen($match[3]) - 2)));
            } elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, null, $cursor)) {
                $tokens[] = stripcslashes(substr($match[0], 1, \strlen($match[0]) - 2));
            } elseif (preg_match('/'.self::REGEX_STRING.'/A', $input, $match, null, $cursor)) {
                $tokens[] = stripcslashes($match[1]);
            } else {
                // should never happen
                throw new InvalidArgumentException(sprintf('Unable to parse input near "... %s ..."', substr($input, $cursor, 10)));
            }

            $cursor += \strlen($match[0]);
        }

        return $tokens;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;

/**
 * Represents a command line option.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class InputOption
{
    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                  $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 (0 === strpos($name, '--')) {
            $name = substr($name, 2);
        }

        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 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 = array();
            } 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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\RuntimeException;

/**
 * ArgvInput represents an input coming from the CLI arguments.
 *
 * Usage:
 *
 *     $input = new ArgvInput();
 *
 * By default, the `$_SERVER['argv']` array is used for the input values.
 *
 * This can be overridden by explicitly passing the input values in the constructor:
 *
 *     $input = new ArgvInput($_SERVER['argv']);
 *
 * If you pass it yourself, don't forget that the first element of the array
 * is the name of the running application.
 *
 * When passing an argument to the constructor, be sure that it respects
 * the same rules as the argv one. It's almost always better to use the
 * `StringInput` when you want to provide your own input.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 *
 * @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html
 * @see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02
 */
class ArgvInput extends Input
{
    private $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 = null, InputDefinition $definition = null)
    {
        if (null === $argv) {
            $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 && 0 === 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($token, 1);

        if (\strlen($name) > 1) {
            if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) {
                // an option with a value (with no space)
                $this->addShortOption($name[0], substr($name, 1));
            } else {
                $this->parseShortOptionSet($name);
            }
        } else {
            $this->addShortOption($name, null);
        }
    }

    /**
     * Parses a short option set.
     *
     * @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($name, null, true);
                throw new RuntimeException(sprintf('The "-%s" option does not exist.', false === $encoding ? $name[$i] : mb_substr($name, $i, 1, $encoding)));
            }

            $option = $this->definition->getOptionForShortcut($name[$i]);
            if ($option->acceptValue()) {
                $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1));

                break;
            } else {
                $this->addLongOption($option->getName(), null);
            }
        }
    }

    /**
     * Parses a long option.
     *
     * @param string $token The current token
     */
    private function parseLongOption($token)
    {
        $name = substr($token, 2);

        if (false !== $pos = strpos($name, '=')) {
            if (0 === \strlen($value = substr($name, $pos + 1))) {
                array_unshift($this->parsed, null);
            }
            $this->addLongOption(substr($name, 0, $pos), $value);
        } else {
            $this->addLongOption($name, null);
        }
    }

    /**
     * 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() ? array($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);

        // Convert empty values to null
        if (!isset($value[0])) {
            $value = null;
        }

        if (null !== $value && !$option->acceptValue()) {
            throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name));
        }

        if (null === $value && $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]) {
                $value = $next;
            } elseif (empty($next)) {
                $value = null;
            } 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()) {
                $value = $option->isValueOptional() ? $option->getDefault() : true;
            }
        }

        if ($option->isArray()) {
            $this->options[$name][] = $value;
        } else {
            $this->options[$name] = $value;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getFirstArgument()
    {
        foreach ($this->tokens as $token) {
            if ($token && '-' === $token[0]) {
                continue;
            }

            return $token;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function hasParameterOption($values)
    {
        $values = (array) $values;

        foreach ($this->tokens as $token) {
            foreach ($values as $value) {
                // Options with values:
                //   For long options, test for '--option=' at beginning
                //   For short options, test for '-o' at beginning
                $leading = 0 === strpos($value, '--') ? $value.'=' : $value;
                if ($token === $value || '' !== $leading && 0 === strpos($token, $leading)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function getParameterOption($values, $default = false)
    {
        $values = (array) $values;
        $tokens = $this->tokens;

        while (0 < \count($tokens)) {
            $token = array_shift($tokens);

            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 = 0 === strpos($value, '--') ? $value.'=' : $value;
                if ('' !== $leading && 0 === 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()
    {
        $self = $this;
        $tokens = array_map(function ($token) use ($self) {
            if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) {
                return $match[1].$self->escapeToken($match[2]);
            }

            if ($token && '-' !== $token[0]) {
                return $self->escapeToken($token);
            }

            return $token;
        }, $this->tokens);

        return implode(' ', $tokens);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Input;

use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;

/**
 * InputInterface is the interface implemented by all input classes.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
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)
     *
     * @return bool true if the value is contained in the raw parameters
     */
    public function hasParameterOption($values);

    /**
     * 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
     *
     * @return mixed The option value
     */
    public function getParameterOption($values, $default = 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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\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 <fabien@symfony.com>
 */
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 $input, OutputInterface $output)
    {
        if ($input->getOption('xml')) {
            @trigger_error('The --xml option was deprecated in version 2.7 and will be removed in version 3.0. Use the --format option instead.', E_USER_DEPRECATED);

            $input->setOption('format', 'xml');
        }

        $helper = new DescriptorHelper();
        $helper->describe($output, $this->getApplication(), array(
            'format' => $input->getOption('format'),
            'raw_text' => $input->getOption('raw'),
            'namespace' => $input->getArgument('namespace'),
        ));
    }

    /**
     * {@inheritdoc}
     */
    private function createDefinition()
    {
        return new InputDefinition(array(
            new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'),
            new InputOption('xml', null, InputOption::VALUE_NONE, 'To output list as XML'),
            new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'),
            new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
        ));
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * HelpCommand displays the help for a given command.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class HelpCommand extends Command
{
    private $command;

    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this->ignoreValidationErrors();

        $this
            ->setName('help')
            ->setDefinition(array(
                new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'),
                new InputOption('xml', null, InputOption::VALUE_NONE, 'To output help as XML'),
                new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
                new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw 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 $input, OutputInterface $output)
    {
        if (null === $this->command) {
            $this->command = $this->getApplication()->find($input->getArgument('command_name'));
        }

        if ($input->getOption('xml')) {
            @trigger_error('The --xml option was deprecated in version 2.7 and will be removed in version 3.0. Use the --format option instead.', E_USER_DEPRECATED);

            $input->setOption('format', 'xml');
        }

        $helper = new DescriptorHelper();
        $helper->describe($output, $this->command, array(
            'format' => $input->getOption('format'),
            'raw_text' => $input->getOption('raw'),
        ));

        $this->command = null;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Command;

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Descriptor\TextDescriptor;
use Symfony\Component\Console\Descriptor\XmlDescriptor;
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\BufferedOutput;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * Base class for all commands.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Command
{
    private $application;
    private $name;
    private $processTitle;
    private $aliases = array();
    private $definition;
    private $help;
    private $description;
    private $ignoreValidationErrors = false;
    private $applicationDefinitionMerged = false;
    private $applicationDefinitionMergedWithArgs = false;
    private $code;
    private $synopsis = array();
    private $usages = array();
    private $helperSet;

    /**
     * @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) {
            $this->setName($name);
        }

        $this->configure();

        if (!$this->name) {
            throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', \get_class($this)));
        }
    }

    /**
     * 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 A HelperSet instance
     */
    public function getHelperSet()
    {
        return $this->helperSet;
    }

    /**
     * Gets the application instance for this command.
     *
     * @return Application 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 $input, OutputInterface $output)
    {
        throw new LogicException('You must override the execute() method in the concrete command class.');
    }

    /**
     * Interacts with the user.
     *
     * This method is executed before the InputDefinition is validated.
     * This means that this is the only place where the command can
     * interactively ask for values of missing required arguments.
     */
    protected function interact(InputInterface $input, OutputInterface $output)
    {
    }

    /**
     * Initializes the command after the input has been bound and before the input
     * is validated.
     *
     * This is mainly useful when a lot of commands extends one main command
     * where some things need to be initialized based on the input arguments and options.
     *
     * @see InputInterface::bind()
     * @see InputInterface::validate()
     */
    protected function initialize(InputInterface $input, OutputInterface $output)
    {
    }

    /**
     * Runs the command.
     *
     * The code to execute is either defined directly with the
     * setCode() method or by overriding the execute() method
     * in a sub-class.
     *
     * @return int The command exit code
     *
     * @throws \Exception When binding input fails. Bypass this by calling {@link ignoreValidationErrors()}.
     *
     * @see setCode()
     * @see execute()
     */
    public function run(InputInterface $input, OutputInterface $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($code)
    {
        if (!\is_callable($code)) {
            throw new InvalidArgumentException('Invalid callable provided to Command::setCode.');
        }

        if (\PHP_VERSION_ID >= 50400 && $code instanceof \Closure) {
            $r = new \ReflectionFunction($code);
            if (null === $r->getClosureThis()) {
                if (\PHP_VERSION_ID < 70000) {
                    // Bug in PHP5: https://bugs.php.net/bug.php?id=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()
    {
        return $this->definition;
    }

    /**
     * Gets the InputDefinition to be used to create XML and Text 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: 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
     *
     * @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                  $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
     *
     * @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.
     *
     * PHP 5.5+ or the proctitle PECL library is required
     *
     * @param string $title The process title
     *
     * @return $this
     */
    public function setProcessTitle($title)
    {
        $this->processTitle = $title;

        return $this;
    }

    /**
     * Returns the command name.
     *
     * @return string The command name
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * 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;

        $placeholders = array(
            '%command.name%',
            '%command.full_name%',
        );
        $replacements = array(
            $name,
            $_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 (0 !== 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);
    }

    /**
     * Returns a text representation of the command.
     *
     * @return string A string representing the command
     *
     * @deprecated since version 2.3, to be removed in 3.0.
     */
    public function asText()
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.3 and will be removed in 3.0.', E_USER_DEPRECATED);

        $descriptor = new TextDescriptor();
        $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true);
        $descriptor->describe($output, $this, array('raw_output' => true));

        return $output->fetch();
    }

    /**
     * Returns an XML representation of the command.
     *
     * @param bool $asDom Whether to return a DOM or an XML string
     *
     * @return string|\DOMDocument An XML string representing the command
     *
     * @deprecated since version 2.3, to be removed in 3.0.
     */
    public function asXml($asDom = false)
    {
        @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.3 and will be removed in 3.0.', E_USER_DEPRECATED);

        $descriptor = new XmlDescriptor();

        if ($asDom) {
            return $descriptor->getCommandDocument($this);
        }

        $output = new BufferedOutput();
        $descriptor->describe($output, $this);

        return $output->fetch();
    }

    /**
     * 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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class LogicException extends \LogicException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * Represents an incorrect option name typed in the console.
 *
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class InvalidOptionException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * ExceptionInterface.
 *
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
interface ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * Represents an incorrect command name typed in the console.
 *
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class CommandNotFoundException extends \InvalidArgumentException implements ExceptionInterface
{
    private $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 = array(), $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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Console\Exception;

/**
 * @author Jérôme Tamarelle <jerome@tamarelle.net>
 */
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Filesystem;

use Symfony\Component\Filesystem\Exception\FileNotFoundException;
use Symfony\Component\Filesystem\Exception\IOException;

/**
 * Provides basic utility to manipulate the file system.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
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) || 0 === stripos($originFile, 'file://');
        if ($originIsLocal && !is_file($originFile)) {
            throw new FileNotFoundException(sprintf('Failed to copy "%s" because file does not exist.', $originFile), 0, null, $originFile);
        }

        $this->mkdir(\dirname($targetFile));

        $doCopy = true;
        if (!$overwriteNewerFiles && null === parse_url($originFile, PHP_URL_HOST) && is_file($targetFile)) {
            $doCopy = filemtime($originFile) > filemtime($targetFile);
        }

        if ($doCopy) {
            // https://bugs.php.net/bug.php?id=64634
            if (false === $source = @fopen($originFile, 'r')) {
                throw new IOException(sprintf('Failed to copy "%s" to "%s" because source file could not be opened for reading.', $originFile, $targetFile), 0, null, $originFile);
            }

            // Stream context created to allow files overwrite when using FTP stream wrapper - disabled by default
            if (false === $target = @fopen($targetFile, 'w', null, stream_context_create(array('ftp' => array('overwrite' => true))))) {
                throw new IOException(sprintf('Failed to copy "%s" to "%s" because target file could not be opened for writing.', $originFile, $targetFile), 0, null, $originFile);
            }

            $bytesCopied = stream_copy_to_stream($source, $target);
            fclose($source);
            fclose($target);
            unset($source, $target);

            if (!is_file($targetFile)) {
                throw new IOException(sprintf('Failed to copy "%s" to "%s".', $originFile, $targetFile), 0, null, $originFile);
            }

            if ($originIsLocal) {
                // Like `cp`, preserve executable permission bits
                @chmod($targetFile, fileperms($targetFile) | (fileperms($originFile) & 0111));

                if ($bytesCopied !== $bytesOrigin = filesize($originFile)) {
                    throw new IOException(sprintf('Failed to copy the whole content of "%s" to "%s" (%g of %g bytes copied).', $originFile, $targetFile, $bytesCopied, $bytesOrigin), 0, null, $originFile);
                }
            }
        }
    }

    /**
     * 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->toIterator($dirs) as $dir) {
            if (is_dir($dir)) {
                continue;
            }

            if (!self::box('mkdir', $dir, $mode, true)) {
                if (!is_dir($dir)) {
                    // The directory was not created by a concurrent process. Let's throw an exception with a developer friendly error message if we have one
                    if (self::$lastError) {
                        throw new IOException(sprintf('Failed to create "%s": %s.', $dir, self::$lastError), 0, null, $dir);
                    }
                    throw new IOException(sprintf('Failed to create "%s"', $dir), 0, null, $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->toIterator($files) as $file) {
            if (\strlen($file) > $maxPathLength) {
                throw new IOException(sprintf('Could not check if file exist because path length exceeds %d characters.', $maxPathLength), 0, null, $file);
            }

            if (!file_exists($file)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Sets access and modification time of file.
     *
     * @param string|iterable $files A filename, an array of files, or a \Traversable instance to create
     * @param int             $time  The touch time as a Unix timestamp
     * @param int             $atime The access time as a Unix timestamp
     *
     * @throws IOException When touch fails
     */
    public function touch($files, $time = null, $atime = null)
    {
        foreach ($this->toIterator($files) as $file) {
            $touch = $time ? @touch($file, $time, $atime) : @touch($file);
            if (true !== $touch) {
                throw new IOException(sprintf('Failed to touch "%s".', $file), 0, null, $file);
            }
        }
    }

    /**
     * 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($files, false);
        } elseif (!\is_array($files)) {
            $files = array($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": %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": %s.', $file, self::$lastError));
                }
            } elseif (!self::box('unlink', $file) && file_exists($file)) {
                throw new IOException(sprintf('Failed to remove file "%s": %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 fail
     */
    public function chmod($files, $mode, $umask = 0000, $recursive = false)
    {
        foreach ($this->toIterator($files) as $file) {
            if (true !== @chmod($file, $mode & ~$umask)) {
                throw new IOException(sprintf('Failed to chmod file "%s".', $file), 0, null, $file);
            }
            if ($recursive && is_dir($file) && !is_link($file)) {
                $this->chmod(new \FilesystemIterator($file), $mode, $umask, true);
            }
        }
    }

    /**
     * Change the owner of an array of files or directories.
     *
     * @param string|iterable $files     A filename, an array of files, or a \Traversable instance to change owner
     * @param string          $user      The new owner user name
     * @param bool            $recursive Whether change the owner recursively or not
     *
     * @throws IOException When the change fail
     */
    public function chown($files, $user, $recursive = false)
    {
        foreach ($this->toIterator($files) as $file) {
            if ($recursive && is_dir($file) && !is_link($file)) {
                $this->chown(new \FilesystemIterator($file), $user, true);
            }
            if (is_link($file) && \function_exists('lchown')) {
                if (true !== @lchown($file, $user)) {
                    throw new IOException(sprintf('Failed to chown file "%s".', $file), 0, null, $file);
                }
            } else {
                if (true !== @chown($file, $user)) {
                    throw new IOException(sprintf('Failed to chown file "%s".', $file), 0, null, $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          $group     The group name
     * @param bool            $recursive Whether change the group recursively or not
     *
     * @throws IOException When the change fail
     */
    public function chgrp($files, $group, $recursive = false)
    {
        foreach ($this->toIterator($files) as $file) {
            if ($recursive && is_dir($file) && !is_link($file)) {
                $this->chgrp(new \FilesystemIterator($file), $group, true);
            }
            if (is_link($file) && \function_exists('lchgrp')) {
                if (true !== @lchgrp($file, $group) || (\defined('HHVM_VERSION') && !posix_getgrnam($group))) {
                    throw new IOException(sprintf('Failed to chgrp file "%s".', $file), 0, null, $file);
                }
            } else {
                if (true !== @chgrp($file, $group)) {
                    throw new IOException(sprintf('Failed to chgrp file "%s".', $file), 0, null, $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), 0, null, $target);
        }

        if (true !== @rename($origin, $target)) {
            if (is_dir($origin)) {
                // See https://bugs.php.net/bug.php?id=54097 & http://php.net/manual/en/function.rename.php#113943
                $this->mirror($origin, $target, null, array('override' => $overwrite, 'delete' => $overwrite));
                $this->remove($origin);

                return;
            }
            throw new IOException(sprintf('Cannot rename "%s" to "%s".', $origin, $target), 0, null, $target);
        }
    }

    /**
     * Tells whether a file exists and is readable.
     *
     * @param string $filename Path to the file
     *
     * @return bool
     *
     * @throws IOException When windows path is longer than 258 characters
     */
    private function isReadable($filename)
    {
        $maxPathLength = PHP_MAXPATHLEN - 2;

        if (\strlen($filename) > $maxPathLength) {
            throw new IOException(sprintf('Could not check if file is readable because path length exceeds %d characters.', $maxPathLength), 0, null, $filename);
        }

        return is_readable($filename);
    }

    /**
     * Creates a symbolic link or copy a directory.
     *
     * @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)) {
            if (null !== self::$lastError) {
                if ('\\' === \DIRECTORY_SEPARATOR && false !== strpos(self::$lastError, 'error code(1314)')) {
                    throw new IOException('Unable to create symlink due to error code 1314: \'A required privilege is not held by the client\'. Do you have the required Administrator-rights?', 0, null, $targetDir);
                }
            }
            throw new IOException(sprintf('Failed to create symbolic link from "%s" to "%s".', $originDir, $targetDir), 0, null, $targetDir);
        }
    }

    /**
     * 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)
    {
        // Normalize separators on Windows
        if ('\\' === \DIRECTORY_SEPARATOR) {
            $endPath = str_replace('\\', '/', $endPath);
            $startPath = str_replace('\\', '/', $startPath);
        }

        $stripDriveLetter = function ($path) {
            if (\strlen($path) > 2 && ':' === $path[1] && '/' === $path[2] && ctype_alpha($path[0])) {
                return substr($path, 2);
            }

            return $path;
        };

        $endPath = $stripDriveLetter($endPath);
        $startPath = $stripDriveLetter($startPath);

        // Split the paths into arrays
        $startPathArr = explode('/', trim($startPath, '/'));
        $endPathArr = explode('/', trim($endPath, '/'));

        $normalizePathArray = function ($pathSegments, $absolute) {
            $result = array();

            foreach ($pathSegments as $segment) {
                if ('..' === $segment && ($absolute || \count($result))) {
                    array_pop($result);
                } elseif ('.' !== $segment) {
                    $result[] = $segment;
                }
            }

            return $result;
        };

        $startPathArr = $normalizePathArray($startPathArr, static::isAbsolutePath($startPath));
        $endPathArr = $normalizePathArray($endPathArr, static::isAbsolutePath($endPath));

        // Find for which directory the common path stops
        $index = 0;
        while (isset($startPathArr[$index]) && isset($endPathArr[$index]) && $startPathArr[$index] === $endPathArr[$index]) {
            ++$index;
        }

        // Determine how deep the start path is relative to the common path (ie, "web/bundles" = 2 levels)
        if (1 === \count($startPathArr) && '' === $startPathArr[0]) {
            $depth = 0;
        } else {
            $depth = \count($startPathArr) - $index;
        }

        // Repeated "../" for each level need to reach the common path
        $traverser = str_repeat('../', $depth);

        $endPathRemainder = implode('/', \array_slice($endPathArr, $index));

        // Construct $endPath from traversing to the common path, then to the remaining $endPath
        $relativePath = $traverser.('' !== $endPathRemainder ? $endPathRemainder.'/' : '');

        return '' === $relativePath ? './' : $relativePath;
    }

    /**
     * Mirrors a directory to another.
     *
     * Copies files and directories from the origin directory into the target directory. By default:
     *
     *  - existing files in the target directory will be overwritten, except if they are newer (see the `override` option)
     *  - files in the target directory that do not exist in the source directory will not be deleted (see the `delete` option)
     *
     * @param string       $originDir The origin directory
     * @param string       $targetDir The target directory
     * @param \Traversable $iterator  Iterator that filters which files and directories to copy
     * @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 = array())
    {
        $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), 0, null, $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), 0, null, $file);
                }
            }
        }
    }

    /**
     * Returns whether the file path is an absolute path.
     *
     * @param string $file A file path
     *
     * @return bool
     */
    public function isAbsolutePath($file)
    {
        return strspn($file, '/\\', 0, 1)
            || (\strlen($file) > 3 && ctype_alpha($file[0])
                && ':' === substr($file, 1, 1)
                && strspn($file, '/\\', 2, 1)
            )
            || 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
     * @param int|null $mode     The file mode (octal). If null, file permissions are not modified
     *                           Deprecated since version 2.3.12, to be removed in 3.0.
     *
     * @throws IOException if the file cannot be written to
     */
    public function dumpFile($filename, $content, $mode = 0666)
    {
        $dir = \dirname($filename);

        if (!is_dir($dir)) {
            $this->mkdir($dir);
        }

        if (!is_writable($dir)) {
            throw new IOException(sprintf('Unable to write to the "%s" directory.', $dir), 0, null, $dir);
        }

        $tmpFile = $this->tempnam($dir, basename($filename));

        if (false === @file_put_contents($tmpFile, $content)) {
            throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename);
        }

        if (null !== $mode) {
            if (\func_num_args() > 2) {
                @trigger_error('Support for modifying file permissions is deprecated since Symfony 2.3.12 and will be removed in 3.0.', E_USER_DEPRECATED);
            }

            $this->chmod($tmpFile, $mode);
        } elseif (file_exists($filename)) {
            @chmod($tmpFile, fileperms($filename));
        }

        $this->rename($tmpFile, $filename, true);
    }

    /**
     * @param mixed $files
     *
     * @return \Traversable
     */
    private function toIterator($files)
    {
        if (!$files instanceof \Traversable) {
            $files = new \ArrayObject(\is_array($files) ? $files : array($files));
        }

        return $files;
    }

    /**
     * Gets a 2-tuple of scheme (may be null) and hierarchical part of a filename (e.g. file:///tmp -> array(file, tmp)).
     *
     * @param string $filename The filename to be parsed
     *
     * @return array The filename scheme and hierarchical part
     */
    private function getSchemeAndHierarchy($filename)
    {
        $components = explode('://', $filename, 2);

        return 2 === \count($components) ? array($components[0], $components[1]) : array(null, $components[0]);
    }

    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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Filesystem;

use Symfony\Component\Filesystem\Exception\IOException;

/**
 * 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 <lyrixx@lyrixx.info>
 * @author Romain Neutron <imprec@gmail.com>
 * @author Nicolas Grekas <p@tchwork.com>
 */
class LockHandler
{
    private $file;
    private $handle;

    /**
     * @param string      $name     The lock name
     * @param string|null $lockPath The directory to store the lock. Default values will use temporary directory
     *
     * @throws IOException If the lock directory could not be created or is not writable
     */
    public function __construct($name, $lockPath = null)
    {
        $lockPath = $lockPath ?: sys_get_temp_dir();

        if (!is_dir($lockPath)) {
            $fs = new Filesystem();
            $fs->mkdir($lockPath);
        }

        if (!is_writable($lockPath)) {
            throw new IOException(sprintf('The directory "%s" is not writable.', $lockPath), 0, null, $lockPath);
        }

        $this->file = sprintf('%s/sf.%s.%s.lock', $lockPath, preg_replace('/[^a-z0-9\._-]+/i', '-', $name), hash('sha256', $name));
    }

    /**
     * Lock the resource.
     *
     * @param bool $blocking Wait until the lock is released
     *
     * @return bool Returns true if the lock was acquired, false otherwise
     *
     * @throws IOException If the lock file could not be created or opened
     */
    public function lock($blocking = false)
    {
        if ($this->handle) {
            return true;
        }

        $error = null;

        // Silence error reporting
        set_error_handler(function ($errno, $msg) use (&$error) {
            $error = $msg;
        });

        if (!$this->handle = fopen($this->file, 'r+') ?: fopen($this->file, 'r')) {
            if ($this->handle = fopen($this->file, 'x')) {
                chmod($this->file, 0666);
            } elseif (!$this->handle = fopen($this->file, 'r+') ?: fopen($this->file, 'r')) {
                usleep(100); // Give some time for chmod() to complete
                $this->handle = fopen($this->file, 'r+') ?: fopen($this->file, 'r');
            }
        }
        restore_error_handler();

        if (!$this->handle) {
            throw new IOException($error, 0, null, $this->file);
        }

        // On Windows, even if PHP doc says the contrary, LOCK_NB works, see
        // https://bugs.php.net/54129
        if (!flock($this->handle, LOCK_EX | ($blocking ? 0 : LOCK_NB))) {
            fclose($this->handle);
            $this->handle = null;

            return false;
        }

        return true;
    }

    /**
     * Release the resource.
     */
    public function release()
    {
        if ($this->handle) {
            flock($this->handle, LOCK_UN | LOCK_NB);
            fclose($this->handle);
            $this->handle = null;
        }
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Filesystem\Exception;

/**
 * IOException interface for file and input/output stream related exceptions thrown by the component.
 *
 * @author Christian Gärtner <christiangaertner.film@googlemail.com>
 */
interface IOExceptionInterface extends ExceptionInterface
{
    /**
     * Returns the associated path for the exception.
     *
     * @return string The path
     */
    public function getPath();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Filesystem\Exception;

/**
 * Exception interface for all exceptions thrown by the component.
 *
 * @author Romain Neutron <imprec@gmail.com>
 */
interface ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Filesystem\Exception;

/**
 * Exception class thrown when a filesystem operation failure happens.
 *
 * @author Romain Neutron <imprec@gmail.com>
 * @author Christian Gärtner <christiangaertner.film@googlemail.com>
 * @author Fabien Potencier <fabien@symfony.com>
 */
class IOException extends \RuntimeException implements IOExceptionInterface
{
    private $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 <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Filesystem\Exception;

/**
 * Exception class thrown when a file couldn't be found.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Christian Gärtner <christiangaertner.film@googlemail.com>
 */
class FileNotFoundException extends IOException
{
    public function __construct($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 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

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Uri\Retrievers;

/**
 * Interface for URI retrievers
 *
 * @author Sander Coolen <sander@jibber.nl>
 */
interface UriRetrieverInterface
{
    /**
     * Retrieve a schema from the specified URI
     *
     * @param string $uri URI that resolves to a JSON schema
     *
     * @throws \JsonSchema\Exception\ResourceNotFoundException
     *
     * @return mixed string|null
     */
    public function retrieve($uri);

    /**
     * Get media content type
     *
     * @return string
     */
    public function getContentType();
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Uri\Retrievers;

use JsonSchema\Exception\ResourceNotFoundException;

/**
 * Tries to retrieve JSON schemas from a URI using file_get_contents()
 *
 * @author Sander Coolen <sander@jibber.nl>
 */
class FileGetContents extends AbstractRetriever
{
    protected $messageBody;

    /**
     * {@inheritdoc}
     *
     * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve()
     */
    public function retrieve($uri)
    {
        $errorMessage = null;
        set_error_handler(function ($errno, $errstr) use (&$errorMessage) {
            $errorMessage = $errstr;
        });
        $response = file_get_contents($uri);
        restore_error_handler();

        if ($errorMessage) {
            throw new ResourceNotFoundException($errorMessage);
        }

        if (false === $response) {
            throw new ResourceNotFoundException('JSON schema not found at ' . $uri);
        }

        if ($response == ''
            && substr($uri, 0, 7) == 'file://' && substr($uri, -1) == '/'
        ) {
            throw new ResourceNotFoundException('JSON schema not found at ' . $uri);
        }

        $this->messageBody = $response;
        if (!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 (0 < preg_match("/Content-Type:(\V*)/ims", $header, $match)) {
            return trim($match[1]);
        }

        return null;
    }
}
<?php
/**
 * JsonSchema
 *
 * @filesource
 */

namespace JsonSchema\Uri\Retrievers;

/**
 * AbstractRetriever implements the default shared behavior
 * that all descendant Retrievers should inherit
 *
 * @author Steven Garcia <webwhammy@gmail.com>
 */
abstract class AbstractRetriever implements UriRetrieverInterface
{
    /**
     * Media content type
     *
     * @var string
     */
    protected $contentType;

    /**
     * {@inheritdoc}
     *
     * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::getContentType()
     */
    public function getContentType()
    {
        return $this->contentType;
    }
}
<?php

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\RuntimeException;
use JsonSchema\Validator;

/**
 * Tries to retrieve JSON schemas from a URI using cURL library
 *
 * @author Sander Coolen <sander@jibber.nl>
 */
class Curl extends AbstractRetriever
{
    protected $messageBody;

    public function __construct()
    {
        if (!function_exists('curl_init')) {
            // Cannot test this, because curl_init is present on all test platforms plus mock
            throw new RuntimeException('cURL not installed'); // @codeCoverageIgnore
        }
    }

    /**
     * {@inheritdoc}
     *
     * @see \JsonSchema\Uri\Retrievers\UriRetrieverInterface::retrieve()
     */
    public function retrieve($uri)
    {
        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, $uri);
        curl_setopt($ch, CURLOPT_HEADER, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, 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 (0 < preg_match("/Content-Type:(\V*)/ims", $response, $match)) {
            $this->contentType = trim($match[1]);

            return true;
        }

        return false;
    }
}
<?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 <fidian@rumkin.com>
 */
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 (5 < count($match)) {
            $components =  array(
                'scheme'    => $match[2],
                'authority' => $match[4],
                'path'      => $match[5]
            );
        }

        if (7 < count($match)) {
            $components['query'] = $match[7];
        }

        if (9 < count($match)) {
            $components['fragment'] = $match[9];
        }

        return $components;
    }

    /**
     * Builds a URI based on n array with the main components
     *
     * @param array $components
     *
     * @return string
     */
    public function generate(array $components)
    {
        $uri = $components['scheme'] . '://'
             . $components['authority']
             . $components['path'];

        if (array_key_exists('query', $components)) {
            $uri .= $components['query'];
        }

        if (array_key_exists('fragment', $components)) {
            $uri .= $components['fragment'];
        }

        return $uri;
    }

    /**
     * Resolves a URI
     *
     * @param string $uri     Absolute or relative
     * @param string $baseUri Optional base URI
     *
     * @return string
     */
    public function resolve($uri, $baseUri = null)
    {
        $components = $this->parse($uri);
        $path = $components['path'];

        if ((array_key_exists('scheme', $components)) && ('http' === $components['scheme'])) {
            return $uri;
        }

        $baseComponents = $this->parse($baseUri);
        $basePath = $baseComponents['path'];

        $baseComponents['path'] = UriResolver::combineRelativePathWithBasePath($path, $basePath);

        return $this->generate($baseComponents);
    }

    /**
     * @param string $uri
     *
     * @return bool
     */
    public function isValid($uri)
    {
        $components = $this->parse($uri);

        return !empty($components);
    }

    /**
     * Set a URL translation rule
     */
    public function setTranslation($from, $to)
    {
        $this->translationMap[$from] = $to;
    }

    /**
     * Apply URI translation rules
     */
    public function translate($uri)
    {
        foreach ($this->translationMap as $from => $to) {
            $uri = preg_replace($from, $to, $uri);
        }

        // translate references to local files within the json-schema package
        $uri = preg_replace('|^package://|', sprintf('file://%s/', realpath(__DIR__ . '/../../..')), $uri);

        return $uri;
    }
}
<?php

/*
 * This file is part of the JsonSchema package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace JsonSchema\Uri;

use JsonSchema\Exception\UriResolverException;
use JsonSchema\UriResolverInterface;

/**
 * Resolves JSON Schema URIs
 *
 * @author Sander Coolen <sander@jibber.nl>
 */
class UriResolver implements UriResolverInterface
{
    /**
     * Parses a URI into five main components
     *
     * @param string $uri
     *
     * @return array
     */
    public function parse($uri)
    {
        preg_match('|^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?|', $uri, $match);

        $components = array();
        if (5 < count($match)) {
            $components =  array(
                'scheme'    => $match[2],
                'authority' => $match[4],
                'path'      => $match[5]
            );
        }
        if (7 < count($match)) {
            $components['query'] = $match[7];
        }
        if (9 < count($match)) {
            $components['fragment'] = $match[9];
        }

        return $components;
    }

    /**
     * Builds a URI based on n array with the main components
     *
     * @param array $components
     *
     * @return string
     */
    public function generate(array $components)
    {
        $uri = $components['scheme'] . '://'
             . $components['authority']
             . $components['path'];

        if (array_key_exists('query', $components) && strlen($components['query'])) {
            $uri .= '?' . $components['query'];
        }
        if (array_key_exists('fragment', $components)) {
            $uri .= '#' . $components['fragment'];
        }

        return $uri;
    }

    /**
     * {@inheritdoc}
     */
    public function resolve($uri, $baseUri = null)
    {
        // treat non-uri base as local file path
        if (
            !is_null($baseUri) &&
            !filter_var($baseUri, \FILTER_VALIDATE_URL) &&
            !preg_match('|^[^/]+://|u', $baseUri)
        ) {
            if (is_file($baseUri)) {
                $baseUri = 'file://' . realpath($baseUri);
            } elseif (is_dir($baseUri)) {
                $baseUri = 'file://' . realpath($baseUri) . '/';
            } else {
                $baseUri = 'file://' . getcwd() . '/' . $baseUri;
            }
        }

        if ($uri == '') {
            return $baseUri;
        }

        $components = $this->parse($uri);
        $path = $components['path'];

        if (!empty($components['scheme'])) {
            return $uri;
        }
        $baseComponents = $this->parse($baseUri);
        $basePath = $baseComponents['path'];

        $baseComponents['path'] = self::combineRelativePathWithBasePath($path, $basePath);
        if (isset($components['fragment'])) {
            $baseComponents['fragment'] = $components['fragment'];
        }

        return $this->generate($baseComponents);
    }

    /**
     * Tries to glue a relative path onto an absolute one
     *
     * @param string $relativePath
     * @param string $basePath
     *
     * @throws UriResolverException
     *
     * @return string Merged path
     */
    public static function combineRelativePathWithBasePath($relativePath, $basePath)
    {
        $relativePath = self::normalizePath($relativePath);
        if ($relativePath == '') {
            return $basePath;
        }
        if ($relativePath[0] == '/') {
            return $relativePath;
        }

        $basePathSegments = explode('/', $basePath);

        preg_match('|^/?(\.\./(?:\./)*)*|', $relativePath, $match);
        $numLevelUp = strlen($match[0]) /3 + 1;
        if ($numLevelUp >= count($basePathSegments)) {
            throw new UriResolverException(sprintf("Unable to resolve URI '%s' from base '%s'", $relativePath, $basePath));
        }

        $basePathSegments = array_slice($basePathSegments, 0, -$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

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 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;

/**
 * @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

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::REGEX, strtoupper($string), $matches)) {
            return null;
        }

        $dateAndTime = $matches[1];
        $microseconds = $matches[2] ?: '.000000';
        $timeZone = 'Z' !== $matches[3] ? $matches[4] . ':' . $matches[5] : '+00:00';
        $dateFormat = strpos($dateAndTime, 'T') === false ? 'Y-m-d H:i:s.uP' : 'Y-m-d\TH:i:s.uP';
        $dateTime = \DateTime::createFromFormat($dateFormat, $dateAndTime . $microseconds . $timeZone, new \DateTimeZone('UTC'));

        return $dateTime ?: null;
    }
}
<?php

/*
 * 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 <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 */
class SchemaConstraint extends Constraint
{
    const DEFAULT_SCHEMA_SPEC = 'http://json-schema.org/draft-04/schema#';

    /**
     * {@inheritdoc}
     */
    public function check(&$element, $schema = null, JsonPointer $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 StringConstraint Constraints, validates an string against a given schema
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 */
class StringConstraint extends Constraint
{
    /**
     * {@inheritdoc}
     */
    public function check(&$element, $schema = null, JsonPointer $path = null, $i = null)
    {
        // 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($string, mb_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\Entity\JsonPointer;
use JsonSchema\Rfc3339;

/**
 * Validates against the "format" property
 *
 * @author Justin Rainbow <justin.rainbow@gmail.com>
 *
 * @see   http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.23
 */
class FormatConstraint extends Constraint
{
    /**
     * {@inheritdoc}
     */
    public function check(&$element, $schema = null, JsonPointer $path = null, $i = null)
    {
        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($path, sprintf('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($path, sprintf('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($path, sprintf('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($path, sprintf('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($element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE)) {
                    $this->addError($path, 'Invalid URL format', 'format', array('format' => $schema->format));
                }
                break;

            case 'uriref':
            case 'uri-reference':
                if (null === filter_var($element, FILTER_VALIDATE_URL, FILTER_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($element, 0, 2) === '//') { // network-path reference
                        $validURL = filter_var('scheme:' . $element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE);
                    } elseif (substr($element, 0, 1) === '/') { // absolute-path reference
                        $validURL = filter_var('scheme://host' . $element, FILTER_VALIDATE_URL, FILTER_NULL_ON_FAILURE);
                    } elseif (strlen($element)) { // relative-path reference
                        $pathParts = explode('/', $element, 2);
                        if (strpos($pathParts[0], ':') !== false) {
                            $validURL = null;
                        } else {
                            $validURL = filter_var('scheme://host/' . $element, FILTER_VALIDATE_URL, FILTER_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($element, FILTER_VALIDATE_EMAIL, $filterFlags)) {
                    $this->addError($path, 'Invalid email', 'format', array('format' => $schema->format));
                }
                break;

            case 'ip-address':
            case 'ipv4':
                if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_NULL_ON_FAILURE | FILTER_FLAG_IPV4)) {
                    $this->addError($path, 'Invalid IP address', 'format', array('format' => $schema->format));
                }
                break;

            case 'ipv6':
                if (null === filter_var($element, FILTER_VALIDATE_IP, FILTER_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', $properties, PREG_GREP_INVERT);

        return empty($invalidEntries);
    }

    protected function validatePhone($phone)
    {
        return preg_match('/^\+?(\(\d{3}\)|\d{3}) \d{3} \d{4}$/', $phone);
    }

    protected function validateHostname($host)
    {
        $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\Constraints\TypeCheck\LooseTypeCheck;
use JsonSchema\Entity\JsonPointer;
use JsonSchema\Exception\ValidationException;
use JsonSchema\Uri\UriResolver;

/**
 * The UndefinedConstraint Constraints
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 */
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 = null, JsonPointer $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, $schema = null, JsonPointer $path, $i = null)
    {
        // check array
        if ($this->getTypeCheck()->isArray($value)) {
            $this->checkArray($value, $schema, $path, $i);
        }

        // check object
        if (LooseTypeCheck::isObject($value)) { // object processing should always be run on assoc arrays,
                                                // so use LooseTypeCheck here even if CHECK_MODE_TYPE_CAST
                                                // is not set (i.e. don't use $this->getTypeCheck() here).
            $this->checkObject(
                $value,
                $schema,
                $path,
                isset($schema->properties) ? $schema->properties : null,
                isset($schema->additionalProperties) ? $schema->additionalProperties : null,
                isset($schema->patternProperties) ? $schema->patternProperties : null,
                $this->appliedDefaults
            );
        }

        // check string
        if (is_string($value)) {
            $this->checkString($value, $schema, $path, $i);
        }

        // check numeric
        if (is_numeric($value)) {
            $this->checkNumber($value, $schema, $path, $i);
        }

        // check enum
        if (isset($schema->enum)) {
            $this->checkEnum($value, $schema, $path, $i);
        }
    }

    /**
     * Validates common properties
     *
     * @param mixed       $value
     * @param mixed       $schema
     * @param JsonPointer $path
     * @param string      $i
     */
    protected function validateCommonProperties(&$value, $schema = null, JsonPointer $path, $i = '')
    {
        // if it extends another schema, it must pass that schema as well
        if (isset($schema->extends)) {
            if (is_string($schema->extends)) {
                $schema->extends = $this->validateUri($schema, $schema->extends);
            }
            if (is_array($schema->extends)) {
                foreach ($schema->extends as $extends) {
                    $this->checkUndefined($value, $extends, $path, $i);
                }
            } else {
                $this->checkUndefined($value, $schema->extends, $path, $i);
            }
        }

        // Apply default values from schema
        if (!$path->fromDefault()) {
            $this->applyDefaultValues($value, $schema, $path);
        }

        // Verify required values
        if ($this->getTypeCheck()->isObject($value)) {
            if (!($value instanceof self) && isset($schema->required) && is_array($schema->required)) {
                // Draft 4 - Required is an array of strings - e.g. "required": ["foo", ...]
                foreach ($schema->required as $required) {
                    if (!$this->getTypeCheck()->propertyExists($value, $required)) {
                        $this->addError(
                            $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, $schema, JsonPointer $path, $i = '')
    {
        // Verify type
        if ($value instanceof self) {
            return;
        }

        if (isset($schema->allOf)) {
            $isValid = true;
            foreach ($schema->allOf as $allOf) {
                $initErrors = $this->getErrors();
                $this->checkUndefined($value, $allOf, $path, $i);
                $isValid = $isValid && (count($this->getErrors()) == count($initErrors));
            }
            if (!$isValid) {
                $this->addError($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($allErrors, array_values($this->getErrors()));
                } catch (ValidationException $e) {
                    // deliberately do nothing here - validation failed, but we want to check
                    // other schema options in the OneOf field.
                }
            }
            if ($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, $dependencies, JsonPointer $path, $i = '')
    {
        foreach ($dependencies as $key => $dependency) {
            if ($this->getTypeCheck()->propertyExists($value, $key)) {
                if (is_string($dependency)) {
                    // Draft 3 string is allowed - e.g. "dependencies": {"bar": "foo"}
                    if (!$this->getTypeCheck()->propertyExists($value, $dependency)) {
                        $this->addError($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;

/**
 * The Base Constraints, all Validators should extend this class
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 */
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('');
        $path = $path->withPropertyPaths(
            array_merge(
                $path->getPropertyPaths(),
                array_filter(array($i), 'strlen')
            )
        );

        return $path;
    }

    /**
     * Validates an array
     *
     * @param mixed            $value
     * @param mixed            $schema
     * @param JsonPointer|null $path
     * @param mixed            $i
     */
    protected function checkArray(&$value, $schema = null, JsonPointer $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 = null, JsonPointer $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 = null, JsonPointer $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 = null, JsonPointer $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 = null, JsonPointer $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 = null, JsonPointer $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 = null, JsonPointer $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 = null, JsonPointer $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

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 LooseTypeCheck implements TypeCheckInterface
{
    public static function isObject($value)
    {
        return
            is_object($value) ||
            (is_array($value) && (count($value) == 0 || self::isAssociativeArray($value)));
    }

    public static function isArray($value)
    {
        return
            is_array($value) &&
            (count($value) == 0 || !self::isAssociativeArray($value));
    }

    public static function propertyGet($value, $property)
    {
        if (is_object($value)) {
            return $value->{$property};
        }

        return $value[$property];
    }

    public static function propertySet(&$value, $property, $data)
    {
        if (is_object($value)) {
            $value->{$property} = $data;
        } else {
            $value[$property] = $data;
        }
    }

    public static function propertyExists($value, $property)
    {
        if (is_object($value)) {
            return property_exists($value, $property);
        }

        return array_key_exists($property, $value);
    }

    public static function propertyCount($value)
    {
        if (is_object($value)) {
            return count(get_object_vars($value));
        }

        return count($value);
    }

    /**
     * Check if the provided array is associative or not
     *
     * @param array $arr
     *
     * @return bool
     */
    private static function isAssociativeArray($arr)
    {
        return array_keys($arr) !== range(0, count($arr) - 1);
    }
}
<?php

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

/*
 * 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 <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 */
class EnumConstraint extends Constraint
{
    /**
     * {@inheritdoc}
     */
    public function check(&$element, $schema = null, JsonPointer $path = null, $i = null)
    {
        // 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;

/**
 * The Constraints Interface
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 */
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 = null, JsonPointer $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 CollectionConstraint Constraints, validates an array against a given schema
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 */
class CollectionConstraint extends Constraint
{
    /**
     * {@inheritdoc}
     */
    public function check(&$value, $schema = null, JsonPointer $path = null, $i = null)
    {
        // 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($e, true);
                }, $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 = null, JsonPointer $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

/*
 * 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 <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 */
class TypeConstraint extends Constraint
{
    /**
     * @var array|string[] type wordings for validation error messages
     */
    public static $wording = 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 = null, JsonPointer $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($path, ucwords(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, $path, null);
                    $error = $validator->getErrors();
                    $isValid = !(bool) $error;
                    $validTypesWording[] = self::$wording['object'];
                }
            } else {
                $this->validateTypeNameWording($tp);
                $validTypesWording[] = self::$wording[$tp];
                if (!$isValid) {
                    $isValid = $this->validateType($value, $tp);
                }
            }
        }
    }

    /**
     * Implodes the given array like implode() with turned around parameters and with the
     * difference, that, if $listEnd isn't false, the last element delimiter is $listEnd instead of
     * $delimiter.
     *
     * @param array  $elements  The elements to implode
     * @param string $delimiter The delimiter to use
     * @param bool   $listEnd   The last delimiter to use (defaults to $delimiter)
     *
     * @return string
     */
    protected function implodeWith(array $elements, $delimiter = ', ', $listEnd = false)
    {
        if ($listEnd === false || !isset($elements[1])) {
            return implode($delimiter, $elements);
        }
        $lastElement  = array_slice($elements, -1);
        $firsElements = join($delimiter, array_slice($elements, 0, -1));
        $implodedElements = array_merge(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($type, true),
                    implode(', ', array_filter(self::$wording)))
            );
        }
    }

    /**
     * Verifies that a given value is of a certain type
     *
     * @param mixed  $value Value to validate
     * @param string $type  TypeConstraint to check against
     *
     * @throws InvalidArgumentException
     *
     * @return bool
     */
    protected function validateType(&$value, $type)
    {
        //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 ObjectConstraint Constraints, validates an object against a given schema
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 */
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 = null, JsonPointer $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($element, JsonPointer $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, $i, in_array($i, $this->appliedDefaults));
                }
            }
        }

        return $matches;
    }

    /**
     * Validates the element properties
     *
     * @param \StdClass        $element        Element to validate
     * @param array            $matches        Matches from patternProperties (if any)
     * @param \StdClass        $schema         ObjectConstraint definition
     * @param JsonPointer|null $path           Current test path
     * @param \StdClass        $properties     Properties
     * @param mixed            $additionalProp Additional properties
     */
    public function validateElement($element, $matches, $schema = null, JsonPointer $path = null,
        $properties = null, $additionalProp = null)
    {
        $this->validateMinMaxConstraint($element, $schema, $path);

        foreach ($element as $i => $value) {
            $definition = $this->getProperty($properties, $i);

            // no additional properties allowed
            if (!in_array($i, $matches) && $additionalProp === false && $this->inlineSchemaProperty !== $i && !$definition) {
                $this->addError($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($value, null, $path, $i, in_array($i, $this->appliedDefaults));
                } else {
                    $this->checkUndefined($value, $additionalProp, $path, $i, in_array($i, $this->appliedDefaults));
                }
            }

            // property requires presence of another
            $require = $this->getProperty($definition, 'requires');
            if ($require && !$this->getProperty($element, $require)) {
                $this->addError($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 = null, JsonPointer $path = null)
    {
        $undefinedConstraint = $this->factory->createInstanceFor('undefined');

        foreach ($properties as $i => $value) {
            $property = &$this->getProperty($element, $i, $undefinedConstraint);
            $definition = $this->getProperty($properties, $i);

            if (is_object($definition)) {
                // Undefined constraint will check for is_object() and quit if is not - so why pass it?
                $this->checkUndefined($property, $definition, $path, $i, in_array($i, $this->appliedDefaults));
            }
        }
    }

    /**
     * retrieves a property from an object or array
     *
     * @param mixed  $element  Element to validate
     * @param string $property Property to retrieve
     * @param mixed  $fallback Default value if property is not found
     *
     * @return mixed
     */
    protected function &getProperty(&$element, $property, $fallback = null)
    {
        if (is_array($element) && (isset($element[$property]) || array_key_exists($property, $element)) /*$this->checkMode == self::CHECK_MODE_TYPE_CAST*/) {
            return $element[$property];
        } elseif (is_object($element) && property_exists($element, $property)) {
            return $element->$property;
        }

        return $fallback;
    }

    /**
     * validating minimum and maximum property constraints (if present) against an element
     *
     * @param \stdClass        $element          Element to validate
     * @param \stdClass        $objectDefinition ObjectConstraint definition
     * @param JsonPointer|null $path             Path to test?
     */
    protected function validateMinMaxConstraint($element, $objectDefinition, JsonPointer $path = null)
    {
        // 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\Entity\JsonPointer;

/**
 * The NumberConstraint Constraints, validates an number against a given schema
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 */
class NumberConstraint extends Constraint
{
    /**
     * {@inheritdoc}
     */
    public function check(&$element, $schema = null, JsonPointer $path = null, $i = null)
    {
        // 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\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;
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;

use JsonSchema\Constraints\BaseConstraint;
use JsonSchema\Constraints\Constraint;

/**
 * A JsonSchema Constraint
 *
 * @author Robert Schönthal <seroscho@googlemail.com>
 * @author Bruno Prieto Reis <bruno.p.reis@gmail.com>
 *
 * @see    README.md
 */
class Validator extends BaseConstraint
{
    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, $schema, Constraint::CHECK_MODE_COERCE_TYPES);
    }
}
<?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 <jnijhuis81@gmail.com>
 */
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('#', $value, 2);
        $this->filename = $splitRef[0];
        if (array_key_exists(1, $splitRef)) {
            $this->propertyPaths = $this->decodePropertyPaths($splitRef[1]);
        }
    }

    /**
     * @param string $propertyPathString
     *
     * @return string[]
     */
    private function decodePropertyPaths($propertyPathString)
    {
        $paths = 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\Iterator;

/**
 * @package JsonSchema\Iterator
 *
 * @author Joost Nijhuis <jnijhuis81@gmail.com>
 */
class ObjectIterator implements \Iterator, \Countable
{
    /** @var object */
    private $object;

    /** @var int */
    private $position = 0;

    /** @var array */
    private $data = 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\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 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;

/**
 * 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\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;

/**
 * @package JsonSchema\Exception
 *
 * @author Joost Nijhuis <jnijhuis81@gmail.com>
 */
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 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;

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 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 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 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
use Gettext\Languages\Exporter\Exporter;
use Gettext\Languages\Language;

// Let's start by imposing that we don't accept any error or warning.
// This is a really life-saving approach.
error_reporting(E_ALL);
set_error_handler(function ($errno, $errstr, $errfile, $errline) {
    Enviro::echoErr("$errstr\nFile: $errfile\nLine: $errline\nCode: $errno\n");
    die(5);
});

require_once dirname(__DIR__).'/src/autoloader.php';

// Parse the command line options
Enviro::initialize();

try {
    if (isset(Enviro::$languages)) {
        $languages = array();
        foreach (Enviro::$languages as $languageId) {
            $language = Language::getById($languageId);
            if (!isset($language)) {
                throw new Exception("Unable to find the language with id '$languageId'");
            }
            $languages[] = $language;
        }
    } else {
        $languages = Language::getAll();
    }
    if (Enviro::$reduce) {
        $languages = Enviro::reduce($languages);
    }
    if (isset(Enviro::$outputFilename)) {
        echo call_user_func(array(Exporter::getExporterClassName(Enviro::$outputFormat), 'toFile'), $languages, Enviro::$outputFilename, array('us-ascii' => Enviro::$outputUSAscii));
    } else {
        echo call_user_func(array(Exporter::getExporterClassName(Enviro::$outputFormat), 'toString'), $languages, array('us-ascii' => Enviro::$outputUSAscii));
    }
} catch (Exception $x) {
    Enviro::echoErr($x->getMessage()."\n");
    Enviro::echoErr("Trace:\n");
    Enviro::echoErr($x->getTraceAsString()."\n");
    die(4);
}

die(0);

/**
 * Helper class to handle command line options.
 */
class Enviro
{
    /**
     * Shall the output contain only US-ASCII characters?
     * @var bool
     */
    public static $outputUSAscii;
    /**
     * The output format.
     * @var string
     */
    public static $outputFormat;
    /**
     * Output file name.
     * @var string
     */
    public static $outputFilename;
    /**
     * List of wanted language IDs; it not set: all languages will be returned.
     * @var array|null
     */
    public static $languages;
    /**
     * Reduce the language list to the minimum common denominator.
     * @var bool
     */
    public static $reduce;
    /**
     * Parse the command line options.
     */
    public static function initialize()
    {
        global $argv;
        self::$outputUSAscii = false;
        self::$outputFormat = null;
        self::$outputFilename = null;
        self::$languages = null;
        self::$reduce = null;
        $exporters = Exporter::getExporters();
        if (isset($argv) && is_array($argv)) {
            foreach ($argv as $argi => $arg) {
                if ($argi === 0) {
                    continue;
                }
                if (is_string($arg)) {
                    $argLC = trim(strtolower($arg));
                    switch ($argLC) {
                        case '--us-ascii':
                            self::$outputUSAscii = true;
                            break;
                        case '--reduce=yes':
                            self::$reduce = true;
                            break;
                        case '--reduce=no':
                            self::$reduce = false;
                            break;
                        default:
                            if (preg_match('/^--output=.+$/', $argLC)) {
                                if (isset(self::$outputFilename)) {
                                    self::echoErr("The output file name has been specified more than once!\n");
                                    self::showSyntax();
                                    die(3);
                                }
                                list(, self::$outputFilename) = explode('=', $arg, 2);
                                self::$outputFilename = trim(self::$outputFilename);
                            } elseif (preg_match('/^--languages?=.+$/', $argLC)) {
                                list(, $s) = explode('=', $arg, 2);
                                $list = explode(',', $s);
                                if (is_array(self::$languages)) {
                                    self::$languages = array_merge(self::$languages, $list);
                                } else {
                                    self::$languages = $list;
                                }
                            } elseif (isset($exporters[$argLC])) {
                                if (isset(self::$outputFormat)) {
                                    self::echoErr("The output format has been specified more than once!\n");
                                    self::showSyntax();
                                    die(3);
                                }
                                self::$outputFormat = $argLC;
                            } else {
                                self::echoErr("Unknown option: $arg\n");
                                self::showSyntax();
                                die(2);
                            }
                            break;
                    }
                }
            }
        }
        if (!isset(self::$outputFormat)) {
            self::showSyntax();
            die(1);
        }
        if (isset(self::$languages)) {
            self::$languages = array_values(array_unique(self::$languages));
        }
        if (!isset(self::$reduce)) {
            self::$reduce = isset(self::$languages) ? false : true;
        }
    }

    /**
     * Write out the syntax.
     */
    public static function showSyntax()
    {
        $exporters = array_keys(Exporter::getExporters(true));
        self::echoErr("Syntax: php ".basename(__FILE__)." [--us-ascii] [--languages=<LanguageId>[,<LanguageId>,...]] [--reduce=yes|no] [--output=<file name>] <".implode('|', $exporters).">\n");
        self::echoErr("Where:\n");
        self::echoErr("--us-ascii : if specified, the output will contain only US-ASCII characters.\n");
        self::echoErr("--languages: (or --language) export only the specified language codes.\n");
        self::echoErr("             Separate languages with commas; you can also use this argument\n");
        self::echoErr("             more than once; it's case insensitive and accepts both '_' and\n");
        self::echoErr("             '-' as locale chunks separator (eg we accept 'it_IT' as well as\n");
        self::echoErr("            'it-it').\n");
        self::echoErr("--reduce   : if set to yes the output won't contain languages with the same\n");
        self::echoErr("             base language and rules.\n For instance nl_BE ('Flemish') will be\n");
        self::echoErr("             omitted because it's the same as nl ('Dutch').\n");
        self::echoErr("             Defaults to 'no' --languages is specified, to 'yes' otherwise.\n");
        self::echoErr("--output   : if specified, the output will be saved to <file name>. If not\n");
        self::echoErr("             specified we'll output to standard output.\n");
        self::echoErr("Output formats\n");
        $len = max(array_map('strlen', $exporters));
        foreach ($exporters as $exporter) {
            self::echoErr(str_pad($exporter, $len).": ".Exporter::getExporterDescription($exporter)."\n");
        }
    }
    /**
     * Print a string to stderr.
     * @param string $str The string to be printed out.
     */
    public static function echoErr($str)
    {
        $hStdErr = @fopen('php://stderr', 'a');
        if ($hStdErr === false) {
            echo $str;
        } else {
            fwrite($hStdErr, $str);
            fclose($hStdErr);
        }
    }
    /**
     * Reduce a language list to the minimum common denominator.
     * @param Language[] $languages
     * @return Language[]
     */
    public static function reduce($languages)
    {
        for ($numChunks = 3; $numChunks >= 2; $numChunks--) {
            $filtered = array();
            foreach ($languages as $language) {
                $chunks = explode('_', $language->id);
                $compatibleFound = false;
                if (count($chunks) === $numChunks) {
                    $categoriesHash = serialize($language->categories);
                    $otherIds = array();
                    $otherIds[] = $chunks[0];
                    for ($k = 2; $k < $numChunks; $k++) {
                        $otherIds[] = $chunks[0].'_'.$chunks[$numChunks - 1];
                    }

                    foreach ($languages as $check) {
                        foreach ($otherIds as $otherId) {
                            if (($check->id === $otherId) && ($check->formula === $language->formula) && (serialize($check->categories) === $categoriesHash)) {
                                $compatibleFound = true;
                                break;
                            }
                        }
                        if ($compatibleFound === true) {
                            break;
                        }
                    }
                }
                if (!$compatibleFound) {
                    $filtered[] = $language;
                }
            }
            $languages = $filtered;
        }

        return $languages;
    }
}
<?php
spl_autoload_register(
    function ($class) {
        if (strpos($class, 'Gettext\\Languages\\') !== 0) {
            return;
        }
        $file = __DIR__.str_replace('\\', DIRECTORY_SEPARATOR, substr($class, strlen('Gettext\\Languages'))).'.php';
        if (is_file($file)) {
            require_once $file;
        }
    }
);
<?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($examples, 0, strlen($examples) -strlen(', …'));
        }
        foreach (explode(',', str_replace(' ', '', $examples)) as $range) {
            if (preg_match('/^\d+$/', $range)) {
                $result[] = intval($range);
            } elseif (preg_match('/^(\d+)~(\d+)$/', $range, $m)) {
                $from = intval($m[1]);
                $to = intval($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;
                } elseif ($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;
            } elseif ($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;
        } else {
            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 (intval($m[1]) === 0) ? true : false;
        }
        if (preg_match('/^(?:v|w)(?: % 10+)? != (\d+)(?:\.\.\d+)?$/', $gettextAtom, $m)) { // For gettext: v == 0, w == 0
            return (intval($m[1]) === 0) ? false : true;
        }
        if (preg_match('/^(?:f|t)(?: % 10+)? == (\d+)(?:\.\.\d+)?$/', $gettextAtom, $m)) { // f == empty, t == empty
            return (intval($m[1]) === 0) ? true : false;
        }
        if (preg_match('/^(?:f|t)(?: % 10+)? != (\d+)(?:\.\.\d+)?$/', $gettextAtom, $m)) { // f == empty, t == empty
            return (intval($m[1]) === 0) ? 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 = intval($m[1]);
                    $to = intval($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\Exporter;

class Php extends Exporter
{
    /**
     * @see 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);
    }
    /**
     * @see Exporter::getDescription
     */
    public static function getDescription()
    {
        return 'Build a PHP array';
    }
}
<?php
namespace Gettext\Languages\Exporter;

use Exception;
use Gettext\Languages\Language;

/**
 * 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 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__);
    }
    /**
     * Convert a list of Language instances to string.
     * @param Language[] $languages The Language instances to convert.
     * @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 Language[] $languages The Language instances to convert.
     * @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__);
    }
}
<?php
namespace Gettext\Languages\Exporter;

use Exception;

class Po extends Exporter
{
    /**
     * @see 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);
    }
    /**
     * @see Exporter::getDescription
     */
    public static function getDescription()
    {
        return 'Build a string to be used for gettext .po files';
    }
}
<?php
namespace Gettext\Languages\Exporter;

use Exception;

class Prettyjson extends Json
{
    /**
     * @see 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;
    }
    /**
     * @see Exporter::getDescription
     */
    public static function getDescription()
    {
        return 'Build an uncompressed JSON-encoded file (PHP 5.4 or later is needed)';
    }
}
<?php
namespace Gettext\Languages\Exporter;

class Html extends Exporter
{
    /**
     * @see Exporter::toStringDo
     */
    protected static function toStringDo($languages)
    {
        return self::buildTable($languages, false);
    }
    protected static function h($str)
    {
        return htmlspecialchars($str, ENT_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);
    }
    /**
     * @see Exporter::getDescription
     */
    public static function getDescription()
    {
        return 'Build a HTML table';
    }
}
<?php
namespace Gettext\Languages\Exporter;

class Json extends Exporter
{
    /**
     * 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;
    }
    /**
     * @see 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());
    }
    /**
     * @see Exporter::getDescription
     */
    public static function getDescription()
    {
        return 'Build a compressed JSON-encoded file';
    }
}
<?php
namespace Gettext\Languages\Exporter;

class Docs extends Html
{
    /**
     * @see 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="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
        <link rel="stylesheet" href="style.css">
    </head>
    <body>
        <a href="https://github.com/mlocati/cldr-to-gettext-plural-rules" 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($languages, true);
        $result .= <<<EOT

        </div>
        <script src="http://code.jquery.com/jquery-1.11.2.min.js"></script>
        <script src="script.js"></script>
    </body>
</html>
EOT;

        return $result;
    }
    /**
     * @see Exporter::isForPublicUse
     */
    public static function isForPublicUse()
    {
        return false;
    }
    /**
     * @see Exporter::getDescription
     */
    public static function getDescription()
    {
        return 'Build the page http://mlocati.github.io/cldr-to-gettext-plural-rules';
    }
}
<?php
namespace Gettext\Languages\Exporter;

class Xml extends Exporter
{
    /**
     * @see 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();
    }
    /**
     * @see 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';
    }
}
<?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;
    /**
     * Returns the loaded CLDR data.
     * @param string $key Can be 'languages', 'territories', 'plurals', 'supersededLanguages', 'scripts', 'standAloneScripts'
     */
    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)$/', $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(
                'bh' => 'Bihari',
                'guw' => 'Gun',
                '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];
    }
    /**
     * 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');
    }
    /**
     * @var array
     */
    private static $plurals;
    /**
     * 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>
     * @var 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;
    }
}
<?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 null|string
     */
    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 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 $category1, Category $category2) {
            return array_search($category1->id, CldrData::$categories) - array_search($category2->id, CldrData::$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 Language[]
     */
    public static function getAll()
    {
        $result = array();
        foreach (array_keys(CldrData::getLanguageNames()) as $cldrLanguageId) {
            $result[] = new Language(CldrData::getLanguageInfo($cldrLanguageId));
        }

        return $result;
    }
    /**
     * Return a Language instance given the language id
     * @param string $id
     * @return Language|null
     */
    public static function getById($id)
    {
        $result = null;
        $info = CldrData::getLanguageInfo($id);
        if (isset($info)) {
            $result = new Language($info);
        }

        return $result;
    }

    /**
     * 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.
     */
    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.
     */
    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;
    }
    /**
     * Build the formula starting from the currently defined categories.
     * @return string
     */
    private function buildFormula()
    {
        $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 = strval($numCategories - 1);
                for ($i = $numCategories - 2; $i >= 0; $i--) {
                    $f = self::reduceFormula($this->categories[$i]->formula);
                    if (!preg_match('/^\([^()]+\)$/', $f)) {
                        $f = "($f)";
                    }
                    $formula = "$f ? $i : $formula";
                    if ($i > 0) {
                        $formula = "($formula)";
                    }
                }

                return $formula;
        }
    }
    /**
     * 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'
            $transliterated = strtr($value, array(
                'À' => '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',
                'è' => 'e', 'é' => 'e', 'ê' => 'e', 'ë' => 'e',
                'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i',
                'ñ' => 'n', 'ò' => 'o', 'ó' => 'o', 'ô' => 'o', 'õ' => 'o', 'ö' => 'o',
                'ù' => 'u', 'ú' => 'u', 'û' => 'u', 'ü' => 'u',
                'ý' => 'y', 'ÿ' => 'y',
            ));
            $transliterated = @iconv('UTF-8', 'US-ASCII//IGNORE//TRANSLIT', $transliterated);
            if (($transliterated === false) || ($transliterated === '')) {
                throw new Exception("Unable to transliterate '$value'");
            }
            $value = $transliterated;
        }
    }
    /**
     * Returns a clone of this instance with all the strings to US-ASCII.
     * @return 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;
    }
}
<?php

spl_autoload_register(function ($class) {
    if (strpos($class, 'Gettext\\') !== 0) {
        return;
    }

    $file = __DIR__.str_replace('\\', DIRECTORY_SEPARATOR, substr($class, strlen('Gettext'))).'.php';

    if (is_file($file)) {
        require_once $file;
    }
});
<?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 $from, Translation $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 $from, Translation $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 $from, Translation $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 $from, Translation $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 $from, Translation $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 $from, Translations $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 $from, Translations $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

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\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;

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\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\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;
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;
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\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;
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;
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($array, true).';';
    }

    /**
     * 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;

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\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\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 = 7 * 4;

        // Size of table with the original strings index
        $originalsIndexSize = $numEntries * (4 + 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 + 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;

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_LANGUAGE, trim($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_DOMAIN, trim($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\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($string, Translations $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\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($string, Translations $translations, array $options = [])
    {
        $options += static::$options;

        $xml = new SimpleXMLElement($string, null, false);

        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) $note, 2);
                                    $translation->addReference($ref[0], isset($ref[1]) ? $ref[1] : null);
                                    break;

                                default:
                                    $translation->addComment((string) $note);
                                    break;
                            }
                        }
                    }

                    $translations[] = $translation;
                }
            }
        }
    }
}
<?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($string, Translations $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+/', $line, 2);
            $key = $splitLine[0];
            $data = isset($splitLine[1]) ? $splitLine[1] : '';

            if ($key === '#~') {
                $translation->setDisabled(true);

                $splitLine = preg_split('/\s+/', $data, 2);
                $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($translation, iterator_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, -1, 1) == '"'
                && isset($lines[$j + 1])
                && substr(trim($lines[$j + 1]), 0, 1) == '"'
            ) {
                $line = substr($line, 0, -1).substr(trim($lines[$j + 1]), 1);
            } else {
                $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($value, 1, -1);
        }

        return strtr(
            $value,
            [
                '\\\\' => '\\',
                '\\a' => "\x07",
                '\\b' => "\x08",
                '\\t' => "\t",
                '\\n' => "\n",
                '\\v' => "\x0b",
                '\\f' => "\x0c",
                '\\r' => "\r",
                '\\"' => '"',
            ]
        );
    }
}
<?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;
use Gettext\Utils\MultidimensionalArrayTrait;

/**
 * Class to get gettext strings from json.
 */
class Json extends Extractor implements ExtractorInterface
{
    use MultidimensionalArrayTrait;

    /**
     * {@inheritdoc}
     */
    public static function fromString($string, Translations $translations, array $options = [])
    {
        $messages = json_decode($string, true);

        if (is_array($messages)) {
            static::fromArray($messages, $translations);
        }
    }
}
<?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($string, Translations $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 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($string, Translations $translations, array $options = [])
    {
        $messages = YamlParser::parse($string);

        if (is_array($messages)) {
            static::fromArray($messages, $translations);
        }
    }
}
<?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($string, Translations $translations, array $options = [])
    {
        if (empty($options['facade'])) {
            $cachePath = empty($options['cachePath']) ? sys_get_temp_dir() : $options['cachePath'];
            $bladeCompiler = new BladeCompiler(new Filesystem(), $cachePath);
            $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\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($string, Translations $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;
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($string, Translations $translations, array $options = [])
    {
        $messages = YamlParser::parse($string);

        if (is_array($messages)) {
            static::fromArray($messages, $translations);
        }
    }
}
<?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($file, Translations $translations, array $options = [])
    {
        foreach (static::getFiles($file) as $file) {
            static::fromArray(include($file), $translations);
        }
    }

    /**
     * {@inheritdoc}
     */
    public static function fromString($string, Translations $translations, array $options = [])
    {
        throw new BadMethodCallException('PhpArray::fromString() cannot be called. Use PhpArray::fromFile()');
    }
}
<?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($file, Translations $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($string, Translations $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 ExtractorInterface, ExtractorMultiInterface
{
    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($string, Translations $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);
        $dom->loadHTML($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 $options, DOMNode $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($context, trim($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 $options, DOMNode $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;
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($string, Translations $translations, array $options = [])
    {
        $messages = json_decode($string, true);

        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 ExtractorInterface, ExtractorMultiInterface
{
    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($string, Translations $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($value, 1, -1);
        }

        if ($value[0] === "'") {
            return strtr(substr($value, 1, -1), ['\\\\' => '\\', '\\\'' => '\'']);
        }

        $value = substr($value, 1, -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;

/**
 * Class to get gettext strings from json files.
 */
class Jed extends Extractor implements ExtractorInterface
{
    /**
     * {@inheritdoc}
     */
    public static function fromString($string, Translations $translations, array $options = [])
    {
        static::extract(json_decode($string, true), $translations);
    }

    /**
     * Handle an array of translations and append to the Translations instance.
     *
     * @param array        $content
     * @param Translations $translations
     */
    public static function extract(array $content, Translations $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($context, array_shift($key))
                ->setTranslation(array_shift($translation))
                ->setPluralTranslations($translation);
        }
    }
}
<?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($string, Translations $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*/', $headerLine, 2);
                    $translations->setHeader($headerChunks[0], isset($headerChunks[1]) ? $headerChunks[1] : '');
                }

                continue;
            }

            $chunks = explode("\x04", $original, 2);

            if (isset($chunks[1])) {
                $context = $chunks[0];
                $original = $chunks[1];
            } else {
                $context = '';
            }

            $chunks = explode("\x00", $original, 2);

            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(4 * $count));
    }
}
<?php

namespace Gettext\Extractors;

use Exception;
use InvalidArgumentException;
use Gettext\Translations;

abstract class Extractor implements ExtractorInterface
{
    /**
     * {@inheritdoc}
     */
    public static function fromFile($file, Translations $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 Exception;
use Gettext\Translations;
use Gettext\Utils\FunctionsScanner;

/**
 * Class to get gettext strings from javascript files.
 */
class JsCode extends Extractor implements ExtractorInterface, ExtractorMultiInterface
{
    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($string, Translations $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;

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->domain, null, $original);
    }

    /**
     * @see TranslatorInterface
     *
     * {@inheritdoc}
     */
    public function ngettext($original, $plural, $value)
    {
        return $this->dnpgettext($this->domain, null, $original, $plural, $value);
    }

    /**
     * @see TranslatorInterface
     *
     * {@inheritdoc}
     */
    public function dngettext($domain, $original, $plural, $value)
    {
        return $this->dnpgettext($domain, null, $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($domain, null, $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 ? 0 : 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) ? 1 : 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($failure, true);
        $code = $expression.' ? '.$success.' : '.$failure;

        if ($inner) {
            return "($code)";
        }

        // note the semicolon. We need that for executing the code.
        return "$code;";
    }
}
<?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->pluralTranslation, 0, $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->comments, true)) {
            $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->extractedComments, true)) {
            $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->flags, true)) {
            $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;

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

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;

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

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 $messages, Translations $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;

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 $messages, Translations $translations)
    {
        foreach ($messages as $original => $translation) {
            if ($original === '') {
                static::extractHeaders($translation, $translations);
                continue;
            }

            $translations->insert(null, $original)->setTranslation($translation);
        }
    }
}
<?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;

/*
 * 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($handle, 0, $options['delimiter'], $options['enclosure'], $options['escape_char']);
        }

        return fgetcsv($handle, 0, $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;

/**
 * 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 string[]|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 string $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 (&$carry, Translations $translations) {
            $carry[$translations->getDomain()] = $translations;
            return $carry;
        }, []);

        $functions = $options['functions'];
        $file = $options['file'];

        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 (!$isDefaultDomain && !$domainTranslations) {
                continue;
            }

            $translation = $domainTranslations->insert($context, $original, $plural);
            $translation->addReference($file, $line);

            if (isset($function[3])) {
                foreach ($function[3] as $extractedComment) {
                    $translation->addExtractedComment($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($argument, 1, -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;

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($headers, Translations $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(':', $line, 2));
                $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 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\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->getComment());
                                }
                            }

                            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->getComment());
                        }
                    }
                    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;

/**
 * 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
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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();
    
    /**
     * 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)
    {
        $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
     * 
     * @return array
     */
    protected function execFunctions($node)
    {
        $traverseChildren = true;
        $continueTraversing = true;
        
        foreach ($this->functions as $fn) {
            $ret = $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 boolena 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($node, true) as $prop) {
            $ucProp = ucfirst($prop);
            $getter = "get$ucProp";
            $setter = "set$ucProp";
            $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);
                        if ($c) {
                            $newChildren[] = $c;
                        }
                    }
                }
                $node->$setter($newChildren);
            } else {
                list($child, $continue) = $this->execFunctions($child);
                $node->$setter($child);
            }
            
            if (!$continue) {
                break;
            }
        }
        
        return $continue;
    }
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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->getType() === 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->getLocation();
                //Store the token end position
                $this->lastTokenIndex = $loc->getEnd()->getIndex();
                if ($this->buffer) {
                    //Fill the "next" key on the comments buffer with the token
                    //start position
                    $this->buffer["next"] = $loc->getStart()->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->getLocation();
        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    $comments   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]->getLocation()->getStart()->getIndex();
            $end = $comments[count($comments) -1]->getLocation()->getEnd()->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->getLocation()->getEnd()->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->getLocation();
        $length1 = $loc1->getEnd()->getIndex() - $loc1->getStart()->getIndex();
        $loc2 = $node2->getLocation();
        $length2 = $loc2->getEnd()->getIndex() - $loc2->getStart()->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 : 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->getLocation();
            $commentNode = new Node\Comment;
            $commentNode->setStartPosition($loc->getStart())
                        ->setEndPosition($loc->getEnd())
                        ->setRawText($comment->getValue());
            $currentComments[] = $commentNode;
        }
        $node->{"set$fn"}($currentComments);
    }
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 * 
 * @abstract
 */
abstract class Scanner
{
    /**
     * 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;
    
    /**
     * Regex to match identifiers parts
     * 
     * @var string 
     */
    protected $idPartRegex;
    
    /**
     * Keywords array
     * 
     * @var array 
     */
    protected $keywords = array();
    
    /**
     * Array of words that are keywords only in strict mode
     * 
     * @var array 
     */
    protected $strictModeKeywords = array();
    
    /**
     * Punctutators array
     * 
     * @var array 
     */
    protected $punctutators = array();
    
    /**
     * Punctutators LSM
     * 
     * @var LSM 
     */
    protected $punctutatorsLSM;
    
    /**
     * 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", 0x00A0, 0xFEFF, 0x00A0,
        0x1680, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006,
        0x2007, 0x2008, 0x2009, 0x200A, 0x202F, 0x205F, 0x3000, 0x2028,
        0x2029
    );
    
    /**
     * Line terminators characters array
     * 
     * @var array 
     * 
     * @static
     */
    public static $lineTerminatorsChars = array("\n", "\r", 0x2028, 0x2029);
    
    /**
     * 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");
    
    /**
     * Class constructor
     * 
     * @param string $source   Source code
     * @param array  $options Parsing options
     */
    function __construct($source, $options)
    {
        $encoding = isset($options["sourceEncoding"]) ?
                    $options["sourceEncoding"] :
                    null;
        
        //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::$lineTerminatorsSequences, self::$lineTerminatorsChars
        );
        foreach (array("whitespaces", "lineTerminators") as $key) {
            foreach ($this->$key as $i => $char) {
                if (is_int($char)) {
                    $this->{$key}[$i] = Utils::unicodeToUtf8($char);
                }
            }
        }
        
        //Create a LSM for punctutators array
        $this->punctutatorsLSM = new LSM($this->punctutators);
        
        //Create a LSM for strings stops
        $this->stringsStopsLSM = new LSM($this->lineTerminators, true);
        
        $this->linesSplitter = "/" .
                               implode("|", $this->lineTerminators) .
                               "/u";
        $this->position = new Position(0, 0, 0);
    }
    
    /**
     * 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->getType() === Token::TYPE_KEYWORD &&
               (in_array($token->getValue(), $this->keywords) || (
                $this->strictMode &&
                in_array($token->getValue(), $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
        $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) {
            $this->tokens = array_slice($this->tokens, 0, $state["tokensNum"]);
            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 $this->isEOF($index) ? null : $this->source[$index];
    }
    
    /**
     * Checks if the given index is at the end of the source code
     * 
     * @param int $index Index, if not given it will use the current index
     * 
     * @return bool
     */
    public function isEOF($index = null)
    {
        if ($index === null) {
            $index = $this->index;
        }
        return $index >= $this->length;
    }
    
    /**
     * 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->getLocation()->getEnd();
        
        //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;
        $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)
    {
        $token = $this->getToken();
        if ($token && $token->getValue() === $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)
    {
        $token = $this->getToken();
        if ($token && in_array($token->getValue(), $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->getLocation()->getEnd()->getLine();
        } else {
            $refLine = $this->getPosition()->getLine();
        }
        $token = $this->getToken();
        return $token &&
               $token->getLocation()->getEnd()->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->getToken();
        if (!$token) {
            return false;
        } elseif (in_array($token->getValue(), $expected)) {
            return true;
        } elseif (!$nextToken) {
            return false;
        }
        if (!$this->getNextToken()) {
            return false;
        }
        foreach ($expected as $val) {
            if (!is_array($val) || $val[0] !== $token->getValue()) {
                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->getValue())) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * Returns the next token
     * 
     * @return Token|null
     */
    public function getNextToken()
    {
        if (!$this->nextToken) {
            $token = $this->getToken();
            $this->currentToken = null;
            $this->nextToken = $this->getToken();
            $this->currentToken = $token;
        }
        return $this->nextToken;
    }
    
    /**
     * Returns the current token 
     * 
     * @return Token|null
     */
    public function getToken()
    {
        //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)
                );
            }
        }
        
        if ($this->isEOF()) {
            //When the end of the source is reached
            //Check if there are open brackets
            foreach ($this->openBrackets as $bracket => $num) {
                if ($num) {
                    return $this->error("Unclosed $bracket");
                }
            }
            
            //Check if there are open templates
            if (count($this->openTemplates)) {
                return $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->scanJSXString())) ||
                ($this->jsx && ($token = $this->scanJSXIdentifier())) ||
                ($token = $this->scanString()) ||
                ($token = $this->scanTemplate()) ||
                ($token = $this->scanNumber()) ||
                ($this->jsx && ($token = $this->scanJSXPunctutator())) ||
                ($token = $this->scanPunctutator()) ||
                ($token = $this->scanKeywordOrIdentifier())
            ) {
                //Set the token stard and end positions
                $token->setStartPosition($startPosition)
                      ->setEndPosition($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;
        }
        return $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 >= 0 && $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->getToken();
        $value = $token ? $token->getValue() : 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->getLocation()->getStart();
        $this->setScanPosition($startPosition);
        
        $buffer = "/";
        $this->index++;
        $this->column++;
        $inClass = false;
        while (true) {
            //In a characters class the delmiter "/" 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) {
                    return $this->error(
                        "Unterminated character class in regexp"
                    );
                } else {
                    return $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->getValue();
            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);
        $this->currentToken = $token->setStartPosition($startPosition)
                                    ->setEndPosition($this->getPosition(true));
                                    
        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
                                return $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($content, 0, -strlen($char));
                                }
                                $this->adjustColumnAndLine($content);
                                $token = new Token(Token::TYPE_COMMENT, $content);
                                $token->setStartPosition($start)
                                      ->setEndPosition($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($content, 0, -strlen($char));
                            }
                            $this->adjustColumnAndLine($content);
                            $token = new Token(Token::TYPE_COMMENT, $content);
                            $token->setStartPosition($start)
                                  ->setEndPosition($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($content, 0, -strlen($char));
                                }
                                $this->adjustColumnAndLine($content);
                                $token = new Token(Token::TYPE_COMMENT, $content);
                                $token->setStartPosition($start)
                                      ->setEndPosition($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) {
                return $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) {
                    return $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 nuber or with a dot (.5)
        $char = $this->charAt();
        if (!(($char >= "0" && $char <= "9") || $char === ".")) {
            return null;
        }
        
        $buffer = "";
        $allowedExp = true;
        
        //Parse the integer part
        if ($char !== ".") {
            
            //Consume all decimal numbers
            $buffer = $this->consumeNumbers();
            $char = $this->charAt();
            $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) {
                    return $this->error("Missing numbers after 0$char");
                }
                $buffer .= $char . $tempBuffer;
                
                //Check that there are not numbers left
                if ($this->consumeNumbers() !== null) {
                    return $this->error();
                }
                
                return new Token(Token::TYPE_NUMERIC_LITERAL, $buffer);
            }
            
            //Consume exponent part if present
            if ($tempBuffer = $this->consumeExponentPart()) {
                $buffer .= $tempBuffer;
                $allowedExp = false;
            }
        }
        
        //Parse the decimal part
        if ($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
            //punctutator
            if ($buffer === ".") {
                $this->index--;
                $this->column--;
                return null;
            }
            
            //Consume exponent part if present
            if (($tempBuffer = $this->consumeExponentPart()) !== null) {
                if (!$allowedExp) {
                    return $this->error("Invalid exponential notation");
                }
                $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;
        while (in_array($char, $this->{$type . "numbers"})) {
            $buffer .= $char;
            $this->index++;
            $this->column++;
            $count ++;
            if ($count === $max) {
                break;
            }
            $char = $this->charAt();
        }
        return $count ? $buffer : null;
    }
    
    /**
     * Consumes the exponent part of a number
     * 
     * @return string|null
     */
    protected function consumeExponentPart()
    {
        $buffer = "";
        $char = $this->charAt();
        if (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) {
                return $this->error("Missing exponent");
            }
            $buffer .= $tempBuffer;
        }
        return $buffer;
    }
    
    /**
     * Punctutator scanning method
     * 
     * @return Token|null
     */
    protected function scanPunctutator()
    {
        $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))) {
                        return $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_PUNCTUTATOR, $char);
        } elseif (
            //Try to match the longest puncutator
            $match = $this->punctutatorsLSM->match($this, $this->index, $char)
        ) {
            $this->index += $match[0];
            $this->column += $match[0];
            $token = new Token(Token::TYPE_PUNCTUTATOR, $match[1]);
        }
        return $token;
    }
    
    /**
     * Keywords and identifiers scanning method
     * 
     * @return Token|null
     */
    protected function scanKeywordOrIdentifier()
    {
        //Consume the maximum number of characters that are unicode escape
        //sequences or valid identifier starts (only the first character) or
        //parts
        $buffer = "";
        $fn = "isIdentifierStart";
        while (($char = $this->charAt()) !== null) {
            if ($this->$fn($char)) {
                $buffer .= $char;
                $this->index++;
                $this->column++;
            } elseif ($seq = $this->consumeUnicodeEscapeSequence()) {
                //Verify that is a valid character
                if (!$this->$fn($seq)) {
                    break;
                }
                $buffer .= $seq;
            } else {
                break;
            }
            $fn = "isIdentifierPart";
        }
        
        //Identify token type
        if ($buffer === "") {
            return null;
        } 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 string|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;
        if ($this->charAt() === "{") {
            //\u{FFF}
            $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 the decoded character
        return Utils::unicodeToUtf8(hexdec($code));
    }
    
    /**
     * Checks if the given character is a valid identifier start
     * 
     * @param string $char Character to check
     * 
     * @return bool
     */
    protected function isIdentifierStart($char)
    {
        return ($char >= "a" && $char <= "z") ||
               ($char >= "A" && $char <= "Z") ||
               $char === "_" || $char === "$" ||
               preg_match($this->idStartRegex, $char);
    }
    
    /**
     * Checks if the given character is a valid identifier part
     * 
     * @param string $char Character to check
     * 
     * @return bool
     */
    protected function isIdentifierPart($char)
    {
        return ($char >= "a" && $char <= "z") ||
               ($char >= "A" && $char <= "Z") ||
               ($char >= "0" && $char <= "9") ||
               $char === "_" || $char === "$" ||
               preg_match($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 string|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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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 immediatelly
            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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
class Token implements \JSONSerializable
{
    //Type constants
    /**
     * Boolean literal
     */
    const TYPE_BOOLEAN_LITERAL = "Boolean";
    
    /**
     * Identifier
     */
    const TYPE_IDENTIFIER = "Identifier";
    
    /**
     * Keyword
     */
    const TYPE_KEYWORD = "Keyword";
    
    /**
     * Null literal
     */
    const TYPE_NULL_LITERAL = "Null";
    
    /**
     * Numeric literal
     */
    const TYPE_NUMERIC_LITERAL = "Numeric";
    
    /**
     * Punctutator
     */
    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 
     */
    protected $type;
    
    /**
     * Token's value
     * 
     * @var string 
     */
    protected $value;
    
    /**
     * Token's location in the source code
     * 
     * @var SourceLocation 
     */
    protected $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->setStart($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->setEnd($position);
        return $this;
    }
    
    /**
     * Returns a serializable version of the node
     * 
     * @return array
     */
    public function jsonSerialize()
    {
        return array(
            "type" => $this->getType(),
            "value" => $this->getValue(),
            "location" => $this->getLocation()
        );
    }
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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
 * positon.
 * 
 * @author Marco Marchiò <marco.mm89@gmail.com>
 */
class SourceLocation implements \JSONSerializable
{
    /**
     * Start position
     * 
     * @var Position 
     */
    protected $start;
    
    /**
     * End position
     * 
     * @var Position 
     */
    protected $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
     */
    public function jsonSerialize()
    {
        return array(
            "start" => $this->getStart(),
            "end" => $this->getEnd()
        );
    }
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\ES2019;

use \Peast\Syntax\Utils;

/**
 * ES2019 scanner.
 * 
 * @author Marco Marchiò <marco.mm89@gmail.com>
 */
class Scanner extends \Peast\Syntax\ES2018\Scanner
{
    /**
     * Class constructor
     * 
     * @param string $source   Source code
     * @param string $encoding Source code encoding, if not specified it
     *                         will assume UTF-8
     */
    function __construct($source, $encoding = null)
    {
        parent::__construct($source, $encoding);
        
        //Allow paragraph and line separators in strings
        $this->stringsStopsLSM->remove(Utils::unicodeToUtf8(0x2028));
        $this->stringsStopsLSM->remove(Utils::unicodeToUtf8(0x2029));
    }
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\ES2019;

use \Peast\Syntax\Node;

/**
 * ES2019 parser class
 * 
 * @author Marco Marchiò <marco.mm89@gmail.com>
 */
class Parser extends \Peast\Syntax\ES2018\Parser
{
    /**
     * 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(")")) {
                    return $this->error();
                }
                $node->setParam($param);
            }
            
            if (!($body = $this->parseBlock())) {
                return $this->error();
            }
            
            $node->setBody($body);
            
            return $this->completeNode($node);
        }
        return null;
    }
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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 preceeds 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 preceeds the
     * argument
     * 
     * @return bool
     */
    public function getPrefix()
    {
        return $this->prefix;
    }
    
    /**
     * Sets the prefix flag that is true when the operator preceeds 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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
interface ModuleDeclaration
{
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
class ThisExpression extends Node implements Expression
{
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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
     */
    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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
class SpreadElement extends Node
{
    /**
     * Map of node properties
     * 
     * @var array 
     */
    protected $propertiesMap = array(
        "argument" => true
    );
    
    /**
     * The node's argument
     * 
     * @var type 
     */
    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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
class ImportDefaultSpecifier extends ModuleSpecifier
{
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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]+$/i",
            "conv" => "bindec",
            "format" => self::BINARY
        ),
        "o" => array(
            "check" => "/^0o[0-7]+$/i",
            "conv" => "octdec",
            "format" => self::OCTAL
        ),
        "x" => array(
            "check" => "/^0x[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;
            if (isset($this->forms[$form])) {
                $formDef = $this->forms[$form];
                if (!preg_match($formDef["check"], $value)) {
                    throw new \Exception("Invalid " . $formDef["format"]);
                }
                $value = $formDef["conv"]($value);
                $format = $formDef["format"];
            } elseif ($startZero && preg_match("/^0[0-7]+$/", $value)) {
                //Legacy octal form
                $value = octdec($value);
                $format = self::OCTAL;
            } elseif (!preg_match("/^(\d*\.?\d*)(?:e[+\-]?\d+)?$/i", $value, $match) ||
                $match[1] === "" || $match[1] === "."
            ) {
                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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
class ClassBody extends Node
{
    /**
     * Map of node properties
     * 
     * @var array 
     */
    protected $propertiesMap = array(
        "body" => true
    );
    
    /**
     * Class methods
     * 
     * @var MethodDefinition[]
     */
    protected $body = array();
    
    /**
     * Returns class methods
     * 
     * @return MethodDefinition[]
     */
    public function getBody()
    {
        return $this->body;
    }
    
    /**
     * Sets class methods
     * 
     * @param MethodDefinition[] $body Class methods array
     * 
     * @return $this
     */
    public function setBody($body)
    {
        $this->assertArrayOf($body, "MethodDefinition");
        $this->body = $body;
        return $this;
    }
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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 paterns or function
 * parameters.
 * For example "...rest" in: [a, ...rest] = b
 * 
 * @author Marco Marchiò <marco.mm89@gmail.com>
 */
class RestElement extends Node implements Pattern
{
    /**
     * Map of node properties
     * 
     * @var array 
     */
    protected $propertiesMap = array(
        "argument" => true
    );
    
    /**
     * The node's argument
     * 
     * @var type 
     */
    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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
interface Pattern
{
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
interface Statement
{
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
class MemberExpression extends Node implements Expression, 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
     */
    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
     */
    public function getProperty()
    {
        return $this->property;
    }
    
    /**
     * Sets the expression's property
     * 
     * @param Expression $property Property
     * 
     * @return $this
     */
    public function setProperty(Expression $property)
    {
        $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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
class ImportNamespaceSpecifier extends ModuleSpecifier
{
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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 tagget template expression.
 * For example: fn`template`
 * 
 * @author Marco Marchiò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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 type 
     */
    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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 * 
 * @abstract
 */
abstract class ModuleSpecifier extends Node
{
    /**
     * Map of node properties
     * 
     * @var array 
     */
    protected $propertiesMap = array(
        "local" => true
    );
    
    /**
     * Local identifier
     * 
     * @var Identifier 
     */
    protected $local;
    
    /**
     * Returns the local identifier
     * 
     * @return Identifier
     */
    public function getLocal()
    {
        return $this->local;
    }
    
    /**
     * Sets the local identifier
     * 
     * @param Identifier $local Local identifier
     * 
     * @return $this
     */
    public function setLocal(Identifier $local)
    {
        $this->local = $local;
        return $this;
    }
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
class CallExpression extends Node implements Expression
{
    /**
     * 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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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 
     */
    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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
interface Declaration extends Statement
{
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
class DebuggerStatement extends Node implements Statement
{
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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 
     */
    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
     */
    public function getKey()
    {
        return $this->key;
    }
    
    /**
     * Sets the method's key
     * 
     * @param Expression $key Method's key
     * 
     * @return $this
     */
    public function setKey(Expression $key)
    {
        $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 type $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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
class ExportAllDeclaration extends Node implements ModuleDeclaration
{
    /**
     * Map of node properties
     * 
     * @var array 
     */
    protected $propertiesMap = array(
        "source" => true
    );
    
    /**
     * The export source
     * 
     * @var Literal 
     */
    protected $source;
    
    /**
     * 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;
    }
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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 preceeds 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 preceeds
     * 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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
class Super extends Node
{
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
class EmptyStatement extends Node implements Statement
{
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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 type 
     */
    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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
class ExportSpecifier extends ModuleSpecifier
{
    /**
     * Map of node properties
     * 
     * @var array 
     */
    protected $propertiesMap = array(
        "exported" => true
    );
    
    /**
     * Exported identifier
     * 
     * @var Identifier 
     */
    protected $exported;
    
    /**
     * Returns the exported identifier
     * 
     * @return Identifier
     */
    public function getExported()
    {
        return $this->exported;
    }
    
    /**
     * Sets the exported identifier
     * 
     * @param Identifier $exported Exported identifier
     * 
     * @return $this
     */
    public function setExported(Identifier $exported)
    {
        $this->exported = $exported;
        return $this;
    }
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * Root node for scripts and modules.
 * 
 * @author Marco Marchiò <marco.mm89@gmail.com>
 */
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;
    }
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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 preceeded 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
     * preceeded by *
     * 
     * @return bool
     */
    public function getDelegate()
    {
        return $this->delegate;
    }
    
    /**
     * Sets the delegate flag that is true when the yield keyword is
     * preceeded 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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
class AssignmentProperty extends Property
{
    /**
     * Returns node's type
     * 
     * @return string
     */
    public function getType()
    {
        return "Property";
    }
    
    /**
     * Sets the propery 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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
class ImportSpecifier extends ModuleSpecifier
{
    /**
     * Map of node properties
     * 
     * @var array 
     */
    protected $propertiesMap = array(
        "imported" => true
    );
    
    /**
     * Imported identifier
     * 
     * @var Identifier 
     */
    protected $imported;
    
    /**
     * Returns the imported identifier
     * 
     * @return Identifier
     */
    public function getImported()
    {
        return $this->imported;
    }
    
    /**
     * Sets the imported identifier
     * 
     * @param Identifier $imported Imported identifier
     * 
     * @return $this
     */
    public function setImported(Identifier $imported)
    {
        $this->imported = $imported;
        return $this;
    }
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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($rawText, 0, 2);
        if ($start === "//") {
            $kind = self::KIND_INLINE;
            $text = substr($rawText, 2);
        } elseif ($start === "/*" && substr($rawText, -2) === "*/") {
            $kind = self::KIND_MULTILINE;
            $text = substr($rawText, 2, -2);
        } elseif ($start === "<!" && substr($rawText, 2, 2) === "--") {
            $kind = self::KIND_HTML_OPEN;
            $text = substr($rawText, 4);
        } elseif ($start === "--" && substr($rawText, 2, 1) === ">") {
            $kind = self::KIND_HTML_CLOSE;
            $text = substr($rawText, 3);
        } else {
            throw new \Exception("Invalid comment");
        }
        return $this->setKind($kind)->setText($text);
    }
    
    /**
     * Sets leading comments array
     * 
     * @param Comments[] $comments Comments array
     * 
     * @return $this
     */
    public function setLeadingComments($comments)
    {
        //Comments cannot be attached to other comments
        return $this;
    }
    
    /**
     * Sets trailing comments array
     * 
     * @param Comments[] $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
     */
    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ò <marco.mm89@gmail.com>
 *
 * 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 identifier.
 * 
 * @author Marco Marchiò <marco.mm89@gmail.com>
 */
class Identifier extends Node implements Expression, Pattern
{
    /**
     * 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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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 $rawValue Raw value
     * 
     * @return $this
     */
    public function setRaw($rawValue)
    {
        
        $parts = explode("/", substr($rawValue, 1));
        $flags = array_pop($parts);
        $this->setPattern(implode("/", $parts));
        $this->setFlags($flags);
        return $this;
    }
    
    /**
     * Returns node's value
     * 
     * @return mixed
     */
    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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
interface Expression
{
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
class JSXSpreadAttribute extends SpreadElement
{
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
class JSXIdentifier extends Identifier
{
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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("JSXMemberExpression", "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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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(
            "JSXText", "JSXExpressionContainer", "JSXSpreadChild", "JSXElement",
            "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ò <marco.mm89@gmail.com>
 *
 * 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 opening element tag.
 * 
 * @author Marco Marchiò <marco.mm89@gmail.com>
 */
class JSXOpeningElement extends JSXBoundaryElement
{
    /**
     * Map of node properties
     * 
     * @var array 
     */
    protected $propertiesMap = array(
        "attributes" => true,
        "selfClosing" => false
    );
    
    /**
     * Children nodes array
     * 
     * @var JSXAttribute[]|JSXSpreadAttribute[]
     */
    protected $attrributes = array();
    
    /**
     * Self closing tag mode
     * 
     * @var bool
     */
    protected $selfClosing = false;
    
    /**
     * Returns the children attributes array
     * 
     * @return JSXAttribute[]|JSXSpreadAttribute[]
     */
    public function getAttributes()
    {
        return $this->attrributes;
    }
    
    /**
     * Sets the attributes nodes array
     * 
     * @param JSXAttribute[]|JSXSpreadAttribute[] $attrributes Attrributes nodes
     *                                                         array
     * 
     * @return $this
     */
    public function setAttributes($attrributes)
    {
        $this->assertArrayOf($attrributes, array(
            "JSXAttribute", "JSXSpreadAttribute"
        ));
        $this->attrributes = $attrributes;
        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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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(
            "JSXText", "JSXExpressionContainer", "JSXSpreadChild", "JSXElement",
            "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, "JSXClosingElement", true);
        $this->closingElement = $closingElement;
        return $this;
    }
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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 Wrappedexpression
     * 
     * @return $this
     */
    public function setExpression(Expression $expression)
    {
        $this->expression = $expression;
        return $this;
    }
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
class JSXClosingFragment extends Node
{
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
class JSXClosingElement extends JSXBoundaryElement
{
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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("JSXIdentifier", "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(
                "\Peast\Syntax\Node\Literal", "JSXExpressionContainer",
                "JSXElement", "JSXFragment"
            ),
            true
        );
        $this->value = $value;
        return $this;
    }
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
class JSXEmptyExpression extends Node
{
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 * 
 * @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("JSXIdentifier", "JSXMemberExpression", "JSXNamespacedName")
        );
        $this->name = $name;
        return $this;
    }
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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("\Peast\Syntax\Node\Expression", "JSXEmptyExpression")
        );
        $this->expression = $expression;
        return $this;
    }
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
class JSXOpeningFragment extends Node
{
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
class NewExpression extends CallExpression
{
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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
     */
    public function jsonSerialize()
    {
        $ret = parent::jsonSerialize();
        unset($ret["parts"]);
        return $ret;
    }
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
class TryStatement extends Node implements Statement
{
    /**
     * Map of node properties
     * 
     * @var array 
     */
    protected $propertiesMap = array(
        "block" => true,
        "handler" => true,
        "finalizer" => true
    );
    
    /**
     * Wrapped block
     * 
     * @var BlockStatements 
     */
    protected $block;
    
    /**
     * Catch clause
     * 
     * @var CatchClause 
     */
    protected $handler;
    
    /**
     * "finally" block
     * 
     * @var BlockStatement 
     */
    protected $finalizer;
    
    /**
     * Returns the wrapped block
     * 
     * @return BlockStatements
     */
    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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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");
        return $this->id = $id;
    }
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
class FunctionExpression extends Function_ implements Expression
{
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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 type $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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\Node;

/**
 * Base class for all the nodes generated by Peast.
 * 
 * @author Marco Marchiò <marco.mm89@gmail.com>
 * 
 * @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 \Peast\Syntax\SourceLocation 
     */
    protected $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 \Peast\Syntax\SourceLocation;
    }
    
    /**
     * Returns node type
     * 
     * @return string
     */
    public function getType()
    {
        $class = explode("\\", get_class($this));
        return array_pop($class);
    }
    
    /**
     * Sets leading comments array
     *
     * @param Comments[] $comments Comments array
     *
     * @return $this
     */
    public function setLeadingComments($comments)
    {
        $this->assertArrayOf($comments, "\Peast\Syntax\Node\Comment");
        $this->leadingComments = $comments;
        return $this;
    }

    /**
     * Returns leading comments array
     *
     * @return Comments[]
     */
    public function getLeadingComments()
    {
        return $this->leadingComments;
    }

    /**
     * Sets trailing comments array
     *
     * @param Comments[] $comments Comments array
     *
     * @return $this
     */
    public function setTrailingComments($comments)
    {
        $this->assertArrayOf($comments, "\Peast\Syntax\Node\Comment");
        $this->trailingComments = $comments;
        return $this;
    }

    /**
     * Returns trailing comments array
     *
     * @return Comments[]
     */
    public function getTrailingComments()
    {
        return $this->trailingComments;
    }

    /**
     * Returns node location in the source code
     * 
     * @return \Peast\Syntax\SourceLocation
     */
    public function getLocation()
    {
        return $this->location;
    }
    
    /**
     * Sets the start position of the node in the source code
     * 
     * @param \Peast\Syntax\Position $position Start position
     * 
     * @return $this
     */
    public function setStartPosition(\Peast\Syntax\Position $position)
    {
        $this->location->setStart($position);
        return $this;
    }
    
    /**
     * Sets the end position of the node in the source code
     * 
     * @param \Peast\Syntax\Position $position Start position
     * 
     * @return $this
     */
    public function setEndPosition(\Peast\Syntax\Position $position)
    {
        $this->location->setEnd($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
     * 
     * @return $this
     */
    public function traverse(callable $fn)
    {
        $traverser = new \Peast\Traverser();
        $traverser->addFunction($fn)->traverse($this);
        return $this;
    }
    
    /**
     * Returns a serializable version of the node
     * 
     * @return array
     */
    public function jsonSerialize()
    {
        $ret = array();
        $props = \Peast\Syntax\Utils::getNodeProperties($this);
        foreach ($props as $prop) {
            $ret[$prop] = $this->{"get" . ucfirst($prop)}();
        }
        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, $allowNull, true);
        } else {
            foreach ($params as $param) {
                foreach ($classes as $class) {
                    if ($param === null && $allowNull) {
                        continue 2;
                    } else {
                        $c = $this->addNamespace($class);
                        if ($param instanceof $c) {
                            continue 2;
                        }
                    }
                }
                $this->typeError($param, $classes, $allowNull, true, true);
            }
        }
    }
    
    /**
     * 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 = $this->addNamespace($class);
                if ($param instanceof $c) {
                    return;
                }
            }
            $this->typeError($param, $classes, $allowNull);
        }
    }
    
    /**
     * Adds the namespace of the current class to the given class name
     * 
     * @param string $class Class name to namespace
     * 
     * @return string
     */
    protected function addNamespace($class)
    {
        if ($class[0] === "\\") {
            return $class;
        }
        $parts = explode("\\", get_class($this));
        $parts[count($parts) -1] = $class;
        return implode("\\", $parts);
    }
    
    /**
     * 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($msg, E_USER_ERROR);
        }
    }
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
class ClassExpression extends Class_ implements Expression
{
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 * 
 * @abstract
 */
abstract class Parser
{
    /**
     * Associated scanner
     * 
     * @var Scanner 
     */
    protected $scanner;
    
    /**
     * 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 array  $options Parsing options
     */
    public function __construct($source, $options = array())
    {
        $this->sourceType = isset($options["sourceType"]) ?
                            $options["sourceType"] :
                            \Peast\Peast::SOURCE_TYPE_SCRIPT;
        
        //Create the scanner
        $classParts = explode("\\", get_class($this));
        array_pop($classParts);
        $classParts[] = "Scanner";
        $scannerClasss = implode("\\", $classParts);
        $this->scanner = new $scannerClasss($source, $options);
        
        //Enable module scanning if required
        if ($this->sourceType === \Peast\Peast::SOURCE_TYPE_MODULE) {
            $this->scanner->enableModuleMode(true);
        }
        
        //Enable comments scanning
        $this->comments = isset($options["comments"]) && $options["comments"];
        if ($this->comments) {
            $this->scanner->enableComments(true);
            //Create the comments registry
            new CommentsRegistry($this);
        }
        
        // Enable jsx syntax if required
        $this->jsx = isset($options["jsx"]) && $options["jsx"];
        
        $this->initContext();
    }
    
    /**
     * Initializes parser context
     * 
     * @return stdClass
     */
    abstract protected function initContext();
    
    /**
     * 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'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, applyng 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       $args   Method arguments
     * 
     * @return mixed
     */
    protected function isolateContext($flags, $fn, $args = array())
    {
        //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 = call_user_func_array(array($this, $fn), $args);
        
        //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->getLocation()->getStart();
        } elseif (is_array($position)) {
            if (count($position)) {
                $position = $position[0]->getLocation()->getStart();
            } else {
                $position = $this->scanner->getPosition();
            }
        }
        $node->setStartPosition($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 Node\Node
     * 
     * @codeCoverageIgnore
     */
    protected function completeNode(Node\Node $node, $position = null)
    {
        //Add the node end position
        $node->setEndPosition(
            $position ? $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->getLocation()->getStart();
                $message = "Unexpected: " . $token->getValue();
            }
        }
        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->getValue() === "}") {
                return true;
            }
        }
        return $this->error();
    }
    
    /**
     * Parses a character separated list of instructions or null if the
     * sequence is not valid
     * 
     * @param callable $fn   Parsing instruction function
     * @param array    $args Arguments that will be passed to the function
     * @param string   $char Separator
     * 
     * @return array
     * 
     * @throws Exception
     */
    protected function charSeparatedListOf($fn, $args = array(), $char = ",")
    {
        $list = array();
        $valid = true;
        while ($param = call_user_func_array(array($this, $fn), $args)) {
            $list[] = $param;
            $valid = true;
            if (!$this->scanner->consume($char)) {
                break;
            } else {
                $valid = false;
            }
        }
        if (!$valid) {
            return $this->error();
        }
        return $list;
    }
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
trait Scanner
{
    /**
     * Tries to reconsume the current token as a jsx text if possible
     * 
     * @return Token|null
     */
    public function reconsumeCurrentTokenAsJSXText()
    {
        $this->nextToken = null;
        $this->currentToken = null;
        $startPosition = $this->getPosition();
        $this->setScanPosition($startPosition);
        $result = $this->consumeUntil(array("{", "<"), false, false);
        if ($result) {
            $this->currentToken = new Token(Token::TYPE_JSX_TEXT, $result[0]);
            $this->currentToken->setStartPosition($startPosition)
                               ->setEndPosition($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 punctutator method in jsx mode
     * 
     * @return Token|null
     */
    public function scanJSXPunctutator()
    {
        //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_PUNCTUTATOR, $char);
        }
        return $this->scanPunctutator();
    }
    
    /**
     * Identifier scanning method in jsx mode
     * 
     * @return Token|null
     */
    public function scanJSXIdentifier()
    {
        $buffer = "";
        $char = $this->charAt();
        if ($char !== null && $this->isIdentifierStart($char)) {
            
            do {
                $buffer .= $char;
                $this->index++;
                $this->column++;
                $char = $this->charAt();
            } while (
                $char !== null &&
                ($this->isIdentifierPart($char) || $char === "-")
            );
        }
        
        return $buffer === "" ? null : new Token(Token::TYPE_JSX_IDENTIFIER, $buffer);
    }
}
<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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->getValue() !== "<") {
            return null;
        }
        
        $endOpeningToken = $this->scanner->getNextToken();
        if (!$endOpeningToken || $endOpeningToken->getValue() !== ">") {
            return null;
        }
        
        $this->scanner->consumeToken();
        $this->scanner->consumeToken();
        
        $children = $this->parseJSXChildren();
        
        if (!($startClosingToken = $this->scanner->consume("<")) ||
            !$this->scanner->consume("/") ||
            !$this->scanner->reconsumeCurrentTokenInJSXMode() ||
            $endOpeningToken->getValue() !== ">") {
            return $this->error();
        }
        $this->scanner->consumeToken();
        
        //Opening tag
        $openingNode = $this->createJSXNode(
            "JSXOpeningFragment",
            $startOpeningToken
        );
        $this->completeNode(
            $openingNode,
            $endOpeningToken->getLocation()->getEnd()
        );
        
        //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->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("}")) {
                return $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->getValue());
        return $this->completeNode($node, $token->getLocation()->getEnd());
    }
    
    /**
     * Parses a jsx element
     * 
     * @return \Peast\Syntax\Node\JSX\JSXElement|null
     */
    protected function parseJSXElement()
    {
        $startOpeningToken = $this->scanner->getToken();
        if (!$startOpeningToken || $startOpeningToken->getValue() !== "<") {
            return null;
        }
        
        $nextToken = $this->scanner->getNextToken();
        if ($nextToken && $nextToken->getValue() === "/") {
            return null;
        }
        
        $this->scanner->consumeToken();
        
        if (!($name = $this->parseJSXIdentifierOrMemberExpression())) {
            return $this->error();
        }
        
        $attributes = $this->parseJSXAttributes();
        
        $selfClosing = $this->scanner->consume("/");
        
        $endOpeningToken = $this->scanner->reconsumeCurrentTokenInJSXMode();
        if (!$endOpeningToken || $endOpeningToken->getValue() !== ">") {
            return $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->getValue() === ">")
            ) {
                $this->scanner->consumeToken();
                if (!$this->isSameJSXElementName($name, $closingName)) {
                    return $this->error("Closing tag does not match opening tag");
                }
            } else {
                return $this->error();
            }
            
        }
        
        //Opening tag
        $openingNode = $this->createJSXNode(
            "JSXOpeningElement",
            $startOpeningToken
        );
        $openingNode->setName($name);
        $openingNode->setSelfClosing($selfClosing);
        if ($attributes) {
            $openingNode->setAttributes($attributes);
        }
        $this->completeNode(
            $openingNode,
            $endOpeningToken->getLocation()->getEnd()
        );
        
        //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->getType() !== Token::TYPE_JSX_IDENTIFIER) {
            return null;
        }
        $this->scanner->consumeToken();
        
        $idNode = $this->createJSXNode("JSXIdentifier", $idToken);
        $idNode->setName($idToken->getValue());
        $idNode = $this->completeNode($idNode);
        
        //Namespaced identifier
        if ($this->scanner->consume(":")) {
            
            $idToken2 = $this->scanner->reconsumeCurrentTokenInJSXMode();
            if (!$idToken2 || $idToken2->getType() !== Token::TYPE_JSX_IDENTIFIER) {
                return $this->error();
            }
            $this->scanner->consumeToken();
            
            $idNode2 = $this->createJSXNode("JSXIdentifier", $idToken2);
            $idNode2->setName($idToken2->getValue());
            $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->getType() !== Token::TYPE_JSX_IDENTIFIER) {
                    return $this->error();
                }
                $this->scanner->consumeToken();
                $nextIds[] = $nextId;
            }
        }
        
        //Create the member expression if required
        $objectNode = $idNode;
        foreach ($nextIds as $nid) {
            $propEnd = $nid->getLocation()->getEnd();
            $propNode = $this->createJSXNode("JSXIdentifier", $nid);
            $propNode->setName($nid->getValue());
            $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\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);
        }
        
        return $this->error();
    }
    
    /**
     * Parses a jsx spread attribute
     * 
     * @return \Peast\Syntax\Node\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->getType() === Token::TYPE_STRING_LITERAL) {
                $this->scanner->consumeToken();
                $value = $this->createNode("StringLiteral", $strToken);
                $value->setRaw($strToken->getValue());
                $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 {
                    return $this->error();
                }
                
            } elseif (
                !($value = $this->parseJSXFragment()) &&
                !($value = $this->parseJSXElement())
            ) {
                return $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   $n1 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ò <marco.mm89@gmail.com>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\ES2016;

/**
 * ES2016 scanner.
 * 
 * @author Marco Marchiò <marco.mm89@gmail.com>
 */
class Scanner extends \Peast\Syntax\ES2015\Scanner
{
    /**
     * Punctutators array
     * 
     * @var array 
     */
    protected $punctutators = array(
        ".", ";", ",", "<", ">", "<=", ">=", "==", "!=", "===", "!==", "+",
        "-", "*", "%", "++", "--", "<<", ">>", ">>>", "&", "|", "^", "!", "~",
        "&&", "||", "?", ":", "=", "+=", "-=", "*=", "%=", "<<=", ">>=", ">>>=",
        "&=", "|=", "^=", "=>", "...", "/", "/=", "**", "**="
    );
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\ES2016;

/**
 * ES2016 parser class
 * 
 * @author Marco Marchiò <marco.mm89@gmail.com>
 */
class Parser extends \Peast\Syntax\ES2015\Parser
{
    /**
     * Assignment operators
     * 
     * @var array 
     */
    protected $assignmentOperators = array(
        "=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "&=", "^=",
        "|=", "**="
    );
    
    /**
     * Logical and binary operators
     * 
     * @var array 
     */
    protected $logicalBinaryOperators = array(
        "||" => 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
    );
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\ES2018;

/**
 * ES2018 scanner.
 * 
 * @author Marco Marchiò <marco.mm89@gmail.com>
 */
class Scanner extends \Peast\Syntax\ES2017\Scanner
{
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\ES2018;

use \Peast\Syntax\Node;

/**
 * ES2018 parser class
 * 
 * @author Marco Marchiò <marco.mm89@gmail.com>
 */
class Parser extends \Peast\Syntax\ES2017\Parser
{
    /**
     * Checks if the given string or number contains invalid esape 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 (!$taggedTemplate) {
            parent::checkInvalidEscapeSequences(
                $val, $number, $forceLegacyOctalCheck, $taggedTemplate
            );
        }
    }
    
    /**
     * 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 ($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()
    {
        if ($token = $this->scanner->consume("...")) {
            
            if ($argument = $this->parseIdentifier(static::$bindingIdentifier)) {
                $node = $this->createNode("RestElement", $token);
                $node->setArgument($argument);
                return $this->completeNode($node);
            }
            
            return $this->error();
        }
        return null;
    }
    
    /**
     * Parses a property in an object literal
     * 
     * @return Node\Property|Node\SpreadElement|null
     */
    protected function parsePropertyDefinition()
    {
        if ($prop = $this->parseSpreadElement()) {
            return $prop;
        }
        return parent::parsePropertyDefinition();
    }
    
    /**
     * 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 = false;
        if ($asyncToken = $this->checkAsyncFunctionStart()) {
            $this->scanner->consumeToken();
            $async = true;
        }
        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->setStartPosition(
                    $tokenBodyStart->getLocation()->getStart()
                );
                $body->setEndPosition($this->scanner->getPosition());
                $node = $this->createNode(
                    "FunctionDeclaration",
                    $async ? $asyncToken : $token
                );
                if ($id) {
                    $node->setId($id);
                }
                $node->setParams($params);
                $node->setBody($body);
                $node->setGenerator($generator);
                $node->setAsync($async);
                return $this->completeNode($node);
            }
            
            return $this->error();
        }
        return null;
    }
    
    /**
     * Parses function or generator expression
     * 
     * @return Node\FunctionExpression|null
     */
    protected function parseFunctionOrGeneratorExpression()
    {
        $async = false;
        if ($asyncToken = $this->checkAsyncFunctionStart()) {
            $this->scanner->consumeToken();
            $async = true;
        }
        if ($token = $this->scanner->consume("function")) {
            
            $generator = $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->setStartPosition(
                    $tokenBodyStart->getLocation()->getStart()
                );
                $body->setEndPosition($this->scanner->getPosition());
                $node = $this->createNode(
                    "FunctionExpression",
                    $async ? $asyncToken : $token
                );
                $node->setId($id);
                $node->setParams($params);
                $node->setBody($body);
                $node->setGenerator($generator);
                $node->setAsync($async);
                return $this->completeNode($node);
            }
            
            return $this->error();
        }
        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;
            $error = true;
        } elseif ($token = $this->scanner->consume("set")) {
            $position = $token;
            $kind = Node\MethodDefinition::KIND_SET;
            $error = true;
        } elseif ($token = $this->scanner->consume("*")) {
            $position = $token;
            $error = true;
            $generator = true;
        } elseif ($token = $this->checkAsyncFunctionStart(false)) {
            $this->scanner->consumeToken();
            $position = $token;
            $error = true;
            $async = true;
            if ($token = $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->parsePropertyName()) {
            
            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->setStartPosition(
                        $tokenBodyStart->getLocation()->getStart()
                    );
                    $body->setEndPosition($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) {
            return $this->error();
        } else {
            $this->scanner->setState($state);
        }
        return null;
    }
    
    /**
     * 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->context->allowAwait &&
                $this->scanner->consume("await")
            ) {
                $forAwait = true;
            }
            
            if ($this->scanner->consume("(") && (
                ($node = $this->parseForVarStatement($startForToken)) ||
                ($node = $this->parseForLetConstStatement($startForToken)) ||
                ($node = $this->parseForNotVarLetConstStatement($startForToken)))
            ) {
                if ($forAwait) {
                    if (!$node instanceof Node\ForOfStatement) {
                        $this->error(
                            "Async iteration is allowed only with for-of statements",
                            $startForToken->getLocation()->getStart()
                        );
                    }
                    $node->setAwait(true);
                }
                return $node;
            }
            
            return $this->error();
        }
        
        return null;
    }
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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, null, PREG_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($str, false);
            } 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;
    }
    
    /**
     * 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($str, 1, -1);
        
        $lineTerminators = self::getLineTerminators();
        
        //Handle escapes
        $patterns = array(
            "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";
                }
                // \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];
            }
        };
        $str = preg_replace_callback($reg, $replacement, $str);
        
        return $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_keys($traversable ? array_filter($props) : $props);
    }
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 * 
 * @codeCoverageIgnore
 */
class EncodingException extends \Exception
{
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\ES2015;

/**
 * ES2015 scanner.
 * 
 * @author Marco Marchiò <marco.mm89@gmail.com>
 */
class Scanner extends \Peast\Syntax\Scanner
{
    use \Peast\Syntax\JSX\Scanner;
    
    /**
     * Regex to match identifiers starts
     * 
     * @var string 
     */
    protected $idStartRegex = "/[\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}\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{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"
    );
    
    /**
     * Punctutators array
     * 
     * @var array 
     */
    protected $punctutators = array(
        ".", ";", ",", "<", ">", "<=", ">=", "==", "!=", "===", "!==", "+",
        "-", "*", "%", "++", "--", "<<", ">>", ">>>", "&", "|", "^", "!", "~",
        "&&", "||", "?", ":", "=", "+=", "-=", "*=", "%=", "<<=", ">>=", ">>>=",
        "&=", "|=", "^=", "=>", "...", "/", "/="
    );
    
    /**
     * Octal numbers
     * 
     * @var array 
     */
    protected $onumbers = array("0", "1", "2", "3", "4", "5", "6", "7");
    
    /**
     * Binary numbers
     * 
     * @var array 
     */
    protected $bnumbers = array("0", "1");
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\ES2015;

use Peast\Syntax\Token;
use \Peast\Syntax\Node;

/**
 * ES2015 parser class
 * 
 * @author Marco Marchiò <marco.mm89@gmail.com>
 */
class Parser extends \Peast\Syntax\Parser
{
    use \Peast\Syntax\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,
        "&&" => 1,
        "|" => 2,
        "^" => 3,
        "&" => 4,
        "===" => 5, "!==" => 5, "==" => 5, "!=" => 5,
        "<=" => 6, ">=" => 6, "<" => 6, ">" => 6,
        "instanceof" => 6, "in" => 6,
        ">>>" => 7, "<<" => 7, ">>" => 7,
        "+" => 8, "-" => 8,
        "*" => 9, "/" => 9, "%" => 9
    );
    
    /**
     * Configurable lookaheads
     * 
     * @var array 
     */
    protected $lookahead = array(
        "export" => array(
            "tokens" => array("function", "class"),
            "next"=> false
        ),    
        "expression" => array(
            "tokens" => array("{", "function", "class", array("let", "[")),
            "next"=> true
        )
    );
    
    /**
     * 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"
    );
    
    /**
     * Initializes parser context
     * 
     * @return void
     */
    protected function initContext()
    {
        $this->context = (object) array(
            "allowReturn" => false,
            "allowIn" => false,
            "allowYield" => false
        );
    }
    
    /**
     * 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 ? $body : $this->scanner->getPosition()
        );
        $node->setSourceType($this->sourceType);
        if ($body) {
            $node->setBody($body);
        }
        $program = $this->completeNode($node);
        
        if ($this->scanner->getToken()) {
            return $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->getLocation();
            $elems = array();
            foreach ($node->getElements() as $elem) {
                $elems[] = $this->expressionToPattern($elem);
            }
                
            $retNode = $this->createNode("ArrayPattern", $loc->getStart());
            $retNode->setElements($elems);
            $this->completeNode($retNode, $loc->getEnd());
            
        } elseif ($node instanceof Node\ObjectExpression) {
            
            $loc = $node->getLocation();
            $props = array();
            foreach ($node->getProperties() as $prop) {
                $props[] = $this->expressionToPattern($prop);
            }
                
            $retNode = $this->createNode("ObjectPattern", $loc->getStart());
            $retNode->setProperties($props);
            $this->completeNode($retNode, $loc->getEnd());
            
        } elseif ($node instanceof Node\Property) {
            
            $loc = $node->getLocation();
            $retNode = $this->createNode(
                "AssignmentProperty", $loc->getStart()
            );
            // 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->getLocation();
                $valNode = $this->createNode("AssignmentPattern", $loc->getStart());
                $valNode->setLeft($key);
                $valNode->setRight($value);
                $this->completeNode($valNode, $loc->getEnd());
                $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->getEnd());
            
        } elseif ($node instanceof Node\SpreadElement) {
            
            $loc = $node->getLocation();
            $retNode = $this->createNode("RestElement", $loc->getStart());
            $retNode->setArgument(
                $this->expressionToPattern($node->getArgument())
            );
            $this->completeNode($retNode, $loc->getEnd());
            
        } elseif ($node instanceof Node\AssignmentExpression) {
            
            $loc = $node->getLocation();
            $retNode = $this->createNode("AssignmentPattern", $loc->getStart());
            $retNode->setLeft($this->expressionToPattern($node->getLeft()));
            $retNode->setRight($node->getRight());
            $this->completeNode($retNode, $loc->getEnd());
            
        } 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()
    {
        if ($statement = $this->parseBlock()) {
            return $statement;
        } elseif ($statement = $this->parseVariableStatement()) {
            return $statement;
        } elseif ($statement = $this->parseEmptyStatement()) {
            return $statement;
        } elseif ($statement = $this->parseIfStatement()) {
            return $statement;
        } elseif ($statement = $this->parseBreakableStatement()) {
            return $statement;
        } elseif ($statement = $this->parseContinueStatement()) {
            return $statement;
        } elseif ($statement = $this->parseBreakStatement()) {
            return $statement;
        } elseif ($this->context->allowReturn && $statement = $this->parseReturnStatement()) {
            return $statement;
        } elseif ($statement = $this->parseWithStatement()) {
            return $statement;
        } elseif ($statement = $this->parseThrowStatement()) {
            return $statement;
        } elseif ($statement = $this->parseTryStatement()) {
            return $statement;
        } elseif ($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()
    {
        if ($declaration = $this->parseFunctionOrGeneratorDeclaration()) {
            return $declaration;
        } elseif ($declaration = $this->parseClassDeclaration()) {
            return $declaration;
        } elseif (
            $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);
            }
            return $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(
                        false, false
                    ))
                )
            ) {
                
                $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(
                            false, false
                        ))
                    ) {
                        $node->setAlternate($alternate);
                        return $this->completeNode($node);
                    }
                } else {
                    return $this->completeNode($node);
                }
            }
            
            return $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);
                }
            }
            
            return $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")) {
            
            if ($this->scanner->consume("(") &&
                ($param = $this->parseCatchParameter()) &&
                $this->scanner->consume(")") &&
                $body = $this->parseBlock()
            ) {

                $node = $this->createNode("CatchClause", $token);
                $node->setParam($param);
                $node->setBody($body);
                return $this->completeNode($node);
            }
            
            return $this->error();
        }
        return null;
    }
    
    /**
     * Parses the catch parameter of a catch block in a try-catch statement
     * 
     * @return 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;
            }
            
            return $this->error();
        }
        return null;
    }
    
    /**
     * Parses a countinue statement
     * 
     * @return Node\ContinueStatement|null
     */
    protected function parseContinueStatement()
    {
        if ($token = $this->scanner->consume("continue")) {
            
            $node = $this->createNode("ContinueStatement", $token);
            
            if ($this->scanner->noLineTerminators()) {
                if ($label = $this->parseIdentifier(static::$labelledIdentifier)) {
                    $node->setLabel($label);
                }
            }
            
            $this->assertEndOfStatement();
            
            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()) {
                if ($label = $this->parseIdentifier(static::$labelledIdentifier)) {
                    $node->setLabel($label);
                }
            }
            
            $this->assertEndOfStatement();
            
            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(
                    false, false
                ))
            ) {
                
                //Labelled functions are not allowed in strict mode 
                if ($body instanceof Node\FunctionDeclaration &&
                    $this->scanner->getStrictMode()) {
                    return $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);

            }

            return $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);
            }
            
            return $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);
            }
            
            return $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);
            }
            
            return $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()) {
                return $this->error(
                    "Multiple default clause in switch statement"
                );
            } else {
                return $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);
            }
            
            return $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);
            }
            
            return $this->error();
        }
        return null;
    }
    
    /**
     * Parses an expression statement
     * 
     * @return Node\ExpressionStatement|null
     */
    protected function parseExpressionStatement()
    {
        if (!$this->scanner->isBefore(
                $this->lookahead["expression"]["tokens"],
                $this->lookahead["expression"]["next"]
            ) &&
            $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);
                return $this->completeNode($node);
            }
            
            return $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);
            }
            
            return $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()) {
                
                $left = $this->createNode(
                    "VariableDeclaration", $varToken
                );
                $left->setKind($left::KIND_VAR);
                $left->setDeclarations(array($decl));
                $left = $this->completeNode($left);
                
                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($left);
                        $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($left);
                        $node->setRight($right);
                        $node->setBody($body);
                        return $this->completeNode($node);
                    }
                }
            }
        }
        
        return $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);
                    }
                }
            }
        }
        
        return $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
     * 
     * @return Node\Node|null
     */
    protected function parseForNotVarLetConstStatement($forToken)
    {
        $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);
            $notBeforeLet = !$this->scanner->isBefore(array("let"));
            $left = $this->parseLeftHandSideExpression();
            $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 ($notBeforeLet && $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);
                }
            }
        }
        
        return $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 ($forToken = $this->scanner->consume("for")) {
            
            if ($this->scanner->consume("(") && (
                ($node = $this->parseForVarStatement($forToken)) ||
                ($node = $this->parseForLetConstStatement($forToken)) ||
                ($node = $this->parseForNotVarLetConstStatement($forToken)))
            ) {
                return $node;
            }
            
            return $this->error();
        }
        
        return 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
    ) {
        if ($token = $this->scanner->consume("function")) {
            
            $generator = $allowGenerator && $this->scanner->consume("*");
            $id = $this->parseIdentifier(static::$bindingIdentifier);
            
            if (($default || $id) &&
                $this->scanner->consume("(") &&
                ($params = $this->isolateContext(
                    $generator ? array("allowYield" => true) : null,
                    "parseFormalParameterList"
                )) !== null &&
                $this->scanner->consume(")") &&
                ($tokenBodyStart = $this->scanner->consume("{")) &&
                (($body = $this->isolateContext(
                    $generator ? array("allowYield" => true) : null,
                    "parseFunctionBody"
                )) || true) &&
                $this->scanner->consume("}")
            ) {
                
                $body->setStartPosition(
                    $tokenBodyStart->getLocation()->getStart()
                );
                $body->setEndPosition($this->scanner->getPosition());
                $node = $this->createNode("FunctionDeclaration", $token);
                if ($id) {
                    $node->setId($id);
                }
                $node->setParams($params);
                $node->setBody($body);
                $node->setGenerator($generator);
                return $this->completeNode($node);
            }
            
            return $this->error();
        }
        return null;
    }
    
    /**
     * Parses function or generator expression
     * 
     * @return Node\FunctionExpression|null
     */
    protected function parseFunctionOrGeneratorExpression()
    {
        if ($token = $this->scanner->consume("function")) {
            
            $generator = (bool) $this->scanner->consume("*");
            
            if ($generator) {
                $flags = array(null, "allowYield" => 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->setStartPosition(
                    $tokenBodyStart->getLocation()->getStart()
                );
                $body->setEndPosition($this->scanner->getPosition());
                $node = $this->createNode("FunctionExpression", $token);
                $node->setId($id);
                $node->setParams($params);
                $node->setBody($body);
                $node->setGenerator($generator);
                return $this->completeNode($node);
            }
            
            return $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()
    {
        $valid = true;
        $list = array();
        while (
            ($param = $this->parseBindingRestElement()) ||
            $param = $this->parseBindingElement()
        ) {
            $valid = true;
            $list[] = $param;
            if ($param->getType() === "RestElement") {
                break;
            } elseif ($this->scanner->consume(",")) {
                $valid = false;
            } else {
                break;
            }
        }
        if (!$valid) {
            return $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 ? $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);
            }
            
            return $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 extendend 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->setStartPosition($token->getLocation()->getStart());
                $body->setEndPosition($this->scanner->getPosition());
                return array($heritage, $body);
            }
        }
        return $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;
            }
            
            return $this->error();
        }
        return null;
    }
    
    /**
     * Parses the class body
     * 
     * @return Node\ClassBody|null
     */
    protected function parseClassBody()
    {
        $body = $this->parseClassElementList();
        $node = $this->createNode(
            "ClassBody", $body ? $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|null
     */
    protected function parseClassElement()
    {
        if ($this->scanner->consume(";")) {
            return true;
        }
        
        $staticToken = $this->scanner->consume("static");
        if ($def = $this->parseMethodDefinition()) {
            if ($staticToken) {
                $def->setStatic(true);
                $def->setStartPosition($staticToken->getLocation()->getStart());
            }
            return $def;
        } elseif ($staticToken) {
            return $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->getValue());
                $node->setDeclarations($declarations);
                return $this->completeNode($node);
            }
            
            // "let" can be used as variable name in non-strict mode
            if ($this->scanner->getStrictMode() || $token->getValue() !== "let") {
                return $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);
            }
            
            return $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->getValue());
                $node->setDeclarations(array($declaration));
                return $this->completeNode($node);
            }
            
            // "let" can be used as variable name in non-strict mode
            if ($this->scanner->getStrictMode() || $token->getValue() !== "let") {
                return $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->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;
            }
            return $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("*")) {
                
                if ($source = $this->parseFromClause()) {
                    $this->assertEndOfStatement();
                    $node = $this->createNode("ExportAllDeclaration", $token);
                    $node->setSource($source);
                    return $this->completeNode($node);
                }
                
            } elseif ($this->scanner->consume("default")) {
                
                if (($declaration = $this->isolateContext(
                        null,
                        "parseFunctionOrGeneratorDeclaration",
                        array(true)
                    )) ||
                    ($declaration = $this->isolateContext(
                        null,
                        "parseClassDeclaration",
                        array(true)
                    ))
                ) {
                    
                    $node = $this->createNode("ExportDefaultDeclaration", $token);
                    $node->setDeclaration($declaration);
                    return $this->completeNode($node);
                    
                } elseif (!$this->scanner->isBefore(
                        $this->lookahead["export"]["tokens"],
                        $this->lookahead["export"]["next"]
                    ) &&
                    ($declaration = $this->isolateContext(
                        array(null, "allowIn" => true),
                        "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(
                    null, "parseVariableStatement"
                )) ||
                $dec = $this->isolateContext(
                    null, "parseDeclaration"
                )
            ) {

                $node = $this->createNode("ExportNamedDeclaration", $token);
                $node->setDeclaration($dec);
                return $this->completeNode($node);
            }
            
            return $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;
            }
            
            return $this->error();
        }
        return null;
    }
    
    /**
     * Parses an export specifier
     * 
     * @return Node\ExportSpecifier|null
     */
    protected function parseExportSpecifier()
    {
        if ($local = $this->parseIdentifier(static::$identifierName)) {
            
            $node = $this->createNode("ExportSpecifier", $local);
            $node->setLocal($local);
            
            if ($this->scanner->consume("as")) {
                
                if ($exported = $this->parseIdentifier(static::$identifierName)) {
                    $node->setExported($exported);
                    return $this->completeNode($node);
                }
                
                return $this->error();
            } else {
                $node->setExported($local);
                return $this->completeNode($node);
            }
        }
        return null;
    }
    
    /**
     * Parses an import declaration
     * 
     * @return Node\ModuleDeclaration|null
     */
    protected function parseImportDeclaration()
    {
        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);
            }
            
            return $this->error();
        }
        return null;
    }
    
    /**
     * Parses an import clause
     * 
     * @return Node\ModuleSpecifier|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) {
                    $ret = array_merge($ret, $specs);
                    return $ret;
                }
                
                return $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);  
            }
            
            return $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;
            }
            
            return $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->parseIdentifier(static::$identifierName);
            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))) {
                return $this->error();
            }
            
            $node->setLocal($local);
            
        } elseif ($requiredAs) {
            return $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 ? $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(
                        $elements, array_fill(0, $elision, null)
                    );
                }
                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);
            }
            
            return $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($name, true, $token);
            }
            
            return $this->error();
        } elseif ($name = $this->parseIdentifier(static::$identifierName)) {
            return array($name, false);
        } elseif ($name = $this->parseStringLiteral()) {
            return array($name, false);
        } elseif ($name = $this->parseNumericLiteral()) {
            return array($name, false);
        }
        return null;
    }
    
    /**
     * Parses a method definition
     * 
     * @return Node\MethodDefinition|null
     */
    protected function parseMethodDefinition()
    {
        $state = $this->scanner->getState();
        $generator = $error = false;
        $position = null;
        $kind = Node\MethodDefinition::KIND_METHOD;
        if ($token = $this->scanner->consume("get")) {
            $position = $token;
            $kind = Node\MethodDefinition::KIND_GET;
            $error = true;
        } elseif ($token = $this->scanner->consume("set")) {
            $position = $token;
            $kind = Node\MethodDefinition::KIND_SET;
            $error = true;
        } elseif ($token = $this->scanner->consume("*")) {
            $position = $token;
            $error = true;
            $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->parsePropertyName()) {
            
            if (!$position) {
                $position = isset($prop[2]) ? $prop[2] : $prop[0];
            }
            if ($tokenFn = $this->scanner->consume("(")) {
                
                if ($generator) {
                    $flags = array(null, "allowYield" => 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->setStartPosition(
                        $tokenBodyStart->getLocation()->getStart()
                    );
                    $body->setEndPosition($this->scanner->getPosition());
                    
                    $nodeFn = $this->createNode("FunctionExpression", $tokenFn);
                    $nodeFn->setParams($params);
                    $nodeFn->setBody($body);
                    $nodeFn->setGenerator($generator);

                    $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) {
            return $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
     * 
     * @return array|null
     */
    protected function parseConciseBody()
    {
        if ($token = $this->scanner->consume("{")) {
            
            if (($body = $this->isolateContext(
                    null,
                    "parseFunctionBody"
                )) &&
                $this->scanner->consume("}")
            ) {
                $body->setStartPosition($token->getLocation()->getStart());
                $body->setEndPosition($this->scanner->getPosition());
                return array($body, false);
            }
            
            return $this->error();
        } elseif (!$this->scanner->isBefore(array("{")) &&
            $body = $this->isolateContext(
                array("allowYield" => false),
                "parseAssignmentExpression"
            )
        ) {
            return array($body, true);
        }
        return null;
    }
    
    /**
     * Parses an arrow function
     * 
     * @return Node\ArrowFunctionExpression|null
     */
    protected function parseArrowFunction()
    {
        $state = $this->scanner->getState();
        if (($params = $this->parseArrowParameters()) !== null) {
            
            if ($this->scanner->noLineTerminators() &&
                $this->scanner->consume("=>")
            ) {
                
                if ($body = $this->parseConciseBody()) {
                    if (is_array($params)) {
                        $pos = $params[1];
                        $params = $params[0];
                    } else {
                        $pos = $params;
                        $params = array($params);
                    }
                    $node = $this->createNode("ArrowFunctionExpression", $pos);
                    $node->setParams($params);
                    $node->setBody($body[0]);
                    $node->setExpression($body[1]);
                    return $this->completeNode($node);
                }
            
                return $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);
            }
            
            return $this->error();
        }
        return null;
    }
    
    /**
     * Parses a property in an object literal
     * 
     * @return Node\Property|null
     */
    protected function parsePropertyDefinition()
    {
        $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);
            }

            return $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 ? $value : $key);
            return $this->completeNode($node);
            
        }
        return null;
    }
    
    /**
     * Parses an itlializer
     * 
     * @return Node\Node|null
     */
    protected function parseInitializer()
    {
        if ($this->scanner->consume("=")) {
            
            if ($value = $this->parseAssignmentExpression()) {
                return $value;
            }
            
            return $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->scanner->consume("}")) {
                $node = $this->createNode("ObjectPattern", $token);
                if ($properties) {
                    $node->setProperties($properties);
                }
                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)) {
                    
                    $right = $this->parseAssignmentExpression();
                    
                    if ($right) {
                        $node = $this->createNode(
                            "AssignmentExpression", $expr
                        );
                        $node->setLeft($this->expressionToPattern($expr));
                        $node->setOperator($operator->getValue());
                        $node->setRight($right);
                        return $this->completeNode($node);
                    }
                    return $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);
                }
                
                return $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())) {
            return null;
        }
        
        $list = array($exp);
        while ($token = $this->scanner->consumeOneOf(array_keys($operators))) {
            if (!($exp = $this->parseUnaryExpression())) {
                return $this->error();
            }
            $list[] = $token->getValue();
            $list[] = $exp;
        }
        
        $len = count($list);
        if ($len > 1) {
            $maxGrade = max($operators);
            for ($grade = $maxGrade; $grade >= 0; $grade--) {
                $class = $grade < 2 ? "LogicalExpression" : "BinaryExpression";
                for ($i = 1; $i < $len; $i += 2) {
                    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]->getLocation()->getEnd()
                        );
                        array_splice($list, $i - 1, 3, array($node));
                        $i -= 2;
                        $len = count($list);
                    }
                }
            }
        }
        return $list[0];
    }
    
    /**
     * Parses a unary expression
     * 
     * @return Node\Node|null
     */
    protected function parseUnaryExpression()
    {
        if ($expr = $this->parsePostfixExpression()) {
            return $expr;
        } elseif ($token = $this->scanner->consumeOneOf($this->unaryOperators)) {
            if ($argument = $this->parseUnaryExpression()) {
                
                $op = $token->getValue();
                
                //Deleting a variable without accessing its properties is a
                //syntax error in strict mode
                if ($op === "delete" &&
                    $this->scanner->getStrictMode() &&
                    $argument instanceof Node\Identifier) {
                    return $this->error(
                        "Deleting an unqualified identifier is not allowed in strict mode"
                    );
                }
                
                if ($op === "++" || $op === "--") {
                    $node = $this->createNode("UpdateExpression", $token);
                    $node->setPrefix(true);
                } else {
                    $node = $this->createNode("UnaryExpression", $token);
                }
                $node->setOperator($op);
                $node->setArgument($argument);
                return $this->completeNode($node);
            }

            return $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)
            ) {
                
                $node = $this->createNode("UpdateExpression", $argument);
                $node->setOperator($token->getValue());
                $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 occurences of "new"
        if ($newToken = $this->scanner->isBefore(array("new"))) {
            while ($newToken = $this->scanner->consume("new")) {
                if ($this->scanner->consume(".")) {
                    //new.target
                    if ($this->scanner->consume("target")) {
                    
                        $node = $this->createNode("MetaProperty", $newToken);
                        $node->setMeta("new");
                        $node->setProperty("target");
                        $object = $this->completeNode($node);
                        break;
                    
                    } else {
                        return $this->error();
                    }
                }
                $newTokens[] = $newToken;
            }
        }
        
        $newTokensCount = count($newTokens);
        
        if (!$object &&
            !($object = $this->parseSuperPropertyOrCall()) &&
            !($object = $this->parsePrimaryExpression())
        ) {
            
            if ($newTokensCount) {
                return $this->error();
            }
            return null;
        }
        
        $valid = true;
        $properties = array();
        while (true) {
            if ($this->scanner->consume(".")) {
                if ($property = $this->parseIdentifier(static::$identifierName)) {
                    $properties[] = array(
                        "type"=> "id",
                        "info" => $property
                    );
                } else {
                    $valid = false;
                    break;
                }
            } elseif ($this->scanner->consume("[")) {
                if (($property = $this->isolateContext(
                        array("allowIn" => true), "parseExpression"
                    )) &&
                    $this->scanner->consume("]")
                ) {
                    $properties[] = array(
                        "type" => "computed",
                        "info" => array(
                            $property, $this->scanner->getPosition()
                        )
                    );
                } else {
                    $valid = false;
                    break;
                }
            } elseif ($property = $this->parseTemplateLiteral(true)) {
                $properties[] = array(
                    "type"=> "template",
                    "info" => $property
                );
            } elseif (($args = $this->parseArguments()) !== null) {
                $properties[] = array(
                    "type"=> "args",
                    "info" => array($args, $this->scanner->getPosition())
                );
            } else {
                break;
            }
        }
        
        $propCount = count($properties);
        
        if (!$valid) {
            return $this->error();
        } elseif (!$propCount && !$newTokensCount) {
            return $object;
        }
        
        $node = null;
        $endPos = $object->getLocation()->getEnd();
        foreach ($properties as $i => $property) {
            $lastNode = $node ? $node : $object;
            if ($property["type"] === "args") {
                if ($newTokensCount) {
                    $node = $this->createNode(
                        "NewExpression", array_pop($newTokens)
                    );
                    $newTokensCount--;
                } else {
                    $node = $this->createNode("CallExpression", $lastNode);
                }
                $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->setProperty($property["info"]);
                $endPos = $property["info"]->getLocation()->getEnd();
            } elseif ($property["type"] === "computed") {
                $node = $this->createNode("MemberExpression", $lastNode);
                $node->setObject($lastNode);
                $node->setProperty($property["info"][0]);
                $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"]->getLocation()->getEnd();
            }
            $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 ? $node : $object;
                $node = $this->createNode("NewExpression", $newTokens[$i]);
                $node->setCallee($lastNode);
                $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);
            }
            
            return $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(
                        $elements, array_fill(0, $elision, null)
                    );
                }
                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);
            }
            
            return $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;
            }
            
            return $this->error();
        }
        return null;
    }
    
    /**
     * Parses an arguments list
     * 
     * @return array|null
     */
    protected function parseArgumentList()
    {
        $list = array();
        $start = $valid = true;
        while (true) {
            $spread = $this->scanner->consume("...");
            $exp = $this->isolateContext(
                array("allowIn" => true), "parseAssignmentExpression"
            );
            if (!$exp) {
                $valid = $valid && $start && !$spread;
                break;
            }
            if ($spread) {
                $node = $this->createNode("SpreadElement", $spread);
                $node->setArgument($exp);
                $list[] = $this->completeNode($node);
            } else {
                $list[] = $exp;
            }
            $start = false;
            $valid = true;
            if (!$this->scanner->consume(",")) {
                break;
            } else {
                $valid = false;
            }
        }
        if (!$valid) {
            return $this->error();
        }
        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);
            }
            
            return $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->parseLiteral()) {
            return $exp;
        } elseif ($exp = $this->parseArrayLiteral()) {
            return $exp;
        } elseif ($exp = $this->parseObjectLiteral()) {
            return $exp;
        } elseif ($exp = $this->parseFunctionOrGeneratorExpression()) {
            return $exp;
        } elseif ($exp = $this->parseClassExpression()) {
            return $exp;
        } elseif ($exp = $this->parseRegularExpressionLiteral()) {
            return $exp;
        } elseif ($exp = $this->parseTemplateLiteral()) {
            return $exp;
        } elseif ($exp = $this->parseIdentifier(static::$identifierReference)) {
            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);
            }
            
            return $this->error();
        }
        return null;
    }
    
    /**
     * 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 preceeds 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->getValue() !== $after) {
                return null;
            }
        }
        $type = $token->getType();
        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->getValue();
        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->setName($value);
        return $this->completeNode($node);
    }
    
    /**
     * Parses a literal
     * 
     * @return Node\Literal|null
     */
    protected function parseLiteral()
    {
        if ($token = $this->scanner->getToken()) {
            if ($token->getType() === Token::TYPE_NULL_LITERAL) {
                $this->scanner->consumeToken();
                $node = $this->createNode("NullLiteral", $token);
                return $this->completeNode($node);
            } elseif ($token->getType() === Token::TYPE_BOOLEAN_LITERAL) {
                $this->scanner->consumeToken();
                $node = $this->createNode("BooleanLiteral", $token);
                $node->setRaw($token->getValue());
                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->getType() === Token::TYPE_STRING_LITERAL) {
            $val = $token->getValue();
            $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|null
     */
    protected function parseNumericLiteral()
    {
        $token = $this->scanner->getToken();
        if ($token && $token->getType() === Token::TYPE_NUMERIC_LITERAL) {
            $val = $token->getValue();
            $this->checkInvalidEscapeSequences($val, true);
            $this->scanner->consumeToken();
            $node = $this->createNode("NumericLiteral", $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->getType() !== Token::TYPE_TEMPLATE) {
            return null;
        }
        
        //Do not parse templates parts
        $val = $token->getValue();
        if ($val[0] !== "`") {
            return null;
        }
        
        $quasis = $expressions = array();
        $valid = false;
        do {
            $this->scanner->consumeToken();
            $val = $token->getValue();
            $this->checkInvalidEscapeSequences($val, false, true, $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->getType() === Token::TYPE_TEMPLATE);
        
        if ($valid) {
            $node = $this->createNode("TemplateLiteral", $quasis[0]);
            $node->setQuasis($quasis);
            $node->setExpressions($expressions);
            return $this->completeNode($node);
        }
        
        return $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->getValue());
            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->getType() === Token::TYPE_STRING_LITERAL
        ) {
            $directive = substr($token->getValue(), 1, -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;
    }
    
    /**
     * Checks if the given string or number contains invalid esape 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
    ) {
        $checkLegacyOctal = $forceLegacyOctalCheck || $this->scanner->getStrictMode();
        if ($number) {
            if ($checkLegacyOctal && preg_match("#^0[0-7]+$#", $val)) {
                return $this->error(
                    "Octal literals are not allowed in strict mode"
                );
            }
        } elseif (strpos($val, "\\") !== false) {
            $hex = "0-9a-fA-F";
            $invalidSyntaxes = array(
                "x[$hex]?[^$hex]",
                "x[$hex]?$",
                "u\{\}",
                "u\{(?:[$hex]*[^$hex\}]+)+[$hex]*\}",
                "u\{[^\}]*$",
                "u(?!{)[$hex]{0,3}[^$hex\{]",
                "u[$hex]{0,3}$"
            );
            if ($checkLegacyOctal) {
                $invalidSyntaxes[] = "[0-7]{2}";
                $invalidSyntaxes[] = "[1-7]";
            }
            $reg = "#(\\\\+)(" . implode("|", $invalidSyntaxes) . ")#";
            if (preg_match_all($reg, $val, $matches, PREG_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";
                        }
                        return $this->error($err);
                    }
                }
            }
        }
    }
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 * 
 * @codeCoverageIgnore
 */
class Exception extends \Exception
{
    /**
     * Error position
     * 
     * @var \Peast\Syntax\Position
     */
    protected $position;
    
    /**
     * Class constructor
     * 
     * @param string                 $message  Error message
     * @param \Peast\Syntax\Position $position Error position
     */
    public function __construct($message, Position $position)
    {
        parent::__construct($message);
        $this->position = $position;
    }
    
    /**
     * Returns the error position
     * 
     * @return \Peast\Syntax\Position 
     */
    public function getPosition()
    {
        return $this->position;
    }
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\ES2017;

/**
 * ES2017 scanner.
 * 
 * @author Marco Marchiò <marco.mm89@gmail.com>
 */
class Scanner extends \Peast\Syntax\ES2016\Scanner
{
    /**
     * 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"
    );
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * For the full copyright and license information refer to the LICENSE file
 * distributed with this source code
 */
namespace Peast\Syntax\ES2017;

use \Peast\Syntax\Node;
use \Peast\Syntax\Token;

/**
 * ES2017 parser class
 * 
 * @author Marco Marchiò <marco.mm89@gmail.com>
 */
class Parser extends \Peast\Syntax\ES2016\Parser
{
    /**
     * Configurable lookaheads
     * 
     * @var array 
     */
    protected $lookahead = array(
        "export" => array(
            "tokens"=> array("function", "class", array("async", true)),
            "next"=> true
        ),
        "expression" => array(
            "tokens"=> array(
                "{", "function", "class", array("async", true), array("let", "[")
            ),
            "next"=> true
        )
    );
    
    /**
     * 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()
    {
        parent::initContext();
        $this->context->allowAwait = false;
    }
    
    /**
     * Parses an arguments list
     * 
     * @return array|null
     */
    protected function parseArgumentList()
    {
        $list = array();
        while (true) {
            $spread = $this->scanner->consume("...");
            $exp = $this->isolateContext(
                array("allowIn" => true), "parseAssignmentExpression"
            );
            if (!$exp) {
                if ($spread) {
                    return $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;
            }
        }
        return $list;
    }
    
    /**
     * Parses a parameter list
     * 
     * @return Node\Node[]|null
     */
    protected function parseFormalParameterList()
    {
        $list = array();
        while (
            ($param = $this->parseBindingRestElement()) ||
            $param = $this->parseBindingElement()
        ) {
            $list[] = $param;
            if ($param->getType() === "RestElement" ||
                !$this->scanner->consume(",")) {
                break;
            }
        }
        return $list;
    }
    
    /**
     * Parses a unary expression
     * 
     * @return Node\Node|null
     */
    protected function parseUnaryExpression()
    {
        $operators = $this->unaryOperators;
        if ($this->context->allowAwait) {
            $operators[] = "await";
        }
        if ($expr = $this->parsePostfixExpression()) {
            return $expr;
        } elseif ($token = $this->scanner->consumeOneOf($operators)) {
            if ($argument = $this->parseUnaryExpression()) {
                
                $op = $token->getValue();
                
                //Deleting a variable without accessing its properties is a
                //syntax error in strict mode
                if ($op === "delete" &&
                    $this->scanner->getStrictMode() &&
                    $argument instanceof Node\Identifier) {
                    return $this->error(
                        "Deleting an unqualified identifier is not allowed in strict mode"
                    );
                }
                
                if ($op === "await") {
                    $node = $this->createNode("AwaitExpression", $token);
                } else {
                    if ($op === "++" || $op === "--") {
                        $node = $this->createNode("UpdateExpression", $token);
                        $node->setPrefix(true);
                    } else {
                        $node = $this->createNode("UnaryExpression", $token);
                    }
                    $node->setOperator($op);
                }
                $node->setArgument($argument);
                return $this->completeNode($node);
            }

            return $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->getValue() === "async" &&
               (
                    !$checkFn ||
                    (($nextToken = $this->scanner->getNextToken()) &&
                    $nextToken->getValue() === "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 = false;
        if ($asyncToken = $this->checkAsyncFunctionStart()) {
            $this->scanner->consumeToken();
            $allowGenerator = false;
            $async = true;
        }
        if ($token = $this->scanner->consume("function")) {
            
            $generator = $allowGenerator && $this->scanner->consume("*");
            $id = $this->parseIdentifier(static::$bindingIdentifier);
            
            if ($generator) {
                $flags = array(null, "allowYield" => true);
            } elseif ($async) {
                $flags = array(null, "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->setStartPosition(
                    $tokenBodyStart->getLocation()->getStart()
                );
                $body->setEndPosition($this->scanner->getPosition());
                $node = $this->createNode(
                    "FunctionDeclaration",
                    $async ? $asyncToken : $token
                );
                if ($id) {
                    $node->setId($id);
                }
                $node->setParams($params);
                $node->setBody($body);
                $node->setGenerator($generator);
                $node->setAsync($async);
                return $this->completeNode($node);
            }
            
            return $this->error();
        }
        return null;
    }
    
    /**
     * Parses function or generator expression
     * 
     * @return Node\FunctionExpression|null
     */
    protected function parseFunctionOrGeneratorExpression()
    {
        $allowGenerator = true;
        $async = false;
        if ($asyncToken = $this->checkAsyncFunctionStart()) {
            $this->scanner->consumeToken();
            $allowGenerator = false;
            $async = true;
        }
        if ($token = $this->scanner->consume("function")) {
            
            $generator = $allowGenerator && $this->scanner->consume("*");
            
            if ($generator) {
                $flags = array(null, "allowYield" => true);
            } elseif ($async) {
                $flags = array(null, "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->setStartPosition(
                    $tokenBodyStart->getLocation()->getStart()
                );
                $body->setEndPosition($this->scanner->getPosition());
                $node = $this->createNode(
                    "FunctionExpression",
                    $async ? $asyncToken : $token
                );
                $node->setId($id);
                $node->setParams($params);
                $node->setBody($body);
                $node->setGenerator($generator);
                $node->setAsync($async);
                return $this->completeNode($node);
            }
            
            return $this->error();
        }
        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;
            $error = true;
        } elseif ($token = $this->scanner->consume("set")) {
            $position = $token;
            $kind = Node\MethodDefinition::KIND_SET;
            $error = true;
        } elseif ($token = $this->scanner->consume("*")) {
            $position = $token;
            $error = true;
            $generator = true;
        } elseif ($token = $this->checkAsyncFunctionStart(false)) {
            $this->scanner->consumeToken();
            $position = $token;
            $error = true;
            $async = 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->parsePropertyName()) {
            
            if (!$position) {
                $position = isset($prop[2]) ? $prop[2] : $prop[0];
            }
            if ($tokenFn = $this->scanner->consume("(")) {
                
                if ($generator) {
                    $flags = array(null, "allowYield" => true);
                } elseif ($async) {
                    $flags = array(null, "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->setStartPosition(
                        $tokenBodyStart->getLocation()->getStart()
                    );
                    $body->setEndPosition($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) {
            return $this->error();
        } else {
            $this->scanner->setState($state);
        }
        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->setStartPosition($token->getLocation()->getStart());
                $body->setEndPosition($this->scanner->getPosition());
                return array($body, false);
            }
            
            return $this->error();
        } elseif (!$this->scanner->isBefore(array("{")) &&
            $body = $this->isolateContext(
                array("allowYield" => false, "allowAwait" => $async),
                "parseAssignmentExpression"
            )
        ) {
            return array($body, true);
        }
        return null;
    }
    
    /**
     * Parses an arrow function
     * 
     * @return Node\ArrowFunctionExpression|null
     */
    protected function parseArrowFunction()
    {
        $state = $this->scanner->getState();
        $async = false;
        if ($asyncToken = $this->checkAsyncFunctionStart(false)) {
            $this->scanner->consumeToken();
            $async = true;
        }
        if (($params = $this->parseArrowParameters()) !== null) {
            
            if ($this->scanner->noLineTerminators() &&
                $this->scanner->consume("=>")
            ) {
                
                if ($body = $this->parseConciseBody($async)) {
                    if (is_array($params)) {
                        $pos = $params[1];
                        $params = $params[0];
                    } else {
                        $pos = $params;
                        $params = array($params);
                    }
                    if ($async) {
                        $pos = $asyncToken;
                    }
                    $node = $this->createNode("ArrowFunctionExpression", $pos);
                    $node->setParams($params);
                    $node->setBody($body[0]);
                    $node->setExpression($body[1]);
                    $node->setAsync($async);
                    return $this->completeNode($node);
                }
            
                return $this->error();
            }
        }
        $this->scanner->setState($state);
        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 = $decl->getId()->getType() === "Identifier" ?
                        $this->parseInitializer() :
                        null;
                if ($init) {
                    $decl->setInit($init);
                    $decl->setEndPosition($init->getLocation()->getEnd());
                }
                
                $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()) {
                        return $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);
                    }
                }
            }
        }
        
        return $this->error();
    }
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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
     */
    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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
class PrettyPrint extends Base
{
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 * 
 * @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;
    
    /**
     * Boolean that indicates if comments must be rendered
     * 
     * @var bool
     */
    protected $renderComments = true;
    
    /**
     * 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ò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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ò <marco.mm89@gmail.com>
 *
 * 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\ES2015\Parser ES2015(string $source, array $options)
 * Returns an instance of the ES2015 parser for the given source. See Peast
 * class documentation to understand the function arguments.
 * 
 * @method static Syntax\ES2015\Parser ES6(string $source, array $options)
 * Returns an instance of the ES2015 parser for the given source. See Peast
 * class documentation to understand function arguments.
 * 
 * @method static Syntax\ES2016\Parser ES2016(string $source, array $options)
 * Returns an instance of the ES2016 parser for the given source. See Peast
 * class documentation to understand function arguments.
 * 
 * @method static Syntax\ES2016\Parser ES7(string $source, array $options)
 * Returns an instance of the ES2016 parser for the given source. See Peast
 * class documentation to understand function arguments.
 * 
 * @method static Syntax\ES2017\Parser ES2017(string $source, array $options)
 * Returns an instance of the ES2017 parser for the given source. See Peast
 * class documentation to understand function arguments.
 * 
 * @method static Syntax\ES2017\Parser ES8(string $source, array $options)
 * Returns an instance of the ES2017 parser for the given source. See Peast
 * class documentation to understand function arguments.
 * 
 * @method static Syntax\ES2018\Parser ES2018(string $source, array $options)
 * Returns an instance of the ES2018 parser for the given source. See Peast
 * class documentation to understand function arguments.
 * 
 * @method static Syntax\ES2018\Parser ES9(string $source, array $options)
 * Returns an instance of the ES2018 parser for the given source. See Peast
 * class documentation to understand function arguments.
 * 
 * @method static Syntax\ES2019\Parser ES2019(string $source, array $options)
 * Returns an instance of the ES2019 parser for the given source. See Peast
 * class documentation to understand function arguments.
 * 
 * @method static Syntax\ES2019\Parser ES10(string $source, array $options)
 * Returns an instance of the ES2019 parser for the given source. See Peast
 * class documentation to understand function arguments.
 * 
 * @method static Syntax\ES2019\Parser latest(string $source, array $options)
 * Returns an instance of the latest parser version for the given source. See
 * Peast class documentation to understand function arguments.
 * 
 * @author Marco Marchiò <marco.mm89@gmail.com>
 */
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"
    );
    
    /**
     * Magic method that exposes all the functions to access specific parser
     * versions
     * 
     * @param string    $version   Parser version
     * @param array     $args      Parser arguments
     * 
     * @return mixed
     */
    public static function __callStatic($version, $args)
    {
        $source = $args[0];
        $options = isset($args[1]) ? $args[1] : array();
        
        if (!in_array($version, self::$versions)) {
            if ($version === "latest") {
                $version = end(self::$versions);
            } elseif (isset(self::$versions[$version])) {
                $version = self::$versions[$version];
            } else {
                throw new \Exception("Invalid version $version");
            }
        }
        
        $class = "\\Peast\\Syntax\\$version\\Parser";
        return new $class($source, $options);
    }
}<?php
/**
 * This file is part of the Peast package
 *
 * (c) Marco Marchiò <marco.mm89@gmail.com>
 *
 * 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ò <marco.mm89@gmail.com>
 */
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",
        "SwitchStatement",
        "TryStatement",
        "WhileStatement",
        "WithStatement",
    );
    
    /**
     * 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($body, true);
                }
            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());
                if (preg_match("#^[a-z]+$#i", $operator)) {
                    $code .= " " .
                             $operator .
                             " ";
                } else {
                    $code .= $this->renderOpts->sao .
                             $operator .
                             $this->renderOpts->sao;
                }
                $code .= $this->renderNode($node->getRight());
            break;
            case "BlockStatement":
            case "ClassBody":
            case "Program":
                $code .= $this->renderStatementBlock(
                    $node->getBody(), false, false, true, false
                );
            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 ";
                }
                $code .= $this->renderNode($node->getCallee()) .
                         "(" .
                         $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 "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, false, false
                );
            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(), null, true
                         ) .
                         $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 * 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 "ExpressionStatement":
                $code .= $this->renderNode($node->getExpression());
            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 "JSXIdentifier":
            case "Identifier":
                $code .= $node->getName();
            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()) .
                         ":" .
                         $this->renderOpts->nl .
                         $this->getIndentation() .
                         $this->renderNode($body);
                if ($this->requiresSemicolon($body)) {
                    $code .= ";";
                }
            break;
            case "JSXText":
            case "Literal":
                $code .= $node->getRaw();
            break;
            case "JSXMemberExpression":
            case "MemberExpression":
                $property = $this->renderNode($node->getProperty());
                $code .= $this->renderNode($node->getObject());
                if ($type === "MemberExpression" && $node->getComputed()) {
                    $code .= "[" . $property . "]";
                } else {
                    $code .= "." . $property;
                }
            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 "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 "RegExpLiteral":
                $code .= $node->getRaw();
            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 "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(), true, false, false
                         );
            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($finalizer, true);
                }
            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 autmatically
     *                                                      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)
    {
        $forceBrackets = null;
        if ($node->getType() === "BlockStatement" && count($node->getBody()) > 1) {
            return $forceBrackets;
        }
        $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 (!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
        );
    }
}## 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}}
## GLOBAL PARAMETERS

{{#parameters}}
  {{synopsis}}
      {{desc}}

{{/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:
 *
 * * MySQL settings
 * * Secret keys
 * * Database table prefix
 * * ABSPATH
 *
 * @link https://codex.wordpress.org/Editing_wp-config.php
 *
 * @package WordPress
 */

// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define( 'DB_NAME', '{{dbname}}' );

/** MySQL database username */
define( 'DB_USER', '{{dbuser}}' );

/** MySQL database password */
define( 'DB_PASSWORD', '{{dbpass}}' );

/** MySQL 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}}';

{{#add-wplang}}
define( 'WPLANG', '{{locale}}' );
{{/add-wplang}}

{{extra-php}}

/* That's all, stop editing! Happy publishing. */

/** Absolute path to the WordPress directory. */
if ( ! defined( 'ABSPATH' ) ) {
	define( 'ABSPATH', dirname( __FILE__ ) . '/' );
}

/** 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}}
<?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_template_directory() . '/blocks';
	{{/theme}}

	$index_js = '{{slug}}/index.js';
	wp_register_script(
		'{{slug}}-block-editor',
		{{#plugin}}
		plugins_url( $index_js, __FILE__ ),
		{{/plugin}}
		{{#theme}}
		get_template_directory_uri() . "/blocks/$index_js",
		{{/theme}}
		array(
			'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_template_directory_uri() . "/blocks/$editor_css",
		{{/theme}}
		array(),
		filemtime( "$dir/$editor_css" )
	);

	$style_css = '{{slug}}/style.css';
	wp_register_style(
		'{{slug}}-block',
		{{#plugin}}
		plugins_url( $style_css, __FILE__ ),
		{{/plugin}}
		{{#theme}}
		get_template_directory_uri() . "/blocks/$style_css",
		{{/theme}}
		array(),
		filemtime( "$dir/$style_css" )
	);

	register_block_type( '{{namespace}}/{{slug}}', array(
		'editor_script' => '{{slug}}-block-editor',
		'editor_style'  => '{{slug}}-block-editor',
		'style'         => '{{slug}}-block',
	) );
}
add_action( 'init', '{{machine_name}}_block_init' );
.DS_Store
phpcs.xml
phpunit.xml
Thumbs.db
wp-cli.local.yml
node_modules/
*.sql
*.tar.gz
*.zip
<?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.
<?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->assertTrue( true );
	}
}
variables:
  # Configure mysql service (https://hub.docker.com/_/mysql/)
  MYSQL_DATABASE: wordpress_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:
  image: tetraweb/php:5.3
  services:
    - mysql:5.6
  script:
  - phpcs
  - phpunit

PHPunit:PHP5.6:MySQL:
  image: tetraweb/php:5.6
  services:
    - mysql:5.6
  script:
  - phpcs
  - phpunit
  
PHPunit:PHP7.0:MySQL:
  image: tetraweb/php:7.0
  services:
    - mysql:5.6
  script:
  - phpcs
  - phpunit

PHPunit:PHP7.1:MySQL:
  image: tetraweb/php:7.1
  services:
    - mysql:5.6
  script:
  - phpcs
  - phpunit

PHPunit:PHP7.2:MySQL:
  image: tetraweb/php:7.2
  services:
    - mysql:5.6
  script:
  - phpcs
  - phpunit
workflows:
  version: 2
  main:
    jobs:
      - php56-build
      - php70-build
      - php71-build
      - php72-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 -c "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:
      - image: circleci/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:
      - image: circleci/php:7.0
      - image: *mysql_image

  php71-build:
    <<: *php_job
    docker:
      - image: circleci/php:7.1
      - image: *mysql_image

  php72-build:
    <<: *php_job
    docker:
      - image: circleci/php:7.2
      - image: *mysql_image
<?php
/**
 * PHPUnit bootstrap file
 *
 * @package {{plugin_package}}
 */

$_tests_dir = getenv( 'WP_TESTS_DIR' );

if ( ! $_tests_dir ) {
	$_tests_dir = rtrim( sys_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; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
	exit( 1 );
}

// 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 dirname( dirname( __FILE__ ) ) . '/{{plugin_slug}}.php';
}
tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' );

// Start up the WP testing environment.
require $_tests_dir . '/includes/bootstrap.php';
/**
 * 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;
	padding: 2px;
}
# A set of files you probably don't want in your WordPress.org distribution
.babelrc
.deployignore
.distignore
.editorconfig
.eslintignore
.eslintrc
.git
.gitignore
.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
Theme %9{{slug}}%n details:
    Name: {{name}}
    Status: {{status}}%n
    Version: {{version}}
    Author: {{author}}
#!/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 -e "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 -s "$1" > "$2";
    elif [ `which wget` ]; then
        wget -nv -O "$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 -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//')
	if [[ -z "$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-nightly
		download https://wordpress.org/nightly-builds/wordpress-latest.zip  $TMPDIR/wordpress-nightly/wordpress-nightly.zip
		unzip -q $TMPDIR/wordpress-nightly/wordpress-nightly.zip -d $TMPDIR/wordpress-nightly/
		mv $TMPDIR/wordpress-nightly/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
		svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes
		svn co --quiet 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/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

}

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
	mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA
}

install_wp
install_test_suite
install_db
# 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
=== {{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 ?>`
<?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->assertTrue( true );
	}
}
/**
 * The following styles get applied inside the editor only.
 *
 * Replace them with your own styles or remove the file completely.
 */

.wp-block-{{namespace}}-{{slug}} {
	border: 1px dotted #f00;
}
<?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',
		array( '{{parent_theme}}-style' )
	);

}
<?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}}'] = array(
		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,
		/* 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_url( add_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_url( add_query_arg( 'preview', 'true', $permalink ) ) ),
	);

	return $messages;
}
add_filter( 'post_updated_messages', '{{machine_name}}_updated_messages' );
( 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.
			html: false,
		},

		/**
		 * 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',
				{ className: props.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
);
	register_taxonomy( '{{slug}}', array( {{post_types}} ), array(
		'hierarchical'      => false,
		'public'            => true,
		'show_in_nav_menus' => true,
		'show_ui'           => true,
		'show_admin_column' => false,
		'query_var'         => true,
		'rewrite'           => true,
		'capabilities'      => array(
			'manage_terms'  => 'edit_posts',
			'edit_terms'    => 'edit_posts',
			'delete_terms'  => 'edit_posts',
			'assign_terms'  => 'edit_posts',
		),
		'labels'            => array(
			'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',
	) );
/*
Theme Name:     {{theme_name}}
Theme URI:      {{theme_uri}}
Description:    {{description}}
Author:         {{author}}
Author URI:     {{author_uri}}
Template:       {{parent_theme}}
Version:        0.1.0
*/
<?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}}'] = array(
		0 => '', // Unused. Messages start at index 1.
		1 => __( '{{label_ucfirst}} added.', '{{textdomain}}' ),
		2 => __( '{{label_ucfirst}} deleted.', '{{textdomain}}' ),
		3 => __( '{{label_ucfirst}} updated.', '{{textdomain}}' ),
		4 => __( '{{label_ucfirst}} not added.', '{{textdomain}}' ),
		5 => __( '{{label_ucfirst}} not updated.', '{{textdomain}}' ),
		6 => __( '{{label_plural_ucfirst}} deleted.', '{{textdomain}}' ),
	);

	return $messages;
}
add_filter( 'term_updated_messages', '{{machine_name}}_updated_messages' );
	register_post_type( '{{slug}}', array(
		'labels'                => array(
			'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'              => array( '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',
	) );
<?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.3-"/>
	<!-- 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">
		<exclude name="WordPress.VIP"/>
	</rule>
	<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>

{
  "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"
  }
}
<?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>
sudo: false
dist: trusty

language: php

notifications:
  email:
    on_success: never
    on_failure: change

branches:
  only:
    - master

cache:
  directories:
    - $HOME/.composer/cache

matrix:
  include:
    - php: 7.2
      env: WP_VERSION=latest
    - php: 7.1
      env: WP_VERSION=latest
    - php: 7.0
      env: WP_VERSION=latest
    {{#wp_versions_to_test}}
    - php: 5.6
      env: WP_VERSION={{.}}
    {{/wp_versions_to_test}}
    - php: 5.6
      env: WP_TRAVISCI=phpcs
    - php: 5.3
      env: WP_VERSION=latest
      dist: precise

before_script:
  - export PATH="$HOME/.composer/vendor/bin:$PATH"
  - |
    if [ -f ~/.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 [[ ! -z "$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
      phpcs --config-set installed_paths $HOME/.composer/vendor/wp-coding-standards/wpcs
    fi

script:
  - |
    if [[ ! -z "$WP_VERSION" ]] ; then
      phpunit
      WP_MULTISITE=1 phpunit
    fi
  - |
    if [[ "$WP_TRAVISCI" == "phpcs" ]] ; then
      phpcs
    fi
pipelines:
  default:
    - step:
        image: php: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 -o /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 -p /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 -o /usr/local/bin/phpunit "https://phar.phpunit.de/phpunit-${PHPUNIT_VERSION}.phar" && chmod +x /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:
        image: php: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 -o /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 -p /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 -o /usr/local/bin/phpunit "https://phar.phpunit.de/phpunit-${PHPUNIT_VERSION}.phar" && chmod +x /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:
        image: php: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 -o /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 -p /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 -o /usr/local/bin/phpunit "https://phar.phpunit.de/phpunit-${PHPUNIT_VERSION}.phar" && chmod +x /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:
        image: php: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 -o /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 -p /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 -o /usr/local/bin/phpunit "https://phar.phpunit.de/phpunit-${PHPUNIT_VERSION}.phar" && chmod +x /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:
      image: mysql:latest
      environment:
        MYSQL_DATABASE: 'wordpress_tests'
        MYSQL_ROOT_PASSWORD: 'root'
<?php
/**
 * PHPUnit bootstrap file
 *
 * @package {{theme_package}}
 */

$_tests_dir = getenv( 'WP_TESTS_DIR' );

if ( ! $_tests_dir ) {
	$_tests_dir = rtrim( sys_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( 1 );
}

// 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';
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

// autoload.php @generated by Composer

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

return ComposerAutoloaderInit5925cbfbda4231e100bd9070d8ec30d5::getLoader();
Copyright (c) Nils Adermann, Jordi Boggiano

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "name": "Package",
    "type": "object",
    "additionalProperties": false,
    "required": [ "name", "description" ],
    "properties": {
        "name": {
            "type": "string",
            "description": "Package name, including 'vendor-name/' prefix."
        },
        "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-]+$"
        },
        "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",
            "description": "Short package description."
        },
        "keywords": {
            "type": "array",
            "items": {
                "type": "string",
                "description": "A tag/keyword that this package relates to."
            }
        },
        "homepage": {
            "type": "string",
            "description": "Homepage URL for the project.",
            "format": "uri"
        },
        "readme": {
            "type": "string",
            "description": "Relative path to the readme document."
        },
        "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+)?\\.\\d+)?\\.\\d+)?"
        },
        "time": {
            "type": "string",
            "description": "Package release date, in 'YYYY-MM-DD', 'YYYY-MM-DD HH:MM:SS' or 'YYYY-MM-DDTHH:MM:SSZ' format."
        },
        "license": {
            "type": ["string", "array"],
            "description": "License name. Or an array of license names."
        },
        "authors": {
            "$ref": "#/definitions/authors"
        },
        "require": {
            "type": "object",
            "description": "This is a hash of package name (keys) and version constraints (values) that are required to run this package.",
            "additionalProperties": {
                "type": "string"
            }
        },
        "replace": {
            "type": "object",
            "description": "This is a hash of package name (keys) and version constraints (values) that can be replaced by this package.",
            "additionalProperties": {
                "type": "string"
            }
        },
        "conflict": {
            "type": "object",
            "description": "This is a hash of package name (keys) and version constraints (values) that conflict with this package.",
            "additionalProperties": {
                "type": "string"
            }
        },
        "provide": {
            "type": "object",
            "description": "This is a hash of package name (keys) and version constraints (values) that this package provides in addition to this package's name.",
            "additionalProperties": {
                "type": "string"
            }
        },
        "require-dev": {
            "type": "object",
            "description": "This is a hash of package name (keys) and version constraints (values) that this package requires for developing it (testing tools and such).",
            "additionalProperties": {
                "type": "string"
            }
        },
        "suggest": {
            "type": "object",
            "description": "This is a hash 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"
            }
        },
        "config": {
            "type": "object",
            "description": "Composer options.",
            "properties": {
                "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."
                },
                "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 a hash of {\"pattern\": \"preference\"}."
                },
                "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": "A hash of domain name => github API oauth tokens, typically {\"github.com\":\"<token>\"}.",
                    "additionalProperties": {
                        "type": "string"
                    }
                },
                "gitlab-oauth": {
                    "type": "object",
                    "description": "A hash of domain name => gitlab API oauth tokens, typically {\"gitlab.com\":\"<token>\"}.",
                    "additionalProperties": {
                        "type": "string"
                    }
                },
                "gitlab-token": {
                    "type": "object",
                    "description": "A hash of domain name => gitlab private tokens, typically {\"gitlab.com\":\"<token>\"}.",
                    "additionalProperties": true
                },
                "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."
                },
                "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": "A hash 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."
                },
                "platform": {
                    "type": "object",
                    "description": "This is a hash of package name (keys) and version (values) that will be used to mock the platform packages on this machine.",
                    "additionalProperties": {
                        "type": "string"
                    }
                },
                "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\"."
                },
                "bin-compat": {
                    "enum": ["auto", "full"],
                    "description": "The compatibility of the binaries, defaults to \"auto\" (automatically guessed) and can be \"full\" (compatible with both Windows and Unix-based systems)."
                },
                "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."
                }
            }
        },
        "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
        },
        "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 a hash 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 a hash 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 directories 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."
                }
            }
        },
        "archive": {
            "type": ["object"],
            "description": "Options for creating package archives for distribution.",
            "properties": {
                "exclude": {
                    "type": "array",
                    "description": "A list of patterns for paths to exclude or include if prefixed with an exclamation mark."
                }
            }
        },
        "repositories": {
            "type": ["object", "array"],
            "description": "A set of additional repositories where packages can be found.",
            "additionalProperties": {
                "oneOf": [
                    { "$ref": "#/definitions/repository" },
                    { "type": "boolean", "enum": [false] }
                ]
            },
            "items": {
                "oneOf": [
                    { "$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.",
            "pattern": "^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."
        },
        "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"
            }
        },
        "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"
            }
        },
        "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"
                }
            }
        },
        "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"
            }
        },
        "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."
        },
        "_comment": {
            "type": ["array", "string"],
            "description": "A key to store comments in"
        }
    },
    "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 a hash 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 a hash 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 directories 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",
            "oneOf": [
                { "$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" },
                "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", "git-bitbucket", "hg", "hg-bitbucket", "fossil", "perforce", "svn"] },
                "url": { "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" },
                "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" }
            }
        },
        "pear-repository": {
            "type": "object",
            "required": ["type", "url"],
            "properties": {
                "type": { "type": "string", "enum": ["pear"] },
                "url": { "type": "string" },
                "vendor-alias": { "type": "string" }
            }
        },
        "package-repository": {
            "type": "object",
            "required": ["type", "package"],
            "properties": {
                "type": { "type": "string", "enum": ["package"] },
                "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
        }
    }
}
##
## ca-bundle.crt -- Bundle of CA Root Certificates
##
## Certificate data from Mozilla as of: Sat Dec 29 20:03:40 2012
##
## 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:
## http://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt?raw=1
##
## 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.
##

# @(#) $RCSfile: certdata.txt,v $ $Revision: 1.87 $ $Date: 2012/12/29 16:32:45 $

EE Certification Centre Root CA
===============================
-----BEGIN CERTIFICATE-----
MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQG
EwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1czEoMCYGA1UEAwwfRUUgQ2Vy
dGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYGCSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIw
MTAxMDMwMTAxMDMwWhgPMjAzMDEyMTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlB
UyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRy
ZSBSb290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUyeuuOF0+W2Ap7kaJjbMeM
TC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvObntl8jixwKIy72KyaOBhU8E2lf/slLo2
rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIwWFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw
93X2PaRka9ZP585ArQ/dMtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtN
P2MbRMNE1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYDVR0T
AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/zQas8fElyalL1BSZ
MEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEF
BQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEFBQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+Rj
xY6hUFaTlrg4wCQiZrxTFGGVv9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqM
lIpPnTX/dqQGE5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u
uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIWiAYLtqZLICjU
3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/vGVCJYMzpJJUPwssd8m92kMfM
dcGWxZ0=
-----END CERTIFICATE-----

GTE CyberTrust Global Root
==========================
-----BEGIN CERTIFICATE-----
MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYDVQQKEw9HVEUg
Q29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNvbHV0aW9ucywgSW5jLjEjMCEG
A1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJvb3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEz
MjM1OTAwWjB1MQswCQYDVQQGEwJVUzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQL
Ex5HVEUgQ3liZXJUcnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0
IEdsb2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrHiM3dFw4u
sJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTSr41tiGeA5u2ylc9yMcql
HHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X404Wqk2kmhXBIgD8SFcd5tB8FLztimQID
AQABMA0GCSqGSIb3DQEBBAUAA4GBAG3rGwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMW
M4ETCJ57NE7fQMh017l93PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OF
NMQkpw0PlZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/
-----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-----

Equifax Secure CA
=================
-----BEGIN CERTIFICATE-----
MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJVUzEQMA4GA1UE
ChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoT
B0VxdWlmYXgxLTArBgNVBAsTJEVxdWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCB
nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPR
fM6fBeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+AcJkVV5MW
8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kCAwEAAaOCAQkwggEFMHAG
A1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UE
CxMkRXF1aWZheCBTZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoG
A1UdEAQTMBGBDzIwMTgwODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvS
spXXR9gjIBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQFMAMB
Af8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUAA4GBAFjOKer89961
zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y7qj/WsjTVbJmcVfewCHrPSqnI0kB
BIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee95
70+sB3c4
-----END CERTIFICATE-----

Digital Signature Trust Co. Global CA 1
=======================================
-----BEGIN CERTIFICATE-----
MIIDKTCCApKgAwIBAgIENnAVljANBgkqhkiG9w0BAQUFADBGMQswCQYDVQQGEwJVUzEkMCIGA1UE
ChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMREwDwYDVQQLEwhEU1RDQSBFMTAeFw05ODEy
MTAxODEwMjNaFw0xODEyMTAxODQwMjNaMEYxCzAJBgNVBAYTAlVTMSQwIgYDVQQKExtEaWdpdGFs
IFNpZ25hdHVyZSBUcnVzdCBDby4xETAPBgNVBAsTCERTVENBIEUxMIGdMA0GCSqGSIb3DQEBAQUA
A4GLADCBhwKBgQCgbIGpzzQeJN3+hijM3oMv+V7UQtLodGBmE5gGHKlREmlvMVW5SXIACH7TpWJE
NySZj9mDSI+ZbZUTu0M7LklOiDfBu1h//uG9+LthzfNHwJmm8fOR6Hh8AMthyUQncWlVSn5JTe2i
o74CTADKAqjuAQIxZA9SLRN0dja1erQtcQIBA6OCASQwggEgMBEGCWCGSAGG+EIBAQQEAwIABzBo
BgNVHR8EYTBfMF2gW6BZpFcwVTELMAkGA1UEBhMCVVMxJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0
dXJlIFRydXN0IENvLjERMA8GA1UECxMIRFNUQ0EgRTExDTALBgNVBAMTBENSTDEwKwYDVR0QBCQw
IoAPMTk5ODEyMTAxODEwMjNagQ8yMDE4MTIxMDE4MTAyM1owCwYDVR0PBAQDAgEGMB8GA1UdIwQY
MBaAFGp5fpFpRhgTCgJ3pVlbYJglDqL4MB0GA1UdDgQWBBRqeX6RaUYYEwoCd6VZW2CYJQ6i+DAM
BgNVHRMEBTADAQH/MBkGCSqGSIb2fQdBAAQMMAobBFY0LjADAgSQMA0GCSqGSIb3DQEBBQUAA4GB
ACIS2Hod3IEGtgllsofIH160L+nEHvI8wbsEkBFKg05+k7lNQseSJqBcNJo4cvj9axY+IO6CizEq
kzaFI4iKPANo08kJD038bKTaKHKTDomAsH3+gG9lbRgzl4vCa4nuYD3Im+9/KzJic5PLPON74nZ4
RbyhkwS7hp86W0N6w4pl
-----END CERTIFICATE-----

Digital Signature Trust Co. Global CA 3
=======================================
-----BEGIN CERTIFICATE-----
MIIDKTCCApKgAwIBAgIENm7TzjANBgkqhkiG9w0BAQUFADBGMQswCQYDVQQGEwJVUzEkMCIGA1UE
ChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMREwDwYDVQQLEwhEU1RDQSBFMjAeFw05ODEy
MDkxOTE3MjZaFw0xODEyMDkxOTQ3MjZaMEYxCzAJBgNVBAYTAlVTMSQwIgYDVQQKExtEaWdpdGFs
IFNpZ25hdHVyZSBUcnVzdCBDby4xETAPBgNVBAsTCERTVENBIEUyMIGdMA0GCSqGSIb3DQEBAQUA
A4GLADCBhwKBgQC/k48Xku8zExjrEH9OFr//Bo8qhbxe+SSmJIi2A7fBw18DW9Fvrn5C6mYjuGOD
VvsoLeE4i7TuqAHhzhy2iCoiRoX7n6dwqUcUP87eZfCocfdPJmyMvMa1795JJ/9IKn3oTQPMx7JS
xhcxEzu1TdvIxPbDDyQq2gyd55FbgM2UnQIBA6OCASQwggEgMBEGCWCGSAGG+EIBAQQEAwIABzBo
BgNVHR8EYTBfMF2gW6BZpFcwVTELMAkGA1UEBhMCVVMxJDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0
dXJlIFRydXN0IENvLjERMA8GA1UECxMIRFNUQ0EgRTIxDTALBgNVBAMTBENSTDEwKwYDVR0QBCQw
IoAPMTk5ODEyMDkxOTE3MjZagQ8yMDE4MTIwOTE5MTcyNlowCwYDVR0PBAQDAgEGMB8GA1UdIwQY
MBaAFB6CTShlgDzJQW6sNS5ay97u+DlbMB0GA1UdDgQWBBQegk0oZYA8yUFurDUuWsve7vg5WzAM
BgNVHRMEBTADAQH/MBkGCSqGSIb2fQdBAAQMMAobBFY0LjADAgSQMA0GCSqGSIb3DQEBBQUAA4GB
AEeNg61i8tuwnkUiBbmi1gMOOHLnnvx75pO2mqWilMg0HZHRxdf0CiUPPXiBng+xZ8SQTGPdXqfi
up/1902lMXucKS1M/mQ+7LZT/uqb7YLbdHVLB3luHtgZg3Pe9T7Qtd7nS2h9Qy4qIOF+oHhEngj1
mPnHfxsb1gYgAlihw6ID
-----END CERTIFICATE-----

Verisign Class 3 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 3 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-----

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-----

ValiCert Class 1 VA
===================
-----BEGIN CERTIFICATE-----
MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp
b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh
bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIy
MjM0OFoXDTE5MDYyNTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0
d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEg
UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0
LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9YLqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIi
GQj4/xEjm84H9b9pGib+TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCm
DuJWBQ8YTfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0LBwG
lN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLWI8sogTLDAHkY7FkX
icnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPwnXS3qT6gpf+2SQMT2iLM7XGCK5nP
Orf1LXLI
-----END CERTIFICATE-----

ValiCert Class 2 VA
===================
-----BEGIN CERTIFICATE-----
MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp
b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh
bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAw
MTk1NFoXDTE5MDYyNjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0
d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIg
UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0
LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQDOOnHK5avIWZJV16vYdA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVC
CSRrCl6zfN1SLUzm1NZ9WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7Rf
ZHM047QSv4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9vUJSZ
SWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTuIYEZoDJJKPTEjlbV
UjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwCW/POuZ6lcg5Ktz885hZo+L7tdEy8
W9ViH0Pd
-----END CERTIFICATE-----

RSA Root Certificate 1
======================
-----BEGIN CERTIFICATE-----
MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRp
b24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZh
bGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAw
MjIzM1oXDTE5MDYyNjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0
d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMg
UG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0
LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQDjmFGWHOjVsQaBalfDcnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td
3zZxFJmP3MKS8edgkpfs2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89H
BFx1cQqYJJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliEZwgs
3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJn0WuPIqpsHEzXcjF
V9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/APhmcGcwTTYJBtYze4D1gCCAPRX5r
on+jjBXu
-----END CERTIFICATE-----

Verisign Class 3 Public Primary Certification Authority - G3
============================================================
-----BEGIN CERTIFICATE-----
MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV
UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv
cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl
IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw
CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy
dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv
cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDMgUHVibGljIFByaW1hcnkg
Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAMu6nFL8eB8aHm8bN3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1
EUGO+i2tKmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGukxUc
cLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBmCC+Vk7+qRy+oRpfw
EuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJXwzw3sJ2zq/3avL6QaaiMxTJ5Xpj
055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWuimi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA
ERSWwauSCPc/L8my/uRan2Te2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5f
j267Cz3qWhMeDGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC
/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565pF4ErWjfJXir0
xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGtTxzhT5yvDwyd93gN2PQ1VoDa
t20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ==
-----END CERTIFICATE-----

Verisign Class 4 Public Primary Certification Authority - G3
============================================================
-----BEGIN CERTIFICATE-----
MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQswCQYDVQQGEwJV
UzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0IE5ldHdv
cmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl
IG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQsw
CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRy
dXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJbmMuIC0gRm9yIGF1dGhv
cml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWduIENsYXNzIDQgUHVibGljIFByaW1hcnkg
Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAK3LpRFpxlmr8Y+1GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaS
tBO3IFsJ+mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0GbdU6LM
8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLmNxdLMEYH5IBtptiW
Lugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XYufTsgsbSPZUd5cBPhMnZo0QoBmrX
Razwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEA
j/ola09b5KROJ1WrIhVZPMq1CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXtt
mhwwjIDLk5Mqg6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm
fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c2NU8Qh0XwRJd
RTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/bLvSHgCwIe34QWKCudiyxLtG
UPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg==
-----END CERTIFICATE-----

Entrust.net Secure Server CA
============================
-----BEGIN CERTIFICATE-----
MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMCVVMxFDASBgNV
BAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5uZXQvQ1BTIGluY29ycC4gYnkg
cmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRl
ZDE6MDgGA1UEAxMxRW50cnVzdC5uZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhv
cml0eTAeFw05OTA1MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIG
A1UEChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBi
eSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1p
dGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0
aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQ
aO2f55M28Qpku0f1BBc/I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5
gXpa0zf3wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OCAdcw
ggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHboIHYpIHVMIHSMQsw
CQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5l
dC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF
bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENl
cnRpZmljYXRpb24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu
dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0MFqBDzIwMTkw
NTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8BdiE1U9s/8KAGv7UISX8+1i0Bow
HQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAaMAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EA
BAwwChsEVjQuMAMCBJAwDQYJKoZIhvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyN
Ewr75Ji174z4xRAN95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9
n9cd2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI=
-----END CERTIFICATE-----

Entrust.net Premium 2048 Secure Server CA
=========================================
-----BEGIN CERTIFICATE-----
MIIEXDCCA0SgAwIBAgIEOGO5ZjANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChMLRW50cnVzdC5u
ZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBpbmNvcnAuIGJ5IHJlZi4gKGxp
bWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNV
BAMTKkVudHJ1c3QubmV0IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQx
NzUwNTFaFw0xOTEyMjQxODIwNTFaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3
d3d3LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTEl
MCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEGA1UEAxMqRW50cnVzdC5u
ZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgpMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEArU1LqRKGsuqjIAcVFmQqK0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOL
Gp18EzoOH1u3Hs/lJBQesYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSr
hRSGlVuXMlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVTXTzW
nLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/HoZdenoVve8AjhUi
VBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH4QIDAQABo3QwcjARBglghkgBhvhC
AQEEBAMCAAcwHwYDVR0jBBgwFoAUVeSB0RGAvtiJuQijMfmhJAkWuXAwHQYDVR0OBBYEFFXkgdER
gL7YibkIozH5oSQJFrlwMB0GCSqGSIb2fQdBAAQQMA4bCFY1LjA6NC4wAwIEkDANBgkqhkiG9w0B
AQUFAAOCAQEAWUesIYSKF8mciVMeuoCFGsY8Tj6xnLZ8xpJdGGQC49MGCBFhfGPjK50xA3B20qMo
oPS7mmNz7W3lKtvtFKkrxjYR0CvrB4ul2p5cGZ1WEvVUKcgF7bISKo30Axv/55IQh7A6tcOdBTcS
o8f0FbnVpDkWm1M6I5HxqIKiaohowXkCIryqptau37AUX7iH0N18f3v/rxzP5tsHrV7bhZ3QKw0z
2wTR5klAEyt2+z7pnIkPFc4YsIV4IU9rTw76NmfNB/L/CNDi3tm/Kq+4h4YhPATKt5Rof8886ZjX
OP/swNlQ8C5LWK5Gb9Auw2DaclVyvUxFnmG6v4SBkgPR0ml8xQ==
-----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-----

Equifax Secure Global eBusiness CA
==================================
-----BEGIN CERTIFICATE-----
MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT
RXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBTZWN1cmUgR2xvYmFsIGVCdXNp
bmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIwMDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMx
HDAaBgNVBAoTE0VxdWlmYXggU2VjdXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEds
b2JhbCBlQnVzaW5lc3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRV
PEnCUdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc58O/gGzN
qfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/o5brhTMhHD4ePmBudpxn
hcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAHMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j
BBgwFoAUvqigdHJQa0S3ySPY+6j/s1draGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hs
MA0GCSqGSIb3DQEBBAUAA4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okEN
I7SS+RkAZ70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv8qIY
NMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV
-----END CERTIFICATE-----

Equifax Secure eBusiness CA 1
=============================
-----BEGIN CERTIFICATE-----
MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT
RXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENB
LTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQwMDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UE
ChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNz
IENBLTEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ
1MRoRvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBuWqDZQu4a
IZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKwEnv+j6YDAgMBAAGjZjBk
MBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEp4MlIR21kW
Nl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRKeDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQF
AAOBgQB1W6ibAxHm6VZMzfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5
lSE/9dR+WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN/Bf+
KpYrtWKmpj29f5JZzVoqgrI3eQ==
-----END CERTIFICATE-----

Equifax Secure eBusiness CA 2
=============================
-----BEGIN CERTIFICATE-----
MIIDIDCCAomgAwIBAgIEN3DPtTANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJVUzEXMBUGA1UE
ChMORXF1aWZheCBTZWN1cmUxJjAkBgNVBAsTHUVxdWlmYXggU2VjdXJlIGVCdXNpbmVzcyBDQS0y
MB4XDTk5MDYyMzEyMTQ0NVoXDTE5MDYyMzEyMTQ0NVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoT
DkVxdWlmYXggU2VjdXJlMSYwJAYDVQQLEx1FcXVpZmF4IFNlY3VyZSBlQnVzaW5lc3MgQ0EtMjCB
nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA5Dk5kx5SBhsoNviyoynF7Y6yEb3+6+e0dMKP/wXn
2Z0GvxLIPw7y1tEkshHe0XMJitSxLJgJDR5QRrKDpkWNYmi7hRsgcDKqQM2mll/EcTc/BPO3QSQ5
BxoeLmFYoBIL5aXfxavqN3HMHMg3OrmXUqesxWoklE6ce8/AatbfIb0CAwEAAaOCAQkwggEFMHAG
A1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORXF1aWZheCBTZWN1cmUx
JjAkBgNVBAsTHUVxdWlmYXggU2VjdXJlIGVCdXNpbmVzcyBDQS0yMQ0wCwYDVQQDEwRDUkwxMBoG
A1UdEAQTMBGBDzIwMTkwNjIzMTIxNDQ1WjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUUJ4L6q9e
uSBIplBqy/3YIHqngnYwHQYDVR0OBBYEFFCeC+qvXrkgSKZQasv92CB6p4J2MAwGA1UdEwQFMAMB
Af8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUAA4GBAAyGgq3oThr1
jokn4jVYPSm0B482UJW/bsGe68SQsoWou7dC4A8HOd/7npCy0cE+U58DRLB+S/Rv5Hwf5+Kx5Lia
78O9zt4LMjTZ3ijtM2vE1Nc9ElirfQkty3D1E4qUoSek1nDFbZS1yX2doNLGCEnZZpum0/QL3MUm
V+GRMOrN
-----END CERTIFICATE-----

AddTrust Low-Value Services Root
================================
-----BEGIN CERTIFICATE-----
MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML
QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRU
cnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMwMTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQsw
CQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBO
ZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ulCDtbKRY6
54eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6ntGO0/7Gcrjyvd7ZWxbWr
oulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyldI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1
Zmne3yzxbrww2ywkEtvrNTVokMsAsJchPXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJui
GMx1I4S+6+JNM3GOGvDC+Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8w
HQYDVR0OBBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8EBTAD
AQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBlMQswCQYDVQQGEwJT
RTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEw
HwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxt
ZBsfzQ3duQH6lmM0MkhHma6X7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0Ph
iVYrqW9yTkkz43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY
eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJlpz/+0WatC7xr
mYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOAWiFeIc9TVPC6b4nbqKqVz4vj
ccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk=
-----END CERTIFICATE-----

AddTrust External Root
======================
-----BEGIN CERTIFICATE-----
MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEUMBIGA1UEChML
QWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFsIFRUUCBOZXR3b3JrMSIwIAYD
VQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEw
NDgzOFowbzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRU
cnVzdCBFeHRlcm5hbCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0Eg
Um9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvtH7xsD821
+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9uMq/NzgtHj6RQa1wVsfw
Tz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzXmk6vBbOmcZSccbNQYArHE504B4YCqOmo
aSYYkKtMsE8jqzpPhNjfzp/haW+710LXa0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy
2xSoRcRdKn23tNbE7qzNE0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv7
7+ldU9U0WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYDVR0P
BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0Jvf6xCZU7wO94CTL
VBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEmMCQGA1UECxMdQWRk
VHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsxIjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENB
IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZl
j7DYd7usQWxHYINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvCNr4TDea9Y355
e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEXc4g/VhsxOBi0cQ+azcgOno4u
G+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5amnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
-----END CERTIFICATE-----

AddTrust Public Services Root
=============================
-----BEGIN CERTIFICATE-----
MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEUMBIGA1UEChML
QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSAwHgYDVQQDExdBZGRU
cnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAxMDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJ
BgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5l
dHdvcmsxIDAeBgNVBAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV6tsfSlbu
nyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nXGCwwfQ56HmIexkvA/X1i
d9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnPdzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSG
Aa2Il+tmzV7R/9x98oTaunet3IAIx6eH1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAw
HM+A+WD+eeSI8t0A65RF62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0G
A1UdDgQWBBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB
/zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDELMAkGA1UEBhMCU0Ux
FDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29yazEgMB4G
A1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4
JNojVhaTdt02KLmuG7jD8WS6IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL
+YPoRNWyQSW/iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao
GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh4SINhwBk/ox9
Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQmXiLsks3/QppEIW1cxeMiHV9H
EufOX1362KqxMy3ZdvJOOjMMK7MtkAY=
-----END CERTIFICATE-----

AddTrust Qualified Certificates Root
====================================
-----BEGIN CERTIFICATE-----
MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEUMBIGA1UEChML
QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSMwIQYDVQQDExpBZGRU
cnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcx
CzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQ
IE5ldHdvcmsxIzAhBgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwqxBb/4Oxx
64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G87B4pfYOQnrjfxvM0PC3
KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i2O+tCBGaKZnhqkRFmhJePp1tUvznoD1o
L/BLcHwTOK28FSXx1s6rosAx1i+f4P8UWfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GR
wVY18BTcZTYJbqukB8c10cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HU
MIHRMB0GA1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/
BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6FrpGkwZzELMAkGA1UE
BhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRUcnVzdCBUVFAgTmV0d29y
azEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlmaWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQAD
ggEBABmrder4i2VhlRO6aQTvhsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxG
GuoYQ992zPlmhpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X
dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3P6CxB9bpT9ze
RXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9YiQBCYz95OdBEsIJuQRno3eDB
iFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5noxqE=
-----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-----

RSA Security 2048 v3
====================
-----BEGIN CERTIFICATE-----
MIIDYTCCAkmgAwIBAgIQCgEBAQAAAnwAAAAKAAAAAjANBgkqhkiG9w0BAQUFADA6MRkwFwYDVQQK
ExBSU0EgU2VjdXJpdHkgSW5jMR0wGwYDVQQLExRSU0EgU2VjdXJpdHkgMjA0OCBWMzAeFw0wMTAy
MjIyMDM5MjNaFw0yNjAyMjIyMDM5MjNaMDoxGTAXBgNVBAoTEFJTQSBTZWN1cml0eSBJbmMxHTAb
BgNVBAsTFFJTQSBTZWN1cml0eSAyMDQ4IFYzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEAt49VcdKA3XtpeafwGFAyPGJn9gqVB93mG/Oe2dJBVGutn3y+Gc37RqtBaB4Y6lXIL5F4iSj7
Jylg/9+PjDvJSZu1pJTOAeo+tWN7fyb9Gd3AIb2E0S1PRsNO3Ng3OTsor8udGuorryGlwSMiuLgb
WhOHV4PR8CDn6E8jQrAApX2J6elhc5SYcSa8LWrg903w8bYqODGBDSnhAMFRD0xS+ARaqn1y07iH
KrtjEAMqs6FPDVpeRrc9DvV07Jmf+T0kgYim3WBU6JU2PcYJk5qjEoAAVZkZR73QpXzDuvsf9/UP
+Ky5tfQ3mBMY3oVbtwyCO4dvlTlYMNpuAWgXIszACwIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/
MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQHw1EwpKrpRa41JPr/JCwz0LGdjDAdBgNVHQ4E
FgQUB8NRMKSq6UWuNST6/yQsM9CxnYwwDQYJKoZIhvcNAQEFBQADggEBAF8+hnZuuDU8TjYcHnmY
v/3VEhF5Ug7uMYm83X/50cYVIeiKAVQNOvtUudZj1LGqlk2iQk3UUx+LEN5/Zb5gEydxiKRz44Rj
0aRV4VCT5hsOedBnvEbIvz8XDZXmxpBp3ue0L96VfdASPz0+f00/FGj1EVDVwfSQpQgdMWD/YIwj
VAqv/qFuxdF6Kmh4zx6CCiC0H63lhbJqaHVOrSU3lIW+vaHU6rcMSzyd6BIA8F+sDeGscGNz9395
nzIlQnQFgCi/vcEkllgVsRch6YlL2weIZ/QVrXA+L02FO8K32/6YaCOJ4XQP3vTFhGMpG8zLB8kA
pKnXwiJPZ9d37CAFYd4=
-----END CERTIFICATE-----

GeoTrust Global CA
==================
-----BEGIN CERTIFICATE-----
MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVTMRYwFAYDVQQK
Ew1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9iYWwgQ0EwHhcNMDIwNTIxMDQw
MDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5j
LjEbMBkGA1UEAxMSR2VvVHJ1c3QgR2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEA2swYYzD99BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjo
BbdqfnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDviS2Aelet
8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU1XupGc1V3sjs0l44U+Vc
T4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+bw8HHa8sHo9gOeL6NlMTOdReJivbPagU
vTLrGAMoUgRx5aszPeE4uwc2hGKceeoWMPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTAD
AQH/MB0GA1UdDgQWBBTAephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVk
DBF9qn1luMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKInZ57Q
zxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfStQWVYrmm3ok9Nns4
d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcFPseKUgzbFbS9bZvlxrFUaKnjaZC2
mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Unhw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6p
XE0zX5IJL4hmXXeXxx12E6nV5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvm
Mw==
-----END CERTIFICATE-----

GeoTrust Global CA 2
====================
-----BEGIN CERTIFICATE-----
MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN
R2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwHhcNMDQwMzA0MDUw
MDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5j
LjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
ggEKAoIBAQDvPE1APRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/
NTL8Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hLTytCOb1k
LUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL5mkWRxHCJ1kDs6ZgwiFA
Vvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7S4wMcoKK+xfNAGw6EzywhIdLFnopsk/b
HdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQF
MAMBAf8wHQYDVR0OBBYEFHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNH
K266ZUapEBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6tdEPx7
srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv/NgdRN3ggX+d6Yvh
ZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywNA0ZF66D0f0hExghAzN4bcLUprbqL
OzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkC
x1YAzUm5s2x7UwQa4qjJqhIFI8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqF
H4z1Ir+rzoPz4iIprn2DQKi6bA==
-----END CERTIFICATE-----

GeoTrust Universal CA
=====================
-----BEGIN CERTIFICATE-----
MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN
R2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVyc2FsIENBMB4XDTA0MDMwNDA1
MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IElu
Yy4xHjAcBgNVBAMTFUdlb1RydXN0IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIP
ADCCAgoCggIBAKYVVaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9t
JPi8cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTTQjOgNB0e
RXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFhF7em6fgemdtzbvQKoiFs
7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2vc7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d
8Lsrlh/eezJS/R27tQahsiFepdaVaH/wmZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7V
qnJNk22CDtucvc+081xdVHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3Cga
Rr0BHdCXteGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZf9hB
Z3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfReBi9Fi1jUIxaS5BZu
KGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+nhutxx9z3SxPGWX9f5NAEC7S8O08
ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0
XG0D08DYj3rWMB8GA1UdIwQYMBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIB
hjANBgkqhkiG9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc
aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fXIwjhmF7DWgh2
qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzynANXH/KttgCJwpQzgXQQpAvvL
oJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0zuzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsK
xr2EoyNB3tZ3b4XUhRxQ4K5RirqNPnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxF
KyDuSN/n3QmOGKjaQI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2
DFKWkoRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9ER/frslK
xfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQtDF4JbAiXfKM9fJP/P6EU
p8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/SfuvmbJxPgWp6ZKy7PtXny3YuxadIwVyQD8vI
P/rmMuGNG2+k5o7Y+SlIis5z/iw=
-----END CERTIFICATE-----

GeoTrust Universal CA 2
=======================
-----BEGIN CERTIFICATE-----
MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMN
R2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwHhcNMDQwMzA0
MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3Qg
SW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUA
A4ICDwAwggIKAoICAQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0
DE81WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUGFF+3Qs17
j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdqXbboW0W63MOhBW9Wjo8Q
JqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxLse4YuU6W3Nx2/zu+z18DwPw76L5GG//a
QMJS9/7jOvdqdzXQ2o3rXhhqMcceujwbKNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2
WP0+GfPtDCapkzj4T8FdIgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP
20gaXT73y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRthAAn
ZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgocQIgfksILAAX/8sgC
SqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4Lt1ZrtmhN79UNdxzMk+MBB4zsslG
8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2
+/CfXGJx7Tz0RzgQKzAfBgNVHSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8E
BAMCAYYwDQYJKoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z
dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQL1EuxBRa3ugZ
4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgrFg5fNuH8KrUwJM/gYwx7WBr+
mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSoag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpq
A1Ihn0CoZ1Dy81of398j9tx4TuaYT1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpg
Y+RdM4kX2TGq2tbzGDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiP
pm8m1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJVOCiNUW7d
FGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH6aLcr34YEoP9VhdBLtUp
gn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwXQMAJKOSLakhT2+zNVVXxxvjpoixMptEm
X36vWkzaH6byHCx+rgIW0lbQL1dTR+iS
-----END CERTIFICATE-----

America Online Root Certification Authority 1
=============================================
-----BEGIN CERTIFICATE-----
MIIDpDCCAoygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT
QW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBPbmxpbmUgUm9vdCBDZXJ0aWZp
Y2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyODA2MDAwMFoXDTM3MTExOTIwNDMwMFowYzELMAkG
A1UEBhMCVVMxHDAaBgNVBAoTE0FtZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2Eg
T25saW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAKgv6KRpBgNHw+kqmP8ZonCaxlCyfqXfaE0bfA+2l2h9LaaLl+lkhsmj76CG
v2BlnEtUiMJIxUo5vxTjWVXlGbR0yLQFOVwWpeKVBeASrlmLojNoWBym1BW32J/X3HGrfpq/m44z
DyL9Hy7nBzbvYjnF3cu6JRQj3gzGPTzOggjmZj7aUTsWOqMFf6Dch9Wc/HKpoH145LcxVR5lu9Rh
sCFg7RAycsWSJR74kEoYeEfffjA3PlAb2xzTa5qGUwew76wGePiEmf4hjUyAtgyC9mZweRrTT6PP
8c9GsEsPPt2IYriMqQkoO3rHl+Ee5fSfwMCuJKDIodkP1nsmgmkyPacCAwEAAaNjMGEwDwYDVR0T
AQH/BAUwAwEB/zAdBgNVHQ4EFgQUAK3Zo/Z59m50qX8zPYEX10zPM94wHwYDVR0jBBgwFoAUAK3Z
o/Z59m50qX8zPYEX10zPM94wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBBQUAA4IBAQB8itEf
GDeC4Liwo+1WlchiYZwFos3CYiZhzRAW18y0ZTTQEYqtqKkFZu90821fnZmv9ov761KyBZiibyrF
VL0lvV+uyIbqRizBs73B6UlwGBaXCBOMIOAbLjpHyx7kADCVW/RFo8AasAFOq73AI25jP4BKxQft
3OJvx8Fi8eNy1gTIdGcL+oiroQHIb/AUr9KZzVGTfu0uOMe9zkZQPXLjeSWdm4grECDdpbgyn43g
Kd8hdIaC2y+CMMbHNYaz+ZZfRtsMRf3zUMNvxsNIrUam4SdHCh0Om7bCd39j8uB9Gr784N/Xx6ds
sPmuujz9dLQR6FgNgLzTqIA6me11zEZ7
-----END CERTIFICATE-----

America Online Root Certification Authority 2
=============================================
-----BEGIN CERTIFICATE-----
MIIFpDCCA4ygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEcMBoGA1UEChMT
QW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBPbmxpbmUgUm9vdCBDZXJ0aWZp
Y2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyODA2MDAwMFoXDTM3MDkyOTE0MDgwMFowYzELMAkG
A1UEBhMCVVMxHDAaBgNVBAoTE0FtZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2Eg
T25saW5lIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQAD
ggIPADCCAgoCggIBAMxBRR3pPU0Q9oyxQcngXssNt79Hc9PwVU3dxgz6sWYFas14tNwC206B89en
fHG8dWOgXeMHDEjsJcQDIPT/DjsS/5uN4cbVG7RtIuOx238hZK+GvFciKtZHgVdEglZTvYYUAQv8
f3SkWq7xuhG1m1hagLQ3eAkzfDJHA1zEpYNI9FdWboE2JxhP7JsowtS013wMPgwr38oE18aO6lhO
qKSlGBxsRZijQdEt0sdtjRnxrXm3gT+9BoInLRBYBbV4Bbkv2wxrkJB+FFk4u5QkE+XRnRTf04JN
RvCAOVIyD+OEsnpD8l7eXz8d3eOyG6ChKiMDbi4BFYdcpnV1x5dhvt6G3NRI270qv0pV2uh9UPu0
gBe4lL8BPeraunzgWGcXuVjgiIZGZ2ydEEdYMtA1fHkqkKJaEBEjNa0vzORKW6fIJ/KD3l67Xnfn
6KVuY8INXWHQjNJsWiEOyiijzirplcdIz5ZvHZIlyMbGwcEMBawmxNJ10uEqZ8A9W6Wa6897Gqid
FEXlD6CaZd4vKL3Ob5Rmg0gp2OpljK+T2WSfVVcmv2/LNzGZo2C7HK2JNDJiuEMhBnIMoVxtRsX6
Kc8w3onccVvdtjc+31D1uAclJuW8tf48ArO3+L5DwYcRlJ4jbBeKuIonDFRH8KmzwICMoCfrHRnj
B453cMor9H124HhnAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE1FwWg4u3Op
aaEg5+31IqEjFNeeMB8GA1UdIwQYMBaAFE1FwWg4u3OpaaEg5+31IqEjFNeeMA4GA1UdDwEB/wQE
AwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAZ2sGuV9FOypLM7PmG2tZTiLMubekJcmnxPBUlgtk87FY
T15R/LKXeydlwuXK5w0MJXti4/qftIe3RUavg6WXSIylvfEWK5t2LHo1YGwRgJfMqZJS5ivmae2p
+DYtLHe/YUjRYwu5W1LtGLBDQiKmsXeu3mnFzcccobGlHBD7GL4acN3Bkku+KVqdPzW+5X1R+FXg
JXUjhx5c3LqdsKyzadsXg8n33gy8CNyRnqjQ1xU3c6U1uPx+xURABsPr+CKAXEfOAuMRn0T//Zoy
zH1kUQ7rVyZ2OuMeIjzCpjbdGe+n/BLzJsBZMYVMnNjP36TMzCmT/5RtdlwTCJfy7aULTd3oyWgO
ZtMADjMSW7yV5TKQqLPGbIOtd+6Lfn6xqavT4fG2wLHqiMDn05DpKJKUe2h7lyoKZy2FAjgQ5ANh
1NolNscIWC2hp1GvMApJ9aZphwctREZ2jirlmjvXGKL8nDgQzMY70rUXOm/9riW99XJZZLF0Kjhf
GEzfz3EEWjbUvy+ZnOjZurGV5gJLIaFb1cFPj65pbVPbAZO1XB4Y3WRayhgoPmMEEf0cjQAPuDff
Z4qdZqkCapH/E8ovXYO8h5Ns3CRRFgQlZvqz2cK6Kb6aSDiCmfS/O0oxGfm/jiEzFMpPVF/7zvuP
cX/9XhmgD0uRuMRUvAawRY8mkaKO/qk=
-----END CERTIFICATE-----

Visa eCommerce Root
===================
-----BEGIN CERTIFICATE-----
MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBrMQswCQYDVQQG
EwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2Ug
QXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1lcmNlIFJvb3QwHhcNMDIwNjI2MDIxODM2
WhcNMjIwNjI0MDAxNjEyWjBrMQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMm
VmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv
bW1lcmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvV95WHm6h2mCxlCfL
F9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVIsZHBAk4ElpF7sDPwsRROEW+1QK8b
RaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdVZqW1LS7YgFmypw23RuwhY/81q6UCzyr0
TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq299yOIzzlr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI
/k4+oKsGGelT84ATB+0tvz8KPFUgOSwsAGl0lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzs
GHxBvfaLdXe6YJ2E5/4tAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEG
MB0GA1UdDgQWBBQVOIMPPyw/cDMezUb+B4wg4NfDtzANBgkqhkiG9w0BAQUFAAOCAQEAX/FBfXxc
CLkr4NWSR/pnXKUTwwMhmytMiUbPWU3J/qVAtmPN3XEolWcRzCSs00Rsca4BIGsDoo8Ytyk6feUW
YFN4PMCvFYP3j1IzJL1kk5fui/fbGKhtcbP3LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pz
zkWKsKZJ/0x9nXGIxHYdkFsd7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBu
YQa7FkKMcPcw++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt
398znM/jra6O1I7mT1GvFpLgXPYHDw==
-----END CERTIFICATE-----

Certum Root CA
==============
-----BEGIN CERTIFICATE-----
MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQK
ExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBDQTAeFw0wMjA2MTExMDQ2Mzla
Fw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBMMRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8u
by4xEjAQBgNVBAMTCUNlcnR1bSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6x
wS7TT3zNJc4YPk/EjG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdL
kKWoePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GIULdtlkIJ
89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapuOb7kky/ZR6By6/qmW6/K
Uz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUgAKpoC6EahQGcxEZjgoi2IrHu/qpGWX7P
NSzVttpd90gzFFS269lvzs2I1qsb2pY7HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkq
hkiG9w0BAQUFAAOCAQEAuI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+
GXYkHAQaTOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTgxSvg
GrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1qCjqTE5s7FCMTY5w/
0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5xO/fIR/RpbxXyEV6DHpx8Uq79AtoS
qFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs6GAqm4VKQPNriiTsBhYscw==
-----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-----

Comodo Secure Services root
===========================
-----BEGIN CERTIFICATE-----
MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS
R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg
TGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRpZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAw
MDAwMFoXDTI4MTIzMTIzNTk1OVowfjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFu
Y2hlc3RlcjEQMA4GA1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAi
BgNVBAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPMcm3ye5drswfxdySRXyWP
9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3SHpR7LZQdqnXXs5jLrLxkU0C8j6ysNstc
rbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rC
oznl2yY4rYsK7hljxxwk3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3V
p6ea5EQz6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNVHQ4E
FgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8w
gYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2RvY2EuY29tL1NlY3VyZUNlcnRpZmlj
YXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRwOi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlm
aWNhdGVTZXJ2aWNlcy5jcmwwDQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm
4J4oqF7Tt/Q05qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj
Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtIgKvcnDe4IRRL
DXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJaD61JlfutuC23bkpgHl9j6Pw
pCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDlizeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1H
RR3B7Hzs/Sk=
-----END CERTIFICATE-----

Comodo Trusted Services root
============================
-----BEGIN CERTIFICATE-----
MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEbMBkGA1UECAwS
R3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRowGAYDVQQKDBFDb21vZG8gQ0Eg
TGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEw
MDAwMDBaFw0yODEyMzEyMzU5NTlaMH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1h
bmNoZXN0ZXIxEDAOBgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUw
IwYDVQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWWfnJSoBVC21ndZHoa0Lh7
3TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMtTGo87IvDktJTdyR0nAducPy9C1t2ul/y
/9c3S0pgePfw+spwtOpZqqPOSC+pw7ILfhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6
juljatEPmsbS9Is6FARW1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsS
ivnkBbA7kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0GA1Ud
DgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB
/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21vZG9jYS5jb20vVHJ1c3RlZENlcnRp
ZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRodHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENl
cnRpZmljYXRlU2VydmljZXMuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8Ntw
uleGFTQQuS9/HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32
pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxISjBc/lDb+XbDA
BHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+xqFx7D+gIIxmOom0jtTYsU0l
R+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/AtyjcndBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O
9y5Xt5hwXsjEeLBi
-----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-----

Staat der Nederlanden Root CA
=============================
-----BEGIN CERTIFICATE-----
MIIDujCCAqKgAwIBAgIEAJiWijANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJOTDEeMBwGA1UE
ChMVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSYwJAYDVQQDEx1TdGFhdCBkZXIgTmVkZXJsYW5kZW4g
Um9vdCBDQTAeFw0wMjEyMTcwOTIzNDlaFw0xNTEyMTYwOTE1MzhaMFUxCzAJBgNVBAYTAk5MMR4w
HAYDVQQKExVTdGFhdCBkZXIgTmVkZXJsYW5kZW4xJjAkBgNVBAMTHVN0YWF0IGRlciBOZWRlcmxh
bmRlbiBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmNK1URF6gaYUmHFt
vsznExvWJw56s2oYHLZhWtVhCb/ekBPHZ+7d89rFDBKeNVU+LCeIQGv33N0iYfXCxw719tV2U02P
jLwYdjeFnejKScfST5gTCaI+Ioicf9byEGW07l8Y1Rfj+MX94p2i71MOhXeiD+EwR+4A5zN9RGca
C1Hoi6CeUJhoNFIfLm0B8mBF8jHrqTFoKbt6QZ7GGX+UtFE5A3+y3qcym7RHjm+0Sq7lr7HcsBth
vJly3uSJt3omXdozSVtSnA71iq3DuD3oBmrC1SoLbHuEvVYFy4ZlkuxEK7COudxwC0barbxjiDn6
22r+I/q85Ej0ZytqERAhSQIDAQABo4GRMIGOMAwGA1UdEwQFMAMBAf8wTwYDVR0gBEgwRjBEBgRV
HSAAMDwwOgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cucGtpb3ZlcmhlaWQubmwvcG9saWNpZXMvcm9v
dC1wb2xpY3kwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSofeu8Y6R0E3QA7Jbg0zTBLL9s+DAN
BgkqhkiG9w0BAQUFAAOCAQEABYSHVXQ2YcG70dTGFagTtJ+k/rvuFbQvBgwp8qiSpGEN/KtcCFtR
EytNwiphyPgJWPwtArI5fZlmgb9uXJVFIGzmeafR2Bwp/MIgJ1HI8XxdNGdphREwxgDS1/PTfLbw
MVcoEoJz6TMvplW0C5GUR5z6u3pCMuiufi3IvKwUv9kP2Vv8wfl6leF9fpb8cbDCTMjfRTTJzg3y
nGQI0DvDKcWy7ZAEwbEpkcUwb8GpcjPM/l0WFywRaed+/sWDCN+83CI6LiBpIzlWYGeQiy52OfsR
iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw==
-----END CERTIFICATE-----

TDC Internet Root CA
====================
-----BEGIN CERTIFICATE-----
MIIEKzCCAxOgAwIBAgIEOsylTDANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJESzEVMBMGA1UE
ChMMVERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTAeFw0wMTA0MDUx
NjMzMTdaFw0yMTA0MDUxNzAzMTdaMEMxCzAJBgNVBAYTAkRLMRUwEwYDVQQKEwxUREMgSW50ZXJu
ZXQxHTAbBgNVBAsTFFREQyBJbnRlcm5ldCBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAxLhAvJHVYx/XmaCLDEAedLdInUaMArLgJF/wGROnN4NrXceO+YQwzho7+vvOi20j
xsNuZp+Jpd/gQlBn+h9sHvTQBda/ytZO5GhgbEaqHF1j4QeGDmUApy6mcca8uYGoOn0a0vnRrEvL
znWv3Hv6gXPU/Lq9QYjUdLP5Xjg6PEOo0pVOd20TDJ2PeAG3WiAfAzc14izbSysseLlJ28TQx5yc
5IogCSEWVmb/Bexb4/DPqyQkXsN/cHoSxNK1EKC2IeGNeGlVRGn1ypYcNIUXJXfi9i8nmHj9eQY6
otZaQ8H/7AQ77hPv01ha/5Lr7K7a8jcDR0G2l8ktCkEiu7vmpwIDAQABo4IBJTCCASEwEQYJYIZI
AYb4QgEBBAQDAgAHMGUGA1UdHwReMFwwWqBYoFakVDBSMQswCQYDVQQGEwJESzEVMBMGA1UEChMM
VERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTENMAsGA1UEAxMEQ1JM
MTArBgNVHRAEJDAigA8yMDAxMDQwNTE2MzMxN1qBDzIwMjEwNDA1MTcwMzE3WjALBgNVHQ8EBAMC
AQYwHwYDVR0jBBgwFoAUbGQBx/2FbazI2p5QCIUItTxWqFAwHQYDVR0OBBYEFGxkAcf9hW2syNqe
UAiFCLU8VqhQMAwGA1UdEwQFMAMBAf8wHQYJKoZIhvZ9B0EABBAwDhsIVjUuMDo0LjADAgSQMA0G
CSqGSIb3DQEBBQUAA4IBAQBOQ8zR3R0QGwZ/t6T609lN+yOfI1Rb5osvBCiLtSdtiaHsmGnc540m
gwV5dOy0uaOXwTUA/RXaOYE6lTGQ3pfphqiZdwzlWqCE/xIWrG64jcN7ksKsLtB9KOy282A4aW8+
2ARVPp7MVdK6/rtHBNcK2RYKNCn1WBPVT8+PVkuzHu7TmHnaCB4Mb7j4Fifvwm899qNLPg7kbWzb
O0ESm70NRyN/PErQr8Cv9u8btRXE64PECV90i9kR+8JWsTz4cMo0jUNAE4z9mQNUecYu6oah9jrU
Cbz0vGbMPVjQV0kK7iXiQe4T+Zs4NNEA9X7nlB38aQNiuJkFBT1reBK9sG9l
-----END CERTIFICATE-----

UTN DATACorp SGC Root CA
========================
-----BEGIN CERTIFICATE-----
MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCBkzELMAkGA1UE
BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl
IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZ
BgNVBAMTElVUTiAtIERBVEFDb3JwIFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBa
MIGTMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4w
HAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRy
dXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ys
raP6LnD43m77VkIVni5c7yPeIbkFdicZD0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlo
wHDyUwDAXlCCpVZvNvlK4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA
9P4yPykqlXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulWbfXv
33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQABo4GrMIGoMAsGA1Ud
DwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRTMtGzz3/64PGgXYVOktKeRR20TzA9
BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3JsLnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dD
LmNybDAqBgNVHSUEIzAhBggrBgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3
DQEBBQUAA4IBAQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft
Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyjj98C5OBxOvG0
I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVHKWss5nbZqSl9Mt3JNjy9rjXx
EZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwP
DPafepE39peC4N1xaf92P2BNPM/3mfnGV/TJVTl4uix5yaaIK/QI
-----END CERTIFICATE-----

UTN USERFirst Hardware Root CA
==============================
-----BEGIN CERTIFICATE-----
MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCBlzELMAkGA1UE
BhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEeMBwGA1UEChMVVGhl
IFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAd
BgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgx
OTIyWjCBlzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0
eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8vd3d3LnVz
ZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdhcmUwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlI
wrthdBKWHTxqctU8EGc6Oe0rE81m65UJM6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFd
tqdt++BxF2uiiPsA3/4aMXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8
i4fDidNdoI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqIDsjf
Pe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9KsyoUhbAgMBAAGjgbkw
gbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFKFyXyYbKJhDlV0HN9WF
lp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNF
UkZpcnN0LUhhcmR3YXJlLmNybDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUF
BwMGBggrBgEFBQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM
//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28GpgoiskliCE7/yMgUsogW
XecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gECJChicsZUN/KHAG8HQQZexB2
lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kn
iCrVWFCVH/A7HFe7fRQ5YiuayZSSKqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67
nfhmqA==
-----END CERTIFICATE-----

Camerfirma Chambers of Commerce Root
====================================
-----BEGIN CERTIFICATE-----
MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe
QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i
ZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAx
NjEzNDNaFw0zNzA5MzAxNjEzNDRaMH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZp
cm1hIFNBIENJRiBBODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3Jn
MSIwIAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0BAQEFAAOC
AQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtbunXF/KGIJPov7coISjlU
xFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0dBmpAPrMMhe5cG3nCYsS4No41XQEMIwRH
NaqbYE6gZj3LJgqcQKH0XZi/caulAGgq7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jW
DA+wWFjbw2Y3npuRVDM30pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFV
d9oKDMyXroDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIGA1Ud
EwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5jaGFtYmVyc2lnbi5v
cmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p26EpW1eLTXYGduHRooowDgYDVR0P
AQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hh
bWJlcnNpZ24ub3JnMCcGA1UdEgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYD
VR0gBFEwTzBNBgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz
aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEBAAxBl8IahsAi
fJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZdp0AJPaxJRUXcLo0waLIJuvvD
L8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wN
UPf6s+xCX6ndbcj0dc97wXImsQEcXCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/n
ADydb47kMgkdTXg0eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1
erfutGWaIZDgqtCYvDi1czyL+Nw=
-----END CERTIFICATE-----

Camerfirma Global Chambersign Root
==================================
-----BEGIN CERTIFICATE-----
MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMe
QUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1i
ZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYx
NDE4WhcNMzcwOTMwMTYxNDE4WjB9MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJt
YSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEg
MB4GA1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUAA4IBDQAw
ggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0Mi+ITaFgCPS3CU6gSS9J
1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/sQJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8O
by4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpVeAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl
6DJWk0aJqCWKZQbua795B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c
8lCrEqWhz0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0TAQH/
BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1iZXJzaWduLm9yZy9j
aGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4wTcbOX60Qq+UDpfqpFDAOBgNVHQ8B
Af8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAHMCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBj
aGFtYmVyc2lnbi5vcmcwKgYDVR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9y
ZzBbBgNVHSAEVDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh
bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0BAQUFAAOCAQEA
PDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUMbKGKfKX0j//U2K0X1S0E0T9Y
gOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXiryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJ
PJ7oKXqJ1/6v/2j1pReQvayZzKWGVwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4
IBHNfTIzSJRUTN3cecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREes
t2d/AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A==
-----END CERTIFICATE-----

NetLock Notary (Class A) Root
=============================
-----BEGIN CERTIFICATE-----
MIIGfTCCBWWgAwIBAgICAQMwDQYJKoZIhvcNAQEEBQAwga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQI
EwdIdW5nYXJ5MREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6
dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9j
ayBLb3pqZWd5em9pIChDbGFzcyBBKSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNDIzMTQ0N1oX
DTE5MDIxOTIzMTQ0N1owga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQH
EwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQuMRowGAYD
VQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBLb3pqZWd5em9pIChDbGFz
cyBBKSBUYW51c2l0dmFueWtpYWRvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvHSM
D7tM9DceqQWC2ObhbHDqeLVu0ThEDaiDzl3S1tWBxdRL51uUcCbbO51qTGL3cfNk1mE7PetzozfZ
z+qMkjvN9wfcZnSX9EUi3fRc4L9t875lM+QVOr/bmJBVOMTtplVjC7B4BPTjbsE/jvxReB+SnoPC
/tmwqcm8WgD/qaiYdPv2LD4VOQ22BFWoDpggQrOxJa1+mm9dU7GrDPzr4PN6s6iz/0b2Y6LYOph7
tqyF/7AlT3Rj5xMHpQqPBffAZG9+pyeAlt7ULoZgx2srXnN7F+eRP2QM2EsiNCubMvJIH5+hCoR6
4sKtlz2O1cH5VqNQ6ca0+pii7pXmKgOM3wIDAQABo4ICnzCCApswDgYDVR0PAQH/BAQDAgAGMBIG
A1UdEwEB/wQIMAYBAf8CAQQwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaC
Ak1GSUdZRUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pv
bGdhbHRhdGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQu
IEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2Vn
LWJpenRvc2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0
ZXRlbGUgYXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFz
IGxlaXJhc2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBh
IGh0dHBzOi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVu
b3J6ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1YW5jZSBh
bmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sg
Q1BTIGF2YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFp
bCBhdCBjcHNAbmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4IBAQBIJEb3ulZv+sgoA0BO5TE5
ayZrU3/b39/zcT0mwBQOxmd7I6gMc90Bu8bKbjc5VdXHjFYgDigKDtIqpLBJUsY4B/6+CgmM0ZjP
ytoUMaFP0jn8DxEsQ8Pdq5PHVT5HfBgaANzze9jyf1JsIPQLX2lS9O74silg6+NJMSEN1rUQQeJB
CWziGppWS3cC9qCbmieH6FUpccKQn0V4GuEVZD3QDtigdp+uxdAu6tYPVuxkf1qbFFgBJ34TUMdr
KuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK8CtmdWOMovsEPoMOmzbwGOQmIMOM
8CgHrTwXZoi1/baI
-----END CERTIFICATE-----

NetLock Business (Class B) Root
===============================
-----BEGIN CERTIFICATE-----
MIIFSzCCBLSgAwIBAgIBaTANBgkqhkiG9w0BAQQFADCBmTELMAkGA1UEBhMCSFUxETAPBgNVBAcT
CEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0b25zYWdpIEtmdC4xGjAYBgNV
BAsTEVRhbnVzaXR2YW55a2lhZG9rMTIwMAYDVQQDEylOZXRMb2NrIFV6bGV0aSAoQ2xhc3MgQikg
VGFudXNpdHZhbnlraWFkbzAeFw05OTAyMjUxNDEwMjJaFw0xOTAyMjAxNDEwMjJaMIGZMQswCQYD
VQQGEwJIVTERMA8GA1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRv
bnNhZ2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxMjAwBgNVBAMTKU5ldExvY2sg
VXpsZXRpIChDbGFzcyBCKSBUYW51c2l0dmFueWtpYWRvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
iQKBgQCx6gTsIKAjwo84YM/HRrPVG/77uZmeBNwcf4xKgZjupNTKihe5In+DCnVMm8Bp2GQ5o+2S
o/1bXHQawEfKOml2mrriRBf8TKPV/riXiK+IA4kfpPIEPsgHC+b5sy96YhQJRhTKZPWLgLViqNhr
1nGTLbO/CVRY7QbrqHvcQ7GhaQIDAQABo4ICnzCCApswEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNV
HQ8BAf8EBAMCAAYwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1GSUdZ
RUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pvbGdhbHRh
dGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQuIEEgaGl0
ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2VnLWJpenRv
c2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUg
YXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJh
c2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBhIGh0dHBz
Oi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVub3J6ZXNA
bmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1YW5jZSBhbmQgdGhl
IHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sgQ1BTIGF2
YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBj
cHNAbmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4GBAATbrowXr/gOkDFOzT4JwG06sPgzTEdM
43WIEJessDgVkcYplswhwG08pXTP2IKlOcNl40JwuyKQ433bNXbhoLXan3BukxowOR0w2y7jfLKR
stE3Kfq51hdcR0/jHTjrn9V7lagonhVK0dHQKwCXoOKSNitjrFgBazMpUIaD8QFI
-----END CERTIFICATE-----

NetLock Express (Class C) Root
==============================
-----BEGIN CERTIFICATE-----
MIIFTzCCBLigAwIBAgIBaDANBgkqhkiG9w0BAQQFADCBmzELMAkGA1UEBhMCSFUxETAPBgNVBAcT
CEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0b25zYWdpIEtmdC4xGjAYBgNV
BAsTEVRhbnVzaXR2YW55a2lhZG9rMTQwMgYDVQQDEytOZXRMb2NrIEV4cHJlc3N6IChDbGFzcyBD
KSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNTE0MDgxMVoXDTE5MDIyMDE0MDgxMVowgZsxCzAJ
BgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6
dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE0MDIGA1UEAxMrTmV0TG9j
ayBFeHByZXNzeiAoQ2xhc3MgQykgVGFudXNpdHZhbnlraWFkbzCBnzANBgkqhkiG9w0BAQEFAAOB
jQAwgYkCgYEA6+ywbGGKIyWvYCDj2Z/8kwvbXY2wobNAOoLO/XXgeDIDhlqGlZHtU/qdQPzm6N3Z
W3oDvV3zOwzDUXmbrVWg6dADEK8KuhRC2VImESLH0iDMgqSaqf64gXadarfSNnU+sYYJ9m5tfk63
euyucYT2BDMIJTLrdKwWRMbkQJMdf60CAwEAAaOCAp8wggKbMBIGA1UdEwEB/wQIMAYBAf8CAQQw
DgYDVR0PAQH/BAQDAgAGMBEGCWCGSAGG+EIBAQQEAwIABzCCAmAGCWCGSAGG+EIBDQSCAlEWggJN
RklHWUVMRU0hIEV6ZW4gdGFudXNpdHZhbnkgYSBOZXRMb2NrIEtmdC4gQWx0YWxhbm9zIFN6b2xn
YWx0YXRhc2kgRmVsdGV0ZWxlaWJlbiBsZWlydCBlbGphcmFzb2sgYWxhcGphbiBrZXN6dWx0LiBB
IGhpdGVsZXNpdGVzIGZvbHlhbWF0YXQgYSBOZXRMb2NrIEtmdC4gdGVybWVrZmVsZWxvc3NlZy1i
aXp0b3NpdGFzYSB2ZWRpLiBBIGRpZ2l0YWxpcyBhbGFpcmFzIGVsZm9nYWRhc2FuYWsgZmVsdGV0
ZWxlIGF6IGVsb2lydCBlbGxlbm9yemVzaSBlbGphcmFzIG1lZ3RldGVsZS4gQXogZWxqYXJhcyBs
ZWlyYXNhIG1lZ3RhbGFsaGF0byBhIE5ldExvY2sgS2Z0LiBJbnRlcm5ldCBob25sYXBqYW4gYSBo
dHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIGNpbWVuIHZhZ3kga2VyaGV0byBheiBlbGxlbm9y
emVzQG5ldGxvY2submV0IGUtbWFpbCBjaW1lbi4gSU1QT1JUQU5UISBUaGUgaXNzdWFuY2UgYW5k
IHRoZSB1c2Ugb2YgdGhpcyBjZXJ0aWZpY2F0ZSBpcyBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIENQ
UyBhdmFpbGFibGUgYXQgaHR0cHM6Ly93d3cubmV0bG9jay5uZXQvZG9jcyBvciBieSBlLW1haWwg
YXQgY3BzQG5ldGxvY2submV0LjANBgkqhkiG9w0BAQQFAAOBgQAQrX/XDDKACtiG8XmYta3UzbM2
xJZIwVzNmtkFLp++UOv0JhQQLdRmF/iewSf98e3ke0ugbLWrmldwpu2gpO0u9f38vf5NNwgMvOOW
gyL1SRt/Syu0VMGAfJlOHdCM7tCs5ZL6dVb+ZKATj7i4Fp1hBWeAyNDYpQcCNJgEjTME1A==
-----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-----

StartCom Certification Authority
================================
-----BEGIN CERTIFICATE-----
MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN
U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu
ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0
NjM2WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRk
LjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMg
U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
ggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPwbm2xPN1y
o4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/
Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/d
eMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt
2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z
6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJ
osmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/
untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVc
UjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT
37uMdBNSSwIDAQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE
FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9jZXJ0LnN0YXJ0
Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3JsLnN0YXJ0Y29tLm9yZy9zZnNj
YS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFMBgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUH
AgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRw
Oi8vY2VydC5zdGFydGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYg
U3RhcnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlhYmlsaXR5
LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2YgdGhlIFN0YXJ0Q29tIENl
cnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFpbGFibGUgYXQgaHR0cDovL2NlcnQuc3Rh
cnRjb20ub3JnL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilT
dGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOC
AgEAFmyZ9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8jhvh
3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUWFjgKXlf2Ysd6AgXm
vB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJzewT4F+irsfMuXGRuczE6Eri8sxHk
fY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3
fsNrarnDy0RLrHiQi+fHLB5LEUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZ
EoalHmdkrQYuL6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq
yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuCO3NJo2pXh5Tl
1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6Vum0ABj6y6koQOdjQK/W/7HW/
lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkyShNOsF/5oirpt9P/FlUQqmMGqz9IgcgA38coro
g14=
-----END CERTIFICATE-----

Taiwan GRCA
===========
-----BEGIN CERTIFICATE-----
MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/MQswCQYDVQQG
EwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4X
DTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1owPzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dv
dmVybm1lbnQgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQAD
ggIPADCCAgoCggIBAJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qN
w8XRIePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1qgQdW8or5
BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKyyhwOeYHWtXBiCAEuTk8O
1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAtsF/tnyMKtsc2AtJfcdgEWFelq16TheEfO
htX7MfP6Mb40qij7cEwdScevLJ1tZqa2jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wov
J5pGfaENda1UhhXcSTvxls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7
Q3hub/FCVGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHKYS1t
B6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoHEgKXTiCQ8P8NHuJB
O9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThNXo+EHWbNxWCWtFJaBYmOlXqYwZE8
lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1UdDgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNV
HRMEBTADAQH/MDkGBGcqBwAEMTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg2
09yewDL7MTqKUWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ
TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyfqzvS/3WXy6Tj
Zwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaKZEk9GhiHkASfQlK3T8v+R0F2
Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFEJPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlU
D7gsL0u8qV1bYH+Mh6XgUmMqvtg7hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6Qz
DxARvBMB1uUO07+1EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+Hbk
Z6MmnD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WXudpVBrkk
7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44VbnzssQwmSNOXfJIoRIM3BKQ
CZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDeLMDDav7v3Aun+kbfYNucpllQdSNpc5Oy
+fwC00fmcc4QAu4njIT/rEUNE1yDMuAlpYYsfPQS
-----END CERTIFICATE-----

Firmaprofesional Root CA
========================
-----BEGIN CERTIFICATE-----
MIIEVzCCAz+gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBnTELMAkGA1UEBhMCRVMxIjAgBgNVBAcT
GUMvIE11bnRhbmVyIDI0NCBCYXJjZWxvbmExQjBABgNVBAMTOUF1dG9yaWRhZCBkZSBDZXJ0aWZp
Y2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODEmMCQGCSqGSIb3DQEJARYXY2FA
ZmlybWFwcm9mZXNpb25hbC5jb20wHhcNMDExMDI0MjIwMDAwWhcNMTMxMDI0MjIwMDAwWjCBnTEL
MAkGA1UEBhMCRVMxIjAgBgNVBAcTGUMvIE11bnRhbmVyIDI0NCBCYXJjZWxvbmExQjBABgNVBAMT
OUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2
ODEmMCQGCSqGSIb3DQEJARYXY2FAZmlybWFwcm9mZXNpb25hbC5jb20wggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQDnIwNvbyOlXnjOlSztlB5uCp4Bx+ow0Syd3Tfom5h5VtP8c9/Qit5V
j1H5WuretXDE7aTt/6MNbg9kUDGvASdYrv5sp0ovFy3Tc9UTHI9ZpTQsHVQERc1ouKDAA6XPhUJH
lShbz++AbOCQl4oBPB3zhxAwJkh91/zpnZFx/0GaqUC1N5wpIE8fUuOgfRNtVLcK3ulqTgesrBlf
3H5idPayBQC6haD9HThuy1q7hryUZzM1gywfI834yJFxzJeL764P3CkDG8A563DtwW4O2GcLiam8
NeTvtjS0pbbELaW+0MOUJEjb35bTALVmGotmBQ/dPz/LP6pemkr4tErvlTcbAgMBAAGjgZ8wgZww
KgYDVR0RBCMwIYYfaHR0cDovL3d3dy5maXJtYXByb2Zlc2lvbmFsLmNvbTASBgNVHRMBAf8ECDAG
AQH/AgEBMCsGA1UdEAQkMCKADzIwMDExMDI0MjIwMDAwWoEPMjAxMzEwMjQyMjAwMDBaMA4GA1Ud
DwEB/wQEAwIBBjAdBgNVHQ4EFgQUMwugZtHq2s7eYpMEKFK1FH84aLcwDQYJKoZIhvcNAQEFBQAD
ggEBAEdz/o0nVPD11HecJ3lXV7cVVuzH2Fi3AQL0M+2TUIiefEaxvT8Ub/GzR0iLjJcG1+p+o1wq
u00vR+L4OQbJnC4xGgN49Lw4xiKLMzHwFgQEffl25EvXwOaD7FnMP97/T2u3Z36mhoEyIwOdyPdf
wUpgpZKpsaSgYMN4h7Mi8yrrW6ntBas3D7Hi05V2Y1Z0jFhyGzflZKG+TQyTmAyX9odtsz/ny4Cm
7YjHX1BiAuiZdBbQ5rQ58SfLyEDW44YQqSMSkuBpQWOnryULwMWSyx6Yo1q6xTMPoJcB3X/ge9YG
VM+h4k0460tQtcsm9MracEpqoeJ5quGnM/b9Sh/22WA=
-----END CERTIFICATE-----

Wells Fargo Root CA
===================
-----BEGIN CERTIFICATE-----
MIID5TCCAs2gAwIBAgIEOeSXnjANBgkqhkiG9w0BAQUFADCBgjELMAkGA1UEBhMCVVMxFDASBgNV
BAoTC1dlbGxzIEZhcmdvMSwwKgYDVQQLEyNXZWxscyBGYXJnbyBDZXJ0aWZpY2F0aW9uIEF1dGhv
cml0eTEvMC0GA1UEAxMmV2VsbHMgRmFyZ28gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcN
MDAxMDExMTY0MTI4WhcNMjEwMTE0MTY0MTI4WjCBgjELMAkGA1UEBhMCVVMxFDASBgNVBAoTC1dl
bGxzIEZhcmdvMSwwKgYDVQQLEyNXZWxscyBGYXJnbyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEv
MC0GA1UEAxMmV2VsbHMgRmFyZ28gUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVqDM7Jvk0/82bfuUER84A4n135zHCLielTWi5MbqNQ1mX
x3Oqfz1cQJ4F5aHiidlMuD+b+Qy0yGIZLEWukR5zcUHESxP9cMIlrCL1dQu3U+SlK93OvRw6esP3
E48mVJwWa2uv+9iWsWCaSOAlIiR5NM4OJgALTqv9i86C1y8IcGjBqAr5dE8Hq6T54oN+J3N0Prj5
OEL8pahbSCOz6+MlsoCultQKnMJ4msZoGK43YjdeUXWoWGPAUe5AeH6orxqg4bB4nVCMe+ez/I4j
sNtlAHCEAQgAFG5Uhpq6zPk3EPbg3oQtnaSFN9OH4xXQwReQfhkhahKpdv0SAulPIV4XAgMBAAGj
YTBfMA8GA1UdEwEB/wQFMAMBAf8wTAYDVR0gBEUwQzBBBgtghkgBhvt7hwcBCzAyMDAGCCsGAQUF
BwIBFiRodHRwOi8vd3d3LndlbGxzZmFyZ28uY29tL2NlcnRwb2xpY3kwDQYJKoZIhvcNAQEFBQAD
ggEBANIn3ZwKdyu7IvICtUpKkfnRLb7kuxpo7w6kAOnu5+/u9vnldKTC2FJYxHT7zmu1Oyl5GFrv
m+0fazbuSCUlFLZWohDo7qd/0D+j0MNdJu4HzMPBJCGHHt8qElNvQRbn7a6U+oxy+hNH8Dx+rn0R
OhPs7fpvcmR7nX1/Jv16+yWt6j4pf0zjAFcysLPp7VMX2YuyFA4w6OXVE8Zkr8QA1dhYJPz1j+zx
x32l2w8n0cbyQIjmH/ZhqPRCyLk306m+LFZ4wnKbWV01QIroTmMatukgalHizqSQ33ZwmVxwQ023
tqcZZE6St8WRPH9IFmV7Fv3L/PvZ1dZPIWU7Sn9Ho/s=
-----END CERTIFICATE-----

Swisscom Root CA 1
==================
-----BEGIN CERTIFICATE-----
MIIF2TCCA8GgAwIBAgIQXAuFXAvnWUHfV8w/f52oNjANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQG
EwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2VydGlmaWNhdGUgU2Vy
dmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3QgQ0EgMTAeFw0wNTA4MTgxMjA2MjBaFw0yNTA4
MTgyMjA2MjBaMGQxCzAJBgNVBAYTAmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGln
aXRhbCBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAxMIIC
IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0LmwqAzZuz8h+BvVM5OAFmUgdbI9m2BtRsiM
MW8Xw/qabFbtPMWRV8PNq5ZJkCoZSx6jbVfd8StiKHVFXqrWW/oLJdihFvkcxC7mlSpnzNApbjyF
NDhhSbEAn9Y6cV9Nbc5fuankiX9qUvrKm/LcqfmdmUc/TilftKaNXXsLmREDA/7n29uj/x2lzZAe
AR81sH8A25Bvxn570e56eqeqDFdvpG3FEzuwpdntMhy0XmeLVNxzh+XTF3xmUHJd1BpYwdnP2IkC
b6dJtDZd0KTeByy2dbcokdaXvij1mB7qWybJvbCXc9qukSbraMH5ORXWZ0sKbU/Lz7DkQnGMU3nn
7uHbHaBuHYwadzVcFh4rUx80i9Fs/PJnB3r1re3WmquhsUvhzDdf/X/NTa64H5xD+SpYVUNFvJbN
cA78yeNmuk6NO4HLFWR7uZToXTNShXEuT46iBhFRyePLoW4xCGQMwtI89Tbo19AOeCMgkckkKmUp
WyL3Ic6DXqTz3kvTaI9GdVyDCW4pa8RwjPWd1yAv/0bSKzjCL3UcPX7ape8eYIVpQtPM+GP+HkM5
haa2Y0EQs3MevNP6yn0WR+Kn1dCjigoIlmJWbjTb2QK5MHXjBNLnj8KwEUAKrNVxAmKLMb7dxiNY
MUJDLXT5xp6mig/p/r+D5kNXJLrvRjSq1xIBOO0CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYw
HQYDVR0hBBYwFDASBgdghXQBUwABBgdghXQBUwABMBIGA1UdEwEB/wQIMAYBAf8CAQcwHwYDVR0j
BBgwFoAUAyUv3m+CATpcLNwroWm1Z9SM0/0wHQYDVR0OBBYEFAMlL95vggE6XCzcK6FptWfUjNP9
MA0GCSqGSIb3DQEBBQUAA4ICAQA1EMvspgQNDQ/NwNurqPKIlwzfky9NfEBWMXrrpA9gzXrzvsMn
jgM+pN0S734edAY8PzHyHHuRMSG08NBsl9Tpl7IkVh5WwzW9iAUPWxAaZOHHgjD5Mq2eUCzneAXQ
MbFamIp1TpBcahQq4FJHgmDmHtqBsfsUC1rxn9KVuj7QG9YVHaO+htXbD8BJZLsuUBlL0iT43R4H
VtA4oJVwIHaM190e3p9xxCPvgxNcoyQVTSlAPGrEqdi3pkSlDfTgnXceQHAm/NrZNuR55LU/vJtl
vrsRls/bxig5OgjOR1tTWsWZ/l2p3e9M1MalrQLmjAcSHm8D0W+go/MpvRLHUKKwf4ipmXeascCl
OS5cfGniLLDqN2qk4Vrh9VDlg++luyqI54zb/W1elxmofmZ1a3Hqv7HHb6D0jqTsNFFbjCYDcKF3
1QESVwA12yPeDooomf2xEG9L/zgtYE4snOtnta1J7ksfrK/7DZBaZmBwXarNeNQk7shBoJMBkpxq
nvy5JMWzFYJ+vq6VK+uxwNrjAWALXmmshFZhvnEX/h0TD/7Gh0Xp/jKgGg0TpJRVcaUWi7rKibCy
x/yP2FS1k2Kdzs9Z+z0YzirLNRWCXf9UIltxUvu3yf5gmwBBZPCqKuy2QkPOiWaByIufOVQDJdMW
NY6E0F/6MBr1mmz0DlP5OlvRHA==
-----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-----

Certplus Class 2 Primary CA
===========================
-----BEGIN CERTIFICATE-----
MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAwPTELMAkGA1UE
BhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFzcyAyIFByaW1hcnkgQ0EwHhcN
OTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2Vy
dHBsdXMxGzAZBgNVBAMTEkNsYXNzIDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBANxQltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR
5aiRVhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyLkcAbmXuZ
Vg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCdEgETjdyAYveVqUSISnFO
YFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yasH7WLO7dDWWuwJKZtkIvEcupdM5i3y95e
e++U8Rs+yskhwcWYAqqi9lt3m/V+llU0HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRME
CDAGAQH/AgEKMAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJ
YIZIAYb4QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMuY29t
L0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/AN9WM2K191EBkOvD
P9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8yfFC82x/xXp8HVGIutIKPidd3i1R
TtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMRFcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+
7UCmnYR0ObncHoUW2ikbhiMAybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW
//1IMwrh3KWBkJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7
l7+ijrRU
-----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-----

DST ACES CA X6
==============
-----BEGIN CERTIFICATE-----
MIIECTCCAvGgAwIBAgIQDV6ZCtadt3js2AdWO4YV2TANBgkqhkiG9w0BAQUFADBbMQswCQYDVQQG
EwJVUzEgMB4GA1UEChMXRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QxETAPBgNVBAsTCERTVCBBQ0VT
MRcwFQYDVQQDEw5EU1QgQUNFUyBDQSBYNjAeFw0wMzExMjAyMTE5NThaFw0xNzExMjAyMTE5NTha
MFsxCzAJBgNVBAYTAlVTMSAwHgYDVQQKExdEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdDERMA8GA1UE
CxMIRFNUIEFDRVMxFzAVBgNVBAMTDkRTVCBBQ0VTIENBIFg2MIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAuT31LMmU3HWKlV1j6IR3dma5WZFcRt2SPp/5DgO0PWGSvSMmtWPuktKe1jzI
DZBfZIGxqAgNTNj50wUoUrQBJcWVHAx+PhCEdc/BGZFjz+iokYi5Q1K7gLFViYsx+tC3dr5BPTCa
pCIlF3PoHuLTrCq9Wzgh1SpL11V94zpVvddtawJXa+ZHfAjIgrrep4c9oW24MFbCswKBXy314pow
GCi4ZtPLAZZv6opFVdbgnf9nKxcCpk4aahELfrd755jWjHZvwTvbUJN+5dCOHze4vbrGn2zpfDPy
MjwmR/onJALJfh1biEITajV8fTXpLmaRcpPVMibEdPVTo7NdmvYJywIDAQABo4HIMIHFMA8GA1Ud
EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgHGMB8GA1UdEQQYMBaBFHBraS1vcHNAdHJ1c3Rkc3Qu
Y29tMGIGA1UdIARbMFkwVwYKYIZIAWUDAgEBATBJMEcGCCsGAQUFBwIBFjtodHRwOi8vd3d3LnRy
dXN0ZHN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L0FDRVMtaW5kZXguaHRtbDAdBgNVHQ4EFgQU
CXIGThhDD+XWzMNqizF7eI+og7gwDQYJKoZIhvcNAQEFBQADggEBAKPYjtay284F5zLNAdMEA+V2
5FYrnJmQ6AgwbN99Pe7lv7UkQIRJ4dEorsTCOlMwiPH1d25Ryvr/ma8kXxug/fKshMrfqfBfBC6t
Fr8hlxCBPeP/h40y3JTlR4peahPJlJU90u7INJXQgNStMgiAVDzgvVJT11J8smk/f3rPanTK+gQq
nExaBqXpIK1FZg9p8d2/6eMyi/rgwYZNcjwu2JN4Cir42NInPRmJX1p7ijvMDNpRrscL9yuwNwXs
vFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf29w4LTJxoeHtxMcfrHuBnQfO3
oKfN5XozNmr6mis=
-----END CERTIFICATE-----

TURKTRUST Certificate Services Provider Root 1
==============================================
-----BEGIN CERTIFICATE-----
MIID+zCCAuOgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBtzE/MD0GA1UEAww2VMOcUktUUlVTVCBF
bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGDAJUUjEP
MA0GA1UEBwwGQU5LQVJBMVYwVAYDVQQKDE0oYykgMjAwNSBUw5xSS1RSVVNUIEJpbGdpIMSwbGV0
acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjAeFw0wNTA1MTMx
MDI3MTdaFw0xNTAzMjIxMDI3MTdaMIG3MT8wPQYDVQQDDDZUw5xSS1RSVVNUIEVsZWt0cm9uaWsg
U2VydGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLExCzAJBgNVBAYMAlRSMQ8wDQYDVQQHDAZB
TktBUkExVjBUBgNVBAoMTShjKSAyMDA1IFTDnFJLVFJVU1QgQmlsZ2kgxLBsZXRpxZ9pbSB2ZSBC
aWxpxZ9pbSBHw7x2ZW5sacSfaSBIaXptZXRsZXJpIEEuxZ4uMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAylIF1mMD2Bxf3dJ7XfIMYGFbazt0K3gNfUW9InTojAPBxhEqPZW8qZSwu5GX
yGl8hMW0kWxsE2qkVa2kheiVfrMArwDCBRj1cJ02i67L5BuBf5OI+2pVu32Fks66WJ/bMsW9Xe8i
Si9BB35JYbOG7E6mQW6EvAPs9TscyB/C7qju6hJKjRTP8wrgUDn5CDX4EVmt5yLqS8oUBt5CurKZ
8y1UiBAG6uEaPj1nH/vO+3yC6BFdSsG5FOpU2WabfIl9BJpiyelSPJ6c79L1JuTm5Rh8i27fbMx4
W09ysstcP4wFjdFMjK2Sx+F4f2VsSQZQLJ4ywtdKxnWKWU51b0dewQIDAQABoxAwDjAMBgNVHRME
BTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAV9VX/N5aAWSGk/KEVTCD21F/aAyT8z5Aa9CEKmu46
sWrv7/hg0Uw2ZkUd82YCdAR7kjCo3gp2D++Vbr3JN+YaDayJSFvMgzbC9UZcWYJWtNX+I7TYVBxE
q8Sn5RTOPEFhfEPmzcSBCYsk+1Ql1haolgxnB2+zUEfjHCQo3SqYpGH+2+oSN7wBGjSFvW5P55Fy
B0SFHljKVETd96y5y4khctuPwGkplyqjrhgjlxxBKot8KsF8kOipKMDTkcatKIdAaLX/7KfS0zgY
nNN9aV3wxqUeJBujR/xpB2jn5Jq07Q+hh4cCzofSSE7hvP/L8XKSRGQDJereW26fyfJOrN3H
-----END CERTIFICATE-----

TURKTRUST Certificate Services Provider Root 2
==============================================
-----BEGIN CERTIFICATE-----
MIIEPDCCAySgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBF
bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEP
MA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUg
QmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwHhcN
MDUxMTA3MTAwNzU3WhcNMTUwOTE2MTAwNzU3WjCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBFbGVr
dHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJUUjEPMA0G
A1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmls
acWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpNn7DkUNMwxmYCMjHWHtPFoylzkkBH3MOrHUTpvqe
LCDe2JAOCtFp0if7qnefJ1Il4std2NiDUBd9irWCPwSOtNXwSadktx4uXyCcUHVPr+G1QRT0mJKI
x+XlZEdhR3n9wFHxwZnn3M5q+6+1ATDcRhzviuyV79z/rxAc653YsKpqhRgNF8k+v/Gb0AmJQv2g
QrSdiVFVKc8bcLyEVK3BEx+Y9C52YItdP5qtygy/p1Zbj3e41Z55SZI/4PGXJHpsmxcPbe9TmJEr
5A++WXkHeLuXlfSfadRYhwqp48y2WBmfJiGxxFmNskF1wK1pzpwACPI2/z7woQ8arBT9pmAPAgMB
AAGjQzBBMB0GA1UdDgQWBBTZN7NOBf3Zz58SFq62iS/rJTqIHDAPBgNVHQ8BAf8EBQMDBwYAMA8G
A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHJglrfJ3NgpXiOFX7KzLXb7iNcX/ntt
Rbj2hWyfIvwqECLsqrkw9qtY1jkQMZkpAL2JZkH7dN6RwRgLn7Vhy506vvWolKMiVW4XSf/SKfE4
Jl3vpao6+XF75tpYHdN0wgH6PmlYX63LaL4ULptswLbcoCb6dxriJNoaN+BnrdFzgw2lGh1uEpJ+
hGIAF728JRhX8tepb1mIvDS3LoV4nZbcFMMsilKbloxSZj2GFotHuFEJjOp9zYhys2AzsfAKRO8P
9Qk3iCQOLGsgOqL6EfJANZxEaGM7rDNvY7wsu/LSy3Z9fYjYHcgFHW68lKlmjHdxx/qR+i9Rnuk5
UrbnBEI=
-----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-----

GeoTrust Primary Certification Authority
========================================
-----BEGIN CERTIFICATE-----
MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQG
EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMoR2VvVHJ1c3QgUHJpbWFyeSBD
ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjExMjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgx
CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQ
cmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9AWbK7hWN
b6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjAZIVcFU2Ix7e64HXprQU9
nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE07e9GceBrAqg1cmuXm2bgyxx5X9gaBGge
RwLmnWDiNpcB3841kt++Z8dtd1k7j53WkBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGt
tm/81w7a4DSwDRp35+MImO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJKoZI
hvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ16CePbJC/kRYkRj5K
Ts4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl4b7UVXGYNTq+k+qurUKykG/g/CFN
NWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6KoKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHa
Floxt/m0cYASSJlyc1pZU8FjUjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG
1riR/aYNKxoUAT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk=
-----END CERTIFICATE-----

thawte Primary Root CA
======================
-----BEGIN CERTIFICATE-----
MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCBqTELMAkGA1UE
BhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2
aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhv
cml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3
MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwg
SW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMv
KGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNVBAMT
FnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCs
oPD7gFnUnMekz52hWXMJEEUMDSxuaPFsW0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ
1CRfBsDMRJSUjQJib+ta3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGc
q/gcfomk6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6Sk/K
aAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94JNqR32HuHUETVPm4p
afs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD
VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XPr87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUF
AAOCAQEAeRHAS7ORtvzw6WfUDW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeE
uzLlQRHAd9mzYJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX
xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2/qxAeeWsEG89
jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/LHbTY5xZ3Y+m4Q6gLkH3LpVH
z7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7jVaMaA==
-----END CERTIFICATE-----

VeriSign Class 3 Public Primary Certification Authority - G5
============================================================
-----BEGIN CERTIFICATE-----
MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCByjELMAkGA1UE
BhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBO
ZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVk
IHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRp
ZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCB
yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2ln
biBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2lnbiwgSW5jLiAtIEZvciBh
dXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmlt
YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
ggEKAoIBAQCvJAgIKXo1nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKz
j/i5Vbext0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIzSdhD
Y2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQGBO+QueQA5N06tRn/
Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+rCpSx4/VBEnkjWNHiDxpg8v+R70r
fk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/
BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2Uv
Z2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy
aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKvMzEzMA0GCSqG
SIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzEp6B4Eq1iDkVwZMXnl2YtmAl+
X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKE
KQsTb47bDN0lAtukixlE0kF6BWlKWE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiC
Km0oHw0LxOXnGiYZ4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vE
ZV8NhnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq
-----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-----

WellsSecure Public Root Certificate Authority
=============================================
-----BEGIN CERTIFICATE-----
MIIEvTCCA6WgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoM
F1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYw
NAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcN
MDcxMjEzMTcwNzU0WhcNMjIxMjE0MDAwNzU0WjCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dl
bGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYD
VQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDub7S9eeKPCCGeOARBJe+rWxxTkqxtnt3CxC5FlAM1
iGd0V+PfjLindo8796jE2yljDpFoNoqXjopxaAkH5OjUDk/41itMpBb570OYj7OeUt9tkTmPOL13
i0Nj67eT/DBMHAGTthP796EfvyXhdDcsHqRePGj4S78NuR4uNuip5Kf4D8uCdXw1LSLWwr8L87T8
bJVhHlfXBIEyg1J55oNjz7fLY4sR4r1e6/aN7ZVyKLSsEmLpSjPmgzKuBXWVvYSV2ypcm44uDLiB
K0HmOFafSZtsdvqKXfcBeYF8wYNABf5x/Qw/zE5gCQ5lRxAvAcAFP4/4s0HvWkJ+We/SlwxlAgMB
AAGjggE0MIIBMDAPBgNVHRMBAf8EBTADAQH/MDkGA1UdHwQyMDAwLqAsoCqGKGh0dHA6Ly9jcmwu
cGtpLndlbGxzZmFyZ28uY29tL3dzcHJjYS5jcmwwDgYDVR0PAQH/BAQDAgHGMB0GA1UdDgQWBBQm
lRkQ2eihl5H/3BnZtQQ+0nMKajCBsgYDVR0jBIGqMIGngBQmlRkQ2eihl5H/3BnZtQQ+0nMKaqGB
i6SBiDCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRww
GgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMg
Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCAQEwDQYJKoZIhvcNAQEFBQADggEBALkVsUSRzCPI
K0134/iaeycNzXK7mQDKfGYZUMbVmO2rvwNa5U3lHshPcZeG1eMd/ZDJPHV3V3p9+N701NX3leZ0
bh08rnyd2wIDBSxxSyU+B+NemvVmFymIGjifz6pBA4SXa5M4esowRBskRDPQ5NHcKDj0E0M1NSlj
qHyita04pO2t/caaH/+Xc/77szWnk4bGdpEA5qxRFsQnMlzbc9qlk1eOPm01JghZ1edE13YgY+es
E2fDbbFwRnzVlhE9iW9dqKHrjQrawx0zbKPqZxmamX9LPYNRKh3KL4YMon4QLSvUFpULB6ouFJJJ
tylv2G0xffX8oRAHh84vWdw+WNs=
-----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-----

IGC/A
=====
-----BEGIN CERTIFICATE-----
MIIEAjCCAuqgAwIBAgIFORFFEJQwDQYJKoZIhvcNAQEFBQAwgYUxCzAJBgNVBAYTAkZSMQ8wDQYD
VQQIEwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVE
Q1NTSTEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZy
MB4XDTAyMTIxMzE0MjkyM1oXDTIwMTAxNzE0MjkyMlowgYUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQI
EwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVEQ1NT
STEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZyMIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsh/R0GLFMzvABIaIs9z4iPf930Pfeo2aSVz2
TqrMHLmh6yeJ8kbpO0px1R2OLc/mratjUMdUC24SyZA2xtgv2pGqaMVy/hcKshd+ebUyiHDKcMCW
So7kVc0dJ5S/znIq7Fz5cyD+vfcuiWe4u0dzEvfRNWk68gq5rv9GQkaiv6GFGvm/5P9JhfejcIYy
HF2fYPepraX/z9E0+X1bF8bc1g4oa8Ld8fUzaJ1O/Id8NhLWo4DoQw1VYZTqZDdH6nfK0LJYBcNd
frGoRpAxVs5wKpayMLh35nnAvSk7/ZR3TL0gzUEl4C7HG7vupARB0l2tEmqKm0f7yd1GQOGdPDPQ
tQIDAQABo3cwdTAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBRjAVBgNVHSAEDjAMMAoGCCqB
egF5AQEBMB0GA1UdDgQWBBSjBS8YYFDCiQrdKyFP/45OqDAxNjAfBgNVHSMEGDAWgBSjBS8YYFDC
iQrdKyFP/45OqDAxNjANBgkqhkiG9w0BAQUFAAOCAQEABdwm2Pp3FURo/C9mOnTgXeQp/wYHE4RK
q89toB9RlPhJy3Q2FLwV3duJL92PoF189RLrn544pEfMs5bZvpwlqwN+Mw+VgQ39FuCIvjfwbF3Q
MZsyK10XZZOYYLxuj7GoPB7ZHPOpJkL5ZB3C55L29B5aqhlSXa/oovdgoPaN8In1buAKBQGVyYsg
Crpa/JosPL3Dt8ldeCUFP1YUmwza+zpI/pdpXsoQhvdOlgQITeywvl3cO45Pwf2aNjSaTFR+FwNI
lQgRHAdvhQh+XU3Endv7rs6y0bO4g2wdsrN58dhwmX7wEwLOXt1R0982gaEbeC9xs/FZTEYYKKuF
0mBWWg==
-----END CERTIFICATE-----

Security Communication EV RootCA1
=================================
-----BEGIN CERTIFICATE-----
MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDElMCMGA1UEChMc
U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMhU2VjdXJpdHkgQ29tbXVuaWNh
dGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIzMloXDTM3MDYwNjAyMTIzMlowYDELMAkGA1UE
BhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKjAoBgNVBAsTIVNl
Y3VyaXR5IENvbW11bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1OXj/l3X3L+SqawSERMqm4miO
/VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1V4qe70gOzXppFodEtZDkBp2uoQSX
WHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4z
ZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDFMxRldAD5kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4
bepJz11sS6/vmsJWXMY1VkJqMF/Cq/biPT+zyRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK
9U2vP9eCOKyrcWUXdYydVZPmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG
SIb3DQEBBQUAA4IBAQCoh+ns+EBnXcPBZsdAS5f8hxOQWsTvoMpfi7ent/HWtWS3irO4G8za+6xm
iEHO6Pzk2x6Ipu0nUBsCMCRGef4Eh3CXQHPRwMFXGZpppSeZq51ihPZRwSzJIxXYKLerJRO1RuGG
Av8mjMSIkh1W/hln8lXkgKNrnKt34VFxDSDbEJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnW
mHyojf6GPgcWkuF75x3sM3Z+Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEW
T1MKZPlO9L9OVL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490
-----END CERTIFICATE-----

OISTE WISeKey Global Root GA CA
===============================
-----BEGIN CERTIFICATE-----
MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCBijELMAkGA1UE
BhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHlyaWdodCAoYykgMjAwNTEiMCAG
A1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBH
bG9iYWwgUm9vdCBHQSBDQTAeFw0wNTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYD
VQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIw
IAYDVQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5
IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy0+zAJs9
Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxRVVuuk+g3/ytr6dTqvirdqFEr12bDYVxg
Asj1znJ7O7jyTmUIms2kahnBAbtzptf2w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbD
d50kc3vkDIzh2TbhmYsFmQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ
/yxViJGg4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t94B3R
LoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw
AwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQwEAYJKwYBBAGCNxUBBAMCAQAwDQYJ
KoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOxSPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vIm
MMkQyh2I+3QZH4VFvbBsUfk2ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4
+vg1YFkCExh8vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa
hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZiFj4A4xylNoEY
okxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ/L7fCg0=
-----END CERTIFICATE-----

Microsec e-Szigno Root CA
=========================
-----BEGIN CERTIFICATE-----
MIIHqDCCBpCgAwIBAgIRAMy4579OKRr9otxmpRwsDxEwDQYJKoZIhvcNAQEFBQAwcjELMAkGA1UE
BhMCSFUxETAPBgNVBAcTCEJ1ZGFwZXN0MRYwFAYDVQQKEw1NaWNyb3NlYyBMdGQuMRQwEgYDVQQL
EwtlLVN6aWdubyBDQTEiMCAGA1UEAxMZTWljcm9zZWMgZS1Temlnbm8gUm9vdCBDQTAeFw0wNTA0
MDYxMjI4NDRaFw0xNzA0MDYxMjI4NDRaMHIxCzAJBgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVz
dDEWMBQGA1UEChMNTWljcm9zZWMgTHRkLjEUMBIGA1UECxMLZS1Temlnbm8gQ0ExIjAgBgNVBAMT
GU1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQDtyADVgXvNOABHzNuEwSFpLHSQDCHZU4ftPkNEU6+r+ICbPHiN1I2uuO/TEdyB5s87lozWbxXG
d36hL+BfkrYn13aaHUM86tnsL+4582pnS4uCzyL4ZVX+LMsvfUh6PXX5qqAnu3jCBspRwn5mS6/N
oqdNAoI/gqyFxuEPkEeZlApxcpMqyabAvjxWTHOSJ/FrtfX9/DAFYJLG65Z+AZHCabEeHXtTRbjc
QR/Ji3HWVBTji1R4P770Yjtb9aPs1ZJ04nQw7wHb4dSrmZsqa/i9phyGI0Jf7Enemotb9HI6QMVJ
PqW+jqpx62z69Rrkav17fVVA71hu5tnVvCSrwe+3AgMBAAGjggQ3MIIEMzBnBggrBgEFBQcBAQRb
MFkwKAYIKwYBBQUHMAGGHGh0dHBzOi8vcmNhLmUtc3ppZ25vLmh1L29jc3AwLQYIKwYBBQUHMAKG
IWh0dHA6Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNydDAPBgNVHRMBAf8EBTADAQH/MIIBcwYD
VR0gBIIBajCCAWYwggFiBgwrBgEEAYGoGAIBAQEwggFQMCgGCCsGAQUFBwIBFhxodHRwOi8vd3d3
LmUtc3ppZ25vLmh1L1NaU1ovMIIBIgYIKwYBBQUHAgIwggEUHoIBEABBACAAdABhAG4A+gBzAO0A
dAB2AOEAbgB5ACAA6QByAHQAZQBsAG0AZQB6AOkAcwDpAGgAZQB6ACAA6QBzACAAZQBsAGYAbwBn
AGEAZADhAHMA4QBoAG8AegAgAGEAIABTAHoAbwBsAGcA4QBsAHQAYQB0APMAIABTAHoAbwBsAGcA
4QBsAHQAYQB0AOEAcwBpACAAUwB6AGEAYgDhAGwAeQB6AGEAdABhACAAcwB6AGUAcgBpAG4AdAAg
AGsAZQBsAGwAIABlAGwAagDhAHIAbgBpADoAIABoAHQAdABwADoALwAvAHcAdwB3AC4AZQAtAHMA
egBpAGcAbgBvAC4AaAB1AC8AUwBaAFMAWgAvMIHIBgNVHR8EgcAwgb0wgbqggbeggbSGIWh0dHA6
Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNybIaBjmxkYXA6Ly9sZGFwLmUtc3ppZ25vLmh1L0NO
PU1pY3Jvc2VjJTIwZS1Temlnbm8lMjBSb290JTIwQ0EsT1U9ZS1Temlnbm8lMjBDQSxPPU1pY3Jv
c2VjJTIwTHRkLixMPUJ1ZGFwZXN0LEM9SFU/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdDtiaW5h
cnkwDgYDVR0PAQH/BAQDAgEGMIGWBgNVHREEgY4wgYuBEGluZm9AZS1zemlnbm8uaHWkdzB1MSMw
IQYDVQQDDBpNaWNyb3NlYyBlLVN6aWduw7MgUm9vdCBDQTEWMBQGA1UECwwNZS1TemlnbsOzIEhT
WjEWMBQGA1UEChMNTWljcm9zZWMgS2Z0LjERMA8GA1UEBxMIQnVkYXBlc3QxCzAJBgNVBAYTAkhV
MIGsBgNVHSMEgaQwgaGAFMegSXUWYYTbMUuE0vE3QJDvTtz3oXakdDByMQswCQYDVQQGEwJIVTER
MA8GA1UEBxMIQnVkYXBlc3QxFjAUBgNVBAoTDU1pY3Jvc2VjIEx0ZC4xFDASBgNVBAsTC2UtU3pp
Z25vIENBMSIwIAYDVQQDExlNaWNyb3NlYyBlLVN6aWdubyBSb290IENBghEAzLjnv04pGv2i3Gal
HCwPETAdBgNVHQ4EFgQUx6BJdRZhhNsxS4TS8TdAkO9O3PcwDQYJKoZIhvcNAQEFBQADggEBANMT
nGZjWS7KXHAM/IO8VbH0jgdsZifOwTsgqRy7RlRw7lrMoHfqaEQn6/Ip3Xep1fvj1KcExJW4C+FE
aGAHQzAxQmHl7tnlJNUb3+FKG6qfx1/4ehHqE5MAyopYse7tDk2016g2JnzgOsHVV4Lxdbb9iV/a
86g4nzUGCM4ilb7N1fy+W955a9x6qWVmvrElWl/tftOsRm1M9DKHtCAE4Gx4sHfRhUZLphK3dehK
yVZs15KrnfVJONJPU+NVkBHbmJbGSfI+9J8b4PeI3CVimUTYc78/MPMMNz7UwiiAc7EBt51alhQB
S6kRnSlqLtBdgcDPsiBDxwPgN05dCtxZICU=
-----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-----

AC Ra\xC3\xADz Certic\xC3\xA1mara S.A.
======================================
-----BEGIN CERTIFICATE-----
MIIGZjCCBE6gAwIBAgIPB35Sk3vgFeNX8GmMy+wMMA0GCSqGSIb3DQEBBQUAMHsxCzAJBgNVBAYT
AkNPMUcwRQYDVQQKDD5Tb2NpZWRhZCBDYW1lcmFsIGRlIENlcnRpZmljYWNpw7NuIERpZ2l0YWwg
LSBDZXJ0aWPDoW1hcmEgUy5BLjEjMCEGA1UEAwwaQUMgUmHDrXogQ2VydGljw6FtYXJhIFMuQS4w
HhcNMDYxMTI3MjA0NjI5WhcNMzAwNDAyMjE0MjAyWjB7MQswCQYDVQQGEwJDTzFHMEUGA1UECgw+
U29jaWVkYWQgQ2FtZXJhbCBkZSBDZXJ0aWZpY2FjacOzbiBEaWdpdGFsIC0gQ2VydGljw6FtYXJh
IFMuQS4xIzAhBgNVBAMMGkFDIFJhw616IENlcnRpY8OhbWFyYSBTLkEuMIICIjANBgkqhkiG9w0B
AQEFAAOCAg8AMIICCgKCAgEAq2uJo1PMSCMI+8PPUZYILrgIem08kBeGqentLhM0R7LQcNzJPNCN
yu5LF6vQhbCnIwTLqKL85XXbQMpiiY9QngE9JlsYhBzLfDe3fezTf3MZsGqy2IiKLUV0qPezuMDU
2s0iiXRNWhU5cxh0T7XrmafBHoi0wpOQY5fzp6cSsgkiBzPZkc0OnB8OIMfuuzONj8LSWKdf/WU3
4ojC2I+GdV75LaeHM/J4Ny+LvB2GNzmxlPLYvEqcgxhaBvzz1NS6jBUJJfD5to0EfhcSM2tXSExP
2yYe68yQ54v5aHxwD6Mq0Do43zeX4lvegGHTgNiRg0JaTASJaBE8rF9ogEHMYELODVoqDA+bMMCm
8Ibbq0nXl21Ii/kDwFJnmxL3wvIumGVC2daa49AZMQyth9VXAnow6IYm+48jilSH5L887uvDdUhf
HjlvgWJsxS3EF1QZtzeNnDeRyPYL1epjb4OsOMLzP96a++EjYfDIJss2yKHzMI+ko6Kh3VOz3vCa
Mh+DkXkwwakfU5tTohVTP92dsxA7SH2JD/ztA/X7JWR1DhcZDY8AFmd5ekD8LVkH2ZD6mq093ICK
5lw1omdMEWux+IBkAC1vImHFrEsm5VoQgpukg3s0956JkSCXjrdCx2bD0Omk1vUgjcTDlaxECp1b
czwmPS9KvqfJpxAe+59QafMCAwEAAaOB5jCB4zAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE
AwIBBjAdBgNVHQ4EFgQU0QnQ6dfOeXRU+Tows/RtLAMDG2gwgaAGA1UdIASBmDCBlTCBkgYEVR0g
ADCBiTArBggrBgEFBQcCARYfaHR0cDovL3d3dy5jZXJ0aWNhbWFyYS5jb20vZHBjLzBaBggrBgEF
BQcCAjBOGkxMaW1pdGFjaW9uZXMgZGUgZ2FyYW507WFzIGRlIGVzdGUgY2VydGlmaWNhZG8gc2Ug
cHVlZGVuIGVuY29udHJhciBlbiBsYSBEUEMuMA0GCSqGSIb3DQEBBQUAA4ICAQBclLW4RZFNjmEf
AygPU3zmpFmps4p6xbD/CHwso3EcIRNnoZUSQDWDg4902zNc8El2CoFS3UnUmjIz75uny3XlesuX
EpBcunvFm9+7OSPI/5jOCk0iAUgHforA1SBClETvv3eiiWdIG0ADBaGJ7M9i4z0ldma/Jre7Ir5v
/zlXdLp6yQGVwZVR6Kss+LGGIOk/yzVb0hfpKv6DExdA7ohiZVvVO2Dpezy4ydV/NgIlqmjCMRW3
MGXrfx1IebHPOeJCgBbT9ZMj/EyXyVo3bHwi2ErN0o42gzmRkBDI8ck1fj+404HGIGQatlDCIaR4
3NAvO2STdPCWkPHv+wlaNECW8DYSwaN0jJN+Qd53i+yG2dIPPy3RzECiiWZIHiCznCNZc6lEc7wk
eZBWN7PGKX6jD/EpOe9+XCgycDWs2rjIdWb8m0w5R44bb5tNAlQiM+9hup4phO9OSzNHdpdqy35f
/RWmnkJDW2ZaiogN9xa5P1FlK2Zqi9E4UqLWRhH6/JocdJ6PlwsCT2TG9WjTSy3/pDceiz+/RL5h
RqGEPQgnTIEgd4kI6mdAXmwIUV80WoyWaM3X94nCHNMyAK9Sy9NgWyo6R35rMDOhYil/SrnhLecU
Iw4OGEfhefwVVdCx/CVxY3UzHCMrr1zZ7Ud3YA47Dx7SwNxkBYn8eNZcLCZDqQ==
-----END CERTIFICATE-----

TC TrustCenter Class 2 CA II
============================
-----BEGIN CERTIFICATE-----
MIIEqjCCA5KgAwIBAgIOLmoAAQACH9dSISwRXDswDQYJKoZIhvcNAQEFBQAwdjELMAkGA1UEBhMC
REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNVBAsTGVRDIFRydXN0Q2VudGVy
IENsYXNzIDIgQ0ExJTAjBgNVBAMTHFRDIFRydXN0Q2VudGVyIENsYXNzIDIgQ0EgSUkwHhcNMDYw
MTEyMTQzODQzWhcNMjUxMjMxMjI1OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1
c3RDZW50ZXIgR21iSDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQTElMCMGA1UE
AxMcVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAKuAh5uO8MN8h9foJIIRszzdQ2Lu+MNF2ujhoF/RKrLqk2jftMjWQ+nEdVl//OEd+DFw
IxuInie5e/060smp6RQvkL4DUsFJzfb95AhmC1eKokKguNV/aVyQMrKXDcpK3EY+AlWJU+MaWss2
xgdW94zPEfRMuzBwBJWl9jmM/XOBCH2JXjIeIqkiRUuwZi4wzJ9l/fzLganx4Duvo4bRierERXlQ
Xa7pIXSSTYtZgo+U4+lK8edJsBTj9WLL1XK9H7nSn6DNqPoByNkN39r8R52zyFTfSUrxIan+GE7u
SNQZu+995OKdy1u2bv/jzVrndIIFuoAlOMvkaZ6vQaoahPUCAwEAAaOCATQwggEwMA8GA1UdEwEB
/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTjq1RMgKHbVkO3kUrL84J6E1wIqzCB
7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRydXN0Y2VudGVyLmRlL2NybC92Mi90
Y19jbGFzc18yX2NhX0lJLmNybIaBn2xkYXA6Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBU
cnVzdENlbnRlciUyMENsYXNzJTIwMiUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21i
SCxPVT1yb290Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u
TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEAjNfffu4bgBCzg/XbEeprS6iSGNn3Bzn1LL4G
dXpoUxUc6krtXvwjshOg0wn/9vYua0Fxec3ibf2uWWuFHbhOIprtZjluS5TmVfwLG4t3wVMTZonZ
KNaL80VKY7f9ewthXbhtvsPcW3nS7Yblok2+XnR8au0WOB9/WIFaGusyiC2y8zl3gK9etmF1Kdsj
TYjKUCjLhdLTEKJZbtOTVAB6okaVhgWcqRmY5TFyDADiZ9lA4CQze28suVyrZZ0srHbqNZn1l7kP
JOzHdiEoZa5X6AeIdUpWoNIFOqTmjZKILPPy4cHGYdtBxceb9w4aUUXCYWvcZCcXjFq32nQozZfk
vQ==
-----END CERTIFICATE-----

TC TrustCenter Class 3 CA II
============================
-----BEGIN CERTIFICATE-----
MIIEqjCCA5KgAwIBAgIOSkcAAQAC5aBd1j8AUb8wDQYJKoZIhvcNAQEFBQAwdjELMAkGA1UEBhMC
REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNVBAsTGVRDIFRydXN0Q2VudGVy
IENsYXNzIDMgQ0ExJTAjBgNVBAMTHFRDIFRydXN0Q2VudGVyIENsYXNzIDMgQ0EgSUkwHhcNMDYw
MTEyMTQ0MTU3WhcNMjUxMjMxMjI1OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1
c3RDZW50ZXIgR21iSDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQTElMCMGA1UE
AxMcVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBALTgu1G7OVyLBMVMeRwjhjEQY0NVJz/GRcekPewJDRoeIMJWHt4bNwcwIi9v8Qbxq63W
yKthoy9DxLCyLfzDlml7forkzMA5EpBCYMnMNWju2l+QVl/NHE1bWEnrDgFPZPosPIlY2C8u4rBo
6SI7dYnWRBpl8huXJh0obazovVkdKyT21oQDZogkAHhg8fir/gKya/si+zXmFtGt9i4S5Po1auUZ
uV3bOx4a+9P/FRQI2AlqukWdFHlgfa9Aigdzs5OW03Q0jTo3Kd5c7PXuLjHCINy+8U9/I1LZW+Jk
2ZyqBwi1Rb3R0DHBq1SfqdLDYmAD8bs5SpJKPQq5ncWg/jcCAwEAAaOCATQwggEwMA8GA1UdEwEB
/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTUovyfs8PYA9NXXAek0CSnwPIA1DCB
7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRydXN0Y2VudGVyLmRlL2NybC92Mi90
Y19jbGFzc18zX2NhX0lJLmNybIaBn2xkYXA6Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBU
cnVzdENlbnRlciUyMENsYXNzJTIwMyUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21i
SCxPVT1yb290Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u
TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEANmDkcPcGIEPZIxpC8vijsrlNirTzwppVMXzE
O2eatN9NDoqTSheLG43KieHPOh6sHfGcMrSOWXaiQYUlN6AT0PV8TtXqluJucsG7Kv5sbviRmEb8
yRtXW+rIGjs/sFGYPAfaLFkB2otE6OF0/ado3VS6g0bsyEa1+K+XwDsJHI/OcpY9M1ZwvJbL2NV9
IJqDnxrcOfHFcqMRA/07QlIp2+gB95tejNaNhk4Z+rwcvsUhpYeeeC422wlxo3I0+GzjBgnyXlal
092Y+tTmBvTwtiBjS+opvaqCZh77gaqnN60TGOaSw4HBM7uIHqHn4rS9MWwOUT1v+5ZWgOI2F9Hc
5A==
-----END CERTIFICATE-----

TC TrustCenter Universal CA I
=============================
-----BEGIN CERTIFICATE-----
MIID3TCCAsWgAwIBAgIOHaIAAQAC7LdggHiNtgYwDQYJKoZIhvcNAQEFBQAweTELMAkGA1UEBhMC
REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNVBAsTG1RDIFRydXN0Q2VudGVy
IFVuaXZlcnNhbCBDQTEmMCQGA1UEAxMdVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBIEkwHhcN
MDYwMzIyMTU1NDI4WhcNMjUxMjMxMjI1OTU5WjB5MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMg
VHJ1c3RDZW50ZXIgR21iSDEkMCIGA1UECxMbVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBMSYw
JAYDVQQDEx1UQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0EgSTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAKR3I5ZEr5D0MacQ9CaHnPM42Q9e3s9B6DGtxnSRJJZ4Hgmgm5qVSkr1YnwC
qMqs+1oEdjneX/H5s7/zA1hV0qq34wQi0fiU2iIIAI3TfCZdzHd55yx4Oagmcw6iXSVphU9VDprv
xrlE4Vc93x9UIuVvZaozhDrzznq+VZeujRIPFDPiUHDDSYcTvFHe15gSWu86gzOSBnWLknwSaHtw
ag+1m7Z3W0hZneTvWq3zwZ7U10VOylY0Ibw+F1tvdwxIAUMpsN0/lm7mlaoMwCC2/T42J5zjXM9O
gdwZu5GQfezmlwQek8wiSdeXhrYTCjxDI3d+8NzmzSQfO4ObNDqDNOMCAwEAAaNjMGEwHwYDVR0j
BBgwFoAUkqR1LKSevoFE63n8isWVpesQdXMwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
AYYwHQYDVR0OBBYEFJKkdSyknr6BROt5/IrFlaXrEHVzMA0GCSqGSIb3DQEBBQUAA4IBAQAo0uCG
1eb4e/CX3CJrO5UUVg8RMKWaTzqwOuAGy2X17caXJ/4l8lfmXpWMPmRgFVp/Lw0BxbFg/UU1z/Cy
vwbZ71q+s2IhtNerNXxTPqYn8aEt2hojnczd7Dwtnic0XQ/CNnm8yUpiLe1r2X1BQ3y2qsrtYbE3
ghUJGooWMNjsydZHcnhLEEYUjl8Or+zHL6sQ17bxbuyGssLoDZJz3KL0Dzq/YSMQiZxIQG5wALPT
ujdEWBF6AmqI8Dc08BnprNRlc/ZpjGSUOnmFKbAWKwyCPwacx/0QK54PLLae4xW/2TYcuiUaUj0a
7CIMHOCkoj3w6DnPgcB77V0fb8XQC9eY
-----END CERTIFICATE-----

Deutsche Telekom Root CA 2
==========================
-----BEGIN CERTIFICATE-----
MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMT
RGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEG
A1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENBIDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5
MjM1OTAwWjBxMQswCQYDVQQGEwJERTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0G
A1UECxMWVC1UZWxlU2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBS
b290IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEUha88EOQ5
bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhCQN/Po7qCWWqSG6wcmtoI
KyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1MjwrrFDa1sPeg5TKqAyZMg4ISFZbavva4VhY
AUlfckE8FQYBjl2tqriTtM2e66foai1SNNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aK
Se5TBY8ZTNXeWHmb0mocQqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTV
jlsB9WoHtxa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAPBgNV
HRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAlGRZrTlk5ynr
E/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756AbrsptJh6sTtU6zkXR34ajgv8HzFZMQSy
zhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpaIzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8
rZ7/gFnkm0W09juwzTkZmDLl6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4G
dyd1Lx+4ivn+xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU
Cm26OWMohpLzGITY+9HPBVZkVw==
-----END CERTIFICATE-----

ComSign Secured CA
==================
-----BEGIN CERTIFICATE-----
MIIDqzCCApOgAwIBAgIRAMcoRwmzuGxFjB36JPU2TukwDQYJKoZIhvcNAQEFBQAwPDEbMBkGA1UE
AxMSQ29tU2lnbiBTZWN1cmVkIENBMRAwDgYDVQQKEwdDb21TaWduMQswCQYDVQQGEwJJTDAeFw0w
NDAzMjQxMTM3MjBaFw0yOTAzMTYxNTA0NTZaMDwxGzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBD
QTEQMA4GA1UEChMHQ29tU2lnbjELMAkGA1UEBhMCSUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
ggEKAoIBAQDGtWhfHZQVw6QIVS3joFd67+l0Kru5fFdJGhFeTymHDEjWaueP1H5XJLkGieQcPOqs
49ohgHMhCu95mGwfCP+hUH3ymBvJVG8+pSjsIQQPRbsHPaHA+iqYHU4Gk/v1iDurX8sWv+bznkqH
7Rnqwp9D5PGBpX8QTz7RSmKtUxvLg/8HZaWSLWapW7ha9B20IZFKF3ueMv5WJDmyVIRD9YTC2LxB
kMyd1mja6YJQqTtoz7VdApRgFrFD2UNd3V2Hbuq7s8lr9gOUCXDeFhF6K+h2j0kQmHe5Y1yLM5d1
9guMsqtb3nQgJT/j8xH5h2iGNXHDHYwt6+UarA9z1YJZQIDTAgMBAAGjgacwgaQwDAYDVR0TBAUw
AwEB/zBEBgNVHR8EPTA7MDmgN6A1hjNodHRwOi8vZmVkaXIuY29tc2lnbi5jby5pbC9jcmwvQ29t
U2lnblNlY3VyZWRDQS5jcmwwDgYDVR0PAQH/BAQDAgGGMB8GA1UdIwQYMBaAFMFL7XC29z58ADsA
j8c+DkWfHl3sMB0GA1UdDgQWBBTBS+1wtvc+fAA7AI/HPg5Fnx5d7DANBgkqhkiG9w0BAQUFAAOC
AQEAFs/ukhNQq3sUnjO2QiBq1BW9Cav8cujvR3qQrFHBZE7piL1DRYHjZiM/EoZNGeQFsOY3wo3a
BijJD4mkU6l1P7CW+6tMM1X5eCZGbxs2mPtCdsGCuY7e+0X5YxtiOzkGynd6qDwJz2w2PQ8KRUtp
FhpFfTMDZflScZAmlaxMDPWLkz/MdXSFmLr/YnpNH4n+rr2UAJm/EaXc4HnFFgt9AmEd6oX5AhVP
51qJThRv4zdLhfXBPGHg/QVBspJ/wx2g0K5SZGBrGMYmnNj1ZOQ2GmKfig8+/21OGVZOIJFsnzQz
OjRXUDpvgV4GxvU+fE6OK85lBi5d0ipTdF7Tbieejw==
-----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-----

T\xc3\x9c\x42\xC4\xB0TAK UEKAE K\xC3\xB6k Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1 - S\xC3\xBCr\xC3\xBCm 3
=============================================================================================================================
-----BEGIN CERTIFICATE-----
MIIFFzCCA/+gAwIBAgIBETANBgkqhkiG9w0BAQUFADCCASsxCzAJBgNVBAYTAlRSMRgwFgYDVQQH
DA9HZWJ6ZSAtIEtvY2FlbGkxRzBFBgNVBAoMPlTDvHJraXllIEJpbGltc2VsIHZlIFRla25vbG9q
aWsgQXJhxZ90xLFybWEgS3VydW11IC0gVMOcQsSwVEFLMUgwRgYDVQQLDD9VbHVzYWwgRWxla3Ry
b25payB2ZSBLcmlwdG9sb2ppIEFyYcWfdMSxcm1hIEVuc3RpdMO8c8O8IC0gVUVLQUUxIzAhBgNV
BAsMGkthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppMUowSAYDVQQDDEFUw5xCxLBUQUsgVUVLQUUg
S8O2ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSAtIFPDvHLDvG0gMzAeFw0wNzA4
MjQxMTM3MDdaFw0xNzA4MjExMTM3MDdaMIIBKzELMAkGA1UEBhMCVFIxGDAWBgNVBAcMD0dlYnpl
IC0gS29jYWVsaTFHMEUGA1UECgw+VMO8cmtpeWUgQmlsaW1zZWwgdmUgVGVrbm9sb2ppayBBcmHF
n3TEsXJtYSBLdXJ1bXUgLSBUw5xCxLBUQUsxSDBGBgNVBAsMP1VsdXNhbCBFbGVrdHJvbmlrIHZl
IEtyaXB0b2xvamkgQXJhxZ90xLFybWEgRW5zdGl0w7xzw7wgLSBVRUtBRTEjMCEGA1UECwwaS2Ft
dSBTZXJ0aWZpa2FzeW9uIE1lcmtlemkxSjBIBgNVBAMMQVTDnELEsFRBSyBVRUtBRSBLw7ZrIFNl
cnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIC0gU8O8csO8bSAzMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEAim1L/xCIOsP2fpTo6iBkcK4hgb46ezzb8R1Sf1n68yJMlaCQvEhO
Eav7t7WNeoMojCZG2E6VQIdhn8WebYGHV2yKO7Rm6sxA/OOqbLLLAdsyv9Lrhc+hDVXDWzhXcLh1
xnnRFDDtG1hba+818qEhTsXOfJlfbLm4IpNQp81McGq+agV/E5wrHur+R84EpW+sky58K5+eeROR
6Oqeyjh1jmKwlZMq5d/pXpduIF9fhHpEORlAHLpVK/swsoHvhOPc7Jg4OQOFCKlUAwUp8MmPi+oL
hmUZEdPpCSPeaJMDyTYcIW7OjGbxmTDY17PDHfiBLqi9ggtm/oLL4eAagsNAgQIDAQABo0IwQDAd
BgNVHQ4EFgQUvYiHyY/2pAoLquvF/pEjnatKijIwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF
MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAB18+kmPNOm3JpIWmgV050vQbTlswyb2zrgxvMTfvCr4
N5EY3ATIZJkrGG2AA1nJrvhY0D7twyOfaTyGOBye79oneNGEN3GKPEs5z35FBtYt2IpNeBLWrcLT
y9LQQfMmNkqblWwM7uXRQydmwYj3erMgbOqwaSvHIOgMA8RBBZniP+Rr+KCGgceExh/VS4ESshYh
LBOhgLJeDEoTniDYYkCrkOpkSi+sDQESeUWoL4cZaMjihccwsnX5OD+ywJO0a+IDRM5noN+J1q2M
dqMTw5RhK2vZbMEHCiIHhWyFJEapvj+LeISCfiQMnf2BN+MlqO02TpUsyZyQ2uypQjyttgI=
-----END CERTIFICATE-----

Buypass Class 2 CA 1
====================
-----BEGIN CERTIFICATE-----
MIIDUzCCAjugAwIBAgIBATANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU
QnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3MgQ2xhc3MgMiBDQSAxMB4XDTA2
MTAxMzEwMjUwOVoXDTE2MTAxMzEwMjUwOVowSzELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBh
c3MgQVMtOTgzMTYzMzI3MR0wGwYDVQQDDBRCdXlwYXNzIENsYXNzIDIgQ0EgMTCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAIs8B0XY9t/mx8q6jUPFR42wWsE425KEHK8T1A9vNkYgxC7M
cXA0ojTTNy7Y3Tp3L8DrKehc0rWpkTSHIln+zNvnma+WwajHQN2lFYxuyHyXA8vmIPLXl18xoS83
0r7uvqmtqEyeIWZDO6i88wmjONVZJMHCR3axiFyCO7srpgTXjAePzdVBHfCuuCkslFJgNJQ72uA4
0Z0zPhX0kzLFANq1KWYOOngPIVJfAuWSeyXTkh4vFZ2B5J2O6O+JzhRMVB0cgRJNcKi+EAUXfh/R
uFdV7c27UsKwHnjCTTZoy1YmwVLBvXb3WNVyfh9EdrsAiR0WnVE1703CVu9r4Iw7DekCAwEAAaNC
MEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUP42aWYv8e3uco684sDntkHGA1sgwDgYDVR0P
AQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQAVGn4TirnoB6NLJzKyQJHyIdFkhb5jatLPgcIV
1Xp+DCmsNx4cfHZSldq1fyOhKXdlyTKdqC5Wq2B2zha0jX94wNWZUYN/Xtm+DKhQ7SLHrQVMdvvt
7h5HZPb3J31cKA9FxVxiXqaakZG3Uxcu3K1gnZZkOb1naLKuBctN518fV4bVIJwo+28TOPX2EZL2
fZleHwzoq0QkKXJAPTZSr4xYkHPB7GEseaHsh7U/2k3ZIQAw3pDaDtMaSKk+hQsUi4y8QZ5q9w5w
wDX3OaJdZtB7WZ+oRxKaJyOkLY4ng5IgodcVf/EuGO70SH8vf/GhGLWhC5SgYiAynB321O+/TIho
-----END CERTIFICATE-----

Buypass Class 3 CA 1
====================
-----BEGIN CERTIFICATE-----
MIIDUzCCAjugAwIBAgIBAjANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEdMBsGA1UECgwU
QnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3MgQ2xhc3MgMyBDQSAxMB4XDTA1
MDUwOTE0MTMwM1oXDTE1MDUwOTE0MTMwM1owSzELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBh
c3MgQVMtOTgzMTYzMzI3MR0wGwYDVQQDDBRCdXlwYXNzIENsYXNzIDMgQ0EgMTCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAKSO13TZKWTeXx+HgJHqTjnmGcZEC4DVC69TB4sSveZn8AKx
ifZgisRbsELRwCGoy+Gb72RRtqfPFfV0gGgEkKBYouZ0plNTVUhjP5JW3SROjvi6K//zNIqeKNc0
n6wv1g/xpC+9UrJJhW05NfBEMJNGJPO251P7vGGvqaMU+8IXF4Rs4HyI+MkcVyzwPX6UvCWThOia
AJpFBUJXgPROztmuOfbIUxAMZTpHe2DC1vqRycZxbL2RhzyRhkmr8w+gbCZ2Xhysm3HljbybIR6c
1jh+JIAVMYKWsUnTYjdbiAwKYjT+p0h+mbEwi5A3lRyoH6UsjfRVyNvdWQrCrXig9IsCAwEAAaNC
MEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUOBTmyPCppAP0Tj4io1vy1uCtQHQwDgYDVR0P
AQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQABZ6OMySU9E2NdFm/soT4JXJEVKirZgCFPBdy7
pYmrEzMqnji3jG8CcmPHc3ceCQa6Oyh7pEfJYWsICCD8igWKH7y6xsL+z27sEzNxZy5p+qksP2bA
EllNC1QCkoS72xLvg3BweMhT+t/Gxv/ciC8HwEmdMldg0/L2mSlf56oBzKwzqBwKu5HEA6BvtjT5
htOzdlSY9EqBs1OdTUDs5XcTRa9bqh/YL0yCe/4qxFi7T/ye/QNlGioOw6UgFpRreaaiErS7GqQj
el/wroQk5PMr+4okoyeYZdowdXb8GZHo2+ubPzK/QJcHJrrM85SFSnonk8+QQtS4Wxam58tAA915
-----END CERTIFICATE-----

EBG Elektronik Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1
==========================================================================
-----BEGIN CERTIFICATE-----
MIIF5zCCA8+gAwIBAgIITK9zQhyOdAIwDQYJKoZIhvcNAQEFBQAwgYAxODA2BgNVBAMML0VCRyBF
bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMTcwNQYDVQQKDC5FQkcg
QmlsacWfaW0gVGVrbm9sb2ppbGVyaSB2ZSBIaXptZXRsZXJpIEEuxZ4uMQswCQYDVQQGEwJUUjAe
Fw0wNjA4MTcwMDIxMDlaFw0xNjA4MTQwMDMxMDlaMIGAMTgwNgYDVQQDDC9FQkcgRWxla3Ryb25p
ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTE3MDUGA1UECgwuRUJHIEJpbGnFn2lt
IFRla25vbG9qaWxlcmkgdmUgSGl6bWV0bGVyaSBBLsWeLjELMAkGA1UEBhMCVFIwggIiMA0GCSqG
SIb3DQEBAQUAA4ICDwAwggIKAoICAQDuoIRh0DpqZhAy2DE4f6en5f2h4fuXd7hxlugTlkaDT7by
X3JWbhNgpQGR4lvFzVcfd2NR/y8927k/qqk153nQ9dAktiHq6yOU/im/+4mRDGSaBUorzAzu8T2b
gmmkTPiab+ci2hC6X5L8GCcKqKpE+i4stPtGmggDg3KriORqcsnlZR9uKg+ds+g75AxuetpX/dfr
eYteIAbTdgtsApWjluTLdlHRKJ2hGvxEok3MenaoDT2/F08iiFD9rrbskFBKW5+VQarKD7JK/oCZ
TqNGFav4c0JqwmZ2sQomFd2TkuzbqV9UIlKRcF0T6kjsbgNs2d1s/OsNA/+mgxKb8amTD8UmTDGy
Y5lhcucqZJnSuOl14nypqZoaqsNW2xCaPINStnuWt6yHd6i58mcLlEOzrz5z+kI2sSXFCjEmN1Zn
uqMLfdb3ic1nobc6HmZP9qBVFCVMLDMNpkGMvQQxahByCp0OLna9XvNRiYuoP1Vzv9s6xiQFlpJI
qkuNKgPlV5EQ9GooFW5Hd4RcUXSfGenmHmMWOeMRFeNYGkS9y8RsZteEBt8w9DeiQyJ50hBs37vm
ExH8nYQKE3vwO9D8owrXieqWfo1IhR5kX9tUoqzVegJ5a9KK8GfaZXINFHDk6Y54jzJ0fFfy1tb0
Nokb+Clsi7n2l9GkLqq+CxnCRelwXQIDAJ3Zo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB
/wQEAwIBBjAdBgNVHQ4EFgQU587GT/wWZ5b6SqMHwQSny2re2kcwHwYDVR0jBBgwFoAU587GT/wW
Z5b6SqMHwQSny2re2kcwDQYJKoZIhvcNAQEFBQADggIBAJuYml2+8ygjdsZs93/mQJ7ANtyVDR2t
FcU22NU57/IeIl6zgrRdu0waypIN30ckHrMk2pGI6YNw3ZPX6bqz3xZaPt7gyPvT/Wwp+BVGoGgm
zJNSroIBk5DKd8pNSe/iWtkqvTDOTLKBtjDOWU/aWR1qeqRFsIImgYZ29fUQALjuswnoT4cCB64k
XPBfrAowzIpAoHMEwfuJJPaaHFy3PApnNgUIMbOv2AFoKuB4j3TeuFGkjGwgPaL7s9QJ/XvCgKqT
bCmYIai7FvOpEl90tYeY8pUm3zTvilORiF0alKM/fCL414i6poyWqD1SNGKfAB5UVUJnxk1Gj7sU
RT0KlhaOEKGXmdXTMIXM3rRyt7yKPBgpaP3ccQfuJDlq+u2lrDgv+R4QDgZxGhBM/nV+/x5XOULK
1+EVoVZVWRvRo68R2E7DpSvvkL/A7IITW43WciyTTo9qKd+FPNMN4KIYEsxVL0e3p5sC/kH2iExt
2qkBR4NkJ2IQgtYSe14DHzSpyZH+r11thie3I6p1GMog57AP14kOpmciY/SDQSsGS7tY1dHXt7kQ
Y9iJSrSq3RZj9W6+YKH47ejWkE8axsWgKdOnIaj1Wjz3x0miIZpKlVIglnKaZsv30oZDfCK+lvm9
AahH3eU7QPl1K5srRmSGjR70j/sHd9DqSaIcjVIUpgqT
-----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-----

CNNIC ROOT
==========
-----BEGIN CERTIFICATE-----
MIIDVTCCAj2gAwIBAgIESTMAATANBgkqhkiG9w0BAQUFADAyMQswCQYDVQQGEwJDTjEOMAwGA1UE
ChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwHhcNMDcwNDE2MDcwOTE0WhcNMjcwNDE2MDcw
OTE0WjAyMQswCQYDVQQGEwJDTjEOMAwGA1UEChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1Qw
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTNfc/c3et6FtzF8LRb+1VvG7q6KR5smzD
o+/hn7E7SIX1mlwhIhAsxYLO2uOabjfhhyzcuQxauohV3/2q2x8x6gHx3zkBwRP9SFIhxFXf2tiz
VHa6dLG3fdfA6PZZxU3Iva0fFNrfWEQlMhkqx35+jq44sDB7R3IJMfAw28Mbdim7aXZOV/kbZKKT
VrdvmW7bCgScEeOAH8tjlBAKqeFkgjH5jCftppkA9nCTGPihNIaj3XrCGHn2emU1z5DrvTOTn1Or
czvmmzQgLx3vqR1jGqCA2wMv+SYahtKNu6m+UjqHZ0gNv7Sg2Ca+I19zN38m5pIEo3/PIKe38zrK
y5nLAgMBAAGjczBxMBEGCWCGSAGG+EIBAQQEAwIABzAfBgNVHSMEGDAWgBRl8jGtKvf33VKWCscC
wQ7vptU7ETAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIB/jAdBgNVHQ4EFgQUZfIxrSr3991S
lgrHAsEO76bVOxEwDQYJKoZIhvcNAQEFBQADggEBAEs17szkrr/Dbq2flTtLP1se31cpolnKOOK5
Gv+e5m4y3R6u6jW39ZORTtpC4cMXYFDy0VwmuYK36m3knITnA3kXr5g9lNvHugDnuL8BV8F3RTIM
O/G0HAiw/VGgod2aHRM2mm23xzy54cXZF/qD1T0VoDy7HgviyJA/qIYM/PmLXoXLT1tLYhFHxUV8
BS9BsZ4QaRuZluBVeftOhpm4lNqGOGqTo+fLbuXf6iFViZx9fX+Y9QCJ7uOEwFyWtcVG6kbghVW2
G8kS1sHNzYDzAgE8yGnLRUhj2JTQ7IUOO04RZfSCjKY9ri4ilAnIXOo8gV0WKgOXFlUJ24pBgp5m
mxE=
-----END CERTIFICATE-----

ApplicationCA - Japanese Government
===================================
-----BEGIN CERTIFICATE-----
MIIDoDCCAoigAwIBAgIBMTANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJKUDEcMBoGA1UEChMT
SmFwYW5lc2UgR292ZXJubWVudDEWMBQGA1UECxMNQXBwbGljYXRpb25DQTAeFw0wNzEyMTIxNTAw
MDBaFw0xNzEyMTIxNTAwMDBaMEMxCzAJBgNVBAYTAkpQMRwwGgYDVQQKExNKYXBhbmVzZSBHb3Zl
cm5tZW50MRYwFAYDVQQLEw1BcHBsaWNhdGlvbkNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAp23gdE6Hj6UG3mii24aZS2QNcfAKBZuOquHMLtJqO8F6tJdhjYq+xpqcBrSGUeQ3DnR4
fl+Kf5Sk10cI/VBaVuRorChzoHvpfxiSQE8tnfWuREhzNgaeZCw7NCPbXCbkcXmP1G55IrmTwcrN
wVbtiGrXoDkhBFcsovW8R0FPXjQilbUfKW1eSvNNcr5BViCH/OlQR9cwFO5cjFW6WY2H/CPek9AE
jP3vbb3QesmlOmpyM8ZKDQUXKi17safY1vC+9D/qDihtQWEjdnjDuGWk81quzMKq2edY3rZ+nYVu
nyoKb58DKTCXKB28t89UKU5RMfkntigm/qJj5kEW8DOYRwIDAQABo4GeMIGbMB0GA1UdDgQWBBRU
WssmP3HMlEYNllPqa0jQk/5CdTAOBgNVHQ8BAf8EBAMCAQYwWQYDVR0RBFIwUKROMEwxCzAJBgNV
BAYTAkpQMRgwFgYDVQQKDA/ml6XmnKzlm73mlL/lupwxIzAhBgNVBAsMGuOCouODl+ODquOCseOD
vOOCt+ODp+ODs0NBMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBADlqRHZ3ODrs
o2dGD/mLBqj7apAxzn7s2tGJfHrrLgy9mTLnsCTWw//1sogJhyzjVOGjprIIC8CFqMjSnHH2HZ9g
/DgzE+Ge3Atf2hZQKXsvcJEPmbo0NI2VdMV+eKlmXb3KIXdCEKxmJj3ekav9FfBv7WxfEPjzFvYD
io+nEhEMy/0/ecGc/WLuo89UDNErXxc+4z6/wCs+CZv+iKZ+tJIX/COUgb1up8WMwusRRdv4QcmW
dupwX3kSa+SjB1oF7ydJzyGfikwJcGapJsErEU4z0g781mzSDjJkaP+tBXhfAx2o45CsJOAPQKdL
rosot4LKGAfmt1t06SAZf7IbiVQ=
-----END CERTIFICATE-----

GeoTrust Primary Certification Authority - G3
=============================================
-----BEGIN CERTIFICATE-----
MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UE
BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChjKSAyMDA4IEdlb1RydXN0
IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFy
eSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIz
NTk1OVowgZgxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAo
YykgMjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNVBAMT
LUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMzCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz+uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5j
K/BGvESyiaHAKAxJcCGVn2TAppMSAmUmhsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdE
c5IiaacDiGydY8hS2pgn5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3C
IShwiP/WJmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exALDmKu
dlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZChuOl1UcCAwEAAaNC
MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMR5yo6hTgMdHNxr
2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IBAQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9
cr5HqQ6XErhK8WTTOd8lNNTBzU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbE
Ap7aDHdlDkQNkv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD
AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUHSJsMC8tJP33s
t/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2Gspki4cErx5z481+oghLrGREt
-----END CERTIFICATE-----

thawte Primary Root CA - G2
===========================
-----BEGIN CERTIFICATE-----
MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDELMAkGA1UEBhMC
VVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMpIDIwMDcgdGhhd3RlLCBJbmMu
IC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3Qg
Q0EgLSBHMjAeFw0wNzExMDUwMDAwMDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEV
MBMGA1UEChMMdGhhd3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBG
b3IgYXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAt
IEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/BebfowJPDQfGAFG6DAJS
LSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6papu+7qzcMBniKI11KOasf2twu8x+qi5
8/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU
mtgAMADna3+FGO6Lts6KDPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUN
G4k8VIZ3KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41oxXZ3K
rr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg==
-----END CERTIFICATE-----

thawte Primary Root CA - G3
===========================
-----BEGIN CERTIFICATE-----
MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCBrjELMAkGA1UE
BhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2
aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIwMDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhv
cml6ZWQgdXNlIG9ubHkxJDAiBgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0w
ODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh
d3RlLCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9uMTgwNgYD
VQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTEkMCIG
A1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAsr8nLPvb2FvdeHsbnndmgcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2At
P0LMqmsywCPLLEHd5N/8YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC
+BsUa0Lfb1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS99irY
7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2SzhkGcuYMXDhpxwTW
vGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUkOQIDAQABo0IwQDAPBgNVHRMBAf8E
BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJ
KoZIhvcNAQELBQADggEBABpA2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweK
A3rD6z8KLFIWoCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu
t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7cKUGRIjxpp7sC
8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fMm7v/OeZWYdMKp8RcTGB7BXcm
er/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZuMdRAGmI0Nj81Aa6sY6A=
-----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-----

VeriSign Class 3 Public Primary Certification Authority - G4
============================================================
-----BEGIN CERTIFICATE-----
MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjELMAkGA1UEBhMC
VVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3
b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVz
ZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmlj
YXRpb24gQXV0aG9yaXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjEL
MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJpU2lnbiBU
cnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRo
b3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5
IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8
Utpkmw4tXNherJI9/gHmGUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGz
rl0Bp3vefLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUwAwEB
/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEw
HzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVyaXNpZ24u
Y29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMWkf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMD
A2gAMGUCMGYhDBgmYFo4e1ZC4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIx
AJw9SDkjOVgaFRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA==
-----END CERTIFICATE-----

NetLock Arany (Class Gold) Főtanúsítvány
============================================
-----BEGIN CERTIFICATE-----
MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQGEwJIVTERMA8G
A1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3MDUGA1UECwwuVGFuw7pzw610
dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNlcnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBB
cmFueSAoQ2xhc3MgR29sZCkgRsWRdGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgx
MjA2MTUwODIxWjCBpzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxO
ZXRMb2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlmaWNhdGlv
biBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNzIEdvbGQpIEbFkXRhbsO6
c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCRec75LbRTDofTjl5Bu
0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrTlF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw
/HpYzY6b7cNGbIRwXdrzAZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAk
H3B5r9s5VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRGILdw
fzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2BJtr+UBdADTHLpl1
neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIB
BjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2MU9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwW
qZw8UQCgwBEIBaeZ5m8BiFRhbvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTta
YtOUZcTh5m2C+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC
bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2FuLjbvrW5Kfna
NwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2XjG4Kvte9nHfRCaexOYNkbQu
dZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=
-----END CERTIFICATE-----

Staat der Nederlanden Root CA - G2
==================================
-----BEGIN CERTIFICATE-----
MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJOTDEeMBwGA1UE
CgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFhdCBkZXIgTmVkZXJsYW5kZW4g
Um9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oXDTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMC
TkwxHjAcBgNVBAoMFVN0YWF0IGRlciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5l
ZGVybGFuZGVuIFJvb3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ
5291qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8SpuOUfiUtn
vWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPUZ5uW6M7XxgpT0GtJlvOj
CwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvEpMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiil
e7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCR
OME4HYYEhLoaJXhena/MUGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpI
CT0ugpTNGmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy5V65
48r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv6q012iDTiIJh8BIi
trzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEKeN5KzlW/HdXZt1bv8Hb/C3m1r737
qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMB
AAGjgZcwgZQwDwYDVR0TAQH/BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcC
ARYxaHR0cDovL3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV
HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqGSIb3DQEBCwUA
A4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLySCZa59sCrI2AGeYwRTlHSeYAz
+51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwj
f/ST7ZwaUb7dRUG/kSS0H4zpX897IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaN
kqbG9AclVMwWVxJKgnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfk
CpYL+63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxLvJxxcypF
URmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkmbEgeqmiSBeGCc1qb3Adb
CG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvkN1trSt8sV4pAWja63XVECDdCcAz+3F4h
oKOKwJCcaNpQ5kUQR3i2TtJlycM33+FCY7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoV
IPVVYpbtbZNQvOSqeK3Zywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm
66+KAQ==
-----END CERTIFICATE-----

CA Disig
========
-----BEGIN CERTIFICATE-----
MIIEDzCCAvegAwIBAgIBATANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJTSzETMBEGA1UEBxMK
QnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwHhcNMDYw
MzIyMDEzOTM0WhcNMTYwMzIyMDEzOTM0WjBKMQswCQYDVQQGEwJTSzETMBEGA1UEBxMKQnJhdGlz
bGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQCS9jHBfYj9mQGp2HvycXXxMcbzdWb6UShGhJd4NLxs/LxFWYgm
GErENx+hSkS943EE9UQX4j/8SFhvXJ56CbpRNyIjZkMhsDxkovhqFQ4/61HhVKndBpnXmjxUizkD
Pw/Fzsbrg3ICqB9x8y34dQjbYkzo+s7552oftms1grrijxaSfQUMbEYDXcDtab86wYqg6I7ZuUUo
hwjstMoVvoLdtUSLLa2GDGhibYVW8qwUYzrG0ZmsNHhWS8+2rT+MitcE5eN4TPWGqvWP+j1scaMt
ymfraHtuM6kMgiioTGohQBUgDCZbg8KpFhXAJIJdKxatymP2dACw30PEEGBWZ2NFAgMBAAGjgf8w
gfwwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUjbJJaJ1yCCW5wCf1UJNWSEZx+Y8wDgYDVR0P
AQH/BAQDAgEGMDYGA1UdEQQvMC2BE2Nhb3BlcmF0b3JAZGlzaWcuc2uGFmh0dHA6Ly93d3cuZGlz
aWcuc2svY2EwZgYDVR0fBF8wXTAtoCugKYYnaHR0cDovL3d3dy5kaXNpZy5zay9jYS9jcmwvY2Ff
ZGlzaWcuY3JsMCygKqAohiZodHRwOi8vY2EuZGlzaWcuc2svY2EvY3JsL2NhX2Rpc2lnLmNybDAa
BgNVHSAEEzARMA8GDSuBHpGT5goAAAABAQEwDQYJKoZIhvcNAQEFBQADggEBAF00dGFMrzvY/59t
WDYcPQuBDRIrRhCA/ec8J9B6yKm2fnQwM6M6int0wHl5QpNt/7EpFIKrIYwvF/k/Ji/1WcbvgAa3
mkkp7M5+cTxqEEHA9tOasnxakZzArFvITV734VP/Q3f8nktnbNfzg9Gg4H8l37iYC5oyOGwwoPP/
CBUz91BKez6jPiCp3C9WgArtQVCwyfTssuMmRAAOb54GvCKWU3BlxFAKRmukLyeBEicTXxChds6K
ezfqwzlhA5WYOudsiCUI/HloDYd9Yvi0X/vF2Ey9WLw/Q1vUHgFNPGO+I++MzVpQuGhU+QqZMxEA
4Z7CRneC9VkGjCFMhwnN5ag=
-----END CERTIFICATE-----

Juur-SK
=======
-----BEGIN CERTIFICATE-----
MIIE5jCCA86gAwIBAgIEO45L/DANBgkqhkiG9w0BAQUFADBdMRgwFgYJKoZIhvcNAQkBFglwa2lA
c2suZWUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKExlBUyBTZXJ0aWZpdHNlZXJpbWlza2Vza3VzMRAw
DgYDVQQDEwdKdXVyLVNLMB4XDTAxMDgzMDE0MjMwMVoXDTE2MDgyNjE0MjMwMVowXTEYMBYGCSqG
SIb3DQEJARYJcGtpQHNrLmVlMQswCQYDVQQGEwJFRTEiMCAGA1UEChMZQVMgU2VydGlmaXRzZWVy
aW1pc2tlc2t1czEQMA4GA1UEAxMHSnV1ci1TSzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAIFxNj4zB9bjMI0TfncyRsvPGbJgMUaXhvSYRqTCZUXP00B841oiqBB4M8yIsdOBSvZiF3tf
TQou0M+LI+5PAk676w7KvRhj6IAcjeEcjT3g/1tf6mTll+g/mX8MCgkzABpTpyHhOEvWgxutr2TC
+Rx6jGZITWYfGAriPrsfB2WThbkasLnE+w0R9vXW+RvHLCu3GFH+4Hv2qEivbDtPL+/40UceJlfw
UR0zlv/vWT3aTdEVNMfqPxZIe5EcgEMPPbgFPtGzlc3Yyg/CQ2fbt5PgIoIuvvVoKIO5wTtpeyDa
Tpxt4brNj3pssAki14sL2xzVWiZbDcDq5WDQn/413z8CAwEAAaOCAawwggGoMA8GA1UdEwEB/wQF
MAMBAf8wggEWBgNVHSAEggENMIIBCTCCAQUGCisGAQQBzh8BAQEwgfYwgdAGCCsGAQUFBwICMIHD
HoHAAFMAZQBlACAAcwBlAHIAdABpAGYAaQBrAGEAYQB0ACAAbwBuACAAdgDkAGwAagBhAHMAdABh
AHQAdQBkACAAQQBTAC0AaQBzACAAUwBlAHIAdABpAGYAaQB0AHMAZQBlAHIAaQBtAGkAcwBrAGUA
cwBrAHUAcwAgAGEAbABhAG0ALQBTAEsAIABzAGUAcgB0AGkAZgBpAGsAYQBhAHQAaQBkAGUAIABr
AGkAbgBuAGkAdABhAG0AaQBzAGUAawBzMCEGCCsGAQUFBwIBFhVodHRwOi8vd3d3LnNrLmVlL2Nw
cy8wKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL3d3dy5zay5lZS9qdXVyL2NybC8wHQYDVR0OBBYE
FASqekej5ImvGs8KQKcYP2/v6X2+MB8GA1UdIwQYMBaAFASqekej5ImvGs8KQKcYP2/v6X2+MA4G
A1UdDwEB/wQEAwIB5jANBgkqhkiG9w0BAQUFAAOCAQEAe8EYlFOiCfP+JmeaUOTDBS8rNXiRTHyo
ERF5TElZrMj3hWVcRrs7EKACr81Ptcw2Kuxd/u+gkcm2k298gFTsxwhwDY77guwqYHhpNjbRxZyL
abVAyJRld/JXIWY7zoVAtjNjGr95HvxcHdMdkxuLDF2FvZkwMhgJkVLpfKG6/2SSmuz+Ne6ML678
IIbsSt4beDI3poHSna9aEhbKmVv8b20OxaAehsmR0FyYgl9jDIpaq9iVpszLita/ZEuOyoqysOkh
Mp6qqIWYNIE5ITuoOlIyPfZrN4YGWhWY3PARZv40ILcD9EEQfTmEeZZyY7aWAuVrua0ZTbvGRNs2
yyqcjg==
-----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-----

ACEDICOM Root
=============
-----BEGIN CERTIFICATE-----
MIIFtTCCA52gAwIBAgIIYY3HhjsBggUwDQYJKoZIhvcNAQEFBQAwRDEWMBQGA1UEAwwNQUNFRElD
T00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00xCzAJBgNVBAYTAkVTMB4XDTA4
MDQxODE2MjQyMloXDTI4MDQxMzE2MjQyMlowRDEWMBQGA1UEAwwNQUNFRElDT00gUm9vdDEMMAoG
A1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00xCzAJBgNVBAYTAkVTMIICIjANBgkqhkiG9w0BAQEF
AAOCAg8AMIICCgKCAgEA/5KV4WgGdrQsyFhIyv2AVClVYyT/kGWbEHV7w2rbYgIB8hiGtXxaOLHk
WLn709gtn70yN78sFW2+tfQh0hOR2QetAQXW8713zl9CgQr5auODAKgrLlUTY4HKRxx7XBZXehuD
YAQ6PmXDzQHe3qTWDLqO3tkE7hdWIpuPY/1NFgu3e3eM+SW10W2ZEi5PGrjm6gSSrj0RuVFCPYew
MYWveVqc/udOXpJPQ/yrOq2lEiZmueIM15jO1FillUAKt0SdE3QrwqXrIhWYENiLxQSfHY9g5QYb
m8+5eaA9oiM/Qj9r+hwDezCNzmzAv+YbX79nuIQZ1RXve8uQNjFiybwCq0Zfm/4aaJQ0PZCOrfbk
HQl/Sog4P75n/TSW9R28MHTLOO7VbKvU/PQAtwBbhTIWdjPp2KOZnQUAqhbm84F9b32qhm2tFXTT
xKJxqvQUfecyuB+81fFOvW8XAjnXDpVCOscAPukmYxHqC9FK/xidstd7LzrZlvvoHpKuE1XI2Sf2
3EgbsCTBheN3nZqk8wwRHQ3ItBTutYJXCb8gWH8vIiPYcMt5bMlL8qkqyPyHK9caUPgn6C9D4zq9
2Fdx/c6mUlv53U3t5fZvie27k5x2IXXwkkwp9y+cAS7+UEaeZAwUswdbxcJzbPEHXEUkFDWug/Fq
TYl6+rPYLWbwNof1K1MCAwEAAaOBqjCBpzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKaz
4SsrSbbXc6GqlPUB53NlTKxQMA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUprPhKytJttdzoaqU
9QHnc2VMrFAwRAYDVR0gBD0wOzA5BgRVHSAAMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly9hY2VkaWNv
bS5lZGljb21ncm91cC5jb20vZG9jMA0GCSqGSIb3DQEBBQUAA4ICAQDOLAtSUWImfQwng4/F9tqg
aHtPkl7qpHMyEVNEskTLnewPeUKzEKbHDZ3Ltvo/Onzqv4hTGzz3gvoFNTPhNahXwOf9jU8/kzJP
eGYDdwdY6ZXIfj7QeQCM8htRM5u8lOk6e25SLTKeI6RF+7YuE7CLGLHdztUdp0J/Vb77W7tH1Pwk
zQSulgUV1qzOMPPKC8W64iLgpq0i5ALudBF/TP94HTXa5gI06xgSYXcGCRZj6hitoocf8seACQl1
ThCojz2GuHURwCRiipZ7SkXp7FnFvmuD5uHorLUwHv4FB4D54SMNUI8FmP8sX+g7tq3PgbUhh8oI
KiMnMCArz+2UW6yyetLHKKGKC5tNSixthT8Jcjxn4tncB7rrZXtaAWPWkFtPF2Y9fwsZo5NjEFIq
nxQWWOLcpfShFosOkYuByptZ+thrkQdlVV9SH686+5DdaaVbnG0OLLb6zqylfDJKZ0DcMDQj3dcE
I2bw/FWAp/tmGYI1Z2JwOV5vx+qQQEQIHriy1tvuWacNGHk0vFQYXlPKNFHtRQrmjseCNj6nOGOp
MCwXEGCSn1WHElkQwg9naRHMTh5+Spqtr0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3o
tkYNbn5XOmeUwssfnHdKZ05phkOTOPu220+DkdRgfks+KzgHVZhepA==
-----END CERTIFICATE-----

Verisign Class 3 Public Primary Certification Authority
=======================================================
-----BEGIN CERTIFICATE-----
MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkGA1UEBhMCVVMx
FzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmltYXJ5
IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVow
XzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAz
IFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUA
A4GNADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhEBarsAx94
f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/isI19wKTakyYbnsZogy1Ol
hec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBABByUqkFFBky
CEHwxWsKzH4PIRnN5GfcX6kb5sroc50i2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWX
bj9T/UWZYB2oK0z5XqcJ2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/
D/xwzoiQ
-----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-----

E-Guven Kok Elektronik Sertifika Hizmet Saglayicisi
===================================================
-----BEGIN CERTIFICATE-----
MIIDtjCCAp6gAwIBAgIQRJmNPMADJ72cdpW56tustTANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQG
EwJUUjEoMCYGA1UEChMfRWxla3Ryb25payBCaWxnaSBHdXZlbmxpZ2kgQS5TLjE8MDoGA1UEAxMz
ZS1HdXZlbiBLb2sgRWxla3Ryb25payBTZXJ0aWZpa2EgSGl6bWV0IFNhZ2xheWljaXNpMB4XDTA3
MDEwNDExMzI0OFoXDTE3MDEwNDExMzI0OFowdTELMAkGA1UEBhMCVFIxKDAmBgNVBAoTH0VsZWt0
cm9uaWsgQmlsZ2kgR3V2ZW5saWdpIEEuUy4xPDA6BgNVBAMTM2UtR3V2ZW4gS29rIEVsZWt0cm9u
aWsgU2VydGlmaWthIEhpem1ldCBTYWdsYXlpY2lzaTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAMMSIJ6wXgBljU5Gu4Bc6SwGl9XzcslwuedLZYDBS75+PNdUMZTe1RK6UxYC6lhj71vY
8+0qGqpxSKPcEC1fX+tcS5yWCEIlKBHMilpiAVDV6wlTL/jDj/6z/P2douNffb7tC+Bg62nsM+3Y
jfsSSYMAyYuXjDtzKjKzEve5TfL0TW3H5tYmNwjy2f1rXKPlSFxYvEK+A1qBuhw1DADT9SN+cTAI
JjjcJRFHLfO6IxClv7wC90Nex/6wN1CZew+TzuZDLMN+DfIcQ2Zgy2ExR4ejT669VmxMvLz4Bcpk
9Ok0oSy1c+HCPujIyTQlCFzz7abHlJ+tiEMl1+E5YP6sOVkCAwEAAaNCMEAwDgYDVR0PAQH/BAQD
AgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJ/uRLOU1fqRTy7ZVZoEVtstxNulMA0GCSqG
SIb3DQEBBQUAA4IBAQB/X7lTW2M9dTLn+sR0GstG30ZpHFLPqk/CaOv/gKlR6D1id4k9CnU58W5d
F4dvaAXBlGzZXd/aslnLpRCKysw5zZ/rTt5S/wzw9JKp8mxTq5vSR6AfdPebmvEvFZ96ZDAYBzwq
D2fK/A+JYZ1lpTzlvBNbCNvj/+27BrtqBrF6T2XGgv0enIu1De5Iu7i9qgi0+6N8y5/NkHZchpZ4
Vwpm+Vganf2XKWDeEaaQHBkc7gGWIjQ0LpH5t8Qn0Xvmv/uARFoW5evg1Ao4vOSR49XrXMGs3xtq
fJ7lddK2l4fbzIcrQzqECK+rPNv3PGYxhrCdU3nt+CPeQuMtgvEP5fqX
-----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-----

TC TrustCenter Universal CA III
===============================
-----BEGIN CERTIFICATE-----
MIID4TCCAsmgAwIBAgIOYyUAAQACFI0zFQLkbPQwDQYJKoZIhvcNAQEFBQAwezELMAkGA1UEBhMC
REUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNVBAsTG1RDIFRydXN0Q2VudGVy
IFVuaXZlcnNhbCBDQTEoMCYGA1UEAxMfVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBIElJSTAe
Fw0wOTA5MDkwODE1MjdaFw0yOTEyMzEyMzU5NTlaMHsxCzAJBgNVBAYTAkRFMRwwGgYDVQQKExNU
QyBUcnVzdENlbnRlciBHbWJIMSQwIgYDVQQLExtUQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0Ex
KDAmBgNVBAMTH1RDIFRydXN0Q2VudGVyIFVuaXZlcnNhbCBDQSBJSUkwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQDC2pxisLlxErALyBpXsq6DFJmzNEubkKLF5+cvAqBNLaT6hdqbJYUt
QCggbergvbFIgyIpRJ9Og+41URNzdNW88jBmlFPAQDYvDIRlzg9uwliT6CwLOunBjvvya8o84pxO
juT5fdMnnxvVZ3iHLX8LR7PH6MlIfK8vzArZQe+f/prhsq75U7Xl6UafYOPfjdN/+5Z+s7Vy+Eut
CHnNaYlAJ/Uqwa1D7KRTyGG299J5KmcYdkhtWyUB0SbFt1dpIxVbYYqt8Bst2a9c8SaQaanVDED1
M4BDj5yjdipFtK+/fz6HP3bFzSreIMUWWMv5G/UPyw0RUmS40nZid4PxWJ//AgMBAAGjYzBhMB8G
A1UdIwQYMBaAFFbn4VslQ4Dg9ozhcbyO5YAvxEjiMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/
BAQDAgEGMB0GA1UdDgQWBBRW5+FbJUOA4PaM4XG8juWAL8RI4jANBgkqhkiG9w0BAQUFAAOCAQEA
g8ev6n9NCjw5sWi+e22JLumzCecYV42FmhfzdkJQEw/HkG8zrcVJYCtsSVgZ1OK+t7+rSbyUyKu+
KGwWaODIl0YgoGhnYIg5IFHYaAERzqf2EQf27OysGh+yZm5WZ2B6dF7AbZc2rrUNXWZzwCUyRdhK
BgePxLcHsU0GDeGl6/R1yrqc0L2z0zIkTO5+4nYES0lT2PLpVDP85XEfPRRclkvxOvIAu2y0+pZV
CIgJwcyRGSmwIC3/yzikQOEXvnlhgP8HA4ZMTnsGnxGGjYnuJ8Tb4rwZjgvDwxPHLQNjO9Po5KIq
woIIlBZU8O8fJ5AluA0OKBtHd0e9HKgl8ZS0Zg==
-----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-----

Certinomis - Autorité Racine
=============================
-----BEGIN CERTIFICATE-----
MIIFnDCCA4SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJGUjETMBEGA1UEChMK
Q2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxJjAkBgNVBAMMHUNlcnRpbm9taXMg
LSBBdXRvcml0w6kgUmFjaW5lMB4XDTA4MDkxNzA4Mjg1OVoXDTI4MDkxNzA4Mjg1OVowYzELMAkG
A1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMxFzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMSYw
JAYDVQQDDB1DZXJ0aW5vbWlzIC0gQXV0b3JpdMOpIFJhY2luZTCCAiIwDQYJKoZIhvcNAQEBBQAD
ggIPADCCAgoCggIBAJ2Fn4bT46/HsmtuM+Cet0I0VZ35gb5j2CN2DpdUzZlMGvE5x4jYF1AMnmHa
wE5V3udauHpOd4cN5bjr+p5eex7Ezyh0x5P1FMYiKAT5kcOrJ3NqDi5N8y4oH3DfVS9O7cdxbwly
Lu3VMpfQ8Vh30WC8Tl7bmoT2R2FFK/ZQpn9qcSdIhDWerP5pqZ56XjUl+rSnSTV3lqc2W+HN3yNw
2F1MpQiD8aYkOBOo7C+ooWfHpi2GR+6K/OybDnT0K0kCe5B1jPyZOQE51kqJ5Z52qz6WKDgmi92N
jMD2AR5vpTESOH2VwnHu7XSu5DaiQ3XV8QCb4uTXzEIDS3h65X27uK4uIJPT5GHfceF2Z5c/tt9q
c1pkIuVC28+BA5PY9OMQ4HL2AHCs8MF6DwV/zzRpRbWT5BnbUhYjBYkOjUjkJW+zeL9i9Qf6lSTC
lrLooyPCXQP8w9PlfMl1I9f09bze5N/NgL+RiH2nE7Q5uiy6vdFrzPOlKO1Enn1So2+WLhl+HPNb
xxaOu2B9d2ZHVIIAEWBsMsGoOBvrbpgT1u449fCfDu/+MYHB0iSVL1N6aaLwD4ZFjliCK0wi1F6g
530mJ0jfJUaNSih8hp75mxpZuWW/Bd22Ql095gBIgl4g9xGC3srYn+Y3RyYe63j3YcNBZFgCQfna
4NH4+ej9Uji29YnfAgMBAAGjWzBZMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G
A1UdDgQWBBQNjLZh2kS40RR9w759XkjwzspqsDAXBgNVHSAEEDAOMAwGCiqBegFWAgIAAQEwDQYJ
KoZIhvcNAQEFBQADggIBACQ+YAZ+He86PtvqrxyaLAEL9MW12Ukx9F1BjYkMTv9sov3/4gbIOZ/x
WqndIlgVqIrTseYyCYIDbNc/CMf4uboAbbnW/FIyXaR/pDGUu7ZMOH8oMDX/nyNTt7buFHAAQCva
R6s0fl6nVjBhK4tDrP22iCj1a7Y+YEq6QpA0Z43q619FVDsXrIvkxmUP7tCMXWY5zjKn2BCXwH40
nJ+U8/aGH88bc62UeYdocMMzpXDn2NU4lG9jeeu/Cg4I58UvD0KgKxRA/yHgBcUn4YQRE7rWhh1B
CxMjidPJC+iKunqjo3M3NYB9Ergzd0A4wPpeMNLytqOx1qKVl4GbUu1pTP+A5FPbVFsDbVRfsbjv
JL1vnxHDx2TCDyhihWZeGnuyt++uNckZM6i4J9szVb9o4XVIRFb7zdNIu0eJOqxp9YDG5ERQL1TE
qkPFMTFYvZbF6nVsmnWxTfj3l/+WFvKXTej28xH5On2KOG4Ey+HTRRWqpdEdnV1j6CTmNhTih60b
WfVEm/vXd3wfAXBioSAaosUaKPQhA+4u2cGA6rnZgtZbdsLLO7XSAPCjDuGtbkD326C00EauFddE
wk01+dIL8hf2rGbVJLJP0RyZwG71fet0BLj5TXcJ17TPBzAJ8bgAVtkXFhYKK4bfjwEZGuW7gmP/
vgt2Fl43N+bYdJeimUV5
-----END CERTIFICATE-----

Root CA Generalitat Valenciana
==============================
-----BEGIN CERTIFICATE-----
MIIGizCCBXOgAwIBAgIEO0XlaDANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJFUzEfMB0GA1UE
ChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290
IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwHhcNMDEwNzA2MTYyMjQ3WhcNMjEwNzAxMTUyMjQ3
WjBoMQswCQYDVQQGEwJFUzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UE
CxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGKqtXETcvIorKA3Qdyu0togu8M1JAJke+WmmmO3I2
F0zo37i7L3bhQEZ0ZQKQUgi0/6iMweDHiVYQOTPvaLRfX9ptI6GJXiKjSgbwJ/BXufjpTjJ3Cj9B
ZPPrZe52/lSqfR0grvPXdMIKX/UIKFIIzFVd0g/bmoGlu6GzwZTNVOAydTGRGmKy3nXiz0+J2ZGQ
D0EbtFpKd71ng+CT516nDOeB0/RSrFOyA8dEJvt55cs0YFAQexvba9dHq198aMpunUEDEO5rmXte
JajCq+TA81yc477OMUxkHl6AovWDfgzWyoxVjr7gvkkHD6MkQXpYHYTqWBLI4bft75PelAgxAgMB
AAGjggM7MIIDNzAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnBraS5n
dmEuZXMwEgYDVR0TAQH/BAgwBgEB/wIBAjCCAjQGA1UdIASCAiswggInMIICIwYKKwYBBAG/VQIB
ADCCAhMwggHoBggrBgEFBQcCAjCCAdoeggHWAEEAdQB0AG8AcgBpAGQAYQBkACAAZABlACAAQwBl
AHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAFIAYQDtAHoAIABkAGUAIABsAGEAIABHAGUAbgBlAHIA
YQBsAGkAdABhAHQAIABWAGEAbABlAG4AYwBpAGEAbgBhAC4ADQAKAEwAYQAgAEQAZQBjAGwAYQBy
AGEAYwBpAPMAbgAgAGQAZQAgAFAAcgDhAGMAdABpAGMAYQBzACAAZABlACAAQwBlAHIAdABpAGYA
aQBjAGEAYwBpAPMAbgAgAHEAdQBlACAAcgBpAGcAZQAgAGUAbAAgAGYAdQBuAGMAaQBvAG4AYQBt
AGkAZQBuAHQAbwAgAGQAZQAgAGwAYQAgAHAAcgBlAHMAZQBuAHQAZQAgAEEAdQB0AG8AcgBpAGQA
YQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAHMAZQAgAGUAbgBjAHUAZQBu
AHQAcgBhACAAZQBuACAAbABhACAAZABpAHIAZQBjAGMAaQDzAG4AIAB3AGUAYgAgAGgAdAB0AHAA
OgAvAC8AdwB3AHcALgBwAGsAaQAuAGcAdgBhAC4AZQBzAC8AYwBwAHMwJQYIKwYBBQUHAgEWGWh0
dHA6Ly93d3cucGtpLmd2YS5lcy9jcHMwHQYDVR0OBBYEFHs100DSHHgZZu90ECjcPk+yeAT8MIGV
BgNVHSMEgY0wgYqAFHs100DSHHgZZu90ECjcPk+yeAT8oWykajBoMQswCQYDVQQGEwJFUzEfMB0G
A1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScwJQYDVQQDEx5S
b290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmGCBDtF5WgwDQYJKoZIhvcNAQEFBQADggEBACRh
TvW1yEICKrNcda3FbcrnlD+laJWIwVTAEGmiEi8YPyVQqHxK6sYJ2fR1xkDar1CdPaUWu20xxsdz
Ckj+IHLtb8zog2EWRpABlUt9jppSCS/2bxzkoXHPjCpaF3ODR00PNvsETUlR4hTJZGH71BTg9J63
NI8KJr2XXPR5OkowGcytT6CYirQxlyric21+eLj4iIlPsSKRZEv1UN4D2+XFducTZnV+ZfsBn5OH
iJ35Rld8TWCvmHMTI6QgkYH60GFmuH3Rr9ZvHmw96RH9qfmCIoaZM3Fa6hlXPZHNqcCjbgcTpsnt
+GijnsNacgmHKNHEc8RzGF9QdRYxn7fofMM=
-----END CERTIFICATE-----

A-Trust-nQual-03
================
-----BEGIN CERTIFICATE-----
MIIDzzCCAregAwIBAgIDAWweMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYDVQQGEwJBVDFIMEYGA1UE
Cgw/QS1UcnVzdCBHZXMuIGYuIFNpY2hlcmhlaXRzc3lzdGVtZSBpbSBlbGVrdHIuIERhdGVudmVy
a2VociBHbWJIMRkwFwYDVQQLDBBBLVRydXN0LW5RdWFsLTAzMRkwFwYDVQQDDBBBLVRydXN0LW5R
dWFsLTAzMB4XDTA1MDgxNzIyMDAwMFoXDTE1MDgxNzIyMDAwMFowgY0xCzAJBgNVBAYTAkFUMUgw
RgYDVQQKDD9BLVRydXN0IEdlcy4gZi4gU2ljaGVyaGVpdHNzeXN0ZW1lIGltIGVsZWt0ci4gRGF0
ZW52ZXJrZWhyIEdtYkgxGTAXBgNVBAsMEEEtVHJ1c3QtblF1YWwtMDMxGTAXBgNVBAMMEEEtVHJ1
c3QtblF1YWwtMDMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtPWFuA/OQO8BBC4SA
zewqo51ru27CQoT3URThoKgtUaNR8t4j8DRE/5TrzAUjlUC5B3ilJfYKvUWG6Nm9wASOhURh73+n
yfrBJcyFLGM/BWBzSQXgYHiVEEvc+RFZznF/QJuKqiTfC0Li21a8StKlDJu3Qz7dg9MmEALP6iPE
SU7l0+m0iKsMrmKS1GWH2WrX9IWf5DMiJaXlyDO6w8dB3F/GaswADm0yqLaHNgBid5seHzTLkDx4
iHQF63n1k3Flyp3HaxgtPVxO59X4PzF9j4fsCiIvI+n+u33J4PTs63zEsMMtYrWacdaxaujs2e3V
cuy+VwHOBVWf3tFgiBCzAgMBAAGjNjA0MA8GA1UdEwEB/wQFMAMBAf8wEQYDVR0OBAoECERqlWdV
eRFPMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAVdRU0VlIXLOThaq/Yy/kgM40
ozRiPvbY7meIMQQDbwvUB/tOdQ/TLtPAF8fGKOwGDREkDg6lXb+MshOWcdzUzg4NCmgybLlBMRmr
sQd7TZjTXLDR8KdCoLXEjq/+8T/0709GAHbrAvv5ndJAlseIOrifEXnzgGWovR/TeIGgUUw3tKZd
JXDRZslo+S4RFGjxVJgIrCaSD96JntT6s3kr0qN51OyLrIdTaEJMUVF0HhsnLuP1Hyl0Te2v9+GS
mYHovjrHF1D2t8b8m7CKa9aIA5GPBnc6hQLdmNVDeD/GMBWsm2vLV7eJUYs66MmEDNuxUCAKGkq6
ahq97BvIxYSazQ==
-----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-----

StartCom Certification Authority
================================
-----BEGIN CERTIFICATE-----
MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN
U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmlu
ZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0
NjM3WhcNMzYwOTE3MTk0NjM2WjB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRk
LjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMg
U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
ggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZkpMyONvg45iPwbm2xPN1y
o4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rfOQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/
Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/CJi/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/d
eMotHweXMAEtcnn6RtYTKqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt
2PZE4XNiHzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMMAv+Z
6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w+2OqqGwaVLRcJXrJ
osmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/
untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVc
UjyJthkqcwEKDwOzEmDyei+B26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT
37uMdBNSSwIDAQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD
VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFulF2mHMMo0aEPQ
Qa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCCATgwLgYIKwYBBQUHAgEWImh0
dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cu
c3RhcnRzc2wuY29tL2ludGVybWVkaWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENv
bW1lcmNpYWwgKFN0YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0
aGUgc2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0aWZpY2F0
aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93d3cuc3RhcnRzc2wuY29t
L3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBG
cmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5
fPGFf59Jb2vKXfuM/gTFwWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWm
N3PH/UvSTa0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst0OcN
Org+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNcpRJvkrKTlMeIFw6T
tn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKlCcWw0bdT82AUuoVpaiF8H3VhFyAX
e2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVFP0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA
2MFrLH9ZXF2RsXAiV+uKa0hK1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBs
HvUwyKMQ5bLmKhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE
JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ8dCAWZvLMdib
D4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnmfyWl8kgAwKQB2j8=
-----END CERTIFICATE-----

StartCom Certification Authority G2
===================================
-----BEGIN CERTIFICATE-----
MIIFYzCCA0ugAwIBAgIBOzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJJTDEWMBQGA1UEChMN
U3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
RzIwHhcNMTAwMTAxMDEwMDAxWhcNMzkxMjMxMjM1OTAxWjBTMQswCQYDVQQGEwJJTDEWMBQGA1UE
ChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3Jp
dHkgRzIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2iTZbB7cgNr2Cu+EWIAOVeq8O
o1XJJZlKxdBWQYeQTSFgpBSHO839sj60ZwNq7eEPS8CRhXBF4EKe3ikj1AENoBB5uNsDvfOpL9HG
4A/LnooUCri99lZi8cVytjIl2bLzvWXFDSxu1ZJvGIsAQRSCb0AgJnooD/Uefyf3lLE3PbfHkffi
Aez9lInhzG7TNtYKGXmu1zSCZf98Qru23QumNK9LYP5/Q0kGi4xDuFby2X8hQxfqp0iVAXV16iul
Q5XqFYSdCI0mblWbq9zSOdIxHWDirMxWRST1HFSr7obdljKF+ExP6JV2tgXdNiNnvP8V4so75qbs
O+wmETRIjfaAKxojAuuKHDp2KntWFhxyKrOq42ClAJ8Em+JvHhRYW6Vsi1g8w7pOOlz34ZYrPu8H
vKTlXcxNnw3h3Kq74W4a7I/htkxNeXJdFzULHdfBR9qWJODQcqhaX2YtENwvKhOuJv4KHBnM0D4L
nMgJLvlblnpHnOl68wVQdJVznjAJ85eCXuaPOQgeWeU1FEIT/wCc976qUM/iUUjXuG+v+E5+M5iS
FGI6dWPPe/regjupuznixL0sAA7IF6wT700ljtizkC+p2il9Ha90OrInwMEePnWjFqmveiJdnxMa
z6eg6+OGCtP95paV1yPIN93EfKo2rJgaErHgTuixO/XWb/Ew1wIDAQABo0IwQDAPBgNVHRMBAf8E
BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUS8W0QGutHLOlHGVuRjaJhwUMDrYwDQYJ
KoZIhvcNAQELBQADggIBAHNXPyzVlTJ+N9uWkusZXn5T50HsEbZH77Xe7XRcxfGOSeD8bpkTzZ+K
2s06Ctg6Wgk/XzTQLwPSZh0avZyQN8gMjgdalEVGKua+etqhqaRpEpKwfTbURIfXUfEpY9Z1zRbk
J4kd+MIySP3bmdCPX1R0zKxnNBFi2QwKN4fRoxdIjtIXHfbX/dtl6/2o1PXWT6RbdejF0mCy2wl+
JYt7ulKSnj7oxXehPOBKc2thz4bcQ///If4jXSRK9dNtD2IEBVeC2m6kMyV5Sy5UGYvMLD0w6dEG
/+gyRr61M3Z3qAFdlsHB1b6uJcDJHgoJIIihDsnzb02CVAAgp9KP5DlUFy6NHrgbuxu9mk47EDTc
nIhT76IxW1hPkWLIwpqazRVdOKnWvvgTtZ8SafJQYqz7Fzf07rh1Z2AQ+4NQ+US1dZxAF7L+/Xld
blhYXzD8AK6vM8EOTmy6p6ahfzLbOOCxchcKK5HsamMm7YnUeMx0HgX4a/6ManY5Ka5lIxKVCCIc
l85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoohdVddLHRDiBYmxOlsGOm
7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulrso8uBtjRkcfGEvRM/TAXw8HaOFvjqerm
obp573PYtlNXLfbQ4ddI
-----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-----
wp-cli/wp-cli 2.4.0 2019-11-12T18:00:32+00:00
composer/ca-bundle 1.2.4 10bb96592168a0f8e8f6dcde3532d9fa50b0b527
composer/composer 1.9.1 bb01f2180df87ce7992b8331a68904f80439dd2f
composer/semver 1.5.0 46d9139568ccb8d9e7cdd4539cab7347568a5e2e
composer/spdx-licenses 1.5.2 7ac1e6aec371357df067f8a688c3d6974df68fa5
composer/xdebug-handler 1.4.0 cbe23383749496fe0f373345208b79568e4bc248
cweagans/composer-patches 1.6.7 2e6f72a2ad8d59cd7e2b729f218bf42adb14f590
gettext/gettext v4.8.0 207719c6eae36f5ac7c2c9542f6a4d4b76307e5a
gettext/languages 2.5.0 78db2d17933f0765a102f368a6663f057162ddbd
justinrainbow/json-schema 5.2.9 44c6787311242a979fa15c704327c20e7221a0e4
mck89/peast v1.9.4 713162de51f5fc26ce716143d1c23ed8c6e2e723
mustache/mustache v2.12.0 fe8fe72e9d580591854de404cc59a1b83ca4d19e
nb/oxymel v0.1.0 cbe626ef55d5c4cc9b5e6e3904b395861ea76e3c
psr/log 1.1.2 446d54b4cb6bf489fc9d75f55843658e6f25d801
rmccue/requests v1.7.0 87932f52ffad70504d93f04f15690cf16a089546
seld/jsonlint 1.7.2 e2e5d290e4d2a4f0eb449f510071392e00e10d19
seld/phar-utils 1.0.1 7009b5139491975ef6486545a39f3e6dad5ac30a
symfony/console v2.8.50 cbcf4b5e233af15cd2bbd50dee1ccc9b7927dc12
symfony/debug v2.8.50 74251c8d50dd3be7c4ce0c7b862497cdc641a5d0
symfony/filesystem v2.8.50 7ae46872dad09dffb7fe1e93a0937097339d0080
symfony/finder v2.8.50 1444eac52273e345d9b95129bf914639305a9ba4
symfony/polyfill-ctype v1.12.0 550ebaac289296ce228a706d0867afc34687e3f4
symfony/polyfill-mbstring v1.12.0 b42a2f66e8f1b15ccf25652c3424265923eb4f17
symfony/process v2.8.50 c3591a09c78639822b0b290d44edb69bf9f05dc8
wp-cli/cache-command v2.0.3 d3264aecf0e981b61adc020f5f6664f6538b7434
wp-cli/checksum-command v2.0.3 7db66668ec116c5ccef7bc27b4354fa81b85018a
wp-cli/config-command v2.0.5 9530dc42eebcae1fde10ad9e4aad312e06267eb9
wp-cli/core-command v2.0.8 be62a9364c77a99698364a888362a8c8b9414f2f
wp-cli/cron-command v2.0.3 b6d0c8ff69cc56d5316a35a7a2fcc314c4069585
wp-cli/db-command v2.0.5 119cf31491577d0a79ad42021dfeb8ac1138342b
wp-cli/embed-command v2.0.4 f67fd41a056c6cb847e8601e058fa836b9e5d325
wp-cli/entity-command v2.0.7 0df89e4fba48177acf768bff9c00cda95a3fe5b9
wp-cli/eval-command v2.0.5 945aaebc761a694cf302985088ef6b19a4567643
wp-cli/export-command v2.0.3 a31b1777a199a8437127ad3db1b6b92e9cb5cd9b
wp-cli/extension-command v2.0.7 e397cf208c0df679656a87041bf34129e7e3d922
wp-cli/i18n-command v2.2.1 6a0582103ecf4a28b3086eac55a9fe590bd3dc96
wp-cli/import-command v2.0.2 e28a7f55138ceb53f2ff5926874d8e5582c87db8
wp-cli/language-command v2.0.5 a14a385efffba2060f947afa85f7ffd7e7cda5d7
wp-cli/maintenance-mode-command v2.0.2 3c80e731e1032607a2e9589ae6b6398e95c05b91
wp-cli/media-command v2.0.7 0b7fbee4c4aa9335b83fd5c65183808949ba1c8e
wp-cli/mustangostang-spyc 0.6.3 6aa0b4da69ce9e9a2c8402dab8d43cf32c581cc7
wp-cli/package-command v2.0.5 52fea16f3cec0577b9c417a19ebc0f328c38d853
wp-cli/php-cli-tools v0.11.11 fe9c7c44a9e1bf2196ec51dc38da0593dbf2993f
wp-cli/rewrite-command v2.0.4 3879bcbf7e695f68097cedb8415ed04915a25465
wp-cli/role-command v2.0.3 bad54a1b02331ee6460cc6a6f967e37dd91e07a3
wp-cli/scaffold-command v2.0.7 fe73e6f71c2a03908bb5ceac17a0e408544a868a
wp-cli/search-replace-command v2.0.5 d53ae0715d3701ef22826d5c7b46973a1b24e472
wp-cli/server-command v2.0.4 b0c8db803aea2133973a9a35b0d94fb62487b456
wp-cli/shell-command v2.0.4 293cc82fe6e99c0168bf834787ac5d0e17825723
wp-cli/super-admin-command v2.0.3 b3f3078d25c17ee586a5f31cb5ce3553613e85b4
wp-cli/widget-command v2.0.3 ad69fa608a30d17f8a73c94fdd64e91c7de458d2
wp-cli/wp-cli dev-master 74c949c74708e3a88ad0add70f3236c8675dfd85
wp-cli/wp-config-transformer v1.2.6 1ca98343443a8e4585865db5f50e8e6121fee70b2.4.0]�H=���[�+�e��GBMB

Zerion Mini Shell 1.0