HEX
Server: PHP/8.3.21 (Development Server)
System: wasi wasmer.sh 0.0.0 0.0.0 wasm32
User: (0)
PHP: 8.3.21
Disabled: NONE
Upload Files
File: /app/wp-cli.phar
#!/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/wp-cli.php_-�h_��y�)vendor/wp-cli/wp-cli/php/class-wp-cli.php�-�h�O� u�/vendor/wp-cli/wp-cli/php/fallback-functions.php\-�h\���'vendor/wp-cli/wp-cli/php/dispatcher.phpX-�hX��Fvendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/DeclareFallbackFunctions.phpC-�hC@*Bg�=vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/LoadExecCommand.phpW-�hWy~��>vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/DeclareMainClass.php-�h�Tj�Gvendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/IncludeFallbackAutoloader.php�-�h�|��m�:vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/LaunchRunner.php-�hA�^U�Bvendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/LoadUtilityFunctions.php)-�h)TYMݤ;vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/BootstrapStep.php�-�h�A7/�Gvendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/IncludeRequestsAutoloader.php�-�h��}3X�<vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/RunnerInstance.php�-�h��k��=vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/ConfigureRunner.php�-�h�B)Q(�Hvendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/DeclareAbstractBaseCommand.phpA-�hA�@<@�>vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/InitializeLogger.php*-�h*\��3�<vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/LoadDispatcher.php5-�h5U��4�Avendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/LoadRequiredCommand.php�-�h����ӤGvendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/RegisterFrameworkCommands.phpg-�hg�>�V�<vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/BootstrapState.php�-�h����פ@vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/InitializeContexts.php-�h0Ca��<vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/AutoloaderStep.php&	-�h&	��Dvendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/InitializeColorization.phpL-�hL��'=�Hvendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/IncludeFrameworkAutoloader.php$-�h$z���7vendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/CheckRoot.php�-�h�����Fvendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/RegisterDeferredCommands.php�-�h��k	�Fvendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/IncludePackageAutoloader.php-�h��F�Evendor/wp-cli/wp-cli/php/WP_CLI/Bootstrap/DefineProtectedCommands.php�-�h�8�>2�0vendor/wp-cli/wp-cli/php/WP_CLI/Configurator.php�+-�h�+�Αˤ2vendor/wp-cli/wp-cli/php/WP_CLI/SynopsisParser.php�-�h���
��Evendor/wp-cli/wp-cli/php/WP_CLI/Exception/NonExistentKeyException.php-�h��4�-vendor/wp-cli/wp-cli/php/WP_CLI/Extractor.php>'-�h>'�m��9vendor/wp-cli/wp-cli/php/WP_CLI/Dispatcher/Subcommand.php6-�h6��ھ�=vendor/wp-cli/wp-cli/php/WP_CLI/Dispatcher/CommandFactory.php�"-�h�"�n�?vendor/wp-cli/wp-cli/php/WP_CLI/Dispatcher/CompositeCommand.php -�h �� ڤ>vendor/wp-cli/wp-cli/php/WP_CLI/Dispatcher/CommandAddition.php#-�h#�躛�:vendor/wp-cli/wp-cli/php/WP_CLI/Dispatcher/RootCommand.phpK-�hK�yդ?vendor/wp-cli/wp-cli/php/WP_CLI/Dispatcher/CommandNamespace.php�-�h��,{��*vendor/wp-cli/wp-cli/php/WP_CLI/Runner.php��-�h��Y�#g�1vendor/wp-cli/wp-cli/php/WP_CLI/Fetchers/User.php-�h==R
�3vendor/wp-cli/wp-cli/php/WP_CLI/Fetchers/Signup.phpt-�htlYQj�1vendor/wp-cli/wp-cli/php/WP_CLI/Fetchers/Base.php�-�h��󯹤4vendor/wp-cli/wp-cli/php/WP_CLI/Fetchers/Comment.php|-�h|m���1vendor/wp-cli/wp-cli/php/WP_CLI/Fetchers/Site.php�-�h�F�)�1vendor/wp-cli/wp-cli/php/WP_CLI/Fetchers/Post.php8-�h8重�.vendor/wp-cli/wp-cli/php/WP_CLI/ComposerIO.php�-�h��мT�/vendor/wp-cli/wp-cli/php/WP_CLI/Completions.php�-�h���Τ.vendor/wp-cli/wp-cli/php/WP_CLI/ProcessRun.php(-�h(c�홤1vendor/wp-cli/wp-cli/php/WP_CLI/Loggers/Quiet.php	-�h	-n���0vendor/wp-cli/wp-cli/php/WP_CLI/Loggers/Base.php�-�h��3T�3vendor/wp-cli/wp-cli/php/WP_CLI/Loggers/Regular.php-�h�[�Ф5vendor/wp-cli/wp-cli/php/WP_CLI/Loggers/Execution.phpa-�hat�W�3vendor/wp-cli/wp-cli/php/WP_CLI/RequestsLibrary.php�-�h���Τ-vendor/wp-cli/wp-cli/php/WP_CLI/Inflector.phpE@-�hE@#7�+vendor/wp-cli/wp-cli/php/WP_CLI/Context.phpM-�hM�[NG�(vendor/wp-cli/wp-cli/php/WP_CLI/NoOp.php�-�h��!��-vendor/wp-cli/wp-cli/php/WP_CLI/FileCache.phpC"-�hC"0#/��2vendor/wp-cli/wp-cli/php/WP_CLI/ContextManager.php�-�h��Z�Ϥ7vendor/wp-cli/wp-cli/php/WP_CLI/Iterators/Exception.phpg-�hg
��4�3vendor/wp-cli/wp-cli/php/WP_CLI/Iterators/Query.php�-�h�(���3vendor/wp-cli/wp-cli/php/WP_CLI/Iterators/Table.phpd
-�hd
��p��7vendor/wp-cli/wp-cli/php/WP_CLI/Iterators/Transform.php�-�h����1vendor/wp-cli/wp-cli/php/WP_CLI/Iterators/CSV.phpY-�hY2.o�1vendor/wp-cli/wp-cli/php/WP_CLI/Context/Admin.php�-�h�
d�ɤ0vendor/wp-cli/wp-cli/php/WP_CLI/Context/Auto.php�-�h��2��/vendor/wp-cli/wp-cli/php/WP_CLI/Context/Cli.php�-�h��A�4vendor/wp-cli/wp-cli/php/WP_CLI/Context/Frontend.php�-�h�Br���.vendor/wp-cli/wp-cli/php/WP_CLI/Autoloader.php--�h-	l�Ѥ,vendor/wp-cli/wp-cli/php/WP_CLI/WpOrgApi.php� -�h� ����-vendor/wp-cli/wp-cli/php/WP_CLI/DocParser.php-�hJ�R"�0vendor/wp-cli/wp-cli/php/WP_CLI/UpgraderSkin.phpr-�hr����Avendor/wp-cli/wp-cli/php/WP_CLI/PackageManagerEventSubscriber.php�-�h�!Җ��6vendor/wp-cli/wp-cli/php/WP_CLI/WpHttpCacheManager.php�-�h�����+vendor/wp-cli/wp-cli/php/WP_CLI/Process.php�
-�h�
o��K�-vendor/wp-cli/wp-cli/php/WP_CLI/Formatter.php%-�h%�M�Mvendor/wp-cli/wp-cli/php/WP_CLI/Traverser/RecursiveDataStructureTraverser.php�-�h�O�K��5vendor/wp-cli/wp-cli/php/WP_CLI/SynopsisValidator.php�-�h����1vendor/wp-cli/wp-cli/php/WP_CLI/ExitException.phpS-�hS{uׄ�$vendor/wp-cli/wp-cli/php/boot-fs.php�-�h��d�e�&vendor/wp-cli/wp-cli/php/bootstrap.php>	-�h>	)>���(vendor/wp-cli/wp-cli/php/config-spec.php-�h� �%vendor/wp-cli/wp-cli/php/utils-wp.php�A-�h�AE�U��#vendor/wp-cli/wp-cli/php/compat.php-�h���1vendor/wp-cli/wp-cli/php/class-wp-cli-command.php�-�h�2�}ä"vendor/wp-cli/wp-cli/php/utils.phpB�-�hB�2�f��,vendor/wp-cli/wp-cli/php/wp-settings-cli.php]C-�h]Cq�U�;vendor/wp-cli/wp-cli/php/commands/src/CLI_Alias_Command.php�8-�h�8	���;vendor/wp-cli/wp-cli/php/commands/src/CLI_Cache_Command.php�-�h��n?�5vendor/wp-cli/wp-cli/php/commands/src/CLI_Command.php3b-�h3b�ڤS�6vendor/wp-cli/wp-cli/php/commands/src/Help_Command.php1-�h1w�ޠ�)vendor/wp-cli/wp-cli/php/commands/cli.php�-�h�jn���*vendor/wp-cli/wp-cli/php/commands/help.php�-�h�lD��php/boot-phar.phpG-�hGuI/�.vendor/wp-cli/mustache/bin/build_bootstrap.php6-�h6w���-vendor/wp-cli/mustache/src/Mustache/Cache.php�-�h��W��1vendor/wp-cli/mustache/src/Mustache/Exception.phpR-�hR]u��.vendor/wp-cli/mustache/src/Mustache/Engine.php1f-�h1fV���Bvendor/wp-cli/mustache/src/Mustache/Exception/RuntimeException.php�-�h�1]�ӤHvendor/wp-cli/mustache/src/Mustache/Exception/UnknownFilterException.php�-�h�CwS�Jvendor/wp-cli/mustache/src/Mustache/Exception/InvalidArgumentException.php�-�h���Q�Hvendor/wp-cli/mustache/src/Mustache/Exception/UnknownHelperException.php�-�h�ҫg4�Avendor/wp-cli/mustache/src/Mustache/Exception/SyntaxException.php�-�h��{�Ť@vendor/wp-cli/mustache/src/Mustache/Exception/LogicException.php�-�h��}A��Jvendor/wp-cli/mustache/src/Mustache/Exception/UnknownTemplateException.php�-�h�&�]��.vendor/wp-cli/mustache/src/Mustache/Parser.php�4-�h�4T�C�?vendor/wp-cli/mustache/src/Mustache/Loader/FilesystemLoader.php�-�h�L$y��:vendor/wp-cli/mustache/src/Mustache/Loader/ArrayLoader.php�-�h��!��>vendor/wp-cli/mustache/src/Mustache/Loader/CascadingLoader.php�-�h����;vendor/wp-cli/mustache/src/Mustache/Loader/StringLoader.php�-�h�<%��;vendor/wp-cli/mustache/src/Mustache/Loader/InlineLoader.php�-�h�\�	�Ivendor/wp-cli/mustache/src/Mustache/Loader/ProductionFilesystemLoader.phpf-�hf����<vendor/wp-cli/mustache/src/Mustache/Loader/MutableLoader.php�-�h���g�7vendor/wp-cli/mustache/src/Mustache/Cache/NoopCache.phpA-�hA���٤=vendor/wp-cli/mustache/src/Mustache/Cache/FilesystemCache.phph-�hh(}4�;vendor/wp-cli/mustache/src/Mustache/Cache/AbstractCache.php�-�h��Y`�8vendor/wp-cli/mustache/src/Mustache/HelperCollection.php�-�h���Ӥ/vendor/wp-cli/mustache/src/Mustache/Context.php!-�h!��>0�.vendor/wp-cli/mustache/src/Mustache/Logger.php$
-�h$
�@Y9�0vendor/wp-cli/mustache/src/Mustache/Template.php1-�h1�Mu��1vendor/wp-cli/mustache/src/Mustache/Tokenizer.php
5-�h
5�;�Ĥ=vendor/wp-cli/mustache/src/Mustache/Logger/AbstractLogger.phpv-�hvn�Y6�;vendor/wp-cli/mustache/src/Mustache/Logger/StreamLogger.php�-�h����J�2vendor/wp-cli/mustache/src/Mustache/Autoloader.php-�h=c�֤.vendor/wp-cli/mustache/src/Mustache/Source.php-�hb׶m�0vendor/wp-cli/mustache/src/Mustache/Compiler.php	a-�h	a�
GФ?vendor/wp-cli/mustache/src/Mustache/Source/FilesystemSource.php�-�h�V����4vendor/wp-cli/mustache/src/Mustache/LambdaHelper.php�-�h��pդ.vendor/wp-cli/mustache/src/Mustache/Loader.phpB-�hBQ��y�)vendor/wp-cli/process/Pipes/UnixPipes.php,-�h,q�,vendor/wp-cli/process/Pipes/WindowsPipes.phpu-�huu��.vendor/wp-cli/process/Pipes/PipesInterface.php�-�h��+���-vendor/wp-cli/process/Pipes/AbstractPipes.php�-�h���<�6vendor/wp-cli/process/Exception/ExceptionInterface.php�-�h��<��4vendor/wp-cli/process/Exception/RuntimeException.php�-�h�>H���<vendor/wp-cli/process/Exception/InvalidArgumentException.php�-�h�˅��<vendor/wp-cli/process/Exception/ProcessTimedOutException.php{-�h{4��2vendor/wp-cli/process/Exception/LogicException.php�-�h���W�:vendor/wp-cli/process/Exception/ProcessFailedException.php�-�h�P��5�*vendor/wp-cli/process/ExecutableFinder.phpO
-�hO
�t�m�&vendor/wp-cli/process/ProcessUtils.php�-�h�aXa�(vendor/wp-cli/process/ProcessBuilder.php
-�h
j'Uh�!vendor/wp-cli/process/Process.phpq�-�hq�D>�o�$vendor/wp-cli/process/PhpProcess.php�	-�h�	����-vendor/wp-cli/process/PhpExecutableFinder.phph
-�hh
���%vendor/wp-cli/process/InputStream.php	-�h	w'�Ȥ*vendor/eftec/bladeone/lib/BladeOneLang.phpm-�hmV���*vendor/eftec/bladeone/lib/BladeOneHtml.php�L-�h�L�N��0vendor/eftec/bladeone/lib/BladeOneCacheRedis.php�-�h�vq)��&vendor/eftec/bladeone/lib/BladeOne.php�-�h�$��,vendor/eftec/bladeone/lib/BladeOneCustom.php�-�h��c|	�+vendor/eftec/bladeone/lib/BladeOneCache.php+-�h+��z<�3vendor/eftec/bladeone/lib/BladeOneHtmlBootstrap.php[(-�h[(![$�@vendor/wp-cli/wp-cli/bundle/rmccue/requests/library/Requests.php�-�h���.�Bvendor/wp-cli/wp-cli/bundle/rmccue/requests/library/Deprecated.php!-�h!Z+�s�Gvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Transport/Fsockopen.php>-�h>YP��Bvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Transport/Curl.phpsL-�hsL�j;ۤ<vendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Response.php�-�h�ח���<vendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Autoload.phpw$-�hw$��r�=vendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception.phpZ-�hZ��_�Lvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Transport/Curl.phpu-�hu*����Gvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Transport.php�-�h�iA�Kvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/ArgumentCount.php�-�h��q�1�Lvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status418.php,-�h,4���Lvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status402.php�-�h�{��̤Lvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status503.php�-�h�w;���Lvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status408.php�-�h�k�TϤLvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status404.php�-�h�y�棤Lvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status416.php-�h��孤Lvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status414.php�-�h�2d���Lvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status401.php�-�h����Pvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/StatusUnknown.php�-�h����*�Lvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status410.php�-�h�η.p�Lvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status431.phpe-�he&X���Lvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status415.php�-�h���P�Lvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status411.php�-�h���[F�Lvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status413.php�-�h�h��m�Lvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status502.php�-�h�����Lvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status306.php�-�h�h$a�Lvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status304.php�-�h����Lvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status409.php�-�h�����Lvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status505.php�-�h�m����Lvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status405.php�-�h�wא�Lvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status406.php�-�h�SbդLvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status403.php�-�h����Lvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status400.php�-�h�ǿH7�Lvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status407.php�-�h�]���Lvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status412.php�-�h�`��Lvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status305.php�-�h�����Lvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status511.phpe-�he���ˤLvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status417.php�-�h�f�+�Lvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status429.phps-�hs��(ɤLvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status504.php�-�h�k�f�Lvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status501.php�-�h�mk��Lvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status428.phpG-�hG^��:�Lvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http/Status500.php�-�h��� �Mvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/InvalidArgument.phpR-�hR��ˤBvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Exception/Http.php-�h&��7vendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Ssl.php1-�h1w	׺�<vendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Requests.phpф-�hф�㋵�9vendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Hooks.phpj-�hj]>�=vendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Transport.php-�h,��S�Lvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Utility/FilteredIterator.phpm-�hm����Uvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Utility/CaseInsensitiveDictionary.php�	-�h�	�5L�Jvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Utility/InputValidator.php�	-�h�	^�	��8vendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Ipv6.php-�h�v�'�8vendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Port.php�-�h���Ҥ>vendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Proxy/Http.phpy-�hy�g�^�7vendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Iri.php�q-�h�qh���8vendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Auth.php\-�h\���>vendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Auth/Basic.php�	-�h�	��Z�;vendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Session.php%#-�h%#L��Dvendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Response/Headers.php-�h^��ͤ:vendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Cookie.php<-�h<�JMؤ?vendor/wp-cli/wp-cli/bundle/rmccue/requests/src/IdnaEncoder.php�0-�h�0��SϤ>vendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Cookie/Jar.php-�h84��>vendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Capability.php�-�h�ޣ@�9vendor/wp-cli/wp-cli/bundle/rmccue/requests/src/Proxy.phpc-�hc�Մe�?vendor/wp-cli/wp-cli/bundle/rmccue/requests/src/HookManager.php�-�h���C�vendor/composer/installed.phph�-�hh�;�e�vendor/composer/ClassLoader.php�>-�h�>�5Ky�2vendor/composer/spdx-licenses/src/SpdxLicenses.phpw&-�hw&eK��*vendor/composer/composer/src/bootstrap.php-�h��u)�Cvendor/composer/composer/src/Composer/Console/GithubActionError.php$	-�h$	T�H�Evendor/composer/composer/src/Composer/Console/HtmlOutputFormatter.php�-�h�:B�=vendor/composer/composer/src/Composer/Console/Application.phpNd-�hNd�B/vendor/composer/composer/src/Composer/Cache.php�)-�h�)��,�2vendor/composer/composer/src/Composer/Util/Git.php�V-�h�V{�0�8vendor/composer/composer/src/Composer/Util/Bitbucket.phpr$-�hr$�AZԤ=vendor/composer/composer/src/Composer/Util/ComposerMirror.php
-�h
�D��1vendor/composer/composer/src/Composer/Util/Hg.phpU
-�hU
�j� �3vendor/composer/composer/src/Composer/Util/Loop.php-�h�=,ͤ>vendor/composer/composer/src/Composer/Util/ProcessExecutor.php�<-�h�<I�
��9vendor/composer/composer/src/Composer/Util/Filesystem.php|q-�h|q4�g��<vendor/composer/composer/src/Composer/Util/PackageSorter.phpH-�hH)�8vendor/composer/composer/src/Composer/Util/TlsHelper.phpq-�hqz w�7vendor/composer/composer/src/Composer/Util/Perforce.php�Q-�h�Q�<+0�7vendor/composer/composer/src/Composer/Util/Silencer.php�-�h��>��=vendor/composer/composer/src/Composer/Util/NoProxyPattern.php�,-�h�,���g�Bvendor/composer/composer/src/Composer/Util/Http/CurlDownloader.php�l-�h�lm
�<vendor/composer/composer/src/Composer/Util/Http/Response.php�-�h��ڦ�?vendor/composer/composer/src/Composer/Util/Http/ProxyHelper.php�-�h�;:h�@vendor/composer/composer/src/Composer/Util/Http/RequestProxy.php�-�h��,:N�@vendor/composer/composer/src/Composer/Util/Http/ProxyManager.php�-�h�t��¤@vendor/composer/composer/src/Composer/Util/Http/CurlResponse.php�-�h��t��9vendor/composer/composer/src/Composer/Util/SyncHelper.php�	-�h�	�ۊd�2vendor/composer/composer/src/Composer/Util/Url.php�-�h���~[�2vendor/composer/composer/src/Composer/Util/Svn.phpi(-�hi(�mޣ�=vendor/composer/composer/src/Composer/Util/HttpDownloader.phpNH-�hNHUM��2vendor/composer/composer/src/Composer/Util/Tar.phpX-�hX�W	W�;vendor/composer/composer/src/Composer/Util/ErrorHandler.php�-�h�R�9vendor/composer/composer/src/Composer/Util/AuthHelper.php�7-�h�7/��>vendor/composer/composer/src/Composer/Util/ConfigValidator.php�"-�h�"��^�2vendor/composer/composer/src/Composer/Util/Zip.php�
-�h�
f�U:�5vendor/composer/composer/src/Composer/Util/GitHub.phpw-�hw�<�@�?vendor/composer/composer/src/Composer/Util/RemoteFilesystem.php
�-�h
��x�8vendor/composer/composer/src/Composer/Util/IniHelper.phpc-�hc�,��?vendor/composer/composer/src/Composer/Util/MetadataMinifier.phpq-�hq�tX�7vendor/composer/composer/src/Composer/Util/Platform.php�-�h�uZ�5vendor/composer/composer/src/Composer/Util/GitLab.phps-�hs���.�Cvendor/composer/composer/src/Composer/Util/StreamContextFactory.phpF%-�hF%{)Az�Rvendor/composer/composer/src/Composer/Exception/IrrecoverableDownloadException.php�-�h���-�Bvendor/composer/composer/src/Composer/Exception/NoSslException.php�-�h�8M5�0vendor/composer/composer/src/Composer/Config.php�X-�h�X]`�I�>vendor/composer/composer/src/Composer/Autoload/ClassLoader.php�>-�h�>�5Ky�Avendor/composer/composer/src/Composer/Autoload/PhpFileCleaner.php
-�h
6�3�Dvendor/composer/composer/src/Composer/Autoload/AutoloadGenerator.php��-�h�����'�Dvendor/composer/composer/src/Composer/Autoload/ClassMapGenerator.php3-�h3ID��Cvendor/composer/composer/src/Composer/Plugin/PreCommandRunEvent.phpM-�hMÒ@	�Fvendor/composer/composer/src/Composer/Plugin/PostFileDownloadEvent.php�
-�h�
��Q��Kvendor/composer/composer/src/Composer/Plugin/Capability/CommandProvider.php_-�h_�μ�Fvendor/composer/composer/src/Composer/Plugin/Capability/Capability.php�-�h�Y9�3�=vendor/composer/composer/src/Composer/Plugin/PluginEvents.php'-�h'��=�Cvendor/composer/composer/src/Composer/Plugin/PrePoolCreateEvent.php�-�h��r��=vendor/composer/composer/src/Composer/Plugin/CommandEvent.php�-�h���Y>�>vendor/composer/composer/src/Composer/Plugin/PluginManager.php��-�h������@vendor/composer/composer/src/Composer/Plugin/PluginInterface.php�-�h�L3]�Evendor/composer/composer/src/Composer/Plugin/PreFileDownloadEvent.php�-�h���R�Gvendor/composer/composer/src/Composer/Plugin/PluginBlockedException.php�-�h�%�:��8vendor/composer/composer/src/Composer/Plugin/Capable.php�-�h�'�}�=vendor/composer/composer/src/Composer/SelfUpdate/Versions.php�-�h�U���9vendor/composer/composer/src/Composer/SelfUpdate/Keys.php�-�h�D��;vendor/composer/composer/src/Composer/InstalledVersions.php�>-�h�>{`���=vendor/composer/composer/src/Composer/Script/ScriptEvents.php'-�h'� �Ƥ6vendor/composer/composer/src/Composer/Script/Event.php[-�h[&m�ԤAvendor/composer/composer/src/Composer/Config/JsonConfigSource.phpB)-�hB)P��Fvendor/composer/composer/src/Composer/Config/ConfigSourceInterface.php	-�h	���o�Mvendor/composer/composer/src/Composer/Question/StrictConfirmationQuestion.php�
-�h�
;�Fvendor/composer/composer/src/Composer/Json/JsonValidationException.phpe-�he�y̤>vendor/composer/composer/src/Composer/Json/JsonManipulator.php+Q-�h+Q@���7vendor/composer/composer/src/Composer/Json/JsonFile.php�+-�h�+�Q�٤<vendor/composer/composer/src/Composer/Json/JsonFormatter.php�-�h��D|�Bvendor/composer/composer/src/Composer/Command/ProhibitsCommand.php�-�h���DD�=vendor/composer/composer/src/Composer/Command/ShowCommand.php��-�h��:
���>vendor/composer/composer/src/Composer/Command/AboutCommand.php-�hq�y9�@vendor/composer/composer/src/Composer/Command/ArchiveCommand.php�-�h���%z�Avendor/composer/composer/src/Composer/Command/SuggestsCommand.php�-�h���Qc�=vendor/composer/composer/src/Composer/Command/FundCommand.php$-�h$2�C�Jvendor/composer/composer/src/Composer/Command/CheckPlatformReqsCommand.phpI-�hI`KY��?vendor/composer/composer/src/Composer/Command/UpdateCommand.php�A-�h�A�J~X�?vendor/composer/composer/src/Composer/Command/GlobalCommand.php�-�h�z@~�Avendor/composer/composer/src/Composer/Command/OutdatedCommand.php�-�h��LBvendor/composer/composer/src/Composer/Command/RunScriptCommand.phpg-�hg�ⶤ?vendor/composer/composer/src/Composer/Command/ConfigCommand.phpq�-�hq�FB�@vendor/composer/composer/src/Composer/Command/DependsCommand.php-�h�m��=vendor/composer/composer/src/Composer/Command/BaseCommand.phps(-�hs(s?�\�=vendor/composer/composer/src/Composer/Command/InitCommand.php��-�h���23,�Avendor/composer/composer/src/Composer/Command/DiagnoseCommand.php�p-�h�p�k�2�?vendor/composer/composer/src/Composer/Command/RemoveCommand.php-8-�h-8Y��U�@vendor/composer/composer/src/Composer/Command/InstallCommand.php5-�h5�ȯ�Cvendor/composer/composer/src/Composer/Command/ClearCacheCommand.php�-�h�X0<ˤ@vendor/composer/composer/src/Composer/Command/RequireCommand.phpOY-�hOY#����=vendor/composer/composer/src/Composer/Command/ExecCommand.php�
-�h�
Β��Avendor/composer/composer/src/Composer/Command/LicensesCommand.php�-�h���zW�=vendor/composer/composer/src/Composer/Command/HomeCommand.php-�h�o�L�?vendor/composer/composer/src/Composer/Command/SearchCommand.php�-�h��A���Bvendor/composer/composer/src/Composer/Command/ReinstallCommand.phph!-�hh!��n��Dvendor/composer/composer/src/Composer/Command/ScriptAliasCommand.php�-�h�^f�̤Evendor/composer/composer/src/Composer/Command/DumpAutoloadCommand.php�-�h��(4��Cvendor/composer/composer/src/Composer/Command/SelfUpdateCommand.php�f-�h�f�u��Fvendor/composer/composer/src/Composer/Command/CreateProjectCommand.php�`-�h�`x�b�Gvendor/composer/composer/src/Composer/Command/BaseDependencyCommand.php�#-�h�#�&��?vendor/composer/composer/src/Composer/Command/StatusCommand.php/!-�h/!�����Avendor/composer/composer/src/Composer/Command/ValidateCommand.php,--�h,-����kvendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterFactory.php�-�h�~�
�qvendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreNothingPlatformRequirementFilter.php'-�h'u��8�mvendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/PlatformRequirementFilterInterface.php�-�h�Vmvendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreAllPlatformRequirementFilter.phpu-�hu
v
��nvendor/composer/composer/src/Composer/Filter/PlatformRequirementFilter/IgnoreListPlatformRequirementFilter.php�	-�h�	TɅ�3vendor/composer/composer/src/Composer/IO/BaseIO.phpt-�htx��5vendor/composer/composer/src/Composer/IO/BufferIO.php�-�h�IL[�3vendor/composer/composer/src/Composer/IO/NullIO.php�-�h���D�6vendor/composer/composer/src/Composer/IO/ConsoleIO.php*-�h*j&U��8vendor/composer/composer/src/Composer/IO/IOInterface.php -�h ��Z�:vendor/composer/composer/src/Composer/Platform/Version.php@-�h@�.-k�:vendor/composer/composer/src/Composer/Platform/Runtime.phpN	-�hN	��*`�?vendor/composer/composer/src/Composer/Platform/HhvmDetector.php-�h�c˲�2vendor/composer/composer/src/Composer/Composer.php	-�h	��*�Ovendor/composer/composer/src/Composer/DependencyResolver/SolverBugException.php,-�h,f�o�bvendor/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasInstalledOperation.php-�hD
ФVvendor/composer/composer/src/Composer/DependencyResolver/Operation/SolverOperation.php-�hi`�<�dvendor/composer/composer/src/Composer/DependencyResolver/Operation/MarkAliasUninstalledOperation.php
-�h
8�ˤYvendor/composer/composer/src/Composer/DependencyResolver/Operation/OperationInterface.php�-�h�
Y/z�Yvendor/composer/composer/src/Composer/DependencyResolver/Operation/UninstallOperation.php*-�h*.�n�Wvendor/composer/composer/src/Composer/DependencyResolver/Operation/InstallOperation.php@-�h@$Z@�Vvendor/composer/composer/src/Composer/DependencyResolver/Operation/UpdateOperation.phpN-�hN�}��Cvendor/composer/composer/src/Composer/DependencyResolver/Solver.php�h-�h�h����Jvendor/composer/composer/src/Composer/DependencyResolver/Rule2Literals.php|
-�h|
���Hvendor/composer/composer/src/Composer/DependencyResolver/GenericRule.php�-�h�P�{��Hvendor/composer/composer/src/Composer/DependencyResolver/PoolBuilder.php�y-�h�y�k|�Jvendor/composer/composer/src/Composer/DependencyResolver/RuleWatchNode.phpt-�ht�g���Dvendor/composer/composer/src/Composer/DependencyResolver/RuleSet.php-�h)�y�Hvendor/composer/composer/src/Composer/DependencyResolver/Transaction.php�6-�h�6����Qvendor/composer/composer/src/Composer/DependencyResolver/LocalRepoTransaction.php-�h,4��Kvendor/composer/composer/src/Composer/DependencyResolver/RuleWatchChain.php�-�h��ջ�Jvendor/composer/composer/src/Composer/DependencyResolver/PoolOptimizer.php�M-�h�Mb*�Dvendor/composer/composer/src/Composer/DependencyResolver/Problem.php�l-�h�l_�Sl�Avendor/composer/composer/src/Composer/DependencyResolver/Rule.php�O-�h�O�dk�Lvendor/composer/composer/src/Composer/DependencyResolver/RuleSetIterator.phpp-�hp��k�Nvendor/composer/composer/src/Composer/DependencyResolver/MultiConflictRule.php7
-�h7
�lX��Mvendor/composer/composer/src/Composer/DependencyResolver/RuleSetGenerator.php�4-�h�4�;�Fvendor/composer/composer/src/Composer/DependencyResolver/Decisions.php�-�h�=�ΤJvendor/composer/composer/src/Composer/DependencyResolver/DefaultPolicy.php�-�h�ZŤAvendor/composer/composer/src/Composer/DependencyResolver/Pool.php�!-�h�!�"gp�Lvendor/composer/composer/src/Composer/DependencyResolver/LockTransaction.php_-�h_DX���Tvendor/composer/composer/src/Composer/DependencyResolver/SolverProblemsException.php-�h�%�ݤKvendor/composer/composer/src/Composer/DependencyResolver/RuleWatchGraph.phpW-�hWA�\ФDvendor/composer/composer/src/Composer/DependencyResolver/Request.php* -�h* 5&>w�Lvendor/composer/composer/src/Composer/DependencyResolver/PolicyInterface.php�-�h��Pj�=vendor/composer/composer/src/Composer/Package/RootPackage.php�
-�h�
z���Avendor/composer/composer/src/Composer/Package/CompletePackage.phpU-�hUdo�֤Cvendor/composer/composer/src/Composer/Package/Comparer/Comparer.php7-�h7J鱄�6vendor/composer/composer/src/Composer/Package/Link.php�-�h�O��E�Pvendor/composer/composer/src/Composer/Package/Loader/InvalidPackageException.php&-�h&�I��Hvendor/composer/composer/src/Composer/Package/Loader/LoaderInterface.phpz-�hz��3H�Dvendor/composer/composer/src/Composer/Package/Loader/ArrayLoader.php�E-�h�E�����Nvendor/composer/composer/src/Composer/Package/Loader/ValidatingArrayLoader.php^q-�h^q?�t�Cvendor/composer/composer/src/Composer/Package/Loader/JsonLoader.php�-�h��Y��Jvendor/composer/composer/src/Composer/Package/Loader/RootPackageLoader.php�,-�h�,Y*�ޤBvendor/composer/composer/src/Composer/Package/PackageInterface.php�--�h�-�_���Hvendor/composer/composer/src/Composer/Package/Version/VersionGuesser.phpr@-�hr@X����Ivendor/composer/composer/src/Composer/Package/Version/VersionSelector.php�(-�h�(Zb]�Ivendor/composer/composer/src/Composer/Package/Version/StabilityFilter.php)-�h)?Zՠ�Gvendor/composer/composer/src/Composer/Package/Version/VersionParser.php�-�h�3�Mդ=vendor/composer/composer/src/Composer/Package/BasePackage.php�-�h��$�p�9vendor/composer/composer/src/Composer/Package/Package.php
E-�h
E�<�2�Dvendor/composer/composer/src/Composer/Package/Dumper/ArrayDumper.php3-�h3�#���>vendor/composer/composer/src/Composer/Package/AliasPackage.php%-�h%�+��Ivendor/composer/composer/src/Composer/Package/Archiver/ArchiveManager.php�-�h��q�ۤLvendor/composer/composer/src/Composer/Package/Archiver/BaseExcludeFilter.php�-�h�/D�Kvendor/composer/composer/src/Composer/Package/Archiver/GitExcludeFilter.php�-�h��̀դPvendor/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFilter.php]-�h]a���Fvendor/composer/composer/src/Composer/Package/Archiver/ZipArchiver.php-�h��).�Pvendor/composer/composer/src/Composer/Package/Archiver/ArchivableFilesFinder.phpj-�hj�_]��Lvendor/composer/composer/src/Composer/Package/Archiver/ArchiverInterface.phpj-�hj]aC�Gvendor/composer/composer/src/Composer/Package/Archiver/PharArchiver.php-�hR}���Pvendor/composer/composer/src/Composer/Package/Archiver/ComposerExcludeFilter.phpZ-�hZ��k�Bvendor/composer/composer/src/Composer/Package/RootAliasPackage.php�-�h���;+�Fvendor/composer/composer/src/Composer/Package/CompleteAliasPackage.phpp-�hp�N\��8vendor/composer/composer/src/Composer/Package/Locker.phpE-�hE;���Fvendor/composer/composer/src/Composer/Package/RootPackageInterface.php1-�h1;��A�Jvendor/composer/composer/src/Composer/Package/CompletePackageInterface.php�-�h��G���2vendor/composer/composer/src/Composer/Compiler.phpm0-�hm0�0��1vendor/composer/composer/src/Composer/Factory.php0x-�h0x(���Ivendor/composer/composer/src/Composer/EventDispatcher/EventDispatcher.phpD^-�hD^�7D�Rvendor/composer/composer/src/Composer/EventDispatcher/ScriptExecutionException.php�-�h�\!'Ƥ?vendor/composer/composer/src/Composer/EventDispatcher/Event.php�-�h�JvU��Rvendor/composer/composer/src/Composer/EventDispatcher/EventSubscriberInterface.php\-�h\���*�3vendor/composer/composer/src/Composer/Installer.php��-�h�������Cvendor/composer/composer/src/Composer/Installer/InstallerEvents.php{-�h{��3�Bvendor/composer/composer/src/Composer/Installer/InstallerEvent.php�-�h�~�,�Avendor/composer/composer/src/Composer/Installer/PackageEvents.php�-�h�Z�V��Hvendor/composer/composer/src/Composer/Installer/MetapackageInstaller.php�-�h��{�Cvendor/composer/composer/src/Composer/Installer/BinaryInstaller.php�9-�h�9ˎj2�Kvendor/composer/composer/src/Composer/Installer/BinaryPresenceInterface.php�-�h������Gvendor/composer/composer/src/Composer/Installer/InstallationManager.php�b-�h�b��u#�Mvendor/composer/composer/src/Composer/Installer/SuggestedPackagesReporter.php!-�h!�K)S�Avendor/composer/composer/src/Composer/Installer/NoopInstaller.php-�h����Fvendor/composer/composer/src/Composer/Installer/InstallerInterface.php�-�h��O�h�Dvendor/composer/composer/src/Composer/Installer/ProjectInstaller.php�
-�h�
�3+��Dvendor/composer/composer/src/Composer/Installer/LibraryInstaller.phpA*-�hA*t\Ƭ�Cvendor/composer/composer/src/Composer/Installer/PluginInstaller.php,-�h,�����@vendor/composer/composer/src/Composer/Installer/PackageEvent.phpM
-�hM
?y�%�Gvendor/composer/composer/src/Composer/Repository/ComposerRepository.php9�-�h9��T��Gvendor/composer/composer/src/Composer/Repository/PlatformRepository.php�~-�h�~���[�Jvendor/composer/composer/src/Composer/Repository/RootPackageRepository.php
-�h
bS�"�Hvendor/composer/composer/src/Composer/Repository/CompositeRepository.phpk-�hk!`U�Fvendor/composer/composer/src/Composer/Repository/PackageRepository.php-�h�~�ФCvendor/composer/composer/src/Composer/Repository/PathRepository.php�-�h���Hvendor/composer/composer/src/Composer/Repository/InstalledRepository.php�2-�h�2�8h�Gvendor/composer/composer/src/Composer/Repository/ArtifactRepository.php_-�h_1���Dvendor/composer/composer/src/Composer/Repository/ArrayRepository.php�)-�h�)��bZ�Evendor/composer/composer/src/Composer/Repository/FilterRepository.phpG-�hG*��Mvendor/composer/composer/src/Composer/Repository/InstalledArrayRepository.php�-�h��i��Bvendor/composer/composer/src/Composer/Repository/VcsRepository.php�Q-�h�Q(�u�Rvendor/composer/composer/src/Composer/Repository/InstalledFilesystemRepository.php�-�h��ޡx�Hvendor/composer/composer/src/Composer/Repository/RepositoryInterface.php�-�h���9�Jvendor/composer/composer/src/Composer/Repository/VersionCacheInterface.php�-�h��m�Y�Pvendor/composer/composer/src/Composer/Repository/WritableRepositoryInterface.php-�h)�놤Hvendor/composer/composer/src/Composer/Repository/LockArrayRepository.phpm-�hm�!��Lvendor/composer/composer/src/Composer/Repository/WritableArrayRepository.php	-�h	��O�Ovendor/composer/composer/src/Composer/Repository/InvalidRepositoryException.php�-�h���$�Fvendor/composer/composer/src/Composer/Repository/RepositoryFactory.php�-�h�Wla?�Tvendor/composer/composer/src/Composer/Repository/ConfigurableRepositoryInterface.php-�h���Fvendor/composer/composer/src/Composer/Repository/RepositoryManager.php�-�h�g}��Pvendor/composer/composer/src/Composer/Repository/RepositorySecurityException.php�-�h�<9�Cvendor/composer/composer/src/Composer/Repository/PearRepository.php2-�h2���'�Ivendor/composer/composer/src/Composer/Repository/FilesystemRepository.php\7-�h\7t�{�Bvendor/composer/composer/src/Composer/Repository/RepositorySet.php�2-�h�2���Bvendor/composer/composer/src/Composer/Repository/Vcs/VcsDriver.php�-�h�7R�m�Kvendor/composer/composer/src/Composer/Repository/Vcs/GitBitbucketDriver.php@-�h@Zkn�Bvendor/composer/composer/src/Composer/Repository/Vcs/SvnDriver.php�0-�h�0�;��Bvendor/composer/composer/src/Composer/Repository/Vcs/GitDriver.php�-�h�
�RԤEvendor/composer/composer/src/Composer/Repository/Vcs/GitHubDriver.php�S-�h�S
y�j�Kvendor/composer/composer/src/Composer/Repository/Vcs/VcsDriverInterface.php%
-�h%
�%��Evendor/composer/composer/src/Composer/Repository/Vcs/GitLabDriver.php3O-�h3O.\�Evendor/composer/composer/src/Composer/Repository/Vcs/FossilDriver.php-�hdr�Avendor/composer/composer/src/Composer/Repository/Vcs/HgDriver.php,-�h,�*YɤGvendor/composer/composer/src/Composer/Repository/Vcs/PerforceDriver.php-�h�h�c�Qvendor/composer/composer/src/Composer/Repository/InstalledRepositoryInterface.phpd-�hdY8O�Hvendor/composer/composer/src/Composer/Downloader/FilesystemException.php#-�h#Q{�(�Qvendor/composer/composer/src/Composer/Downloader/MaxFileSizeExceededException.phpo-�ho�:���Lvendor/composer/composer/src/Composer/Downloader/DvcsDownloaderInterface.php-�h�v�(�Bvendor/composer/composer/src/Composer/Downloader/TarDownloader.php$-�h$���3�Gvendor/composer/composer/src/Composer/Downloader/TransportException.php)-�h)4N�Gvendor/composer/composer/src/Composer/Downloader/PerforceDownloader.php/
-�h/
��:�Evendor/composer/composer/src/Composer/Downloader/FossilDownloader.php�-�h������Bvendor/composer/composer/src/Composer/Downloader/RarDownloader.php�	-�h�	�K��Avendor/composer/composer/src/Composer/Downloader/HgDownloader.php�
-�h�
�|p�Bvendor/composer/composer/src/Composer/Downloader/GitDownloader.php�c-�h�c��ۤHvendor/composer/composer/src/Composer/Downloader/DownloaderInterface.php�-�h�x��D�Cvendor/composer/composer/src/Composer/Downloader/FileDownloader.phpwL-�hwL�����Fvendor/composer/composer/src/Composer/Downloader/ArchiveDownloader.php�!-�h�!���M�Rvendor/composer/composer/src/Composer/Downloader/VcsCapableDownloaderInterface.php-�h���Bvendor/composer/composer/src/Composer/Downloader/VcsDownloader.phpJ2-�hJ2�{�Cvendor/composer/composer/src/Composer/Downloader/GzipDownloader.phpx-�hx�˕��Cvendor/composer/composer/src/Composer/Downloader/PathDownloader.php-2-�h-2��d�Jvendor/composer/composer/src/Composer/Downloader/ChangeReportInterface.php�-�h��)Ÿ�Cvendor/composer/composer/src/Composer/Downloader/PharDownloader.php-�h��`�Bvendor/composer/composer/src/Composer/Downloader/ZipDownloader.php0-�h0{H�ѤDvendor/composer/composer/src/Composer/Downloader/DownloadManager.phpa>-�ha>��Bvendor/composer/composer/src/Composer/Downloader/SvnDownloader.php�"-�h�"8+�Avendor/composer/composer/src/Composer/Downloader/XzDownloader.php-�h���ʤ'vendor/composer/semver/src/Interval.php~-�h~E|�V�=vendor/composer/semver/src/Constraint/MatchNoneConstraint.php�-�h�?T>��4vendor/composer/semver/src/Constraint/Constraint.php�1-�h�1��.�<vendor/composer/semver/src/Constraint/MatchAllConstraint.php�-�h��D��/vendor/composer/semver/src/Constraint/Bound.phpe
-�he
�M��9vendor/composer/semver/src/Constraint/MultiConstraint.phpA%-�hA%��w��=vendor/composer/semver/src/Constraint/ConstraintInterface.php�-�h���vx�/vendor/composer/semver/src/CompilingMatcher.php -�h }~�@�)vendor/composer/semver/src/Comparator.phpD
-�hD
Ș܁�%vendor/composer/semver/src/Semver.php?
-�h?
=��(vendor/composer/semver/src/Intervals.php�O-�h�O3���,vendor/composer/semver/src/VersionParser.phpAV-�hAVn�!Z�*vendor/composer/ca-bundle/src/CaBundle.php�E-�h�E�Ƥ!vendor/composer/autoload_psr4.phpd
-�hd
��%vendor/composer/InstalledVersions.php�>-�h�>{`���"vendor/composer/platform_check.php�-�h�T��t�:vendor/composer/metadata-minifier/src/MetadataMinifier.php�
-�h�
�l�3�'vendor/composer/autoload_namespaces.php-�h?v]g�"vendor/composer/autoload_files.php�-�h��g~e�%vendor/composer/autoload_classmap.php�9-�h�9���F�!vendor/composer/autoload_real.phpE
-�hE
y򡗤-vendor/composer/xdebug-handler/src/Status.php+-�h+&e�4vendor/composer/xdebug-handler/src/XdebugHandler.php�U-�h�U_Q�0vendor/composer/xdebug-handler/src/PhpConfig.php�-�h�Q=D��.vendor/composer/xdebug-handler/src/Process.phpw-�hw�ð
�3vendor/composer/pcre/src/MatchWithOffsetsResult.php�-�h�pa8w�*vendor/composer/pcre/src/ReplaceResult.php�-�h��K�2�!vendor/composer/pcre/src/Preg.php>*-�h>*6%
Ƥ(vendor/composer/pcre/src/MatchResult.php�-�h����+vendor/composer/pcre/src/MatchAllResult.phpv-�hv��*:�"vendor/composer/pcre/src/Regex.phpa-�haX�~�6vendor/composer/pcre/src/MatchAllWithOffsetsResult.php�-�h���-�*vendor/composer/pcre/src/PcreException.php�-�h�e,��#vendor/composer/autoload_static.php�o-�h�oA�7�.vendor/symfony/polyfill-mbstring/bootstrap.php�-�h�m�|D�-vendor/symfony/polyfill-mbstring/Mbstring.phpn-�hnp^�C�Fvendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php9-�h9>|zK�@vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php�`-�h�`�S�@vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php�_-�h�_Z����Dvendor/symfony/polyfill-mbstring/Resources/mb_convert_variables.php8?-�h?�6vendor/symfony/finder/Exception/ExceptionInterface.php"-�h"ڀfW�9vendor/symfony/finder/Exception/AccessDeniedException.php�-�h��cWޤ vendor/symfony/finder/Finder.php{N-�h{N�I��9vendor/symfony/finder/Iterator/FilenameFilterIterator.php�-�h��p��<vendor/symfony/finder/Iterator/FilecontentFilterIterator.php�-�h�r�~�=vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php.-�h.�sӤ9vendor/symfony/finder/Iterator/FileTypeFilterIterator.php>-�h>p��Y�Avendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php�	-�h�	t�褤7vendor/symfony/finder/Iterator/CustomFilterIterator.php�-�h��� 
�5vendor/symfony/finder/Iterator/PathFilterIterator.php�-�h�}���=vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.phpz-�hz6�즤1vendor/symfony/finder/Iterator/FilterIterator.php�-�h�VASs�3vendor/symfony/finder/Iterator/SortableIterator.php@
-�h@
�[�:vendor/symfony/finder/Iterator/DateRangeFilterIterator.php�-�h�$99�;vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php�-�h��*��:vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php�-�h�8K��vendor/symfony/finder/Glob.php�-�h����{�5vendor/symfony/finder/Comparator/NumberComparator.php
-�h
����/vendor/symfony/finder/Comparator/Comparator.php-�h~4�ؤ3vendor/symfony/finder/Comparator/DateComparator.php�-�h���Gä%vendor/symfony/finder/SplFileInfo.phpW-�hW!tVb�9vendor/symfony/console/Formatter/OutputFormatterStyle.php2-�h2S���Bvendor/symfony/console/Formatter/OutputFormatterStyleInterface.php<-�h<���Z�4vendor/symfony/console/Formatter/OutputFormatter.php�-�h�N��x�>vendor/symfony/console/Formatter/OutputFormatterStyleStack.php�	-�h�	���Ҥ=vendor/symfony/console/Formatter/OutputFormatterInterface.php�-�h�#7�u�/vendor/symfony/console/Output/ConsoleOutput.php�-�h��JDb�(vendor/symfony/console/Output/Output.php{-�h{�7�,vendor/symfony/console/Output/NullOutput.phpn-�hnt�D��0vendor/symfony/console/Output/BufferedOutput.phpI-�hI�\��8vendor/symfony/console/Output/ConsoleOutputInterface.php�-�h�����.vendor/symfony/console/Output/StreamOutput.phpo-�ho�~�1vendor/symfony/console/Output/OutputInterface.php
-�h
���&�9vendor/symfony/console/Descriptor/DescriptorInterface.php�-�h�P�Z��3vendor/symfony/console/Descriptor/XmlDescriptor.php�#-�h�#��dK�<vendor/symfony/console/Descriptor/ApplicationDescription.php�-�h�����8vendor/symfony/console/Descriptor/MarkdownDescriptor.php�-�h�f 0>�4vendor/symfony/console/Descriptor/TextDescriptor.phpf0-�hf0��W�0vendor/symfony/console/Descriptor/Descriptor.phpd-�hd���T�4vendor/symfony/console/Descriptor/JsonDescriptor.php-�hn��7vendor/symfony/console/Exception/ExceptionInterface.php�-�h����U�5vendor/symfony/console/Exception/RuntimeException.php�-�h��*b�=vendor/symfony/console/Exception/InvalidArgumentException.php�-�h��u i�=vendor/symfony/console/Exception/CommandNotFoundException.php�-�h�.�o��;vendor/symfony/console/Exception/InvalidOptionException.php�-�h��;�3vendor/symfony/console/Exception/LogicException.php�-�h�SML��Dvendor/symfony/console/DependencyInjection/AddConsoleCommandPass.php�-�h�A|\�(vendor/symfony/console/ConsoleEvents.php!-�h!���6vendor/symfony/console/EventListener/ErrorListener.php�
-�h�
e?�ߤ0vendor/symfony/console/Helper/TableSeparator.php-�h&��
�+vendor/symfony/console/Helper/HelperSet.php�	-�h�	�3C�(vendor/symfony/console/Helper/Helper.php�-�h�}Ku��0vendor/symfony/console/Helper/QuestionHelper.phpB-�hB�ݗ^�+vendor/symfony/console/Helper/TableCell.phpR-�hR�J{��6vendor/symfony/console/Helper/DebugFormatterHelper.phpH-�hH]R`A�2vendor/symfony/console/Helper/DescriptorHelper.php�	-�h�	����7vendor/symfony/console/Helper/SymfonyQuestionHelper.php�-�h�9���1vendor/symfony/console/Helper/HelperInterface.phpp-�hp���n�/vendor/symfony/console/Helper/ProcessHelper.php�-�h��t��3vendor/symfony/console/Helper/ProgressIndicator.php�-�h��ДP�,vendor/symfony/console/Helper/TableStyle.php{-�h{�pn�1vendor/symfony/console/Helper/FormatterHelper.php�
-�h�

U���2vendor/symfony/console/Helper/InputAwareHelper.php�-�h����-vendor/symfony/console/Helper/ProgressBar.php?C-�h?C8���'vendor/symfony/console/Helper/Table.php�M-�h�M�!<�,vendor/symfony/console/Question/Question.php�-�h��Ί��2vendor/symfony/console/Question/ChoiceQuestion.php]-�h]�5�'�8vendor/symfony/console/Question/ConfirmationQuestion.php#-�h#ρ��=vendor/symfony/console/CommandLoader/FactoryCommandLoader.php7-�h7�m_�?vendor/symfony/console/CommandLoader/ContainerCommandLoader.php>-�h>#��*�?vendor/symfony/console/CommandLoader/CommandLoaderInterface.php�-�h����#vendor/symfony/console/Terminal.php�-�h�u��/vendor/symfony/console/Logger/ConsoleLogger.php�-�h���U�.vendor/symfony/console/Command/HelpCommand.php?	-�h?	���'�0vendor/symfony/console/Command/LockableTrait.php�-�h���O�*vendor/symfony/console/Command/Command.php�L-�h�LF��.vendor/symfony/console/Command/ListCommand.php�	-�h�	�#�-vendor/symfony/console/Style/SymfonyStyle.phpO/-�hO/Az�/vendor/symfony/console/Style/StyleInterface.php(-�h(�5 �,vendor/symfony/console/Style/OutputStyle.php�-�h����~�/vendor/symfony/console/Tester/CommandTester.php�-�h��Dd��3vendor/symfony/console/Tester/ApplicationTester.php
-�h
Z���4vendor/symfony/console/Event/ConsoleCommandEvent.php%-�h%{˾�6vendor/symfony/console/Event/ConsoleTerminateEvent.php-�h{e��2vendor/symfony/console/Event/ConsoleErrorEvent.php�-�h�l���6vendor/symfony/console/Event/ConsoleExceptionEvent.phpT-�hT����-vendor/symfony/console/Event/ConsoleEvent.php�-�h�xS*�,vendor/symfony/console/Input/StringInput.phpF	-�hF	��c:�&vendor/symfony/console/Input/Input.phpr-�hrU�jx�4vendor/symfony/console/Input/InputAwareInterface.php:-�h:� '��/vendor/symfony/console/Input/InputInterface.php�-�h��Έڤ+vendor/symfony/console/Input/ArrayInput.php0-�h0mQj�*vendor/symfony/console/Input/ArgvInput.php&/-�h&/����.vendor/symfony/console/Input/InputArgument.phpO
-�hO
(��0vendor/symfony/console/Input/InputDefinition.php�+-�h�+�|�9vendor/symfony/console/Input/StreamableInputInterface.phpi-�hi���,vendor/symfony/console/Input/InputOption.php�-�h���6]�&vendor/symfony/console/Application.php6�-�h6��A�c�+vendor/symfony/polyfill-ctype/bootstrap.php-�h�01��'vendor/symfony/polyfill-ctype/Ctype.php}-�h}\�谤:vendor/symfony/filesystem/Exception/ExceptionInterface.php�-�h�h���=vendor/symfony/filesystem/Exception/FileNotFoundException.php�-�h��0z¤3vendor/symfony/filesystem/Exception/IOException.php�-�h�ё�A�<vendor/symfony/filesystem/Exception/IOExceptionInterface.php�-�h���i��(vendor/symfony/filesystem/Filesystem.phpGt-�hGt.WX��)vendor/symfony/filesystem/LockHandler.php>-�h>8H�8vendor/react/promise/src/UnhandledRejectionException.phpe-�heg4ߤ&vendor/react/promise/src/functions.php�7-�h�7x=��6vendor/react/promise/src/Exception/LengthException.php^-�h^?q�.vendor/react/promise/src/PromisorInterface.php�-�h�w�;�-vendor/react/promise/src/FulfilledPromise.php^-�h^�v�$vendor/react/promise/src/Promise.php�"-�h�"��m�8vendor/react/promise/src/CancellablePromiseInterface.php�-�h��\lb�-vendor/react/promise/src/PromiseInterface.php�-�h�^�
Ǥ,vendor/react/promise/src/RejectedPromise.php�-�h��M�0�%vendor/react/promise/src/Deferred.php�-�h�
̤5vendor/react/promise/src/ExtendedPromiseInterface.phpv
-�hv
��I�.vendor/react/promise/src/functions_include.phpa-�ha�|�(vendor/react/promise/src/LazyPromise.php�-�h��M�J�.vendor/react/promise/src/CancellationQueue.phpu-�hu���n�5vendor/wp-cli/cache-command/src/Transient_Command.php�U-�h�UT�1vendor/wp-cli/cache-command/src/Cache_Command.php6-�h6 �3T�-vendor/wp-cli/cache-command/cache-command.php7-�h7��z��/vendor/wp-cli/import-command/import-command.php-�h&��3vendor/wp-cli/import-command/src/Import_Command.php�9-�h�9m�x�/vendor/wp-cli/entity-command/entity-command.php�-�h��jl�9vendor/wp-cli/entity-command/src/Comment_Meta_Command.php�-�h�Cq��6vendor/wp-cli/entity-command/src/Network_Namespace.php*-�h*{�d�:vendor/wp-cli/entity-command/src/Menu_Location_Command.php�-�h�:��E�6vendor/wp-cli/entity-command/src/Site_Meta_Command.php�-�h���R�9vendor/wp-cli/entity-command/src/Network_Meta_Command.php]-�h]�kN�9vendor/wp-cli/entity-command/src/User_Session_Command.php�-�h������1vendor/wp-cli/entity-command/src/Term_Command.phpM-�hMN��;vendor/wp-cli/entity-command/src/WP_CLI/CommandWithMeta.php�9-�h�9��{�<vendor/wp-cli/entity-command/src/WP_CLI/CommandWithTerms.php�-�h��3֤?vendor/wp-cli/entity-command/src/WP_CLI/CommandWithDBObject.php�-�h���#�1vendor/wp-cli/entity-command/src/Post_Command.php {-�h {��6u�5vendor/wp-cli/entity-command/src/Taxonomy_Command.php-�h��X�1vendor/wp-cli/entity-command/src/User_Command.php��-�h����:T�8vendor/wp-cli/entity-command/src/Site_Option_Command.php='-�h='�(�3vendor/wp-cli/entity-command/src/Signup_Command.php!-�h!h�s(�1vendor/wp-cli/entity-command/src/Menu_Command.php2-�h2�-�6vendor/wp-cli/entity-command/src/Menu_Item_Command.php�9-�h�9��'��6vendor/wp-cli/entity-command/src/User_Meta_Command.php?%-�h?%[7�m�6vendor/wp-cli/entity-command/src/Post_Term_Command.php�-�h����6vendor/wp-cli/entity-command/src/Post_Meta_Command.php#-�h#�~$�6vendor/wp-cli/entity-command/src/User_Term_Command.php&-�h&�{b�3vendor/wp-cli/entity-command/src/Option_Command.phpO-�hOjd!��Fvendor/wp-cli/entity-command/src/User_Application_Password_Command.phpC-�hC]��B�1vendor/wp-cli/entity-command/src/Site_Command.php��-�h����'i�6vendor/wp-cli/entity-command/src/Term_Meta_Command.php�-�h��B�s�6vendor/wp-cli/entity-command/src/Post_Type_Command.php�-�h���֤4vendor/wp-cli/entity-command/src/Comment_Command.phpH-�hHN]1"�4vendor/wp-cli/widget-command/src/Sidebar_Command.php�-�h��=���3vendor/wp-cli/widget-command/src/Widget_Command.phpcG-�hcG�����/vendor/wp-cli/widget-command/widget-command.php8-�h8q?e�Bvendor/wp-cli/search-replace-command/src/WP_CLI/SearchReplacer.phpB-�hB���Cvendor/wp-cli/search-replace-command/src/Search_Replace_Command.phpZ�-�hZ�B����?vendor/wp-cli/search-replace-command/search-replace-command.php+-�h+DKI��3vendor/wp-cli/language-command/language-command.php&-�h&�����=vendor/wp-cli/language-command/src/Theme_Language_Command.phpLC-�hLCӛ�ѤBvendor/wp-cli/language-command/src/WP_CLI/LanguagePackUpgrader.php�
-�h�
�n��Dvendor/wp-cli/language-command/src/WP_CLI/CommandWithTranslation.phpb!-�hb!ȅ0�Cvendor/wp-cli/language-command/src/Site_Switch_Language_Command.php�-�h��1�<vendor/wp-cli/language-command/src/Core_Language_Command.php�*-�h�*C�
��9vendor/wp-cli/language-command/src/Language_Namespace.php�-�h�-���>vendor/wp-cli/language-command/src/Plugin_Language_Command.php�D-�h�D���&�3vendor/wp-cli/i18n-command/src/PhpCodeExtractor.php�-�h����Ϥ8vendor/wp-cli/i18n-command/src/IterableCodeExtractor.php�,-�h�,=����6vendor/wp-cli/i18n-command/src/PhpFunctionsScanner.php
-�h
񫌘�1vendor/wp-cli/i18n-command/src/MakePotCommand.php4-�h4X�zC�2vendor/wp-cli/i18n-command/src/MakeJsonCommand.php{2-�h{2�1O�3vendor/wp-cli/i18n-command/src/MapCodeExtractor.phpw-�hw"��֤1vendor/wp-cli/i18n-command/src/MakePhpCommand.phpP	-�hP	U��N�1vendor/wp-cli/i18n-command/src/BlockExtractor.php�-�h�����5vendor/wp-cli/i18n-command/src/BladeCodeExtractor.php�-�h���$i�5vendor/wp-cli/i18n-command/src/JsFunctionsScanner.php%/-�h%/q�Ő�4vendor/wp-cli/i18n-command/src/PhpArrayGenerator.phpj-�hj��Q3�/vendor/wp-cli/i18n-command/src/JedGenerator.phpm-�hm�ʓ�6vendor/wp-cli/i18n-command/src/JsonSchemaExtractor.php-�h5�Ⴄ2vendor/wp-cli/i18n-command/src/JsCodeExtractor.php�-�h�F�9<�4vendor/wp-cli/i18n-command/src/FileDataExtractor.php�-�h�-��&�8vendor/wp-cli/i18n-command/src/BladeGettextExtractor.php�-�h������2vendor/wp-cli/i18n-command/src/UpdatePoCommand.php�
-�h�
��Ĥ0vendor/wp-cli/i18n-command/src/MakeMoCommand.php-�h+�3�3vendor/wp-cli/i18n-command/src/CommandNamespace.php�-�h��lh��/vendor/wp-cli/i18n-command/src/PotGenerator.php�-�h����+vendor/wp-cli/i18n-command/i18n-command.php�-�h�D��5vendor/wp-cli/rewrite-command/src/Rewrite_Command.php�1-�h�1���1vendor/wp-cli/rewrite-command/rewrite-command.php-�h9'Rr�#vendor/wp-cli/wp-cli/utils/find-php\-�h\�\�)�@vendor/wp-cli/wp-cli/utils/get-package-require-from-composer.php�-�h���+vendor/wp-cli/role-command/role-command.php/-�h/�x���/vendor/wp-cli/role-command/src/Role_Command.php7,-�h7,}��7vendor/wp-cli/role-command/src/Capabilities_Command.phpy-�hy�u�?�*vendor/wp-cli/export-command/functions.phpp-�hp��:vendor/wp-cli/export-command/src/WP_Iterator_Exception.php9-�h9�w�<vendor/wp-cli/export-command/src/WP_Export_WXR_Formatter.php�&-�h�&��Ѣ�:vendor/wp-cli/export-command/src/WP_Export_Base_Writer.php�-�h�Ǵb��4vendor/wp-cli/export-command/src/WP_Map_Iterator.phpZ-�hZD�ꫤ9vendor/wp-cli/export-command/src/WP_Post_IDs_Iterator.phpI-�hId,�֤8vendor/wp-cli/export-command/src/WP_Export_Exception.php>-�h>M��Avendor/wp-cli/export-command/src/WP_Export_Split_Files_Writer.php-�h�Ls��3vendor/wp-cli/export-command/src/Export_Command.phpJ9-�hJ9�x1�7vendor/wp-cli/export-command/src/WP_Export_Returner.phpB-�hB���M�<vendor/wp-cli/export-command/src/WP_Export_XML_Over_HTTP.phpa-�ha]����=vendor/wp-cli/export-command/src/WP_Export_Term_Exception.phpC-�hC�Ǥ:vendor/wp-cli/export-command/src/WP_Export_File_Writer.php>-�h>R}T��4vendor/wp-cli/export-command/src/WP_Export_Query.php8-�h8D/��5vendor/wp-cli/export-command/src/WP_Export_Oxymel.php-�h֦嵤/vendor/wp-cli/export-command/export-command.php-�h��ͤ+vendor/wp-cli/cron-command/cron-command.phpu-�hu��5R�8vendor/wp-cli/cron-command/src/Cron_Schedule_Command.phpg-�hg���5vendor/wp-cli/cron-command/src/Cron_Event_Command.php�A-�h�A�@դ/vendor/wp-cli/cron-command/src/Cron_Command.php�-�h�#,�/�Cvendor/wp-cli/maintenance-mode-command/maintenance-mode-command.phpL-�hL9�v4�Evendor/wp-cli/maintenance-mode-command/src/MaintenanceModeCommand.php�-�h�,t��?vendor/wp-cli/wp-config-transformer/src/WPConfigTransformer.php�+-�h�+Ņ���5vendor/wp-cli/extension-command/extension-command.php-�hs��v�Avendor/wp-cli/extension-command/src/Theme_AutoUpdates_Command.phpw-�hwzpL�Cvendor/wp-cli/extension-command/src/WP_CLI/ParsePluginNameInput.php�-�h�1'TJ�Bvendor/wp-cli/extension-command/src/WP_CLI/ParseThemeNameInput.php@-�h@��7-�=vendor/wp-cli/extension-command/src/WP_CLI/Fetchers/Theme.php-�h�i��>vendor/wp-cli/extension-command/src/WP_CLI/Fetchers/Plugin.php!-�h!Yȩ��Gvendor/wp-cli/extension-command/src/WP_CLI/DestructiveThemeUpgrader.php�-�h�e�Hvendor/wp-cli/extension-command/src/WP_CLI/DestructivePluginUpgrader.php�-�h�_���Avendor/wp-cli/extension-command/src/WP_CLI/CommandWithUpgrade.php7x-�h7x����Bvendor/wp-cli/extension-command/src/Plugin_AutoUpdates_Command.php&-�h&S�O�9vendor/wp-cli/extension-command/src/Theme_Mod_Command.php�-�h�9n��5vendor/wp-cli/extension-command/src/Theme_Command.php�g-�h�g:���6vendor/wp-cli/extension-command/src/Plugin_Command.php>�-�h>�Yic��=vendor/wp-cli/checksum-command/src/Core_Command_Namespace.php�-�h�{���Gvendor/wp-cli/checksum-command/src/WP_CLI/Fetchers/UnfilteredPlugin.php9-�h9���Ф<vendor/wp-cli/checksum-command/src/Checksum_Core_Command.php�-�h�{��?vendor/wp-cli/checksum-command/src/Plugin_Command_Namespace.php�-�h��:f�>vendor/wp-cli/checksum-command/src/Checksum_Plugin_Command.php�%-�h�%�2�m�<vendor/wp-cli/checksum-command/src/Checksum_Base_Command.php�-�h�XD#�3vendor/wp-cli/checksum-command/checksum-command.php�-�h�K0�Ǥ;vendor/wp-cli/core-command/src/WP_CLI/Core/CoreUpgrader.php�-�h��)Z��Ivendor/wp-cli/core-command/src/WP_CLI/Core/NonDestructiveCoreUpgrader.php�-�h�!]7Ĥ/vendor/wp-cli/core-command/src/Core_Command.php�-�h�+�P4�+vendor/wp-cli/core-command/core-command.php�-�h�	|��<vendor/wp-cli/package-command/src/WP_CLI/JsonManipulator.phpS-�hSϢ��?vendor/wp-cli/package-command/src/WP_CLI/Package/ComposerIO.php�-�h�R𻭤Nvendor/wp-cli/package-command/src/WP_CLI/Package/Compat/NullIOMethodsTrait.php�-�h�����_vendor/wp-cli/package-command/src/WP_CLI/Package/Compat/Min_Composer_2_3/NullIOMethodsTrait.phpL-�hLY��Ϥ`vendor/wp-cli/package-command/src/WP_CLI/Package/Compat/Min_Composer_1_10/NullIOMethodsTrait.php)-�h)�;JY�5vendor/wp-cli/package-command/src/Package_Command.php��-�h���v���1vendor/wp-cli/package-command/package-command.php.-�h.�S���0vendor/wp-cli/php-cli-tools/lib/cli/Progress.php�
-�h�
�a��/vendor/wp-cli/php-cli-tools/lib/cli/Streams.php#-�h#y�_W�,vendor/wp-cli/php-cli-tools/lib/cli/Tree.php�-�h��@�.vendor/wp-cli/php-cli-tools/lib/cli/Colors.php�!-�h�!�_�t�4vendor/wp-cli/php-cli-tools/lib/cli/progress/Bar.php�	-�h�	n�(O�6vendor/wp-cli/php-cli-tools/lib/cli/table/Renderer.phpG-�hGY�ߒ�5vendor/wp-cli/php-cli-tools/lib/cli/table/Tabular.phpE-�hE���^�3vendor/wp-cli/php-cli-tools/lib/cli/table/Ascii.php�-�h�:tt�5vendor/wp-cli/php-cli-tools/lib/cli/unicode/regex.phpb-�hbɶ9ͤ5vendor/wp-cli/php-cli-tools/lib/cli/tree/Renderer.phpO-�hO�c"Ӥ5vendor/wp-cli/php-cli-tools/lib/cli/tree/Markdown.php�-�h��T魤2vendor/wp-cli/php-cli-tools/lib/cli/tree/Ascii.phpx-�hx9���-vendor/wp-cli/php-cli-tools/lib/cli/Shell.phpF
-�hF
6�}^�+vendor/wp-cli/php-cli-tools/lib/cli/cli.php�@-�h�@�"�1�1vendor/wp-cli/php-cli-tools/lib/cli/Arguments.phpn/-�hn/�M�6vendor/wp-cli/php-cli-tools/lib/cli/notify/Spinner.php-�h8�u�3vendor/wp-cli/php-cli-tools/lib/cli/notify/Dots.phpd-�hd�w�z�.vendor/wp-cli/php-cli-tools/lib/cli/Notify.php-�h}/ˤ/vendor/wp-cli/php-cli-tools/lib/cli/Memoize.php�-�h����-vendor/wp-cli/php-cli-tools/lib/cli/Table.php�-�h����:vendor/wp-cli/php-cli-tools/lib/cli/arguments/Argument.php0-�h0�C
��7vendor/wp-cli/php-cli-tools/lib/cli/arguments/Lexer.php�	-�h�	N�YޤBvendor/wp-cli/php-cli-tools/lib/cli/arguments/InvalidArguments.php-�h���#�<vendor/wp-cli/php-cli-tools/lib/cli/arguments/HelpScreen.php�
-�h�
m�,vendor/wp-cli/php-cli-tools/http-console.php-�h��>�$vendor/wp-cli/php-cli-tools/test.phpi-�hi3M��'vendor/wp-cli/db-command/db-command.php�-�h�5���+vendor/wp-cli/db-command/src/DB_Command.php-�h�T�3vendor/wp-cli/eval-command/src/EvalFile_Command.php�	-�h�	Yy��/vendor/wp-cli/eval-command/src/Eval_Command.php]-�h]'����+vendor/wp-cli/eval-command/eval-command.php1-�h1/�"2�4vendor/wp-cli/embed-command/src/Provider_Command.php�-�h�WV��*vendor/wp-cli/embed-command/src/oEmbed.php�-�h��p�>�3vendor/wp-cli/embed-command/src/Handler_Command.php�	-�h�	!��!�4vendor/wp-cli/embed-command/src/Embeds_Namespace.php.-�h.`H>ؤ1vendor/wp-cli/embed-command/src/Cache_Command.php,-�h,�q��1vendor/wp-cli/embed-command/src/Fetch_Command.php%-�h%
�nW�-vendor/wp-cli/embed-command/embed-command.phpx-�hx#�Τ1vendor/wp-cli/shell-command/src/Shell_Command.phpU-�hU�n*�5vendor/wp-cli/shell-command/src/WP_CLI/Shell/REPL.php�-�h�Vo�-vendor/wp-cli/shell-command/shell-command.php�-�h��'���)vendor/wp-cli/mustangostang-spyc/Spyc.php�-�h����x�.vendor/wp-cli/mustangostang-spyc/php4/5to4.php�-�h�uS])�/vendor/wp-cli/mustangostang-spyc/php4/test.php4�-�h� f3P�/vendor/wp-cli/mustangostang-spyc/php4/spyc.php4Iu-�hIu�-.�-vendor/wp-cli/mustangostang-spyc/src/Spyc.php˅-�h˅�~�7vendor/wp-cli/mustangostang-spyc/includes/functions.php-�h��:�7vendor/wp-cli/scaffold-command/src/Scaffold_Command.php��-�h�����;vendor/wp-cli/scaffold-command/templates/block-php.mustache.-�h.F^��3vendor/wp-cli/scaffold-command/scaffold-command.php
-�h
��9�3vendor/wp-cli/config-command/src/Config_Command.php/�-�h/�����/vendor/wp-cli/config-command/config-command.php-�h��}�=vendor/wp-cli/super-admin-command/src/Super_Admin_Command.php�-�h�t�
~�9vendor/wp-cli/super-admin-command/super-admin-command.php�-�h���¤/vendor/wp-cli/server-command/server-command.php�-�h������3vendor/wp-cli/server-command/src/Server_Command.php,-�h,��6\�'vendor/wp-cli/server-command/router.phpN-�hN���-vendor/wp-cli/media-command/media-command.php�-�h�NZ两1vendor/wp-cli/media-command/src/Media_Command.php��-�h��?N�Q�6vendor/wp-cli/wp-cli-tests/utils/generate-coverage.php�-�h�ɐ��.vendor/wp-cli/wp-cli-tests/utils/polyfills.php
-�h
y����,vendor/wp-cli/wp-cli-tests/utils/no-mail.php�-�h�"5��1vendor/wp-cli/wp-cli-tests/bin/run-phpcbf-cleanup�-�h�%��1vendor/wp-cli/wp-cli-tests/bin/run-php-unit-tests-�h���z�.vendor/wp-cli/wp-cli-tests/bin/run-phpcs-tests�-�h�.&���>vendor/wp-cli/wp-cli-tests/src/Context/WhenStepDefinitions.php�-�h�3��-�>vendor/wp-cli/wp-cli-tests/src/Context/ThenStepDefinitions.php�8-�h�8~(~E�?vendor/wp-cli/wp-cli-tests/src/Context/GivenStepDefinitions.php�I-�h�It���2vendor/wp-cli/wp-cli-tests/src/Context/Support.php<-�h<[��Ƥ9vendor/wp-cli/wp-cli-tests/src/Context/FeatureContext.php��-�h��+׍/�vendor/nb/oxymel/Oxymel.php)-�h)�(rդvendor/nb/oxymel/OxymelTest.php�-�h�BL�V�%vendor/psr/log/Psr/Log/NullLogger.php�-�h����I�*vendor/psr/log/Psr/Log/LoggerInterface.php*-�h*1b!q�#vendor/psr/log/Psr/Log/LogLevel.phpP-�hP���3vendor/psr/log/Psr/Log/InvalidArgumentException.php`-�h` �X1�&vendor/psr/log/Psr/Log/LoggerTrait.phpW
-�hW
�W�j�)vendor/psr/log/Psr/Log/AbstractLogger.php -�h G���/vendor/psr/log/Psr/Log/LoggerAwareInterface.php)-�h)�j��+vendor/psr/log/Psr/Log/LoggerAwareTrait.php�-�h�Q'��7vendor/psr/container/src/NotFoundExceptionInterface.php-�h�-���8vendor/psr/container/src/ContainerExceptionInterface.php�-�h�N>K��/vendor/psr/container/src/ContainerInterface.phpJ-�hJ"x��;vendor/seld/jsonlint/src/Seld/JsonLint/ParsingException.php�-�h��#��4vendor/seld/jsonlint/src/Seld/JsonLint/Undefined.php-�h�g��0vendor/seld/jsonlint/src/Seld/JsonLint/Lexer.php�"-�h�"�H*�5vendor/seld/jsonlint/src/Seld/JsonLint/JsonParser.phpt`-�ht`S`=�@vendor/seld/jsonlint/src/Seld/JsonLint/DuplicateKeyException.php�-�h�>f	��)vendor/seld/phar-utils/src/Timestamps.php�-�h�(�6�%vendor/seld/phar-utils/src/Linter.php�
-�h�
'4#��;vendor/justinrainbow/json-schema/src/JsonSchema/Rfc3339.phpv-�hvx�$��Qvendor/justinrainbow/json-schema/src/JsonSchema/Exception/ValidationException.php-�hE�B*�Pvendor/justinrainbow/json-schema/src/JsonSchema/Exception/ExceptionInterface.phpI-�hI%|��Nvendor/justinrainbow/json-schema/src/JsonSchema/Exception/RuntimeException.phpa-�ha`���Tvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaException.phpN-�hN��ݠ�Wvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSourceUriException.php\-�h\�iR�Vvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidArgumentException.phpy-�hy�WΖ�Svendor/justinrainbow/json-schema/src/JsonSchema/Exception/JsonDecodingException.php�-�h�٣��Wvendor/justinrainbow/json-schema/src/JsonSchema/Exception/ResourceNotFoundException.phpT-�hT��:�^vendor/justinrainbow/json-schema/src/JsonSchema/Exception/UnresolvableJsonPointerException.php�-�h�.�mi�]vendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidSchemaMediaTypeException.phpW-�hW%*)֤Tvendor/justinrainbow/json-schema/src/JsonSchema/Exception/InvalidConfigException.phpQ-�hQ�J�Rvendor/justinrainbow/json-schema/src/JsonSchema/Exception/UriResolverException.phpJ-�hJ��-�Kvendor/justinrainbow/json-schema/src/JsonSchema/Iterator/ObjectIterator.php�
-�h�
H���Nvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeConstraint.php1-�h1Je[�Nvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/EnumConstraint.phpq-�hq�`� �Yvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/StrictTypeCheck.php+-�h+��QܤXvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/LooseTypeCheck.php-�h�
���\vendor/justinrainbow/json-schema/src/JsonSchema/Constraints/TypeCheck/TypeCheckInterface.php�-�h�c>�ϤPvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/SchemaConstraint.php�-�h��fK�Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/NumberConstraint.php�
-�h�
��9|�Jvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Constraint.php�-�h��ד�Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/StringConstraint.php�-�h�~��G�Tvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/CollectionConstraint.php�-�h�N���Svendor/justinrainbow/json-schema/src/JsonSchema/Constraints/UndefinedConstraint.php�>-�h�>��ST�Nvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/BaseConstraint.php%-�h%��2��Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ObjectConstraint.php�-�h�Fgc��Pvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/FormatConstraint.php�"-�h�"ͥ#K�Gvendor/justinrainbow/json-schema/src/JsonSchema/Constraints/Factory.phpF-�hF:0�/�Svendor/justinrainbow/json-schema/src/JsonSchema/Constraints/ConstraintInterface.php-�ht�e�=vendor/justinrainbow/json-schema/src/JsonSchema/Validator.php�
-�h�
8���Ivendor/justinrainbow/json-schema/src/JsonSchema/UriRetrieverInterface.php-�h5�|[�Fvendor/justinrainbow/json-schema/src/JsonSchema/Entity/JsonPointer.php>
-�h>
�W�Hvendor/justinrainbow/json-schema/src/JsonSchema/UriResolverInterface.php-�hI��ѤCvendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriResolver.php;-�h;���Dvendor/justinrainbow/json-schema/src/JsonSchema/Uri/UriRetriever.php�$-�h�$X�h��Rvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/PredefinedArray.php-�h0�Rvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/FileGetContents.phpj
-�hj
[}x#�Tvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/AbstractRetriever.phpr-�hr���M�Gvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/Curl.phpo-�ho�n�Xvendor/justinrainbow/json-schema/src/JsonSchema/Uri/Retrievers/UriRetrieverInterface.php�-�h�?�r@�Avendor/justinrainbow/json-schema/src/JsonSchema/SchemaStorage.php+-�h+��_�Jvendor/justinrainbow/json-schema/src/JsonSchema/SchemaStorageInterface.php"-�h"x&7�)vendor/gettext/languages/src/CldrData.php�5-�h�5F��ؤ)vendor/gettext/languages/src/Language.php�>-�h�>z:���.vendor/gettext/languages/src/Exporter/Ruby.php�-�h�<A�v�,vendor/gettext/languages/src/Exporter/Po.php�-�h�ҔX�.vendor/gettext/languages/src/Exporter/Json.php
-�h
��*�-vendor/gettext/languages/src/Exporter/Php.php@-�h@�Pڤ4vendor/gettext/languages/src/Exporter/Prettyjson.phpD-�hDV&
��-vendor/gettext/languages/src/Exporter/Xml.php	-�h	9���.vendor/gettext/languages/src/Exporter/Html.php;-�h;��V��2vendor/gettext/languages/src/Exporter/Exporter.php�-�h��k��)vendor/gettext/languages/src/Category.php�-�h���j��1vendor/gettext/languages/src/FormulaConverter.php�-�h��h�+vendor/gettext/languages/src/autoloader.phpZ-�hZ�.�r�3vendor/gettext/gettext/src/translator_functions.php0-�h0��Ȥ,vendor/gettext/gettext/src/Generators/Po.php
-�h
��(S�,vendor/gettext/gettext/src/Generators/Mo.php�-�h�0�Ф-vendor/gettext/gettext/src/Generators/Jed.phpp-�hp�,k.�.vendor/gettext/gettext/src/Generators/Json.phpO-�hOf�'N�-vendor/gettext/gettext/src/Generators/Csv.php�-�h���{�.vendor/gettext/gettext/src/Generators/Yaml.php�-�h�����/vendor/gettext/gettext/src/Generators/Xliff.php6-�h6þQ��2vendor/gettext/gettext/src/Generators/PhpArray.php�-�h��H�;�7vendor/gettext/gettext/src/Generators/CsvDictionary.php�-�h�CT �8vendor/gettext/gettext/src/Generators/YamlDictionary.php�-�h�L5���3vendor/gettext/gettext/src/Generators/Generator.php�-�h��3�<vendor/gettext/gettext/src/Generators/GeneratorInterface.php�-�h��g�8vendor/gettext/gettext/src/Generators/JsonDictionary.php=-�h=��{�0vendor/gettext/gettext/src/GettextTranslator.php�-�h�޼�|�)vendor/gettext/gettext/src/Translator.php�-�h�J�>�$vendor/gettext/gettext/src/Merge.phpD-�hD�m6P�-vendor/gettext/gettext/src/BaseTranslator.php�-�h�3v�Ĥ*vendor/gettext/gettext/src/Translation.php�+-�h�+��9�5vendor/gettext/gettext/src/Utils/FunctionsScanner.phpI-�hIahk$�8vendor/gettext/gettext/src/Utils/PhpFunctionsScanner.php�-�h�jG"�-vendor/gettext/gettext/src/Utils/CsvTrait.php�-�h�S�*�:vendor/gettext/gettext/src/Utils/HeadersExtractorTrait.php~-�h~���Ф3vendor/gettext/gettext/src/Utils/ParsedFunction.php-�h��b��2vendor/gettext/gettext/src/Utils/ParsedComment.php\-�h\�ܺC�4vendor/gettext/gettext/src/Utils/DictionaryTrait.php�-�h��'��:vendor/gettext/gettext/src/Utils/HeadersGeneratorTrait.php7-�h76ÉG�7vendor/gettext/gettext/src/Utils/JsFunctionsScanner.php�$-�h�$(��d�1vendor/gettext/gettext/src/Utils/StringReader.php�-�h��P8Z�?vendor/gettext/gettext/src/Utils/MultidimensionalArrayTrait.php�-�h��i$a�+vendor/gettext/gettext/src/Translations.phpA-�hAO4#N�2vendor/gettext/gettext/src/TranslatorInterface.php%
-�h%
����)vendor/gettext/gettext/src/autoloader.php-�hA�p�,vendor/gettext/gettext/src/Extractors/Po.php�-�h��!�,vendor/gettext/gettext/src/Extractors/Mo.phpm-�hme�p�-vendor/gettext/gettext/src/Extractors/Jed.php-�h{c��.vendor/gettext/gettext/src/Extractors/Json.php.-�h.�w=u�3vendor/gettext/gettext/src/Extractors/Extractor.php�-�h�{�n�<vendor/gettext/gettext/src/Extractors/ExtractorInterface.php
-�h
p}YФ.vendor/gettext/gettext/src/Extractors/Twig.php<-�h<��K�1vendor/gettext/gettext/src/Extractors/PhpCode.php?-�h?…�'�/vendor/gettext/gettext/src/Extractors/Blade.php�-�h�5x�-vendor/gettext/gettext/src/Extractors/Csv.php-�h�m8¤/vendor/gettext/gettext/src/Extractors/VueJs.phpn6-�hn6��>�.vendor/gettext/gettext/src/Extractors/Yaml.php]-�h]jя"�/vendor/gettext/gettext/src/Extractors/Xliff.php�-�h�W}�Avendor/gettext/gettext/src/Extractors/ExtractorMultiInterface.php�-�h���]��2vendor/gettext/gettext/src/Extractors/PhpArray.phpG-�hG�/DѤ7vendor/gettext/gettext/src/Extractors/CsvDictionary.phpR-�hRW�G��8vendor/gettext/gettext/src/Extractors/YamlDictionary.phpQ-�hQ�t�s�0vendor/gettext/gettext/src/Extractors/JsCode.php(-�h(8�vU�8vendor/gettext/gettext/src/Extractors/JsonDictionary.php(-�h(�-�*�2vendor/mck89/peast/lib/Peast/Formatter/Compact.phpG-�hG�o�6vendor/mck89/peast/lib/Peast/Formatter/PrettyPrint.phpr-�hr�j�Ϥ3vendor/mck89/peast/lib/Peast/Formatter/Expanded.php�-�h���x�/vendor/mck89/peast/lib/Peast/Formatter/Base.phpl-�hl�=ڤ)vendor/mck89/peast/lib/Peast/Renderer.phpZ�-�hZ��s�
�3vendor/mck89/peast/lib/Peast/Selector/Exception.php�-�h�BQ:��0vendor/mck89/peast/lib/Peast/Selector/Parser.php�B-�h�BG	d�1vendor/mck89/peast/lib/Peast/Selector/Matches.php�-�h�>�z�7vendor/mck89/peast/lib/Peast/Selector/Node/Selector.php�-�h��:rդ9vendor/mck89/peast/lib/Peast/Selector/Node/Combinator.phpS-�hS����4vendor/mck89/peast/lib/Peast/Selector/Node/Group.php
-�h
H;��8vendor/mck89/peast/lib/Peast/Selector/Node/Part/Type.php-�h��wۤBvendor/mck89/peast/lib/Peast/Selector/Node/Part/PseudoSelector.phpZ-�hZ8,�n�8vendor/mck89/peast/lib/Peast/Selector/Node/Part/Part.phpm-�hm�^Ƥ?vendor/mck89/peast/lib/Peast/Selector/Node/Part/PseudoIndex.php(	-�h(	�z\F�@vendor/mck89/peast/lib/Peast/Selector/Node/Part/PseudoSimple.phpf-�hf�/o�=vendor/mck89/peast/lib/Peast/Selector/Node/Part/Attribute.php�-�h��ŋ��:vendor/mck89/peast/lib/Peast/Selector/Node/Part/Pseudo.php�-�h�@-핤*vendor/mck89/peast/lib/Peast/Traverser.php�-�h�8�+��&vendor/mck89/peast/lib/Peast/Query.php@-�h@�ޖؤ1vendor/mck89/peast/lib/Peast/Syntax/Exception.php-�h7]i�7vendor/mck89/peast/lib/Peast/Syntax/ES2019/Features.php�-�h�0��^�.vendor/mck89/peast/lib/Peast/Syntax/Parser.php=-�h=�+�N�7vendor/mck89/peast/lib/Peast/Syntax/ES2023/Features.php-�hl�3��0vendor/mck89/peast/lib/Peast/Syntax/Features.php�-�h�Uv��6vendor/mck89/peast/lib/Peast/Syntax/ParserAbstract.php^$-�h^$�4��9vendor/mck89/peast/lib/Peast/Syntax/EncodingException.php�-�h�L�p�2vendor/mck89/peast/lib/Peast/Syntax/JSX/Parser.php6-�h6��դ3vendor/mck89/peast/lib/Peast/Syntax/JSX/Scanner.phps
-�hs
֙Ԥ0vendor/mck89/peast/lib/Peast/Syntax/Position.php�-�h�k�wG�8vendor/mck89/peast/lib/Peast/Syntax/CommentsRegistry.php�(-�h�(2�)>�7vendor/mck89/peast/lib/Peast/Syntax/ES2021/Features.php�-�h�KY�A�7vendor/mck89/peast/lib/Peast/Syntax/ES2018/Features.php(-�h(��pf�7vendor/mck89/peast/lib/Peast/Syntax/ES2015/Features.php�-�h����@�7vendor/mck89/peast/lib/Peast/Syntax/ES2020/Features.php-�h�O�-vendor/mck89/peast/lib/Peast/Syntax/Token.php�-�h���B�5vendor/mck89/peast/lib/Peast/Syntax/EventsEmitter.php-�h�{u�/vendor/mck89/peast/lib/Peast/Syntax/Scanner.php��-�h��BAפ-vendor/mck89/peast/lib/Peast/Syntax/Utils.php�&-�h�&�L�s�7vendor/mck89/peast/lib/Peast/Syntax/ES2025/Features.php-�h�cT��7vendor/mck89/peast/lib/Peast/Syntax/ES2024/Features.php�-�h���U�7vendor/mck89/peast/lib/Peast/Syntax/ES2017/Features.php-�h��H@�+vendor/mck89/peast/lib/Peast/Syntax/LSM.php�-�h���/�6vendor/mck89/peast/lib/Peast/Syntax/SourceLocation.php�-�h��<��7vendor/mck89/peast/lib/Peast/Syntax/ES2022/Features.phpC-�hC���7vendor/mck89/peast/lib/Peast/Syntax/ES2016/Features.php-�h�t~��?vendor/mck89/peast/lib/Peast/Syntax/Node/VariableDeclarator.php�-�h��b�٤<vendor/mck89/peast/lib/Peast/Syntax/Node/ImportSpecifier.php�-�h��!�I�;vendor/mck89/peast/lib/Peast/Syntax/Node/ThisExpression.php�-�h��%a��9vendor/mck89/peast/lib/Peast/Syntax/Node/ArrayPattern.php�-�h��ʑ(�7vendor/mck89/peast/lib/Peast/Syntax/Node/SwitchCase.phpX-�hX�E�6vendor/mck89/peast/lib/Peast/Syntax/Node/Statement.php�-�h�����=vendor/mck89/peast/lib/Peast/Syntax/Node/ObjectExpression.php�-�h�'!E�=vendor/mck89/peast/lib/Peast/Syntax/Node/UpdateExpression.php;	-�h;	
��<vendor/mck89/peast/lib/Peast/Syntax/Node/AwaitExpression.phpU-�hU���>vendor/mck89/peast/lib/Peast/Syntax/Node/LogicalExpression.phpZ-�hZ��S��=vendor/mck89/peast/lib/Peast/Syntax/Node/BinaryExpression.php�-�h�vX$�7vendor/mck89/peast/lib/Peast/Syntax/Node/Identifier.php�-�h��O�Evendor/mck89/peast/lib/Peast/Syntax/Node/ExportDefaultDeclaration.php"-�h"S(�6�=vendor/mck89/peast/lib/Peast/Syntax/Node/MethodDefinition.php�-�h��w\�;vendor/mck89/peast/lib/Peast/Syntax/Node/BreakStatement.phpy-�hy��2�;vendor/mck89/peast/lib/Peast/Syntax/Node/EmptyStatement.php�-�h�n���<vendor/mck89/peast/lib/Peast/Syntax/Node/ClassExpression.php�-�h�p~�äBvendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXOpeningElement.php�-�h��D&��=vendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXAttribute.php}-�h}���Bvendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXClosingElement.php�-�h��f�<vendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXFragment.php�
-�h�
̌Է�Gvendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXExpressionContainer.php�-�h�]{�W�8vendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXText.php�-�h��Ƒ��?vendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXSpreadChild.php�-�h�iMb��Cvendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXSpreadAttribute.php�-�h�K&Q��Cvendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXBoundaryElement.php�-�h����;vendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXElement.php�
-�h�
.*kB�Cvendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXOpeningFragment.php�-�h����Bvendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXNamespacedName.phpz-�hz�Ԅ=�Dvendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXMemberExpression.phpf-�hfh�Cvendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXClosingFragment.php�-�h�N�\�>vendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXIdentifier.php�-�h����L�Cvendor/mck89/peast/lib/Peast/Syntax/Node/JSX/JSXEmptyExpression.php�-�h�:�8��8vendor/mck89/peast/lib/Peast/Syntax/Node/CatchClause.php�-�h�����<vendor/mck89/peast/lib/Peast/Syntax/Node/ModuleSpecifier.php�-�h��GjC�8vendor/mck89/peast/lib/Peast/Syntax/Node/StaticBlock.php�-�h��X~V�=vendor/mck89/peast/lib/Peast/Syntax/Node/LabeledStatement.php�-�h�(���<vendor/mck89/peast/lib/Peast/Syntax/Node/TemplateLiteral.phpk
-�hk
��;`�;vendor/mck89/peast/lib/Peast/Syntax/Node/NumericLiteral.php�-�h���
�Bvendor/mck89/peast/lib/Peast/Syntax/Node/ConditionalExpression.php	-�h	�����9vendor/mck89/peast/lib/Peast/Syntax/Node/ForStatement.phpC-�hC1���:vendor/mck89/peast/lib/Peast/Syntax/Node/NewExpression.php�-�h��>��6vendor/mck89/peast/lib/Peast/Syntax/Node/ClassBody.php,-�h,�7���<vendor/mck89/peast/lib/Peast/Syntax/Node/SwitchStatement.php�-�h����:vendor/mck89/peast/lib/Peast/Syntax/Node/WithStatement.phpi-�hi$���:vendor/mck89/peast/lib/Peast/Syntax/Node/RegExpLiteral.phpJ
-�hJ
tq�<vendor/mck89/peast/lib/Peast/Syntax/Node/ChainExpression.php�-�h���e�8vendor/mck89/peast/lib/Peast/Syntax/Node/RestElement.php�-�h�7^g��<vendor/mck89/peast/lib/Peast/Syntax/Node/TemplateElement.php�	-�h�	7��Ѥ;vendor/mck89/peast/lib/Peast/Syntax/Node/ForInStatement.php	-�h	P㛤:vendor/mck89/peast/lib/Peast/Syntax/Node/StringLiteral.php�	-�h�	5�v�:vendor/mck89/peast/lib/Peast/Syntax/Node/SpreadElement.php�-�h�$U�g�:vendor/mck89/peast/lib/Peast/Syntax/Node/BigIntLiteral.php-�hAͤ>vendor/mck89/peast/lib/Peast/Syntax/Node/ContinueStatement.php�-�h���ZY�9vendor/mck89/peast/lib/Peast/Syntax/Node/MetaProperty.php-�h��:�Avendor/mck89/peast/lib/Peast/Syntax/Node/AssignmentExpression.php2	-�h2	��T��;vendor/mck89/peast/lib/Peast/Syntax/Node/BlockStatement.php�-�h�����4vendor/mck89/peast/lib/Peast/Syntax/Node/Comment.phpW-�hWC>,�<vendor/mck89/peast/lib/Peast/Syntax/Node/ExportSpecifier.php�-�h�(���8vendor/mck89/peast/lib/Peast/Syntax/Node/NullLiteral.php-�h�K��1vendor/mck89/peast/lib/Peast/Syntax/Node/Node.php� -�h� �\��8vendor/mck89/peast/lib/Peast/Syntax/Node/IfStatement.php�
-�h�
%��>vendor/mck89/peast/lib/Peast/Syntax/Node/PrivateIdentifier.php-�h�o� �;vendor/mck89/peast/lib/Peast/Syntax/Node/BooleanLiteral.phpI-�hIm��|�7vendor/mck89/peast/lib/Peast/Syntax/Node/Expression.php�-�h�zw�!�=vendor/mck89/peast/lib/Peast/Syntax/Node/ImportExpression.php�-�h�4�@vendor/mck89/peast/lib/Peast/Syntax/Node/VariableDeclaration.phpc-�hc��I�6vendor/mck89/peast/lib/Peast/Syntax/Node/Function_.php�
-�h�
w�v�?vendor/mck89/peast/lib/Peast/Syntax/Node/PropertyDefinition.php�-�h����H�<vendor/mck89/peast/lib/Peast/Syntax/Node/YieldExpression.php0-�h0�)�4vendor/mck89/peast/lib/Peast/Syntax/Node/Pattern.php�-�h�����9vendor/mck89/peast/lib/Peast/Syntax/Node/TryStatement.php	-�h	�8�"�<vendor/mck89/peast/lib/Peast/Syntax/Node/UnaryExpression.phpR	-�hR	+%6�8vendor/mck89/peast/lib/Peast/Syntax/Node/Declaration.php�-�h�8�4�<vendor/mck89/peast/lib/Peast/Syntax/Node/ImportAttribute.php�-�h�r!�z�4vendor/mck89/peast/lib/Peast/Syntax/Node/Program.php�	-�h�	�Ԁv�Dvendor/mck89/peast/lib/Peast/Syntax/Node/ArrowFunctionExpression.php�-�h�х��@vendor/mck89/peast/lib/Peast/Syntax/Node/FunctionDeclaration.php�-�h�7���2vendor/mck89/peast/lib/Peast/Syntax/Node/Super.php�-�h�RC�(�:vendor/mck89/peast/lib/Peast/Syntax/Node/ObjectPattern.php�-�h����ܤ9vendor/mck89/peast/lib/Peast/Syntax/Node/ChainElement.php�-�h�"�
+�3vendor/mck89/peast/lib/Peast/Syntax/Node/Class_.phpy-�hyn�;vendor/mck89/peast/lib/Peast/Syntax/Node/ForOfStatement.php�-�h���,�<vendor/mck89/peast/lib/Peast/Syntax/Node/ReturnStatement.php�-�h��:C�Evendor/mck89/peast/lib/Peast/Syntax/Node/TaggedTemplateExpression.php\-�h\�-��>vendor/mck89/peast/lib/Peast/Syntax/Node/ModuleDeclaration.php�-�h��nt��Evendor/mck89/peast/lib/Peast/Syntax/Node/ImportNamespaceSpecifier.php�-�h�M�c�@vendor/mck89/peast/lib/Peast/Syntax/Node/ExpressionStatement.php�-�h�[�m�Cvendor/mck89/peast/lib/Peast/Syntax/Node/ExportNamedDeclaration.phpG-�hG%���?vendor/mck89/peast/lib/Peast/Syntax/Node/AssignmentProperty.php�-�h���Aۤ?vendor/mck89/peast/lib/Peast/Syntax/Node/SequenceExpression.php�-�h�����5vendor/mck89/peast/lib/Peast/Syntax/Node/Property.php-�hr��Dvendor/mck89/peast/lib/Peast/Syntax/Node/ParenthesizedExpression.php�-�h�(��Ӥ;vendor/mck89/peast/lib/Peast/Syntax/Node/CallExpression.phpW-�hW���
�=vendor/mck89/peast/lib/Peast/Syntax/Node/ClassDeclaration.php�-�h�%^�j�>vendor/mck89/peast/lib/Peast/Syntax/Node/ImportDeclaration.php�	-�h�	��2�4vendor/mck89/peast/lib/Peast/Syntax/Node/Literal.php-�h�5ʤ?vendor/mck89/peast/lib/Peast/Syntax/Node/FunctionExpression.php�-�h����U�;vendor/mck89/peast/lib/Peast/Syntax/Node/WhileStatement.php*-�h*ꮓ��>vendor/mck89/peast/lib/Peast/Syntax/Node/AssignmentPattern.php�-�h��RY�<vendor/mck89/peast/lib/Peast/Syntax/Node/ArrayExpression.php-�ht8��=vendor/mck89/peast/lib/Peast/Syntax/Node/DoWhileStatement.php2-�h2�����>vendor/mck89/peast/lib/Peast/Syntax/Node/DebuggerStatement.php�-�h�di��=vendor/mck89/peast/lib/Peast/Syntax/Node/MemberExpression.php`
-�h`
��G��;vendor/mck89/peast/lib/Peast/Syntax/Node/ThrowStatement.phpl-�hl�����Cvendor/mck89/peast/lib/Peast/Syntax/Node/ImportDefaultSpecifier.php�-�h���J�Avendor/mck89/peast/lib/Peast/Syntax/Node/ExportAllDeclaration.phpV	-�hV	��TH�&vendor/mck89/peast/lib/Peast/Peast.php -�h �9@y�2vendor/wp-cli/wp-cli/templates/man-params.mustache?-�h?��n�+vendor/wp-cli/wp-cli/templates/man.mustache�-�h��I<�9vendor/wp-cli/config-command/templates/wp-config.mustacheZ
-�hZ
�SԤ6vendor/wp-cli/core-command/templates/versions.mustache�-�h�SU"��?vendor/wp-cli/extension-command/templates/theme-status.mustachex-�hxǁgѤ@vendor/wp-cli/extension-command/templates/plugin-status.mustache�-�h�o[�5�Cvendor/wp-cli/scaffold-command/templates/plugin-distignore.mustacheL-�hLڋ�=�?vendor/wp-cli/scaffold-command/templates/plugin-gitlab.mustache�-�h��Ӥ8vendor/wp-cli/scaffold-command/templates/plugin.mustacheq-�hqG�qҤDvendor/wp-cli/scaffold-command/templates/plugin-test-sample.mustache'-�h'\~��Avendor/wp-cli/scaffold-command/templates/block-style-css.mustache-�h��ؤCvendor/wp-cli/scaffold-command/templates/taxonomy_extended.mustache
-�h
�>^u�Bvendor/wp-cli/scaffold-command/templates/block-editor-css.mustache�-�h��,V]�>vendor/wp-cli/scaffold-command/templates/theme-status.mustachex-�hxǁgѤ=vendor/wp-cli/scaffold-command/templates/child_theme.mustache�-�h����?vendor/wp-cli/scaffold-command/templates/plugin-github.mustache*-�h*�ג�;vendor/wp-cli/scaffold-command/templates/post_type.mustache�
-�h�
?�4�?vendor/wp-cli/scaffold-command/templates/plugin-circle.mustache<-�h<y�5�6vendor/wp-cli/scaffold-command/templates/.editorconfig�-�h��hѤAvendor/wp-cli/scaffold-command/templates/plugin-composer.mustaches-�hs�Oqb�<vendor/wp-cli/scaffold-command/templates/install-wp-tests.sh�-�h�)5۔�@vendor/wp-cli/scaffold-command/templates/block-index-js.mustache�
-�h�
"��0�Gvendor/wp-cli/scaffold-command/templates/child_theme_functions.mustacheT-�hT�?8�8vendor/wp-cli/scaffold-command/templates/.phpcs.xml.dist=-�h=[�-�Bvendor/wp-cli/scaffold-command/templates/plugin-bitbucket.mustache�-�h�倧E�Avendor/wp-cli/scaffold-command/templates/theme-bootstrap.mustache�-�h��r��Bvendor/wp-cli/scaffold-command/templates/plugin-gitignore.mustachef-�hf���Cvendor/wp-cli/scaffold-command/templates/theme-test-sample.mustache&-�h&���9vendor/wp-cli/scaffold-command/templates/phpunit.xml.dist�-�h�+���?vendor/wp-cli/scaffold-command/templates/plugin-readme.mustache-�hb��Bvendor/wp-cli/scaffold-command/templates/plugin-bootstrap.mustache�-�h����I�:vendor/wp-cli/scaffold-command/templates/taxonomy.mustache�
-�h�
�s��Dvendor/wp-cli/scaffold-command/templates/post_type_extended.mustache�-�h�L�}��vendor/autoload.php�-�h��#y�� vendor/composer/composer/LICENSE,-�h,V�g��1vendor/composer/composer/res/composer-schema.jsonh�-�hh��^��Cvendor/wp-cli/wp-cli/bundle/rmccue/requests/certificates/cacert.pem}-�h}���ۤ&vendor/wp-cli/wp-cli/COMPOSER_VERSIONS�-�h���o�vendor/wp-cli/wp-cli/VERSION-�h�'Ĥ<?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'] = ( ! empty( getenv( 'WP_CLI_USER_AGENT' ) ) ? getenv( 'WP_CLI_USER_AGENT' ) : 'WP CLI ' . WP_CLI_VERSION );
$_SERVER['REQUEST_METHOD']  = 'GET';
$_SERVER['REMOTE_ADDR']     = '127.0.0.1';

require_once WP_CLI_ROOT . '/php/bootstrap.php';

if ( getenv( 'WP_CLI_EARLY_REQUIRE' ) ) {
	foreach ( explode( ',', getenv( 'WP_CLI_EARLY_REQUIRE' ) ) as $wp_cli_early_require ) {
		require_once trim( $wp_cli_early_require );
	}
	unset( $wp_cli_early_require );
}

WP_CLI\bootstrap();
<?php

use cli\Colors;
use Mustangostang\Spyc;
use WP_CLI\Configurator;
use WP_CLI\Dispatcher;
use WP_CLI\Dispatcher\CommandAddition;
use WP_CLI\Dispatcher\CommandFactory;
use WP_CLI\Dispatcher\CommandNamespace;
use WP_CLI\Dispatcher\CompositeCommand;
use WP_CLI\Dispatcher\RootCommand;
use WP_CLI\DocParser;
use WP_CLI\ExitException;
use WP_CLI\FileCache;
use WP_CLI\Loggers\Execution;
use WP_CLI\Process;
use WP_CLI\ProcessRun;
use WP_CLI\Runner;
use WP_CLI\SynopsisParser;
use WP_CLI\Utils;
use WP_CLI\WpHttpCacheManager;

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

	private static $logger;

	private static $hooks = [];

	private static $hooks_passed = [];

	private static $capture_exit = false;

	private static $deferred_additions = [];

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

	/**
	 * Get the logger instance.
	 *
	 * @return object $logger Logger instance.
	 */
	public static function get_logger() {
		return self::$logger;
	}

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

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

		return $configurator;
	}

	public static function get_root_command() {
		static $root;

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

		return $root;
	}

	public static function get_runner() {
		static $runner;

		if ( ! $runner ) {
			$runner = new Runner();
		}

		return $runner;
	}

	/**
	 * @return FileCache
	 */
	public static function get_cache() {
		static $cache;

		if ( ! $cache ) {
			$dir      = Utils\get_cache_dir();
			$ttl      = getenv( 'WP_CLI_CACHE_EXPIRY' ) ? : 15552000;
			$max_size = getenv( 'WP_CLI_CACHE_MAX_SIZE' ) ? : 314572800;
			// 6 months, 300mb
			$cache = new FileCache( $dir, $ttl, $max_size );

			// Clean older files on shutdown with 1/50 probability.
			// phpcs:ignore WordPress.WP.AlternativeFunctions.rand_mt_rand -- no crypto and WP not loaded.
			if ( 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 Utils\get_flag_value( $url_parts, $key, '' );
		};

		if ( isset( $url_parts['host'] ) ) {
			if ( isset( $url_parts['scheme'] ) && 'https' === strtolower( $url_parts['scheme'] ) ) {
				$_SERVER['HTTPS'] = 'on';
			}

			$_SERVER['HTTP_HOST'] = $url_parts['host'];
			if ( isset( $url_parts['port'] ) ) {
				$_SERVER['HTTP_HOST'] .= ':' . $url_parts['port'];
			}

			$_SERVER['SERVER_NAME'] = $url_parts['host'];
		}

		$_SERVER['REQUEST_URI']  = $f( 'path' ) . ( isset( $url_parts['query'] ) ? '?' . $url_parts['query'] : '' );
		$_SERVER['SERVER_PORT']  = Utils\get_flag_value( $url_parts, 'port', '80' );
		$_SERVER['QUERY_STRING'] = $f( 'query' );
	}

	/**
	 * @return WpHttpCacheManager
	 */
	public static function get_http_cache_manager() {
		static $http_cacher;

		if ( ! $http_cacher ) {
			$http_cacher = new 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 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>` (1) - Just before a command is invoked.
	 * * `after_invoke:<command>` (1) - Just after a command is invoked.
	 * * `find_command_to_run_pre` - Just before WP-CLI finds the command to run.
	 * * `before_registering_contexts` (1) - Before the contexts are registered.
	 * * `before_wp_load` - Just before the WP load process begins.
	 * * `before_wp_config_load` - After wp-config.php has been located.
	 * * `after_wp_config_load` - After wp-config.php has been loaded into scope.
	 * * `after_wp_load` - Just after the WP load process has completed.
	 * * `before_run_command` (3) - Just before the command is executed.
	 *
	 * The parentheses behind the hook name denote the number of arguments
	 * being passed into the hook. For such hooks, the callback should return
	 * the first argument again, making them work like a WP filter.
	 *
	 * WP-CLI commands can create their own hooks with `WP_CLI::do_hook()`.
	 *
	 * If additional arguments are passed through the `WP_CLI::do_hook()` call,
	 * these will be passed on to the callback provided by `WP_CLI::add_hook()`.
	 *
	 * ```
	 * # `wp network meta` confirms command is executing in multisite context.
	 * WP_CLI::add_command( 'network meta', 'Network_Meta_Command', array(
	 *    'before_invoke' => function ( $name ) {
	 *        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 void
	 */
	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  ...$args Optional. Arguments that will be passed onto the
	 *                        callback provided by `WP_CLI::add_hook()`.
	 * @return null|mixed Returns the first optional argument if optional
	 *                    arguments were passed, otherwise returns null.
	 */
	public static function do_hook( $when, ...$args ) {
		self::$hooks_passed[ $when ] = $args;

		$has_args = count( $args ) > 0;

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

			return null;
		}

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

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

			if ( $has_args ) {
				$return_value = $callback( ...$args );
				if ( isset( $return_value ) ) {
					$args[0] = $return_value;
				}
			} else {
				$callback();
			}
		}

		if ( $has_args ) {
			return $args[0];
		}

		return null;
	}

	/**
	 * Add a callback to a WordPress action or filter.
	 *
	 * `add_action()` without needing access to `add_action()`. If WordPress is
	 * already loaded though, you should use `add_action()` (and `add_filter()`)
	 * instead.
	 *
	 * @access public
	 * @category Registration
	 *
	 * @param string $tag Named WordPress action or filter.
	 * @param mixed $function_to_add Callable to execute when the action or filter is evaluated.
	 * @param integer $priority Priority to add the callback as.
	 * @param integer $accepted_args Number of arguments to pass to callback.
	 * @return true
	 */
	public static function add_wp_hook( $tag, $function_to_add, $priority = 10, $accepted_args = 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 ] = [
				'function'      => $function_to_add,
				'accepted_args' => $accepted_args,
			];
			unset( $merged_filters[ $tag ] );
		}

		return true;
	}

	/**
	 * Build Unique ID for storage and retrieval.
	 *
	 * Essentially _wp_filter_build_unique_id() without needing access to _wp_filter_build_unique_id()
	 */
	private static function wp_hook_build_unique_id( $tag, $function, $priority ) {
		global $wp_filter;
		static $filter_id_count = 0;

		if ( is_string( $function ) ) {
			return $function;
		}

		if ( is_object( $function ) ) {
			// Closures are currently implemented as objects.
			$function = [ $function, '' ];
		} else {
			$function = (array) $function;
		}

		if ( is_object( $function[0] ) ) {
			// Object Class Calling.
			if ( function_exists( 'spl_object_hash' ) ) {
				return spl_object_hash( $function[0] ) . $function[1];
			}

			$obj_idx = get_class( $function[0] ) . $function[1];
			if ( ! isset( $function[0]->wp_filter_id ) ) {
				if ( false === $priority ) {
					return false;
				}
				$obj_idx                  .= isset( $wp_filter[ $tag ][ $priority ] ) ? count( (array) $wp_filter[ $tag ][ $priority ] ) : $filter_id_count;
				$function[0]->wp_filter_id = $filter_id_count;
				++$filter_id_count;
			} else {
				$obj_idx .= $function[0]->wp_filter_id;
			}

			return $obj_idx;
		}

		if ( is_string( $function[0] ) ) {
			// Static Calling.
			return $function[0] . '::' . $function[1];
		}
	}

	/**
	 * Register a command to WP-CLI.
	 *
	 * WP-CLI supports using any callable class, function, or closure as a
	 * command. `WP_CLI::add_command()` is used for both internal and
	 * third-party command registration.
	 *
	 * Command arguments are parsed from PHPDoc by default, but also can be
	 * supplied as an optional third argument during registration.
	 *
	 * ```
	 * # Register a custom 'foo' command to output a supplied positional param.
	 * #
	 * # $ wp foo bar --append=qux
	 * # Success: bar qux
	 *
	 * /**
	 *  * My awesome closure command
	 *  *
	 *  * <message>
	 *  * : An awesome message to display
	 *  *
	 *  * --append=<message>
	 *  * : An awesome message to append to the original message.
	 *  *
	 *  * @when before_wp_load
	 *  *\/
	 * $foo = function( $args, $assoc_args ) {
	 *     WP_CLI::success( $args[0] . ' ' . $assoc_args['append'] );
	 * };
	 * WP_CLI::add_command( 'foo', $foo );
	 * ```
	 *
	 * @access public
	 * @category Registration
	 *
	 * @param string   $name Name for the command (e.g. "post list" or "site empty").
	 * @param callable|object|string $callable Command implementation as a class, function or closure.
	 * @param array    $args {
	 *    Optional. An associative array with additional registration parameters.
	 *
	 *    @type callable $before_invoke Callback to execute before invoking the command.
	 *    @type callable $after_invoke  Callback to execute after invoking the command.
	 *    @type string   $shortdesc     Short description (80 char or less) for the command.
	 *    @type string   $longdesc      Description of arbitrary length for examples, etc.
	 *    @type string   $synopsis      The synopsis for the command (string or array).
	 *    @type string   $when          Execute callback on a named WP-CLI hook (e.g. before_wp_load).
	 *    @type bool     $is_deferred   Whether the command addition had already been deferred.
	 * }
	 * @return bool True on success, false if deferred, hard error if registration failed.
	 */
	public static function add_command( $name, $callable, $args = [] ) {
		// Bail immediately if the WP-CLI executable has not been run.
		if ( ! defined( 'WP_CLI' ) ) {
			return false;
		}

		$valid = false;
		if ( is_callable( $callable ) ) {
			$valid = true;
		} elseif ( is_string( $callable ) && class_exists( (string) $callable ) ) {
			$valid = true;
		} elseif ( is_object( $callable ) ) {
			$valid = true;
		} elseif ( Utils\is_valid_class_and_method_pair( $callable ) ) {
			$valid = true;
		}
		if ( ! $valid ) {
			if ( is_array( $callable ) ) {
				$callable[0] = is_object( $callable[0] ) ? get_class( $callable[0] ) : $callable[0];
				$callable    = [ $callable[0], $callable[1] ];
			}
			self::error( sprintf( 'Callable %s does not exist, and cannot be registered as `wp %s`.', json_encode( $callable ), $name ) );
		}

		$addition = new CommandAddition();
		self::do_hook( "before_add_command:{$name}", $addition );

		if ( $addition->was_aborted() ) {
			self::warning( "Aborting the addition of the command '{$name}' with reason: {$addition->get_reason()}." );
			return false;
		}

		foreach ( [ 'before_invoke', 'after_invoke' ] as $when ) {
			if ( isset( $args[ $when ] ) ) {
				self::add_hook( "{$when}:{$name}", $args[ $when ] );
			}
		}

		$path = preg_split( '/\s+/', $name );

		$leaf_name = array_pop( $path );

		$command = self::get_root_command();

		while ( ! empty( $path ) ) {
			$subcommand_name = $path[0];
			$parent          = implode( ' ', $path );
			$subcommand      = $command->find_subcommand( $path );

			// Parent not found. Defer addition or create an empty container as
			// needed.
			if ( ! $subcommand ) {
				if ( isset( $args['is_deferred'] ) && $args['is_deferred'] ) {
					$subcommand = new CompositeCommand(
						$command,
						$subcommand_name,
						new DocParser( '' )
					);

					self::debug(
						"Adding empty container for deferred command: {$name}",
						'commands'
					);

					$command->add_subcommand( $subcommand_name, $subcommand );
				} else {
					self::debug( "Deferring command: {$name}", 'commands' );

					self::defer_command_addition(
						$name,
						$parent,
						$callable,
						$args
					);

					return false;
				}
			}

			$command = $subcommand;
		}

		$leaf_command = CommandFactory::create( $leaf_name, $callable, $command );

		// Only add a command namespace if the command itself does not exist yet.
		if ( $leaf_command instanceof CommandNamespace
			&& array_key_exists( $leaf_name, $command->get_subcommands() ) ) {
			return false;
		}

		// Reattach commands attached to namespace to real command.
		$subcommand_name  = (array) $leaf_name;
		$existing_command = $command->find_subcommand( $subcommand_name );
		if ( $existing_command instanceof CompositeCommand && $existing_command->can_have_subcommands() ) {
			if ( $leaf_command instanceof CommandNamespace || ! $leaf_command->can_have_subcommands() ) {
				$command_to_keep = $existing_command;
			} else {
				$command_to_keep = $leaf_command;
			}

			self::merge_sub_commands( $command_to_keep, $existing_command, $leaf_command );
		}

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

		if ( ! $command->can_have_subcommands() ) {
			throw new Exception(
				sprintf(
					"'%s' can't have subcommands.",
					implode( ' ', Dispatcher\get_path( $command ) )
				)
			);
		}

		/** @var Dispatcher\Subcommand $leaf_command */

		if ( isset( $args['shortdesc'] ) ) {
			$leaf_command->set_shortdesc( $args['shortdesc'] );
		}

		if ( isset( $args['longdesc'] ) ) {
			$leaf_command->set_longdesc( $args['longdesc'] );
		}

		if ( isset( $args['synopsis'] ) ) {
			if ( is_string( $args['synopsis'] ) ) {
				$leaf_command->set_synopsis( $args['synopsis'] );
			} elseif ( is_array( $args['synopsis'] ) ) {
				$synopsis = SynopsisParser::render( $args['synopsis'] );
				$leaf_command->set_synopsis( $synopsis );
				$long_desc = '';
				$bits      = explode( ' ', $synopsis );
				foreach ( $args['synopsis'] as $key => $arg ) {
					$long_desc .= $bits[ $key ] . "\n";
					if ( ! empty( $arg['description'] ) ) {
						$long_desc .= ': ' . $arg['description'] . "\n";
					}
					$yamlify = [];
					foreach ( [ 'default', 'options' ] as $key ) {
						if ( isset( $arg[ $key ] ) ) {
							$yamlify[ $key ] = $arg[ $key ];
						}
					}
					if ( ! empty( $yamlify ) ) {
						$long_desc .= Spyc::YAMLDump( $yamlify );
						$long_desc .= '---' . "\n";
					}
					$long_desc .= "\n";
				}
				if ( ! empty( $long_desc ) ) {
					$long_desc = rtrim( $long_desc, "\r\n" );
					$long_desc = '## OPTIONS' . "\n\n" . $long_desc;
					if ( ! empty( $args['longdesc'] ) ) {
						$long_desc .= "\n\n" . ltrim( $args['longdesc'], "\r\n" );
					}
					$leaf_command->set_longdesc( $long_desc );
				}
			}
		}

		if ( isset( $args['when'] ) ) {
			self::get_runner()->register_early_invoke( $args['when'], $leaf_command );
		}

		if ( ! empty( $parent ) ) {
			$sub_command = 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;
	}

	/**
	 * Merge the sub-commands of two commands into a single command to keep.
	 *
	 * @param CompositeCommand $command_to_keep Command to merge the sub commands into. This is typically one of the
	 *                                          two others.
	 * @param CompositeCommand $old_command     Command that was already registered.
	 * @param CompositeCommand $new_command     New command that is being added.
	 */
	private static function merge_sub_commands(
		CompositeCommand $command_to_keep,
		CompositeCommand $old_command,
		CompositeCommand $new_command
	) {
		foreach ( $old_command->get_subcommands() as $subname => $subcommand ) {
			$command_to_keep->add_subcommand( $subname, $subcommand, false );
		}

		foreach ( $new_command->get_subcommands() as $subname => $subcommand ) {
			$command_to_keep->add_subcommand( $subname, $subcommand, 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 = [] ) {
		$args['is_deferred']               = true;
		self::$deferred_additions[ $name ] = [
			'parent'   => $parent,
			'callable' => $callable,
			'args'     => $args,
		];
		self::add_hook(
			"after_add_command:$parent",
			function () use ( $name ) {
				$deferred_additions = WP_CLI::get_deferred_additions();

				if ( ! array_key_exists( $name, $deferred_additions ) ) {
					return;
				}

				$callable = $deferred_additions[ $name ]['callable'];
				$args     = $deferred_additions[ $name ]['args'];
				WP_CLI::remove_deferred_addition( $name );

				WP_CLI::add_command( $name, $callable, $args );
			}
		);
	}

	/**
	 * Get the list of outstanding deferred command additions.
	 *
	 * @return array Array of outstanding command additions.
	 */
	public static function get_deferred_additions() {
		return self::$deferred_additions;
	}

	/**
	 * Remove a command addition from the list of outstanding deferred additions.
	 */
	public static function remove_deferred_addition( $name ) {
		if ( ! array_key_exists( $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 void
	 */
	public static function line( $message = '' ) {
		echo $message . "\n";
	}

	/**
	 * Display informational message without prefix.
	 *
	 * Message is written to STDOUT, or discarded when `--quiet` flag is supplied.
	 *
	 * ```
	 * # `wp cli update` lets user know of each step in the update process.
	 * WP_CLI::log( sprintf( 'Downloading from %s...', $download_url ) );
	 * ```
	 *
	 * @access public
	 * @category Output
	 *
	 * @param string $message Message to write to STDOUT.
	 */
	public static function log( $message ) {
		if ( null === self::$logger ) {
			return;
		}

		self::$logger->info( $message );
	}

	/**
	 * Display success message prefixed with "Success: ".
	 *
	 * Success message is written to STDOUT, or discarded when `--quiet` flag is supplied.
	 *
	 * 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 void
	 */
	public static function success( $message ) {
		if ( null === self::$logger ) {
			return;
		}

		self::$logger->success( $message );
	}

	/**
	 * Display debug message prefixed with "Debug: " when `--debug` is used.
	 *
	 * Debug message is written to STDERR, and includes script execution time.
	 *
	 * Helpful for optionally showing greater detail when needed. Used throughout
	 * WP-CLI bootstrap process for easier debugging and profiling.
	 *
	 * ```
	 * # Called in `WP_CLI\Runner::set_wp_root()`.
	 * private static function set_wp_root( $path ) {
	 *     define( 'ABSPATH', Utils\trailingslashit( $path ) );
	 *     WP_CLI::debug( 'ABSPATH defined: ' . ABSPATH );
	 *     $_SERVER['DOCUMENT_ROOT'] = realpath( $path );
	 * }
	 *
	 * # Debug details only appear when `--debug` is used.
	 * # $ wp --debug
	 * # [...]
	 * # Debug: ABSPATH defined: /srv/www/wordpress-develop.dev/src/ (0.225s)
	 * ```
	 *
	 * @access public
	 * @category Output
	 *
	 * @param string|WP_Error|Exception|Throwable $message Message to write to STDERR.
	 * @param string|bool $group Organize debug message to a specific group.
	 * Use `false` to not group the message.
	 * @return void
	 */
	public static function debug( $message, $group = false ) {
		static $storage = [];

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

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

		self::$logger->debug( self::error_to_string( $message ), $group );
	}

	/**
	 * Display warning message prefixed with "Warning: ".
	 *
	 * Warning message is written to STDERR, or discarded when `--quiet` flag is supplied.
	 *
	 * Use instead of `WP_CLI::debug()` when script execution should be permitted
	 * to continue.
	 *
	 * ```
	 * # `wp plugin activate` skips activation when plugin is network active.
	 * $status = $this->get_status( $plugin->file );
	 * // Network-active is the highest level of activation status
	 * if ( 'active-network' === $status ) {
	 *   WP_CLI::warning( "Plugin '{$plugin->name}' is already network active." );
	 *   continue;
	 * }
	 * ```
	 *
	 * @access public
	 * @category Output
	 *
	 * @param string|WP_Error|Exception|Throwable $message Message to write to STDERR.
	 * @return void
	 */
	public static function warning( $message ) {
		if ( null === self::$logger ) {
			return;
		}

		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|Exception|Throwable $message Message to write to STDERR.
	 * @param boolean|integer            $exit    True defaults to exit(1).
	 * @return null
	 */
	public static function error( $message, $exit = true ) {
		if ( null !== self::$logger && ! isset( self::get_runner()->assoc_args['completions'] ) ) {
			self::$logger->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( '', $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
	 * @return never
	 */
	public static function halt( $return_code ) {
		if ( self::$capture_exit ) {
			throw new ExitException( '', $return_code );
		}
		exit( $return_code );
	}

	/**
	 * Display a multi-line error message in a red box. Doesn't exit script.
	 *
	 * Error message is written to STDERR.
	 *
	 * @access public
	 * @category Output
	 *
	 * @param array $message_lines Multi-line error message to be displayed.
	 */
	public static function error_multi_line( $message_lines ) {
		if ( null === self::$logger ) {
			return;
		}

		if ( ! isset( self::get_runner()->assoc_args['completions'] ) && is_array( $message_lines ) ) {
			self::$logger->error_multi_line( array_map( [ __CLASS__, 'error_to_string' ], $message_lines ) );
		}
	}

	/**
	 * Ask for confirmation before running a destructive operation.
	 *
	 * If 'y' is provided to the question, the script execution continues. If
	 * 'n' or any other response is provided to the question, script exits.
	 *
	 * ```
	 * # `wp db drop` asks for confirmation before dropping the database.
	 *
	 * WP_CLI::confirm( "Are you sure you want to drop the database?", $assoc_args );
	 * ```
	 *
	 * @access public
	 * @category Input
	 *
	 * @param string $question Question to display before the prompt.
	 * @param array $assoc_args Skips prompt if 'yes' is provided.
	 */
	public static function confirm( $question, $assoc_args = [] ) {
		if ( ! Utils\get_flag_value( $assoc_args, 'yes' ) ) {
			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 = '';
			$line      = fgets( STDIN );

			while ( false !== $line ) {
				$raw_value .= $line;
				$line       = fgets( STDIN );
			}
		}

		return $raw_value;
	}

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

		echo $value . "\n";
	}

	/**
	 * Convert a WP_Error or Exception into a string
	 *
	 * @param string|WP_Error|Exception|Throwable $errors
	 * @throws InvalidArgumentException
	 *
	 * @return string
	 */
	public static function error_to_string( $errors ) {
		if ( is_string( $errors ) ) {
			return $errors;
		}

		// Only json_encode() the data when it needs it.
		$render_data = function ( $data ) {
			if ( is_array( $data ) || is_object( $data ) ) {
				return json_encode( $data );
			}

			return '"' . $data . '"';
		};

		if ( $errors instanceof WP_Error ) {
			foreach ( $errors->get_error_messages() as $message ) {
				if ( $errors->get_error_data() ) {
					return $message . ' ' . $render_data( $errors->get_error_data() );
				}

				return $message;
			}
		}

		// PHP 7+: internal and user exceptions must implement Throwable interface.
		// PHP 5: internal and user exceptions must extend Exception class.
		if ( ( interface_exists( 'Throwable' ) && ( $errors instanceof Throwable ) ) || ( $errors instanceof Exception ) ) {
			return get_class( $errors ) . ': ' . $errors->getMessage();
		}

		throw new InvalidArgumentException(
			sprintf(
				"Unsupported argument type passed to WP_CLI::error_to_string(): '%s'",
				gettype( $errors )
			)
		);
	}

	/**
	 * Launch an arbitrary external process that takes over I/O.
	 *
	 * ```
	 * # `wp core download` falls back to the `tar` binary when PharData isn't available
	 * if ( ! class_exists( 'PharData' ) ) {
	 *     $cmd = "tar xz --strip-components=1 --directory=%s -f $tarball";
	 *     WP_CLI::launch( Utils\esc_cmd( $cmd, $dest ) );
	 *     return;
	 * }
	 * ```
	 *
	 * @access public
	 * @category Execution
	 *
	 * @param string $command External process to launch.
	 * @param boolean $exit_on_error Whether to exit if the command returns an elevated return code.
	 * @param boolean $return_detailed Whether to return an exit status (default) or detailed execution results.
	 * @return int|ProcessRun The command exit status, or a ProcessRun object for full details.
	 */
	public static function launch( $command, $exit_on_error = true, $return_detailed = false ) {
		Utils\check_proc_available( 'launch' );

		$proc    = Process::create( $command );
		$results = $proc->run();

		if ( -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|ProcessRun The command exit status, or a ProcessRun instance
	 */
	public static function launch_self( $command, $args = [], $assoc_args = [], $exit_on_error = true, $return_detailed = false, $runtime_args = [] ) {
		$reused_runtime_args = [
			'path',
			'url',
			'user',
			'allow-root',
		];

		foreach ( $reused_runtime_args as $key ) {
			if ( isset( $runtime_args[ $key ] ) ) {
				$assoc_args[ $key ] = $runtime_args[ $key ];
				continue;
			}

			$value = self::get_runner()->config[ $key ];
			if ( $value ) {
				$assoc_args[ $key ] = $value;
			}
		}

		$php_bin = 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 = 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://make.wordpress.org/cli/handbook/references/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 ( ! self::has_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.
	 * * Include additional arguments that are passed to the command.
	 *
	 * ```
	 * $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.
	 *   'command_args' => [ '--skip-themes' ], // Additional arguments to be passed to the $command.
	 * );
	 * $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.
	 *
	 *     @type bool        $launch     Launches a new process (true) or reuses the existing process (false). Default: true.
	 *     @type bool        $exit_error Halts the script on error. Default: true.
	 *     @type bool|string $return     Returns output as an object when set to 'all' (string), return just the 'stdout', 'stderr', or 'return_code' (string) of command, or print directly to stdout/stderr (false). Default: false.
	 *     @type bool|string $parse      Parse returned output as 'json' (string); otherwise, output is unchanged (false). Default: false.
	 * @param array $command_args Contains additional command line arguments for the command. Each element represents a single argument. Default: empty array.
	 * }
	 * @return mixed
	 */
	public static function runcommand( $command, $options = [] ) {
		$defaults     = [
			'launch'       => true,  // Launch a new process, or reuse the existing.
			'exit_error'   => true,  // Exit on error by default.
			'return'       => false, // Capture and return output, or render in realtime.
			'parse'        => false, // Parse returned output as a particular format.
			'command_args' => [],    // Include optional command arguments.
		];
		$options      = array_merge( $defaults, $options );
		$launch       = $options['launch'];
		$exit_error   = $options['exit_error'];
		$return       = $options['return'];
		$parse        = $options['parse'];
		$command_args = $options['command_args'];

		if ( ! empty( $command_args ) ) {
			$command .= ' ' . implode( ' ', $command_args );
		}

		$retval = null;
		if ( $launch ) {
			Utils\check_proc_available( 'launch option' );

			$descriptors = [
				0 => STDIN,
				1 => STDOUT,
				2 => STDERR,
			];

			if ( $return ) {
				$descriptors = [
					0 => STDIN,
					1 => [ 'pipe', 'w' ],
					2 => [ '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}";

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

			$stdout = '';
			$stderr = '';

			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) [
					'stdout'      => trim( $stdout ),
					'stderr'      => trim( $stderr ),
					'return_code' => $return_code,
				];
			}
		} else {
			$configurator                               = self::get_configurator();
			$argv                                       = Utils\parse_str_to_argv( $command );
			list( $args, $assoc_args, $runtime_config ) = $configurator->parse_args( $argv );
			if ( $return ) {
				$existing_logger = self::$logger;
				self::$logger    = new Execution();
				self::$logger->ob_start();
			}
			if ( ! $exit_error ) {
				self::$capture_exit = true;
			}
			try {
				self::get_runner()->run_command(
					$args,
					$assoc_args,
					[
						'back_compat_conversions' => true,
					]
				);
				$return_code = 0;
			} catch ( ExitException $e ) {
				$return_code = $e->getCode();
			}
			if ( $return ) {
				$execution_logger = self::$logger;
				$execution_logger->ob_end();
				self::$logger = $existing_logger;
				$stdout       = $execution_logger->stdout;
				$stderr       = $execution_logger->stderr;
				if ( true === $return || 'stdout' === $return ) {
					$retval = trim( $stdout );
				} elseif ( 'stderr' === $return ) {
					$retval = trim( $stderr );
				} elseif ( 'return_code' === $return ) {
					$retval = $return_code;
				} elseif ( 'all' === $return ) {
					$retval = (object) [
						'stdout'      => trim( $stdout ),
						'stderr'      => trim( $stderr ),
						'return_code' => $return_code,
					];
				}
			}
			if ( ! $exit_error ) {
				self::$capture_exit = false;
			}
		}
		if ( ( true === $return || 'stdout' === $return )
			&& 'json' === $parse ) {
			$retval = json_decode( $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 = [] ) {
		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

/**
 * This file contains fallback functions that might have been disabled but are required nevertheless.
 */

if ( PHP_MAJOR_VERSION >= 8 && ! function_exists( 'ini_set' ) ) {
	function ini_set( $option, $value ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- This is a stub only.
		return false;
	}
}
<?php

namespace WP_CLI\Dispatcher;

/**
 * Get the path to a command, e.g. "core download"
 *
 * @param Subcommand|CompositeCommand $command
 * @return string[]
 */
function get_path( $command ) {
	$path = [];

	do {
		array_unshift( $path, $command->get_name() );
		$command = $command->get_parent();
	} while ( $command );

	return $path;
}
<?php

namespace WP_CLI\Bootstrap;

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

		return $state;
	}
}
<?php

namespace WP_CLI\Bootstrap;

use WP_CLI;

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

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

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

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

		return $state;
	}
}
<?php

namespace WP_CLI\Bootstrap;

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

use WP_CLI;

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

	/**
	 * Get the autoloader paths to scan for an autoloader.
	 *
	 * @return string[] Array of autoloader paths, or an empty array if none are found.
	 */
	protected function get_autoloader_paths() {
		$autoloader_paths = [
			WP_CLI_VENDOR_DIR . '/autoload.php',
		];

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

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

		return $autoloader_paths;
	}
}
<?php

namespace WP_CLI\Bootstrap;

/**
 * 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()->register_context_manager(
			$state->getValue( 'context_manager' )
		);

		$runner()->start();

		return $state;
	}
}
<?php

namespace WP_CLI\Bootstrap;

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

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

		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\Bootstrap;

use WP_CLI\Autoloader;
use WP_CLI\RequestsLibrary;
use WP_CLI\Utils;

/**
 * Class IncludeRequestsAutoloader.
 *
 * Loads the Requests autoloader that best fits the current environment.
 *
 * If a WordPress installation is found, it autoloads that version of Requests.
 * Otherwise, it loads the version of Requests bundled with WP-CLI.
 *
 * This is done in order to avoid conflicts between Requests versions.
 *
 * @package WP_CLI\Bootstrap
 */
final class IncludeRequestsAutoloader implements BootstrapStep {

	/**
	 * Requests is being used from the WordPress installation.
	 *
	 * @var string
	 */
	const FROM_WP_CORE = 'wp-core';

	/**
	 * Requests is being used from the WP-CLI dependencies.
	 *
	 * @var string
	 */
	const FROM_WP_CLI = 'wp-cli';

	/**
	 * 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 Requests is already loaded, don't do anything.
		if ( class_exists( RequestsLibrary::CLASS_NAME_V2, false ) || class_exists( RequestsLibrary::CLASS_NAME_V1, false ) ) {
			return $state;
		}

		$runner = new RunnerInstance();

		// Use `--path` from the alias if one is matching.
		$alias_path = null;
		if ( $runner()->alias
			&& isset( $runner()->aliases[ $runner()->alias ]['path'] ) ) {
			$alias_path = $runner()->aliases[ $runner()->alias ]['path'];
			// Make sure it isn't an invalid value.
			if ( is_bool( $alias_path ) || empty( $alias_path ) ) {
				return $state;
			}
			if ( ! Utils\is_path_absolute( $alias_path ) ) {
				$alias_path = getcwd() . '/' . $alias_path;
			}
			$wp_root = rtrim( $alias_path, '/' );
		} else {
			// Make sure we don't deal with an invalid `--path` value.
			$config = $runner()->config;
			if ( isset( $config['path'] ) &&
				( is_bool( $config['path'] ) || empty( $config['path'] ) )
			) {
				return $state;
			}
			$wp_root = rtrim( $runner()->find_wp_root(), '/' );
		}

		// First try to detect a newer Requests version bundled with WordPress.
		if ( file_exists( $wp_root . '/wp-includes/Requests/src/Autoload.php' ) ) {
			if ( ! class_exists( '\\WpOrg\\Requests\\Autoload', false ) ) {
				require_once $wp_root . '/wp-includes/Requests/src/Autoload.php';
			}

			if ( class_exists( '\\WpOrg\\Requests\\Autoload' ) ) {
				\WpOrg\Requests\Autoload::register();
				$this->store_requests_meta( RequestsLibrary::CLASS_NAME_V2, self::FROM_WP_CORE );
				return $state;
			}
		}

		// Then see if we can detect the older version bundled with WordPress.
		if ( file_exists( $wp_root . '/wp-includes/class-requests.php' ) ) {
			if ( ! class_exists( '\\Requests', false ) ) {
				require_once $wp_root . '/wp-includes/class-requests.php';
			}

			if ( class_exists( '\\Requests' ) ) {
				\Requests::register_autoloader();
				$this->store_requests_meta( RequestsLibrary::CLASS_NAME_V1, self::FROM_WP_CORE );
				return $state;
			}
		}

		// Finally, fall back to the Requests version bundled with WP-CLI.
		$autoloader = new Autoloader();
		$autoloader->add_namespace(
			'WpOrg\Requests',
			WP_CLI_ROOT . '/bundle/rmccue/requests/src'
		);

		$autoloader->register();

		\WpOrg\Requests\Autoload::register();

		$this->store_requests_meta( RequestsLibrary::CLASS_NAME_V2, self::FROM_WP_CLI );

		return $state;
	}

	/**
	 * Store meta information about the used Requests integration.
	 *
	 * This can be used for all the conditional code that needs to work
	 * across multiple Requests versions.
	 *
	 * @param string $class_name The class name of the Requests integration.
	 * @param string $source     The source of the Requests integration.
	 */
	private function store_requests_meta( $class_name, $source ) {
		RequestsLibrary::set_version(
			RequestsLibrary::CLASS_NAME_V2 === $class_name
				? RequestsLibrary::VERSION_V2
				: RequestsLibrary::VERSION_V1
		);
		RequestsLibrary::set_source( $source );
		RequestsLibrary::set_class_name( $class_name );
	}
}
<?php

namespace WP_CLI\Bootstrap;

use WP_CLI;
use WP_CLI\Runner;

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

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

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

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

namespace WP_CLI\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();

		$state->setValue( 'config', $runner()->config );
		$state->setValue( 'arguments', $runner()->arguments );
		$state->setValue( 'assoc_args', $runner()->assoc_args );

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

use DirectoryIterator;

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

	/**
	 * Process this single bootstrapping step.
	 *
	 * @param BootstrapState $state Contextual state to pass into the step.
	 *
	 * @return BootstrapState Modified state to pass to the next step.
	 */
	public function 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;

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

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 ( [ '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;

use DirectoryIterator;
use Exception;
use WP_CLI;

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

	/**
	 * Process this single bootstrapping step.
	 *
	 * @param BootstrapState $state Contextual state to pass into the step.
	 *
	 * @return BootstrapState Modified state to pass to the next step.
	 */
	public function 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 BootstrapState.
 *
 * Represents the state that is passed from one bootstrap step to the next.
 *
 * @package WP_CLI\Bootstrap
 *
 * Maintain BC: Changing the method names in this class breaks autoload interactions between Phar
 * & framework/commands you use outside of Phar (like when running the Phar WP inside of a command folder).
 * @phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid
 */
class BootstrapState {

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

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

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

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

namespace WP_CLI\Bootstrap;

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

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

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

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

		$contexts = WP_CLI::do_hook( 'before_registering_contexts', $contexts );

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

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

		return $state;
	}
}
<?php

namespace WP_CLI\Bootstrap;

use Exception;
use WP_CLI;

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

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

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

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

		if ( false === $autoloader_paths ) {
			// Skip this autoload 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 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;

use WP_CLI\Autoloader;

/**
 * Class IncludeFrameworkAutoloader.
 *
 * Loads the framework autoloader through an autoloader 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',
			'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 . '/wp-cli/mustangostang-spyc/Spyc.php';

		$autoloader->register();

		return $state;
	}
}
<?php

namespace WP_CLI\Bootstrap;

use WP_CLI;
use WP_CLI\Utils;

/**
 * Class CheckRoot.
 *
 * Check if the user is running as root and aborts with a warning if they are.
 *
 * @package WP_CLI\Bootstrap
 */
class CheckRoot 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 ) {
		$config = $state->getValue( 'config', [] );
		if ( array_key_exists( 'allow-root', $config ) && true === $config['allow-root'] ) {
			// They're aware of the risks and set a flag to allow root.
			return $state;
		}

		if ( getenv( 'WP_CLI_ALLOW_ROOT' ) ) {
			// They're aware of the risks and set an environment variable to allow root.
			return $state;
		}

		$args = $state->getValue( 'arguments', [] );
		if ( count( $args ) >= 2 && 'cli' === $args[0] && in_array( $args[1], [ 'update', 'info' ], true ) ) {
			// Make it easier to update root-owned copies.
			return $state;
		}

		if ( ! function_exists( 'posix_geteuid' ) ) {
			// POSIX functions not available.
			return $state;
		}

		if ( posix_geteuid() !== 0 ) {
			// Not root.
			return $state;
		}

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

namespace WP_CLI\Bootstrap;

use WP_CLI;
use WP_CLI\Utils;

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

	/**
	 * Process this single bootstrapping step.
	 *
	 * @param BootstrapState $state Contextual state to pass into the step.
	 *
	 * @return BootstrapState Modified state to pass to the next step.
	 */
	public function 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',
			[ $this, 'add_deferred_commands' ]
		);

		return $state;
	}

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

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

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

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

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

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

namespace WP_CLI\Bootstrap;

use WP_CLI;

/**
 * Class 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 [
				$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 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 [
			'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;

use Mustangostang\Spyc;
use SplFileInfo;

use function WP_CLI\Utils\is_path_absolute;
use function WP_CLI\Utils\normalize_path;

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

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

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

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

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

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

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

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

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

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

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

		$env_files = getenv( 'WP_CLI_REQUIRE' )
		? array_filter( array_map( 'trim', explode( ',', getenv( 'WP_CLI_REQUIRE' ) ) ) )
		: [];

		if ( ! empty( $env_files ) ) {
			if ( ! isset( $this->config['require'] ) ) {
				$this->config['require'] = [];
			}
			$this->config['require'] = array_unique( array_merge( $env_files, $this->config['require'] ) );
		}
	}

	/**
	 * Loads the config spec file.
	 *
	 * @param string $path Path to the config spec file.
	 */
	private function load_config_spec( $path ) {
		$config_spec = include $path;
		// A way for platforms to modify $config_spec.
		// Use with caution!
		$config_spec_filter_callback = defined( 'WP_CLI_CONFIG_SPEC_FILTER_CALLBACK' ) ? constant( 'WP_CLI_CONFIG_SPEC_FILTER_CALLBACK' ) : false;
		if ( $config_spec_filter_callback && is_callable( $config_spec_filter_callback ) ) {
			$config_spec = $config_spec_filter_callback( $config_spec );
		}
		$this->spec = $config_spec;
	}

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

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

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

		return $this->aliases;
	}

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

	/**
	 * Splits positional args from associative args.
	 *
	 * @param array<string> $arguments
	 * @return array{0: array<string>, 1: array<array{0: string, 1: string|bool}>, 2: array<array{0: string, 1: string|bool}>, 3: array<array{0: string, 1: string|bool}>}
	 */
	public static function extract_assoc( $arguments ) {
		$positional_args = [];
		$assoc_args      = [];
		$global_assoc    = [];
		$local_assoc     = [];

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

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

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

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

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

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

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

		return [ $assoc_args, $runtime_config ];
	}

	/**
	 * Handle turning an $assoc_arg into a runtime arg.
	 */
	private function assoc_arg_to_runtime_config( $key, $value, &$runtime_config ) {
		$details = $this->spec[ $key ];
		if ( isset( $details['deprecated'] ) ) {
			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'] ) ) {
			// Refactor with the WP-CLI `Path` class, once it's available.
			// See: https://github.com/wp-cli/wp-cli/issues/5007
			$inherit_path = is_path_absolute( $yaml['_']['inherit'] )
				? $yaml['_']['inherit']
				: ( new SplFileInfo( normalize_path( dirname( $path ) . '/' . $yaml['_']['inherit'] ) ) )->getRealPath();

			$this->merge_yml( $inherit_path, $current_alias );
		}
		// Prepare the base path for absolutized alias paths.
		$yml_file_dir = $path ? dirname( $path ) : false;
		foreach ( $yaml as $key => $value ) {
			if ( preg_match( '#' . self::ALIAS_REGEX . '#', $key ) ) {
				$this->aliases[ $key ] = [];
				$is_alias              = false;
				foreach ( self::$alias_spec as $i ) {
					if ( isset( $value[ $i ] ) ) {
						if ( 'path' === $i && ! isset( $value['ssh'] ) ) {
							self::absolutize( $value[ $i ], $yml_file_dir );
						}
						$this->aliases[ $key ][ $i ] = $value[ $i ];
						$is_alias                    = true;
					}
				}
				// If it's not an alias, it might be a group of aliases.
				if ( ! $is_alias && is_array( $value ) ) {
					$alias_group = [];
					foreach ( $value as $k ) {
						if ( preg_match( '#' . self::ALIAS_REGEX . '#', $k ) ) {
							$alias_group[] = $k;
						}
					}
					$this->aliases[ $key ] = $alias_group;
				}
			} elseif ( ! isset( $this->spec[ $key ] ) || false === $this->spec[ $key ]['file'] ) {
				if ( isset( $this->extra_config[ $key ] )
					&& ! empty( $yaml['_']['merge'] )
					&& is_array( $this->extra_config[ $key ] )
					&& is_array( $value ) ) {
					$this->extra_config[ $key ] = array_merge( $this->extra_config[ $key ], $value );
				} else {
					$this->extra_config[ $key ] = $value;
				}
			} elseif ( $this->spec[ $key ]['multiple'] ) {
				self::arrayify( $value );
				$this->config[ $key ] = array_merge( $this->config[ $key ], $value );
			} else {
				if ( $current_alias && in_array( $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 = Utils\expand_globs( $value );
				}

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

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

		$config = Spyc::YAMLLoad( $yml_file );

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

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

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

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

		return $config;
	}

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

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

namespace WP_CLI;

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

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

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

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

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

		return $params;
	}

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

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

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

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

					if ( ! empty( $arg['value']['optional'] ) ) {
						$arg_value = "[{$arg_value}]";
					}

					$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 = implode( '', $bits );

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

		return rtrim( $rendered, ' ' );
	}

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

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

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

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

			$value = substr( $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 = [
						'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 [ true, substr( $token, 1, -1 ) ];
		}

		return [ false, $token ];
	}

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

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

namespace WP_CLI\Exception;

use OutOfBoundsException;
use WP_CLI\Traverser\RecursiveDataStructureTraverser;

class NonExistentKeyException extends OutOfBoundsException {
	/** @var RecursiveDataStructureTraverser */
	protected $traverser;

	/**
	 * @param RecursiveDataStructureTraverser $traverser
	 */
	public function set_traverser( $traverser ) {
		$this->traverser = $traverser;
	}

	/**
	 * @return RecursiveDataStructureTraverser
	 */
	public function get_traverser() {
		return $this->traverser;
	}
}
<?php

namespace WP_CLI;

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

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

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

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

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

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

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

		if ( ! file_exists( $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_exists( $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 );
				}
			} elseif ( 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 = [
			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\Dispatcher;

use WP_CLI;
use WP_CLI\DocParser;
use WP_CLI\SynopsisParser;
use WP_CLI\SynopsisValidator;
use WP_CLI\Utils;

/**
 * A leaf node in the command tree.
 *
 * @package WP_CLI
 */
class Subcommand extends CompositeCommand {

	private $alias;

	private $when_invoked;

	public function __construct( $parent, $name, $docparser, $when_invoked ) {
		$this->alias = $docparser->get_tag( 'alias' );

		parent::__construct( $parent, $name, $docparser );

		$this->when_invoked = $when_invoked;

		$this->synopsis = $docparser->get_synopsis();
		if ( ! $this->synopsis && $this->longdesc ) {
			$this->synopsis = self::extract_synopsis( $this->longdesc );
		}
	}

	/**
	 * Extract the synopsis from PHPdoc string.
	 *
	 * @param string $longdesc Command docs via PHPdoc
	 * @return string
	 */
	private static function extract_synopsis( $longdesc ) {
		preg_match_all( '/(.+?)[\r\n]+:/', $longdesc, $matches );
		return implode( ' ', $matches[1] );
	}

	/**
	 * Subcommands can't have subcommands because they
	 * represent code to be executed.
	 *
	 * @return bool
	 */
	public function can_have_subcommands() {
		return false;
	}

	/**
	 * Get the synopsis string for this subcommand.
	 * A synopsis defines what runtime arguments are
	 * expected, useful to humans and argument validation.
	 *
	 * @return string
	 */
	public function get_synopsis() {
		return $this->synopsis;
	}

	/**
	 * Set the synopsis string for this subcommand.
	 *
	 * @param string $synopsis
	 */
	public function set_synopsis( $synopsis ) {
		$this->synopsis = $synopsis;
	}

	/**
	 * If an alias is set, grant access to it.
	 * Aliases permit subcommands to be instantiated
	 * with a secondary identity.
	 *
	 * @return string
	 */
	public function get_alias() {
		return $this->alias;
	}

	/**
	 * Print the usage details to the end user.
	 *
	 * @param string $prefix
	 */
	public function show_usage( $prefix = 'usage: ' ) {
		\WP_CLI::line( $this->get_usage( $prefix ) );
	}

	/**
	 * Get the usage of the subcommand as a formatted string.
	 *
	 * @param string $prefix
	 * @return string
	 */
	public function get_usage( $prefix ) {
		return sprintf(
			'%s%s %s',
			$prefix,
			implode( ' ', get_path( $this ) ),
			$this->get_synopsis()
		);
	}

	/**
	 * Wrapper for CLI Tools' prompt() method.
	 *
	 * @param string $question
	 * @param string $default
	 * @return string|false
	 */
	private function prompt( $question, $default ) {

		$question .= ': ';
		if ( function_exists( 'readline' ) ) {
			return readline( $question );
		}

		echo $question;

		$ret = stream_get_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 [ $args, $assoc_args ];
		}

		// To skip the already provided positional arguments, we need to count
		// how many we had already received.
		$arg_index = 0;

		$spec = array_filter(
			SynopsisParser::parse( $synopsis ),
			function ( $spec_arg ) use ( $args, $assoc_args, &$arg_index ) {
				switch ( $spec_arg['type'] ) {
					case 'positional':
						// Only prompt for the positional arguments that are not
						// yet provided, based purely on number.
						return $arg_index++ >= count( $args );
					case 'generic':
						// Always prompt for generic arguments.
						return true;
					case 'assoc':
					case 'flag':
					default:
						// Prompt for the specific flags that were not provided
						// yet, based on name.
						return ! isset( $assoc_args[ $spec_arg['name'] ] );
				}
			}
		);

		$spec = array_values( $spec );

		$prompt_args = WP_CLI::get_config( 'prompt' );
		if ( true !== $prompt_args ) {
			$prompt_args = explode( ',', $prompt_args );
		}

		// 'positional' arguments are positional (aka zero-indexed)
		// so $args needs to be reset before prompting for new arguments
		$args = [];

		foreach ( $spec as $key => $spec_arg ) {

			// When prompting for specific arguments (e.g. --prompt=user_pass),
			// ignore all arguments that don't match.
			if ( is_array( $prompt_args ) ) {
				if ( 'assoc' !== $spec_arg['type'] ) {
					continue;
				}
				if ( ! in_array( $spec_arg['name'], $prompt_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 [ $args, $assoc_args ];
					}

					if ( $key ) {
						$key_prompt_count = strlen( $key_prompt ) - strlen( $value_token ) - 1;
						$value_prompt     = str_repeat( ' ', $key_prompt_count ) . '=' . $value_token;

						$value = $this->prompt( $value_prompt, $default );
						if ( false === $value ) {
							return [ $args, $assoc_args ];
						}

						$assoc_args[ $key ] = $value;

						$repeat = true;
					} else {
						$repeat = false;
					}
				} while ( $repeat );

			} else {
				$prompt = $current_prompt . $spec_arg['token'];
				if ( 'flag' === $spec_arg['type'] ) {
					$prompt .= ' (Y/n)';
				}

				$response = $this->prompt( $prompt, $default );
				if ( false === $response ) {
					return [ $args, $assoc_args ];
				}

				if ( $response ) {
					switch ( $spec_arg['type'] ) {
						case 'positional':
							if ( $spec_arg['repeating'] ) {
								$response = explode( ' ', $response );
							} else {
								$response = [ $response ];
							}
							$args = array_merge( $args, $response );
							break;
						case 'assoc':
							$assoc_args[ $spec_arg['name'] ] = $response;
							break;
						case 'flag':
							if ( 'Y' === strtoupper( $response ) ) {
								$assoc_args[ $spec_arg['name'] ] = true;
							}
							break;
					}
				}
			}
		}

		return [ $args, $assoc_args ];
	}

	/**
	 * Validate the supplied arguments to the command.
	 * Throws warnings or errors if arguments are missing
	 * or invalid.
	 *
	 * @param array $args
	 * @param array $assoc_args
	 * @param array $extra_args
	 * @return array list of invalid $assoc_args keys to unset
	 */
	private function validate_args( $args, $assoc_args, $extra_args ) {
		$synopsis = $this->get_synopsis();
		if ( ! $synopsis ) {
			return [ [], $args, $assoc_args, $extra_args ];
		}

		$validator = new SynopsisValidator( $synopsis );

		$cmd_path = implode( ' ', get_path( $this ) );
		foreach ( $validator->get_unknown() as $token ) {
			\WP_CLI::warning(
				sprintf(
					'The `%s` command has an invalid synopsis part: %s',
					$cmd_path,
					$token
				)
			);
		}

		if ( ! $validator->enough_positionals( $args ) ) {
			$this->show_usage();
			exit( 1 );
		}

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

		$synopsis_spec = SynopsisParser::parse( $synopsis );
		$i             = 0;
		$errors        = [
			'fatal'   => [],
			'warning' => [],
		];
		$mock_doc      = [ $this->get_shortdesc(), '' ];
		$mock_doc      = array_merge( $mock_doc, explode( "\n", $this->get_longdesc() ) );
		$mock_doc      = '/**' . PHP_EOL . '* ' . implode( PHP_EOL . '* ', $mock_doc ) . PHP_EOL . '*/';
		$docparser     = new DocParser( $mock_doc );
		foreach ( $synopsis_spec as $spec ) {
			if ( 'positional' === $spec['type'] ) {
				$spec_args = $docparser->get_arg_args( $spec['name'] );
				if ( ! isset( $args[ $i ] ) ) {
					if ( isset( $spec_args['default'] ) ) {
						$args[ $i ] = $spec_args['default'];
					}
				}
				if ( isset( $spec_args['options'] ) ) {
					if ( ! empty( $spec['repeating'] ) ) {
						do {
							// phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict -- This is a loose comparison by design.
							if ( isset( $args[ $i ] ) && ! in_array( $args[ $i ], $spec_args['options'] ) ) {
								\WP_CLI::error( 'Invalid value specified for positional arg.' );
							}
							++$i;
						} while ( isset( $args[ $i ] ) );
					} elseif ( isset( $args[ $i ] ) && ! in_array( $args[ $i ], $spec_args['options'] ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict -- This is a loose comparison by design.
						\WP_CLI::error( 'Invalid value specified for positional arg.' );
					}
				}
				++$i;
			} elseif ( 'assoc' === $spec['type'] ) {
				$spec_args = $docparser->get_param_args( $spec['name'] );
				if ( ! isset( $assoc_args[ $spec['name'] ] ) && ! isset( $extra_args[ $spec['name'] ] ) ) {
					if ( isset( $spec_args['default'] ) ) {
						$assoc_args[ $spec['name'] ] = $spec_args['default'];
					}
				}
				if ( isset( $assoc_args[ $spec['name'] ] ) && isset( $spec_args['options'] ) ) {
					$value   = $assoc_args[ $spec['name'] ];
					$options = $spec_args['options'];
					// phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict -- This is a loose comparison by design.
					if ( ! in_array( $value, $options ) ) {
						// Try whether it might be a comma-separated list of multiple values.
						$values = array_map( 'trim', explode( ',', $value ) );
						$count  = count( $values );
						if (
							$count > 1
							&&
							count(
								array_filter(
									$values,
									static function ( $value ) use ( $options ) {
										return in_array( $value, $options, true );
									}
								)
							) === $count
						) {
							continue;
						}
						$errors['fatal'][ $spec['name'] ] = "Invalid value specified for '{$spec['name']}'";
					}
				}
			}
		}

		list( $returned_errors, $to_unset ) = $validator->validate_assoc(
			array_merge( \WP_CLI::get_config(), $extra_args, $assoc_args )
		);
		foreach ( [ 'fatal', 'warning' ] as $error_type ) {
			$errors[ $error_type ] = array_merge( $errors[ $error_type ], $returned_errors[ $error_type ] );
		}

		if ( 'help' !== $this->name ) {
			foreach ( $validator->unknown_assoc( $assoc_args ) as $key ) {
				$suggestion    = Utils\get_suggestion(
					$key,
					$this->get_parameters( $synopsis_spec ),
					$threshold = 2
				);

				$errors['fatal'][] = sprintf(
					'unknown --%s parameter%s',
					$key,
					! empty( $suggestion ) ? PHP_EOL . "Did you mean '--{$suggestion}'?" : ''
				);
			}
		}

		if ( ! empty( $errors['fatal'] ) ) {
			$out = 'Parameter errors:';
			foreach ( $errors['fatal'] as $key => $error ) {
				$out .= "\n {$error}";
				$desc = $docparser->get_param_desc( $key );
				if ( '' !== $desc ) {
					$out .= " ({$desc})";
				}
			}

			\WP_CLI::error( $out );
		}

		array_map( '\\WP_CLI::warning', $errors['warning'] );

		return [ $to_unset, $args, $assoc_args, $extra_args ];
	}

	/**
	 * Invoke the subcommand with the supplied arguments.
	 * Given a --prompt argument, interactively request input
	 * from the end user.
	 *
	 * @param array $args
	 * @param array $assoc_args
	 */
	public function invoke( $args, $assoc_args, $extra_args ) {
		static $prompted_once = false;

		if ( 'help' !== $this->name ) {
			if ( \WP_CLI::get_config( 'prompt' ) && ! $prompted_once ) {
				list( $_args, $assoc_args ) = $this->prompt_args( $args, $assoc_args );
				$args                       = array_merge( $args, $_args );
				$prompted_once              = true;
			}
		}

		$extra_positionals = [];
		foreach ( $extra_args as $k => $v ) {
			if ( is_numeric( $k ) ) {
				if ( ! isset( $args[ $k ] ) ) {
					$extra_positionals[ $k ] = $v;
				}
				unset( $extra_args[ $k ] );
			}
		}
		$args += $extra_positionals;

		list( $to_unset, $args, $assoc_args, $extra_args ) = $this->validate_args( $args, $assoc_args, $extra_args );

		foreach ( $to_unset as $key ) {
			unset( $assoc_args[ $key ] );
		}

		$path   = get_path( $this->get_parent() );
		$parent = implode( ' ', array_slice( $path, 1 ) );
		$cmd    = $this->name;
		if ( $parent ) {
			WP_CLI::do_hook( "before_invoke:{$parent}", $parent );
			$cmd = $parent . ' ' . $cmd;
		}
		WP_CLI::do_hook( "before_invoke:{$cmd}", $cmd );

		// Check if `--prompt` arg passed or not.
		if ( $prompted_once ) {
			// Unset empty args.
			$actual_args = $assoc_args;
			foreach ( $actual_args as $key ) {
				if ( empty( $actual_args[ $key ] ) ) {
					unset( $actual_args[ $key ] );
				}
			}

			WP_CLI::log(
				sprintf(
					'wp %s %s',
					$cmd,
					ltrim(
						implode(
							' ',
							[
								ltrim( Utils\args_to_str( $args ), ' ' ),
								ltrim( Utils\assoc_args_to_str( $actual_args ), ' ' ),
							]
						),
						' '
					)
				)
			);
		}

		call_user_func( $this->when_invoked, $args, array_merge( $extra_args, $assoc_args ) );

		if ( $parent ) {
			WP_CLI::do_hook( "after_invoke:{$parent}", $parent );
		}
		WP_CLI::do_hook( "after_invoke:{$cmd}", $cmd );
	}

	/**
	 * Get an array of parameter names, by merging the command-specific and the
	 * global parameters.
	 *
	 * @param array $spec Optional. Specification of the current command.
	 *
	 * @return array Array of parameter names
	 */
	private function get_parameters( $spec = [] ) {
		$local_parameters  = array_column( $spec, 'name' );
		$global_parameters = array_column(
			SynopsisParser::parse( $this->get_global_params() ),
			'name'
		);

		return array_unique( array_merge( $local_parameters, $global_parameters ) );
	}
}
<?php

namespace WP_CLI\Dispatcher;

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

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

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

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

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

		return $command;
	}

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

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

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

			if ( ! $name ) {
				$name = $reflection->name;
			}
		}
		if ( ! $doc_comment ) {
			WP_CLI::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( [ $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 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, [ $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 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 ) {
		$contents    = null;
		$doc_comment = $reflection->getDocComment();

		if ( false !== $doc_comment || ! ( ini_get( 'opcache.enable_cli' ) && ! ini_get( 'opcache.save_comments' ) ) ) { // phpcs:ignore PHPCompatibility.IniDirectives.NewIniDirectives
			// Either have doc comment, or no doc comment and save comments enabled - standard situation.
			if ( ! getenv( 'WP_CLI_TEST_GET_DOC_COMMENT' ) ) {
				return $doc_comment;
			}
		}

		$filename = $reflection->getFileName();

		if ( isset( self::$file_contents[ $filename ] ) ) {
			$contents = self::$file_contents[ $filename ];
		} elseif ( is_readable( $filename ) ) {
			$contents = file_get_contents( $filename );
			if ( is_string( $contents ) && '' !== $contents ) {
				$contents                         = explode( "\n", $contents );
				self::$file_contents[ $filename ] = $contents;
			}
		}

		if ( ! empty( $contents ) ) {
			return self::extract_last_doc_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 );
		$comment_start2_pos = strrpos( $subcontent, '/**' );

		while ( false !== $comment_start2_pos && false === strpos( $subcontent, '*/', $comment_start2_pos ) ) {
			$comment_start_pos  = $comment_start2_pos;
			$subcontent         = substr( $subcontent, 0, $comment_start_pos );
			$comment_start2_pos = strrpos( $subcontent, '/**' );
		}

		return substr( $content, $comment_start_pos, $comment_end_pos + 2 );
	}
}
<?php

namespace WP_CLI\Dispatcher;

use WP_CLI;
use WP_CLI\DocParser;
use WP_CLI\Utils;

/**
 * A non-leaf node in the command tree.
 * Contains one or more Subcommands.
 *
 * @package WP_CLI
 */
class CompositeCommand {

	protected $name;
	protected $shortdesc;
	protected $longdesc;
	protected $synopsis;
	protected $hook;
	protected $docparser;

	protected $parent;
	protected $subcommands = [];

	/**
	 * Instantiate a new CompositeCommand
	 *
	 * @param mixed $parent Parent command (either Root or Composite)
	 * @param string $name Represents how command should be invoked
	 * @param DocParser $docparser
	 */
	public function __construct( $parent, $name, $docparser ) {
		$this->parent = $parent;

		$this->name = $name;

		$this->shortdesc = $docparser->get_shortdesc();
		$this->longdesc  = $docparser->get_longdesc();
		$this->docparser = $docparser;
		$this->hook      = $parent->get_hook();

		$when_to_invoke = $docparser->get_tag( 'when' );
		if ( $when_to_invoke ) {
			$this->hook = $when_to_invoke;
			WP_CLI::get_runner()->register_early_invoke( $when_to_invoke, $this );
		}
	}

	/**
	 * Get the parent composite (or root) command
	 *
	 * @return mixed
	 */
	public function get_parent() {
		return $this->parent;
	}

	/**
	 * Add a named subcommand to this composite command's
	 * set of contained subcommands.
	 *
	 * @param string                      $name     Represents how subcommand should be invoked
	 * @param Subcommand|CompositeCommand $command  Cub-command to add.
	 * @param bool                        $override Optional. Whether to override an existing subcommand of the same
	 *                                              name.
	 */
	public function add_subcommand( $name, $command, $override = true ) {
		if ( $override || ! array_key_exists( $name, $this->subcommands ) ) {
			$this->subcommands[ $name ] = $command;
		}
	}

	/**
	 * Remove a named subcommand from this composite command's set of contained
	 * subcommands
	 *
	 * @param string $name Represents how subcommand should be invoked
	 */
	public function remove_subcommand( $name ) {
		if ( isset( $this->subcommands[ $name ] ) ) {
			unset( $this->subcommands[ $name ] );
		}
	}


	/**
	 * Composite commands always contain subcommands.
	 *
	 * @return true
	 */
	public function can_have_subcommands() {
		return true;
	}

	/**
	 * Get the subcommands contained by this composite
	 * command.
	 *
	 * @return array
	 */
	public function get_subcommands() {
		ksort( $this->subcommands );

		return $this->subcommands;
	}

	/**
	 * Get the name of this composite command.
	 *
	 * @return string
	 */
	public function get_name() {
		return $this->name;
	}

	/**
	 * Get the short description for this composite
	 * command.
	 *
	 * @return string
	 */
	public function get_shortdesc() {
		return $this->shortdesc;
	}

	/**
	 * Get the hook name for this composite
	 * command.
	 *
	 * @return string
	 */
	public function get_hook() {
		return $this->hook;
	}

	/**
	 * Set the short description for this composite command.
	 *
	 * @param string $shortdesc
	 */
	public function set_shortdesc( $shortdesc ) {
		$this->shortdesc = Utils\normalize_eols( $shortdesc );
	}

	/**
	 * Get the long description for this composite
	 * command.
	 *
	 * @return string
	 */
	public function get_longdesc() {
		return $this->longdesc . $this->get_global_params();
	}

	/**
	 * Set the long description for this composite command
	 *
	 * @param string $longdesc
	 */
	public function set_longdesc( $longdesc ) {
		$this->longdesc = Utils\normalize_eols( $longdesc );
	}

	/**
	 * Get the synopsis for this composite command.
	 * As a collection of subcommands, the composite
	 * command is only intended to invoke those
	 * subcommands.
	 *
	 * @return string
	 */
	public function get_synopsis() {
		return '<command>';
	}

	/**
	 * Get the usage for this composite command.
	 *
	 * @return string
	 */
	public function get_usage( $prefix ) {
		return sprintf(
			'%s%s %s',
			$prefix,
			implode( ' ', get_path( $this ) ),
			$this->get_synopsis()
		);
	}

	/**
	 * Show the usage for all subcommands contained
	 * by the composite command.
	 */
	public function show_usage() {
		$methods = $this->get_subcommands();

		$i = 0;

		foreach ( $methods as $subcommand ) {
			$prefix = ( 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 ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- arguments not used, as only help displayed.
		$this->show_usage();
	}

	/**
	 * Given supplied arguments, find a contained
	 * subcommand
	 *
	 * @param array $args
	 * @return Subcommand|false
	 */
	public function find_subcommand( &$args ) {
		$name = array_shift( $args );

		$subcommands = $this->get_subcommands();

		if ( ! isset( $subcommands[ $name ] ) ) {
			$aliases = self::get_aliases( $subcommands );

			if ( isset( $aliases[ $name ] ) ) {
				$name = $aliases[ $name ];
			}
		}

		if ( ! isset( $subcommands[ $name ] ) ) {
			return false;
		}

		return $subcommands[ $name ];
	}

	/**
	 * Get any registered aliases for this composite command's
	 * subcommands.
	 *
	 * @param array $subcommands
	 * @return array
	 */
	private static function get_aliases( $subcommands ) {
		$aliases = [];

		foreach ( $subcommands as $name => $subcommand ) {
			$alias = $subcommand->get_alias();
			if ( $alias ) {
				$aliases[ $alias ] = $name;
			}
		}

		return $aliases;
	}

	/**
	 * Composite commands can only be known by one name.
	 *
	 * @return string|false
	 */
	public function get_alias() {
		return false;
	}

	/***
	 * Get the list of global parameters
	 *
	 * @param string $root_command whether to include or not root command specific description
	 * @return string
	 */
	protected function get_global_params( $root_command = false ) {
		$binding                 = [];
		$binding['root_command'] = $root_command;

		if ( ! $this->can_have_subcommands() || ( is_object( $this->parent ) && get_class( $this->parent ) === 'WP_CLI\Dispatcher\CompositeCommand' ) ) {
			$binding['is_subcommand'] = true;
		}

		foreach ( WP_CLI::get_configurator()->get_spec() as $key => $details ) {
			if ( false === $details['runtime'] ) {
				continue;
			}

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

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

			if ( true === $details['runtime'] ) {
				$synopsis = "--[no-]$key";
			} else {
				$synopsis = "--$key" . $details['runtime'];
			}

			// Check if global parameters synopsis should be displayed or not.
			if ( 'true' !== getenv( 'WP_CLI_SUPPRESS_GLOBAL_PARAMS' ) ) {
				$binding['parameters'][]   = [
					'synopsis' => $synopsis,
					'desc'     => $details['desc'],
				];
				$binding['has_parameters'] = true;
			}
		}

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

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

namespace WP_CLI\Dispatcher;

use WP_CLI;

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

/**
 * Adds a command namespace without actual functionality.
 *
 * This is meant to provide the means to attach meta information to a namespace
 * when there's no actual command needed.
 *
 * In case a real command gets registered for the same name, it replaces the
 * command namespace.
 *
 * @package WP_CLI
 */
class CommandNamespace extends CompositeCommand {

	/**
	 * Show the usage for all subcommands contained
	 * by the composite command.
	 */
	public function show_usage() {
		$methods = $this->get_subcommands();

		$i     = 0;
		$count = 0;

		foreach ( $methods as $subcommand ) {
			$prefix = ( 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;

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

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

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

	private $global_config_path;
	private $project_config_path;

	private $config;
	private $extra_config;

	private $context_manager;

	private $alias;

	private $aliases;

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

	private $colorize = false;

	private $early_invoke = [];

	private $global_config_path_debug;

	private $project_config_path_debug;

	private $required_files;

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

		return $this->$key;
	}

	public function register_context_manager( ContextManager $context_manager ) {
		$this->context_manager = $context_manager;
	}

	/**
	 * Register a command for early invocation, generally before WordPress loads.
	 *
	 * @param string $when Named execution hook
	 * @param Subcommand $command
	 */
	public function register_early_invoke( $when, $command ) {
		$cmd_path     = array_slice( Dispatcher\get_path( $command ), 1 );
		$command_name = implode( ' ', $cmd_path );
		WP_CLI::debug( "Attaching command '{$command_name}' to hook {$when}", 'bootstrap' );
		$this->early_invoke[ $when ][] = $cmd_path;
		if ( $command->get_alias() ) {
			array_pop( $cmd_path );
			$cmd_path[] = $command->get_alias();
			$alias_name = implode( ' ', $cmd_path );
			WP_CLI::debug( "Attaching command alias '{$alias_name}' to hook {$when}", 'bootstrap' );
			$this->early_invoke[ $when ][] = $cmd_path;
		}
	}

	/**
	 * Perform the early invocation of a command.
	 *
	 * @param string $when Named execution hook
	 */
	private function do_early_invoke( $when ) {
		WP_CLI::debug( "Executing hook: {$when}", 'hooks' );
		if ( ! isset( $this->early_invoke[ $when ] ) ) {
			return;
		}

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

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

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

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

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

		// If global config doesn't exist create one.
		if ( true === $create_config_file && ! file_exists( $config_path ) ) {
			$this->global_config_path_debug = "Default global config doesn't exist, creating one in {$config_path}";

			$dir = dirname( $config_path );

			if ( ! is_dir( $dir ) ) {
				mkdir( $dir, 0755, true );
			}

			touch( $config_path );

			if ( file_exists( $config_path ) ) {
				WP_CLI::debug( "Default global config does not exist, creating one in $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 = [
			'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;
			}
		);

		if ( null === $project_config_path ) {
			$this->project_config_path_debug = 'No project config found';
			return false;
		}

		$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.
	 */
	public function find_wp_root() {
		if ( isset( $this->config['path'] ) &&
			( is_bool( $this->config['path'] ) || empty( $this->config['path'] ) )
		) {
			WP_CLI::error( 'The --path parameter cannot be empty when provided.' );
		}

		if ( ! empty( $this->config['path'] ) ) {
			$path = $this->config['path'];
			if ( ! Utils\is_path_absolute( $path ) ) {
				$path = getcwd() . '/' . $path;
			}

			return $path;
		}

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

		$dir = getcwd();

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

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

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

		return getcwd();
	}

	/**
	 * 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(
				[
					'The --path parameter cannot be used when ABSPATH is already defined elsewhere',
					'ABSPATH is defined as: "' . ABSPATH . '"',
				]
			);
		}
		WP_CLI::debug( 'ABSPATH defined: ' . ABSPATH, 'bootstrap' );

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

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

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

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

			return $url;
		}

		return false;
	}

	/**
	 * Checks if the arguments passed to the WP-CLI binary start with the specified prefix.
	 *
	 * @param array $prefix An array of strings specifying the expected start of the arguments passed to the WP-CLI binary.
	 *                      For example, `['user', 'list']` checks if the arguments passed to the WP-CLI binary start with `user list`.
	 *
	 * @return bool `true` if the arguments passed to the WP-CLI binary start with the specified prefix, `false` otherwise.
	 */
	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 = [];

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

					if ( 'network' === $parent_name && 'option' === $child ) {
						$suggestion = 'meta';
					}

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

				// If the functions are available, it means WordPress is available
				// and has already been loaded.
				if ( function_exists( '\taxonomy_exists' ) ) {
					if ( \taxonomy_exists( $cmd_path[0] ) ) {
						$suggestion = 'wp term <command>';
					} elseif ( \post_type_exists( $cmd_path[0] ) ) {
						$suggestion = "wp post --post_type={$cmd_path[0]} <command>";
					}
				}

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

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

			$command = $subcommand;
		}

		return [ $command, $args, $cmd_path ];
	}

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

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

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

		$name = implode( ' ', $cmd_path );

		$extra_args = [];

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

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

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

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

	private function run_command_and_exit( $help_exit_warning = '' ) {
		$this->show_synopsis_if_composite_command();
		$this->run_command( $this->arguments, $this->assoc_args );
		if ( $this->cmd_starts_with( [ 'help' ] ) ) {
			// Help couldn't find the command so exit with suggestion.
			$suggestion_or_disabled = $this->find_command_to_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", "docker-compose", or "docker-compose-run").
	 *
	 * @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 ) {
			WP_CLI::warning( "WP_CLI_SSH_PRE_CMD found, executing the following command(s) on the remote machine:\n $pre_cmd" );

			$pre_cmd = rtrim( $pre_cmd, ';' ) . '; ';
		}

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

		$wp_binary = getenv( 'WP_CLI_SSH_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 = [];
			foreach ( $this->aliases[ $this->alias ] as $key => $value ) {
				if ( 'ssh' === $key ) {
					continue;
				}
				$runtime_alias[ $key ] = $value;
			}
			if ( ! empty( $runtime_alias ) ) {
				$encoded_alias = json_encode(
					[
						$this->alias => $runtime_alias,
					]
				);
				$wp_binary     = "WP_CLI_RUNTIME_ALIAS='{$encoded_alias}' {$wp_binary} {$this->alias}";
			}
		}

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

		$wp_command = $pre_cmd . $env_vars . $wp_binary . ' ' . implode( ' ', array_map( 'escapeshellarg', $wp_args ) );

		if ( isset( $bits['scheme'] ) && 'docker-compose-run' === $bits['scheme'] ) {
			$wp_command = implode( ' ', $wp_args );
		}

		$escaped_command = $this->generate_ssh_command( $bits, $wp_command );

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

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

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

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

		/*
		 * posix_isatty(STDIN) is generally true unless something was passed on stdin
		 * If autodetection leads to false (fd on stdin), then `-i` is passed to `docker` cmd
		 * (unless WP_CLI_DOCKER_NO_INTERACTIVE is set)
		 */
		$is_stdout_tty = function_exists( 'posix_isatty' ) && posix_isatty( STDOUT );
		$is_stdin_tty  = function_exists( 'posix_isatty' ) ? posix_isatty( STDIN ) : true;

		if ( in_array( $bits['scheme'], [ 'docker', 'docker-compose', 'docker-compose-run' ], true ) ) {
				$docker_compose_v2_version_cmd = Utils\esc_cmd( Utils\force_env_on_nix_systems( 'docker' ) . ' compose %s', 'version' );
				$docker_compose_cmd            = ! empty( Process::create( $docker_compose_v2_version_cmd )->run()->stdout )
						? 'docker compose'
						: 'docker-compose';
		}

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

			$escaped_command = sprintf(
				$command,
				$bits['user'] ? '--user ' . escapeshellarg( $bits['user'] ) . ' ' : '',
				$bits['path'] ? '--workdir ' . escapeshellarg( $bits['path'] ) . ' ' : '',
				$is_stdout_tty && ! getenv( 'WP_CLI_DOCKER_NO_TTY' ) ? '-t  ' : '',
				$is_stdin_tty || getenv( 'WP_CLI_DOCKER_NO_INTERACTIVE' ) ? '' : '-i ',
				escapeshellarg( $bits['host'] ),
				escapeshellarg( $wp_command )
			);
		}

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

			$escaped_command = sprintf(
				$command,
				$docker_compose_cmd,
				$bits['user'] ? '--user ' . escapeshellarg( $bits['user'] ) . ' ' : '',
				$bits['path'] ? '--workdir ' . escapeshellarg( $bits['path'] ) . ' ' : '',
				$is_stdout_tty || getenv( 'WP_CLI_DOCKER_NO_TTY' ) ? '' : '-T ',
				escapeshellarg( $bits['host'] ),
				escapeshellarg( $wp_command )
			);
		}

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

			$escaped_command = sprintf(
				$command,
				$docker_compose_cmd,
				$bits['user'] ? '--user ' . escapeshellarg( $bits['user'] ) . ' ' : '',
				$bits['path'] ? '--workdir ' . escapeshellarg( $bits['path'] ) . ' ' : '',
				$is_stdout_tty || getenv( 'WP_CLI_DOCKER_NO_TTY' ) ? '' : '-T ',
				$is_stdin_tty || getenv( 'WP_CLI_DOCKER_NO_INTERACTIVE' ) ? '' : '-i ',
				escapeshellarg( $bits['host'] ),
				$wp_command
			);
		}

		// For "vagrant" & "ssh" schemes which don't provide a working-directory option, use `cd`
		if ( $bits['path'] ) {
			$wp_command = 'cd ' . escapeshellarg( $bits['path'] ) . '; ' . $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'] ) || ( isset( $values['Host'] ) && $bits['host'] === $values['Host'] ) ) {
				$bits['scheme'] = 'ssh';
				$bits['host']   = isset( $values['HostName'] ) ? $values['HostName'] : '';
				$bits['port']   = isset( $values['Port'] ) ? $values['Port'] : '';
				$bits['user']   = isset( $values['User'] ) ? $values['User'] : '';
				$bits['key']    = isset( $values['IdentityFile'] ) ? $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 %s %s %s';

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

			if ( ! empty( $this->alias ) ) {
				$alias_config = isset( $this->aliases[ $this->alias ] ) ? $this->aliases[ $this->alias ] : false;

				if ( is_array( $alias_config ) ) {
					$bits['proxyjump'] = isset( $alias_config['proxyjump'] ) ? $alias_config['proxyjump'] : '';
					$bits['key']       = isset( $alias_config['key'] ) ? $alias_config['key'] : '';
				}
			}

			$command_args = [
				$bits['proxyjump'] ? sprintf( '-J %s', escapeshellarg( $bits['proxyjump'] ) ) : '',
				$bits['port'] ? sprintf( '-p %d', (int) $bits['port'] ) : '',
				$bits['key'] ? sprintf( '-i %s', escapeshellarg( $bits['key'] ) ) : '',
				$is_stdout_tty ? '-t' : '-T',
				WP_CLI::get_config( 'debug' ) ? '-vvv' : '-q',
			];

			$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( 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.
	 *
	 * @param string $wp_config_path Optional. Config file path. If left empty, it tries to
	 *                               locate the wp-config.php file automatically.
	 *
	 * @return string
	 */
	public function get_wp_config_code( $wp_config_path = '' ) {
		if ( empty( $wp_config_path ) ) {
			$wp_config_path = Utils\locate_wp_config();
		}

		$wp_config_code = file_get_contents( $wp_config_path );

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

			$length = strlen( $bom_sequence );

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

				$wp_config_code = substr( $wp_config_code, $length );
			}
		}

		$count = 0;

		$wp_config_code = preg_replace( '/\s*require(?:_once)?\s*.*wp-settings\.php.*\s*;/', '', $wp_config_code, -1, $count );

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

		$source = Utils\replace_path_consts( $wp_config_code, $wp_config_path );
		return preg_replace( '|^\s*\<\?php\s*|', '', $source );
	}

	/**
	 * Transparently convert deprecated syntaxes
	 *
	 * @param array $args
	 * @param array $assoc_args
	 * @return array
	 */
	private static function back_compat_conversions( $args, $assoc_args ) {
		$top_level_aliases = [
			'sql'  => 'db',
			'blog' => 'site',
		];
		if ( count( $args ) > 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 ( [ 'cli', 'aliases' ] === array_slice( $args, 0, 2 ) ) {
			list( $args[0], $args[1], $args[2] ) = [ '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 ( [ 'core', 'config' ] === array_slice( $args, 0, 2 ) ) {
			list( $args[0], $args[1] ) = [ 'config', 'create' ];
		}
		// core language  ->  language core
		if ( [ 'core', 'language' ] === array_slice( $args, 0, 2 ) ) {
			list( $args[0], $args[1] ) = [ 'language', 'core' ];
		}

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

		// checksum plugin  ->  plugin verify-checksums
		if ( [ 'checksum', 'plugin' ] === array_slice( $args, 0, 2 ) ) {
			list( $args[0], $args[1] ) = [ '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], [ '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 ( [ 'plugin', 'scaffold' ] === array_slice( $args, 0, 2 ) ) {
			list( $args[0], $args[1] ) = [ $args[1], $args[0] ];
		}

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

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

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

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

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

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

		return [ $args, $assoc_args ];
	}

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

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

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

		WP_CLI::set_logger( $logger );
	}

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

	/**
	 * Do WordPress core files exist?
	 *
	 * @return bool
	 */
	private function wp_exists() {
		return file_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( [ '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" .
				'The used path is: ' . ABSPATH . "\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 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}";
			$pipes          = [];
			$proc           = Utils\proc_open_compat( $full_command, [ STDIN, STDOUT, STDERR ], $pipes );
			proc_close( $proc );
		}
	}

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

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

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

		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( [ 'help' ] )
			&& ( ! $this->wp_exists()
				|| ! Utils\locate_wp_config()
				|| count( $this->arguments ) > 2
			) ) {
			$this->auto_check_update();
			$this->run_command( $this->arguments, $this->assoc_args );
			// Help didn't exit so failed to find the command at this stage.
		}

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

		$this->do_early_invoke( 'before_wp_load' );

		$this->check_wp_version();

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

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

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

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

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

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

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

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

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

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

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

		$this->load_wordpress();

		$this->run_command_and_exit();
	}

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

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

		$wp_cli_is_loaded = true;

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

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

		$this->check_wp_version();

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

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

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

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

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

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

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

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

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

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

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

		// Fix memory limit. See https://core.trac.wordpress.org/ticket/14889
		// phpcs:ignore WordPress.PHP.IniSet.memory_limit_Disallowed -- 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) [
			'id'            => 1,
			'blog_id'       => 1,
			'domain'        => $url_parts['host'],
			'path'          => $url_parts['path'],
			'cookie_domain' => $url_parts['host'],
			'site_name'     => 'WordPress',
		];

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

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

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

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

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

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

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

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

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

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

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

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

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

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

		// Use our own debug mode handling instead of WP core
		WP_CLI::add_wp_hook(
			'enable_wp_debug_mode_checks',
			static function ( $ret ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- WP core hook.
				Utils\wp_debug_mode();
				return false;
			}
		);

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

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

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

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

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

		// Avoid uncaught exception when using wp_mail() without defined $_SERVER['SERVER_NAME']
		WP_CLI::add_wp_hook(
			'wp_mail_from',
			static function ( $from_email ) {
				if ( 'wordpress@' === $from_email ) {
					$sitename = 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_home_url() or get_site_url().
		WP_CLI::add_wp_hook(
			'home_url',
			static function ( $url, $path, $scheme, $blog_id ) {
				if ( empty( $blog_id ) || ! is_multisite() ) {
					$url = get_option( 'home' );
				} else {
					switch_to_blog( $blog_id );
					$url = get_option( 'home' );
					restore_current_blog();
				}

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

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

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

				return $url;
			},
			0,
			4
		);

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

	/**
	 * Set up the filters to skip the loaded plugins
	 */
	private function setup_skip_plugins_filters() {
		$wp_cli_filter_active_plugins = static function ( $plugins ) {
			$skipped_plugins = WP_CLI::get_runner()->config['skip-plugins'];
			if ( true === $skipped_plugins ) {
				return [];
			}
			if ( ! is_array( $plugins ) ) {
				return $plugins;
			}
			foreach ( $plugins as $a => $b ) {
				// active_sitewide_plugins stores plugin name as the key.
				if ( false !== 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 = [
			'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                      = [
			'pre_option_template',
			'option_template',
			'pre_option_stylesheet',
			'option_stylesheet',
		];
		foreach ( $hooks as $hook ) {
			add_filter( $hook, $wp_cli_filter_active_theme, 999 );
		}

		// Noop memoization added in WP 6.4.
		// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- WordPress core global.
		$GLOBALS['wp_stylesheet_path'] = null;
		// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- WordPress core global.
		$GLOBALS['wp_template_path'] = null;

		// Remove theme-related actions not directly tied into the theme lifecycle.
		if ( WP_CLI::get_runner()->config['skip-themes'] ) {
			$theme_related_actions = [
				[ 'init', '_register_theme_block_patterns' ],          // Block patterns registration in WP Core.
				[ 'init', 'gutenberg_register_theme_block_patterns' ], // Block patterns registration in the GB plugin.
			];
			foreach ( $theme_related_actions as $action ) {
				list( $hook, $callback ) = $action;
				remove_action( $hook, $callback );
			}
		}

		// 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 );
				}
				// Noop memoization added in WP 6.4 again.
				// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- WordPress core global.
				$GLOBALS['wp_stylesheet_path'] = null;
				// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- WordPress core global.
				$GLOBALS['wp_template_path'] = null;
			},
			0
		);
	}

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

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

		return false;
	}

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

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

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

		$existing_phar = realpath( $_SERVER['argv'][0] );
		// Phar needs to be writable to be easily updateable.
		if ( ! is_writable( $existing_phar ) || ! is_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(
			[ 'cli', 'check-update' ],
			[
				'format' => 'count',
			]
		);
		$count = ob_get_clean();
		if ( ! $count ) {
			return;
		}

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

	/**
	 * Get a suggestion on similar (sub)commands when the user entered an
	 * unknown (sub)command.
	 *
	 * @param string                $entry        User entry that didn't match an
	 *                                            existing command.
	 * @param CompositeCommand|null $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, $root_command = null ) {
		$commands = [];
		if ( ( $root_command instanceof CompositeCommand ) === false ) {
			$root_command = WP_CLI::get_root_command();
		}
		$this->enumerate_commands( $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_Disallowed
	}
}
<?php

namespace WP_CLI\Fetchers;

use WP_CLI;
use WP_User;

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

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

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

		if ( getenv( 'WP_CLI_FORCE_USER_LOGIN' ) ) {
			$this->msg = "Invalid user login: '%s'";
			return get_user_by( 'login', $arg );
		}

		if ( is_numeric( $arg ) ) {
			$check = get_user_by( 'login', $arg );
			$user  = get_user_by( 'id', $arg );
			if ( $check && $user ) {
				WP_CLI::warning(
					sprintf(
						'Ambiguous user match detected (both ID and user_login exist for identifier \'%d\'). WP-CLI will default to the ID, but you can force user_login instead with WP_CLI_FORCE_USER_LOGIN=1.',
						$arg
					)
				);
			}
		} elseif ( is_email( $arg ) ) {
			$user = get_user_by( 'email', $arg );
			// Logins can be emails.
			if ( ! $user ) {
				$user = get_user_by( 'login', $arg );
			}
		} else {
			$user = get_user_by( 'login', $arg );
		}

		return $user;
	}
}
<?php

namespace WP_CLI\Fetchers;

/**
 * Fetch a signup based on one of its attributes.
 */
class Signup extends Base {

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

	/**
	 * Get a signup.
	 *
	 * @param int|string $signup
	 * @return object|false
	 */
	public function get( $signup ) {
		return $this->get_signup( $signup );
	}

	/**
	 * Get a signup by one of its identifying attributes.
	 *
	 * @param string $arg The raw CLI argument.
	 * @return object|false The item if found; false otherwise.
	 */
	protected function get_signup( $arg ) {
		global $wpdb;

		$signup_object = null;

		// Fetch signup with signup_id.
		if ( is_numeric( $arg ) ) {
			$result = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->signups WHERE signup_id = %d", $arg ) );

			if ( $result ) {
				$signup_object = $result;
			}
		}

		if ( ! $signup_object ) {
			// Try to fetch with other keys.
			foreach ( array( 'user_login', 'user_email', 'activation_key' ) as $field ) {
				// phpcs:ignore WordPress.DB.PreparedSQL
				$result = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->signups WHERE $field = %s", $arg ) );

				if ( $result ) {
					$signup_object = $result;
					break;
				}
			}
		}

		if ( $signup_object ) {
			return $signup_object;
		}

		return false;
	}
}
<?php

namespace WP_CLI\Fetchers;

use WP_CLI;
use WP_CLI\ExitException;

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

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

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

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

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

		return $item;
	}

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

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

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

		return $items;
	}
}
<?php

namespace WP_CLI\Fetchers;

use WP_Comment;

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

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

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

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

		return $comment;
	}
}
<?php

namespace WP_CLI\Fetchers;

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

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

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

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

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

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

		return false;
	}
}
<?php

namespace WP_CLI\Fetchers;

use WP_Post;

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

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

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

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

		return $post;
	}
}
<?php

namespace WP_CLI;

use Composer\IO\NullIO;
use WP_CLI;

/**
 * A Composer IO class so we can provide some level of interactivity from WP-CLI
 */
class ComposerIO extends NullIO {

	/**
	 * {@inheritDoc}
	 */
	public function isVerbose() {
		return true;
	}

	/**
	 * {@inheritDoc}
	 */
	public function write( $messages, $newline = true, $verbosity = self::NORMAL ) {
		self::output_clean_message( $messages );
	}

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

	private static function output_clean_message( $messages ) {
		$messages = (array) preg_replace( '#<(https?)([^>]+)>#', '$1$2', $messages );
		foreach ( $messages as $message ) {
			// phpcs:ignore WordPress.WP.AlternativeFunctions.strip_tags_strip_tags
			WP_CLI::log( strip_tags( trim( $message ) ) );
		}
	}
}
<?php

namespace WP_CLI;

use WP_CLI;

class Completions {

	private $cur_word;
	private $words;
	private $opts = [];

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

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

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

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

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

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

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

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

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

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

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

					$this->add( $opt );
				}
			}

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

				$opt = "--{$param}";

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

				$this->add( $opt );
			}
		}
	}

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

		# Avoid having to polyfill array_key_last().
		end( $words );
		$last_arg_i = key( $words );
		foreach ( $words as $i => $arg ) {
			if ( preg_match( '|^--([^=]+)(=?)|', $arg, $matches ) ) {
				if ( $i === $last_arg_i && '' === $matches[2] ) {
					continue;
				}
				$assoc_args[ $matches[1] ] = true;
			} else {
				$positional_args[] = $arg;
			}
		}

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

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

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

		return [ $command, $args, $assoc_args ];
	}

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

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

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

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

		return $params;
	}

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

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

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

namespace WP_CLI;

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

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

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

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

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

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

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

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

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

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

		return $out;
	}
}
<?php

namespace WP_CLI\Loggers;

use WP_CLI;

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

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

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

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

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

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

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

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

namespace WP_CLI\Loggers;

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

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

	protected $in_color = false;

	abstract public function info( $message );

	abstract public function success( $message );

	abstract public function warning( $message );

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

	/**
	 * Write a message to STDERR, prefixed with "Debug: ".
	 *
	 * @param string $message Message to write.
	 * @param string|bool $group Organize debug message to a specific group.
	 * Use `false` for no group.
	 */
	public function debug( $message, $group = false ) {
		static $start_time = null;
		if ( null === $start_time ) {
			$start_time = 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 = Colors::colorize( "$color$label:%n", $this->in_color );
		} else {
			$label = "$label:";
		}
		$this->write( $handle, "$label $message\n" );
	}
}
<?php

namespace WP_CLI\Loggers;

use cli\Colors;

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

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

	/**
	 * Write an informational message to STDOUT.
	 *
	 * @param string $message Message to write.
	 */
	public function info( $message ) {
		$this->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_lines Message to write.
	 */
	public function error_multi_line( $message_lines ) {
		// Convert tabs to four spaces, as some shells will output the tabs as variable-length.
		$message_lines = array_map(
			function ( $line ) {
				return str_replace( "\t", '    ', $line );
			},
			$message_lines
		);

		$longest = max( array_map( 'strlen', $message_lines ) );

		// Write an empty line before the message.
		$empty_line = 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    = 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;

use WP_CLI;

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

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

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

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

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

		$this->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() {
		ob_start( [ $this, 'ob_start_callback' ], 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 Exception;
use RuntimeException;
use WP_CLI;

/**
 * Class RequestsLibrary.
 *
 * A class to manage the version and source of the Requests library used by WP-CLI.
 */
final class RequestsLibrary {

	/**
	 * Version 1 of the Requests library.
	 *
	 * @var string
	 */
	const VERSION_V1 = 'v1';

	/**
	 * Version 2 of the Requests library.
	 *
	 * @var string
	 */
	const VERSION_V2 = 'v2';

	/**
	 * Array of valid versions for the Requests library.
	 *
	 * @var array<string>
	 */
	const VALID_VERSIONS = [ self::VERSION_V1, self::VERSION_V2 ];

	/**
	 * Requests library bundled with WordPress Core being used.
	 *
	 * @var string
	 */
	const SOURCE_WP_CORE = 'wp-core';

	/**
	 * Requests library bundled with WP-CLI being used.
	 *
	 * @var string
	 */
	const SOURCE_WP_CLI = 'wp-cli';

	/**
	 * Array of valid source for the Requests library.
	 *
	 * @var array<string>
	 */
	const VALID_SOURCES = [ self::SOURCE_WP_CORE, self::SOURCE_WP_CLI ];

	/**
	 * Class name of the Requests main class for v1.
	 *
	 * @var string
	 */
	const CLASS_NAME_V1 = '\Requests';

	/**
	 * Class name of the Requests main class for v2.
	 *
	 * @var string
	 */
	const CLASS_NAME_V2 = '\WpOrg\Requests\Requests';

	/**
	 * Version of the Requests library being used.
	 *
	 * @var string
	 */
	private static $version = self::VERSION_V2;

	/**
	 * Source of the Requests library being used.
	 *
	 * @var string
	 */
	private static $source = self::SOURCE_WP_CLI;

	/**
	 * Class name of the Requests library being used.
	 *
	 * @var string
	 */
	private static $class_name = self::CLASS_NAME_V2;

	/**
	 * Check if the current version is v1.
	 *
	 * @return bool Whether the current version is v1.
	 */
	public static function is_v1() {
		return self::get_version() === self::VERSION_V1;
	}

	/**
	 * Check if the current version is v2.
	 *
	 * @return bool Whether the current version is v2.
	 */
	public static function is_v2() {
		return self::get_version() === self::VERSION_V2;
	}

	/**
	 * Check if the current source for the Requests library is WordPress Core.
	 *
	 * @return bool Whether the current source is WordPress Core.
	 */
	public static function is_core() {
		return self::get_source() === self::SOURCE_WP_CORE;
	}

	/**
	 * Check if the current source for the Requests library is WP-CLI.
	 *
	 * @return bool Whether the current source is WP-CLI.
	 */
	public static function is_cli() {
		return self::get_source() === self::SOURCE_WP_CLI;
	}

	/**
	 * Get the current version.
	 *
	 * @return string The current version.
	 */
	public static function get_version() {
		return self::$version;
	}

	/**
	 * Set the version of the library.
	 *
	 * @param string $version The version to set.
	 * @throws RuntimeException if the version is invalid.
	 */
	public static function set_version( $version ) {
		if ( ! is_string( $version ) ) {
			throw new RuntimeException( 'RequestsLibrary::$version must be a string.' );
		}

		if ( ! in_array( $version, self::VALID_VERSIONS, true ) ) {
			throw new RuntimeException(
				sprintf(
					'Invalid RequestsLibrary::$version, must be one of: %s.',
					implode( ', ', self::VALID_VERSIONS )
				)
			);
		}

		WP_CLI::debug( 'Setting RequestsLibrary::$version to ' . $version, 'bootstrap' );

		self::$version = $version;
	}

	/**
	 * Get the current class name.
	 *
	 * @return string The current class name.
	 */
	public static function get_class_name() {
		return self::$class_name;
	}

	/**
	 * Set the class name for the library.
	 *
	 * @param string $class_name The class name to set.
	 */
	public static function set_class_name( $class_name ) {
		if ( ! is_string( $class_name ) ) {
			throw new RuntimeException( 'RequestsLibrary::$class_name must be a string.' );
		}

		WP_CLI::debug( 'Setting RequestsLibrary::$class_name to ' . $class_name, 'bootstrap' );

		self::$class_name = $class_name;
	}

	/**
	 * Get the current source.
	 *
	 * @return string The current source.
	 */
	public static function get_source() {
		return self::$source;
	}

	/**
	 * Set the source of the library.
	 *
	 * @param string $source The source to set.
	 * @throws RuntimeException if the source is invalid.
	 */
	public static function set_source( $source ) {
		if ( ! is_string( $source ) ) {
			throw new RuntimeException( 'RequestsLibrary::$source must be a string.' );
		}

		if ( ! in_array( $source, self::VALID_SOURCES, true ) ) {
			throw new RuntimeException(
				sprintf(
					'Invalid RequestsLibrary::$source, must be one of: %s.',
					implode( ', ', self::VALID_SOURCES )
				)
			);
		}

		WP_CLI::debug( 'Setting RequestsLibrary::$source to ' . $source, 'bootstrap' );

		self::$source = $source;
	}

	/**
	 * Check if a given exception was issued by the Requests library.
	 *
	 * This is used because we cannot easily catch multiple different exception
	 * classes with PHP 5.6. Because of that, we catch generic exceptions, check if
	 * they match the Requests library, and re-throw them if they do not.
	 *
	 * @param Exception $exception Exception to check.
	 * @return bool Whether the provided exception was issued by the Requests library.
	 */
	public static function is_requests_exception( Exception $exception ) {
		return is_a( $exception, '\Requests_Exception' )
			|| is_a( $exception, '\WpOrg\Requests\Exception' );
	}

	/**
	 * Register the autoloader for the Requests library.
	 *
	 * This checks for the detected setup and register the corresponding
	 * autoloader if it is still needed.
	 */
	public static function register_autoloader() {
		$includes_path = defined( 'WPINC' ) ? WPINC : 'wp-includes';

		if ( self::is_v1() && ! class_exists( self::CLASS_NAME_V1 ) ) {
			if ( self::is_core() ) {
				require_once ABSPATH . $includes_path . '/class-requests.php';
			} else {
				require_once WP_CLI_VENDOR_DIR . '/rmccue/requests/library/Requests.php';
			}
			\Requests::register_autoloader();
		}

		if ( self::is_v2() && ! class_exists( self::CLASS_NAME_V2 ) ) {
			if ( self::is_core() ) {
				require_once ABSPATH . $includes_path . '/Requests/Autoload.php';
			} else {
				self::maybe_define_wp_cli_root();
				if ( file_exists( WP_CLI_ROOT . '/bundle/rmccue/requests/src/Autoload.php' ) ) {
					require_once WP_CLI_ROOT . '/bundle/rmccue/requests/src/Autoload.php';
				} else {
					require_once WP_CLI_VENDOR_DIR . '/rmccue/requests/src/Autoload.php';
				}
			}
			\WpOrg\Requests\Autoload::register();
		}
	}

	/**
	 * Get the path to the bundled certificate.
	 *
	 * @return string The path to the bundled certificate.
	 */
	public static function get_bundled_certificate_path() {
		if ( self::is_core() ) {
			$includes_path = defined( 'WPINC' ) ? WPINC : 'wp-includes';
			return ABSPATH . $includes_path . '/certificates/ca-bundle.crt';
		} elseif ( self::is_v1() ) {
			return WP_CLI_VENDOR_DIR . '/rmccue/requests/library/Requests/Transport/cacert.pem';
		} else {
			self::maybe_define_wp_cli_root();
			if ( file_exists( WP_CLI_ROOT . '/bundle/rmccue/requests/certificates/cacert.pem' ) ) {
				return WP_CLI_ROOT . '/bundle/rmccue/requests/certificates/cacert.pem';
			}
			return WP_CLI_VENDOR_DIR . '/rmccue/requests/certificates/cacert.pem';
		}
	}

	/**
	 * Define WP_CLI_ROOT if it is not already defined.
	 */
	private static function maybe_define_wp_cli_root() {
		if ( ! defined( 'WP_CLI_ROOT' ) ) {
			define( 'WP_CLI_ROOT', dirname( dirname( __DIR__ ) ) );
		}
	}
}
<?php
/*
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the MIT license. For more information, see
 * <https://www.doctrine-project.org>.
 */

namespace WP_CLI;

/**
 * Doctrine inflector has static methods for inflecting text.
 *
 * The methods in these classes are from several different sources collected
 * across several different php projects and several different authors. The
 * original author names and emails are not known.
 *
 * Pluralize & Singularize implementation are borrowed from CakePHP with some modifications.
 *
 * @link   www.doctrine-project.org
 * @since  1.0
 * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
 * @author Jonathan H. Wage <jonwage@gmail.com>
 */
class Inflector {

	/**
	 * Plural inflector rules.
	 *
	 * @var array
	 */
	private static $plural = [
		'rules'       => [
			'/(s)tatus$/i'                         => '\1\2tatuses',
			'/(quiz)$/i'                           => '\1zes',
			'/^(ox)$/i'                            => '\1\2en',
			'/([m|l])ouse$/i'                      => '\1ice',
			'/(matr|vert|ind)(ix|ex)$/i'           => '\1ices',
			'/(x|ch|ss|sh)$/i'                     => '\1es',
			'/([^aeiouy]|qu)y$/i'                  => '\1ies',
			'/(hive)$/i'                           => '\1s',
			'/(?:([^f])fe|([lr])f)$/i'             => '\1\2ves',
			'/sis$/i'                              => 'ses',
			'/([ti])um$/i'                         => '\1a',
			'/(p)erson$/i'                         => '\1eople',
			'/(m)an$/i'                            => '\1en',
			'/(c)hild$/i'                          => '\1hildren',
			'/(f)oot$/i'                           => '\1eet',
			'/(buffal|her|potat|tomat|volcan)o$/i' => '\1\2oes',
			'/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$/i' => '\1i',
			'/us$/i'                               => 'uses',
			'/(alias)$/i'                          => '\1es',
			'/(analys|ax|cris|test|thes)is$/i'     => '\1es',
			'/s$/'                                 => 's',
			'/^$/'                                 => '',
			'/$/'                                  => 's',
		],
		'uninflected' => [
			'.*[nrlm]ese',
			'.*deer',
			'.*fish',
			'.*measles',
			'.*ois',
			'.*pox',
			'.*sheep',
			'people',
			'cookie',
		],
		'irregular'   => [
			'atlas'        => 'atlases',
			'axe'          => 'axes',
			'beef'         => 'beefs',
			'brother'      => 'brothers',
			'cafe'         => 'cafes',
			'chateau'      => 'chateaux',
			'child'        => 'children',
			'cookie'       => 'cookies',
			'corpus'       => 'corpuses',
			'cow'          => 'cows',
			'criterion'    => 'criteria',
			'curriculum'   => 'curricula',
			'demo'         => 'demos',
			'domino'       => 'dominoes',
			'echo'         => 'echoes',
			'foot'         => 'feet',
			'fungus'       => 'fungi',
			'ganglion'     => 'ganglions',
			'genie'        => 'genies',
			'genus'        => 'genera',
			'graffito'     => 'graffiti',
			'hippopotamus' => 'hippopotami',
			'hoof'         => 'hoofs',
			'human'        => 'humans',
			'iris'         => 'irises',
			'leaf'         => 'leaves',
			'loaf'         => 'loaves',
			'man'          => 'men',
			'medium'       => 'media',
			'memorandum'   => 'memoranda',
			'money'        => 'monies',
			'mongoose'     => 'mongooses',
			'motto'        => 'mottoes',
			'move'         => 'moves',
			'mythos'       => 'mythoi',
			'niche'        => 'niches',
			'nucleus'      => 'nuclei',
			'numen'        => 'numina',
			'occiput'      => 'occiputs',
			'octopus'      => 'octopuses',
			'opus'         => 'opuses',
			'ox'           => 'oxen',
			'penis'        => 'penises',
			'person'       => 'people',
			'plateau'      => 'plateaux',
			'runner-up'    => 'runners-up',
			'sex'          => 'sexes',
			'soliloquy'    => 'soliloquies',
			'son-in-law'   => 'sons-in-law',
			'syllabus'     => 'syllabi',
			'testis'       => 'testes',
			'thief'        => 'thieves',
			'tooth'        => 'teeth',
			'tornado'      => 'tornadoes',
			'trilby'       => 'trilbys',
			'turf'         => 'turfs',
			'volcano'      => 'volcanoes',
		],
	];

	/**
	 * Singular inflector rules.
	 *
	 * @var array
	 */
	private static $singular = [
		'rules'       => [
			'/(s)tatuses$/i'                         => '\1\2tatus',
			'/^(.*)(menu)s$/i'                       => '\1\2',
			'/(quiz)zes$/i'                          => '\\1',
			'/(matr)ices$/i'                         => '\1ix',
			'/(vert|ind)ices$/i'                     => '\1ex',
			'/^(ox)en/i'                             => '\1',
			'/(alias)(es)*$/i'                       => '\1',
			'/(buffal|her|potat|tomat|volcan)oes$/i' => '\1o',
			'/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us',
			'/([ftw]ax)es/i'                         => '\1',
			'/(analys|ax|cris|test|thes)es$/i'       => '\1is',
			'/(shoe|slave)s$/i'                      => '\1',
			'/(o)es$/i'                              => '\1',
			'/ouses$/'                               => 'ouse',
			'/([^a])uses$/'                          => '\1us',
			'/([m|l])ice$/i'                         => '\1ouse',
			'/(x|ch|ss|sh)es$/i'                     => '\1',
			'/(m)ovies$/i'                           => '\1\2ovie',
			'/(s)eries$/i'                           => '\1\2eries',
			'/([^aeiouy]|qu)ies$/i'                  => '\1y',
			'/([lr])ves$/i'                          => '\1f',
			'/(tive)s$/i'                            => '\1',
			'/(hive)s$/i'                            => '\1',
			'/(drive)s$/i'                           => '\1',
			'/([^fo])ves$/i'                         => '\1fe',
			'/(^analy)ses$/i'                        => '\1sis',
			'/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis',
			'/([ti])a$/i'                            => '\1um',
			'/(p)eople$/i'                           => '\1\2erson',
			'/(m)en$/i'                              => '\1an',
			'/(c)hildren$/i'                         => '\1\2hild',
			'/(f)eet$/i'                             => '\1oot',
			'/(n)ews$/i'                             => '\1\2ews',
			'/eaus$/'                                => 'eau',
			'/^(.*us)$/'                             => '\\1',
			'/s$/i'                                  => '',
		],
		'uninflected' => [
			'.*[nrlm]ese',
			'.*deer',
			'.*fish',
			'.*measles',
			'.*ois',
			'.*pox',
			'.*sheep',
			'.*ss',
		],
		'irregular'   => [
			'criteria' => 'criterion',
			'curves'   => 'curve',
			'emphases' => 'emphasis',
			'foes'     => 'foe',
			'hoaxes'   => 'hoax',
			'media'    => 'medium',
			'neuroses' => 'neurosis',
			'waves'    => 'wave',
			'oases'    => 'oasis',
		],
	];

	/**
	 * Words that should not be inflected.
	 *
	 * @var array
	 */
	private static $uninflected = [
		'Amoyese',
		'bison',
		'Borghese',
		'bream',
		'breeches',
		'britches',
		'buffalo',
		'cantus',
		'carp',
		'chassis',
		'clippers',
		'cod',
		'coitus',
		'Congoese',
		'contretemps',
		'corps',
		'debris',
		'diabetes',
		'djinn',
		'eland',
		'elk',
		'equipment',
		'Faroese',
		'flounder',
		'Foochowese',
		'gallows',
		'Genevese',
		'Genoese',
		'Gilbertese',
		'graffiti',
		'headquarters',
		'herpes',
		'hijinks',
		'Hottentotese',
		'information',
		'innings',
		'jackanapes',
		'Kiplingese',
		'Kongoese',
		'Lucchese',
		'mackerel',
		'Maltese',
		'.*?media',
		'mews',
		'moose',
		'mumps',
		'Nankingese',
		'news',
		'nexus',
		'Niasese',
		'Pekingese',
		'Piedmontese',
		'pincers',
		'Pistoiese',
		'pliers',
		'Portuguese',
		'proceedings',
		'rabies',
		'rice',
		'rhinoceros',
		'salmon',
		'Sarawakese',
		'scissors',
		'sea[- ]bass',
		'series',
		'Shavese',
		'shears',
		'siemens',
		'species',
		'staff',
		'swine',
		'testes',
		'trousers',
		'trout',
		'tuna',
		'Vermontese',
		'Wenchowese',
		'whiting',
		'wildebeest',
		'Yengeese',
	];

	/**
	 * Method cache array.
	 *
	 * @var array
	 */
	private static $cache = [];

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

	/**
	 * Converts a word into the format for a Doctrine table name. Converts 'ModelName' to 'model_name'.
	 *
	 * @param string $word The word to tableize.
	 *
	 * @return string The tableized word.
	 */
	public static function tableize( $word ) {
		return 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 delimiters 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 delimiters 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 delimiter-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'] = [];
				self::$cache['tableize']  = [];
			} elseif ( 'singular' === $type ) {
				self::$cache['singularize'] = [];
			}
		}

		self::${$type}['rules'] = $rules + self::${$type}['rules'];
	}

	/**
	 * Returns a word in plural form.
	 *
	 * @param string $word The word in singular form.
	 *
	 * @return string The word in plural form.
	 */
	public static function pluralize( $word ) {
		if ( isset( self::$cache['pluralize'][ $word ] ) ) {
			return self::$cache['pluralize'][ $word ];
		}

		if ( ! isset( self::$plural['merged']['irregular'] ) ) {
			self::$plural['merged']['irregular'] = self::$plural['irregular'];
		}

		if ( ! isset( self::$plural['merged']['uninflected'] ) ) {
			self::$plural['merged']['uninflected'] = array_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 ];
			}
		}

		// Just so a string is always returned.
		// This should never be reached.
		return $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;

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

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

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

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

namespace WP_CLI;

/**
 * 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 DateTime;
use Exception;
use Symfony\Component\Finder\Finder;
use WP_CLI;

/**
 * Reads/writes to a filesystem cache
 */
class FileCache {

	/**
	 * @var string cache path
	 */
	protected $root;
	/**
	 * @var bool
	 */
	protected $enabled = true;
	/**
	 * @var int files time to live
	 */
	protected $ttl;
	/**
	 * @var int max total size
	 */
	protected $max_size;
	/**
	 * @var string key allowed chars (regex class)
	 */
	protected $whitelist;

	/**
	 * @param string $cache_dir  location of the cache
	 * @param int    $ttl        cache files default time to live (expiration)
	 * @param int    $max_size   max total cache size
	 * @param string $whitelist  List of characters that are allowed in path names (used in a regex character class)
	 */
	public function __construct( $cache_dir, $ttl, $max_size, $whitelist = 'a-z0-9._-' ) {
		$this->root      = Utils\trailingslashit( $cache_dir );
		$this->ttl       = (int) $ttl;
		$this->max_size  = (int) $max_size;
		$this->whitelist = $whitelist;

		if ( ! $this->ensure_dir_exists( $this->root ) ) {
			$this->enabled = false;
		}
	}

	/**
	 * Cache is enabled
	 *
	 * @return bool
	 */
	public function is_enabled() {
		return $this->enabled;
	}

	/**
	 * Cache root
	 *
	 * @return string
	 */
	public function get_root() {
		return $this->root;
	}


	/**
	 * Check if a file is in cache and return its filename
	 *
	 * @param string $key cache key
	 * @param int    $ttl time to live
	 * @return bool|string filename or false
	 */
	public function has( $key, $ttl = null ) {
		if ( ! $this->enabled ) {
			return false;
		}

		$filename = $this->filename( $key );

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

		// Use ttl param or global ttl.
		if ( null === $ttl ) {
			$ttl = $this->ttl;
		} elseif ( $this->ttl > 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; tmp file filepath from HTTP response
	 * @return bool
	 */
	public function import( $key, $source ) {
		$filename = $this->prepare_write( $key );

		if ( ! is_readable( $source ) ) {
			return false;
		}

		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();
				$expire->modify( '-' . $ttl . ' seconds' );

				$finder = $this->get_finder()->date( 'until ' . $expire->format( 'Y-m-d H:i:s' ) );
				foreach ( $finder as $file ) {
					unlink( $file->getRealPath() );
				}
			} catch ( Exception $e ) {
				WP_CLI::error( $e->getMessage() );
			}
		}

		// Unlink older files if max cache size is exceeded.
		if ( $max_size > 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 = [];

		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 ) ) {
				$message = "Failed to create directory '{$dir}'";
				$error   = error_get_last();
				if ( is_array( $error ) ) {
					$message .= ": {$error['message']}";
				}
				WP_CLI::warning( "{$message}." );
				return false;
			}
		}

		return true;
	}

	/**
	 * Prepare cache write
	 *
	 * @param string $key cache key
	 * @return bool|string The destination filename or false when cache disabled or directory creation fails.
	 */
	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 rtrim( implode( '/', $parts ), '.' );
	}

	/**
	 * Destination filename from key
	 *
	 * @param string $key
	 * @return string filename
	 */
	protected function filename( $key ) {
		return $this->root . $this->validate_key( $key );
	}

	/**
	 * Get a Finder that iterates in cache root only the files
	 *
	 * @return Finder
	 */
	protected function get_finder() {
		return Finder::create()->in( $this->root )->files();
	}
}
<?php

namespace WP_CLI;

use WP_CLI;

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

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

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

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

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

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

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

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

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

namespace WP_CLI\Iterators;

use RuntimeException;

class Exception extends RuntimeException {}
<?php

namespace WP_CLI\Iterators;

use Iterator;

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

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

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

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

		$this->count_query = preg_replace( '/^.*? FROM /', 'SELECT COUNT(*) FROM ', $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;
	}

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

	#[\ReturnTypeWillChange]
	public function key() {
		return $this->global_index;
	}

	#[\ReturnTypeWillChange]
	public function next() {
		++$this->index_in_results;
		++$this->global_index;
	}

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

	#[\ReturnTypeWillChange]
	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;

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

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

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

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

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

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

	private static function build_where_conditions( $where ) {
		global $wpdb;
		if ( is_array( $where ) ) {
			$conditions = [];
			foreach ( $where as $key => $value ) {
				if ( is_array( $value ) ) {
					$conditions[] = $key . ' IN (' . esc_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;

use IteratorIterator;

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

	private $transformers = [];

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

	#[\ReturnTypeWillChange]
	public function current() {
		$value = parent::current();

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

		return $value;
	}
}
<?php

namespace WP_CLI\Iterators;

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

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

	#[ReturnTypeWillChange]
	public function rewind() {
		rewind( $this->file_pointer );

		$this->columns = fgetcsv( $this->file_pointer, self::ROW_SIZE, $this->delimiter, '"', '\\' );

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

	#[ReturnTypeWillChange]
	public function current() {
		return $this->current_element;
	}

	#[ReturnTypeWillChange]
	public function key() {
		return $this->current_index;
	}

	#[ReturnTypeWillChange]
	public function next() {
		$this->current_element = false;

		while ( true ) {
			$row = fgetcsv( $this->file_pointer, self::ROW_SIZE, $this->delimiter, '"', '\\' );

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

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

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

				break;
			}
		}
	}

	#[ReturnTypeWillChange]
	public function count() {
		$file = new SplFileObject( $this->filename, 'r' );
		$file->seek( PHP_INT_MAX );
		return $file->key() + 1;
	}

	#[ReturnTypeWillChange]
	public function valid() {
		return is_array( $this->current_element );
	}
}
<?php

namespace WP_CLI\Context;

use WP_CLI;
use WP_CLI\Context;
use WP_Session_Tokens;

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

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

			return;
		}

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

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

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

		// Bootstrap the WordPress administration area.
		WP_CLI::add_wp_hook(
			'init',
			function () {
				$this->log_in_as_admin_user();
				$this->load_admin_environment();
			},
			defined( 'PHP_INT_MIN' ) ? PHP_INT_MIN : -2147483648, // phpcs:ignore PHPCompatibility.Constants.NewConstants.php_int_minFound
			0
		);
	}

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

		wp_set_current_user( $admin_user_id );

		$expiration = time() + DAY_IN_SECONDS;

		$_COOKIE[ AUTH_COOKIE ] = wp_generate_auth_cookie(
			$admin_user_id,
			$expiration,
			'auth'
		);

		$_COOKIE[ SECURE_AUTH_COOKIE ] = wp_generate_auth_cookie(
			$admin_user_id,
			$expiration,
			'secure_auth'
		);
	}

	/**
	 * Load the admin environment.
	 *
	 * This tries to load `wp-admin/admin.php` while trying to avoid issues
	 * like re-loading the wp-config.php file (which redeclares constants).
	 *
	 * To make this work across WordPress versions, we use the actual file and
	 * modify it on-the-fly.
	 *
	 * @global string $hook_suffix
	 * @global string $pagenow
	 * @global int    $wp_db_version
	 * @global array  $_wp_submenu_nopriv
	 *
	 * @return void
	 */
	private function load_admin_environment() {
		global $hook_suffix, $pagenow, $wp_db_version, $_wp_submenu_nopriv;

		if ( ! isset( $hook_suffix ) ) {
			$hook_suffix = 'index'; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
		}

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

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

		$admin_php_file = file_get_contents( ABSPATH . 'wp-admin/admin.php' );

		// First we remove the opening and closing PHP tags.
		$admin_php_file = preg_replace( '/^<\?php\s+/', '', $admin_php_file );
		$admin_php_file = preg_replace( '/\s+\?>$/', '', $admin_php_file );

		// Then we remove the loading of either wp-config.php or wp-load.php.
		$admin_php_file = preg_replace( '/^\s*(?:include|require).*[\'"]\/?wp-(?:load|config)\.php[\'"]\s*\)?;\s*$/m', '', $admin_php_file );

		// We also remove the authentication redirect.
		$admin_php_file = preg_replace( '/^\s*auth_redirect\(\);$/m', '', $admin_php_file );

		// Finally, we avoid sending headers.
		$admin_php_file   = preg_replace( '/^\s*nocache_headers\(\);$/m', '', $admin_php_file );
		$_GET['noheader'] = true;

		eval( $admin_php_file ); // phpcs:ignore Squiz.PHP.Eval.Discouraged
	}
}
<?php

namespace WP_CLI\Context;

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

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

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

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

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

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

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

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

		return Context::CLI;
	}

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

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

		return false;
	}
}
<?php

namespace WP_CLI\Context;

use WP_CLI;
use WP_CLI\Context;

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

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

namespace WP_CLI\Context;

use WP_CLI;
use WP_CLI\Context;

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

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

namespace WP_CLI;

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

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

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

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

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

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

		return $this;
	}

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

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

			// Move on if the object does not belong to the current namespace.
			if ( 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(
				[ $namespace['root'], '\\' ],
				[ '', DIRECTORY_SEPARATOR ],
				$class
			);

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

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

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

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

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

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

		return $this->add_trailing_backslash( $root );
	}

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

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

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

namespace WP_CLI;

use RuntimeException;

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

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

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

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

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

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

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

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

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

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

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

	/**
	 * Gets the checksums for the given version of WordPress core.
	 *
	 * @param string $version Version string to query.
	 * @param string $locale  Optional. Locale to query. Defaults to 'en_US'.
	 * @return bool|array False on failure. An array of checksums on success.
	 * @throws RuntimeException If the remote request fails.
	 */
	public function get_core_checksums( $version, $locale = 'en_US' ) {
		$data = [
			'version' => $version,
			'locale'  => $locale,
		];

		$url = sprintf(
			'%s?%s',
			self::CORE_CHECKSUMS_ENDPOINT,
			http_build_query( $data, '', '&' )
		);

		$response = $this->json_get_request( $url );

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

		return $response['checksums'];
	}

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

		$response = $this->json_get_request( $url );

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

		return $response;
	}

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

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

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

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

		return $offer;
	}

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

		$response = $this->json_get_request( $url );

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

		return $response['files'];
	}

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

		if ( ! empty( $fields ) ) {
			$request['fields'] = $fields;
		}

		$url = sprintf(
			'%s?%s',
			self::PLUGIN_INFO_ENDPOINT,
			http_build_query( compact( 'action', 'request' ), '', '&' )
		);

		$response = $this->json_get_request( $url );

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

		return $response;
	}

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

		if ( ! empty( $fields ) ) {
			$request['fields'] = $fields;
		}

		$url = sprintf(
			'%s?%s',
			self::THEME_INFO_ENDPOINT,
			http_build_query( compact( 'action', 'request' ), '', '&' )
		);

		$response = $this->json_get_request( $url );

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

		return $response;
	}

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

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

		$response = $this->get_request( $url, $headers, $options );

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

		$data = json_decode( $response, true );

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

		return $data;
	}

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

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

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

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

namespace WP_CLI;

use Mustangostang\Spyc;

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

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

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

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

		return $comment;
	}

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

		return $matches[1];
	}

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

		$longdesc = substr( $this->doc_comment, strlen( $shortdesc ) );

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

			$lines[] = $line;
		}

		return trim( implode( "\n", $lines ) );
	}

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

		return '';
	}

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

		return $matches[1];
	}

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

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

		return '';
	}

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

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

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

		return '';
	}

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

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

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

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

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

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

		if ( $document ) {
			return Spyc::YAMLLoadString( implode( "\n", $document ) );
		}
		return null;
	}
}
<?php

namespace WP_CLI;

use WP_CLI;
use WP_Upgrader_Skin;

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

	public $api;

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

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

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

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

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

		$this->process_feedback( $string, $args );
	}

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

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

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

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

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

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

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

		$operation = $event->getOperation();

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

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

			switch ( $reason->getReason() ) {

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

			}

			if ( ! empty( $composer_error ) ) {
				WP_CLI::log( sprintf( ' - Warning: %s', $composer_error ) );
			}
		}
	}
}
<?php

namespace WP_CLI;

use WP_CLI;

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

	/**
	 * @var array<string, array{key:string, ttl:int}> map whitelisted urls to keys and ttls
	 */
	protected $whitelist = [];

	/**
	 * @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', [ $this, 'filter_pre_http_request' ], 10, 3 );
		add_filter( 'http_response', [ $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 [
					'response' => [
						'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
	 * @return array
	 */
	public function filter_http_response( $response, $args, $url ) {
		// check if whitelisted
		if ( ! isset( $this->whitelist[ $url ] ) ) {
			return $response;
		}
		// check if downloading
		if ( 'GET' !== $args['method'] || empty( $args['filename'] ) ) {
			return $response;
		}
		// check if download was successful
		if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
			return $response;
		}
		// cache downloaded file
		$this->cache->import( $this->whitelist[ $url ]['key'], $response['filename'] );
		return $response;
	}

	/**
	 * whitelist a package url
	 *
	 * @param string $url
	 * @param string $group   package group (themes, plugins, ...)
	 * @param string $slug    package slug
	 * @param string $version package version
	 * @param int    $ttl
	 */
	public function whitelist_package( $url, $group, $slug, $version, $ttl = null ) {
		$ext = 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 ] = [
			'key' => $key,
			'ttl' => $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 RuntimeException;

/**
 * Run a system process, and learn what happened.
 */
class Process {
	/**
	 * @var string The full command to execute by the system.
	 */
	private $command;

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

	/**
	 * @var array Environment variables to set when running the command.
	 */
	private $env;

	/**
	 * @var array Descriptor spec for `proc_open()`.
	 */
	private static $descriptors = [
		0 => STDIN,
		1 => [ 'pipe', 'w' ],
		2 => [ 'pipe', 'w' ],
	];

	/**
	 * @var bool Whether to log run time info or not.
	 */
	public static $log_run_times = false;

	/**
	 * @var array Array of process run time info, keyed by process command, each a 2-element array containing run time and run count.
	 */
	public static $run_times = [];

	/**
	 * @param string      $command Command to execute.
	 * @param string|null $cwd     Directory to execute the command in.
	 * @param array|null  $env     Environment variables to set when running the command.
	 *
	 * @return Process
	 */
	public static function create( $command, $cwd = null, $env = [] ) {
		$proc = new self();

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

		return $proc;
	}

	private function __construct() {}

	/**
	 * Run the command.
	 *
	 * @return ProcessRun
	 */
	public function run() {
		Utils\check_proc_available( 'Process::run' );

		$start_time = microtime( true );

		$pipes = [];
		$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 ] = [ 0, 0 ];
			}
			self::$run_times[ $this->command ][0] += $run_time;
			++self::$run_times[ $this->command ][1];
		}

		return new ProcessRun(
			[
				'stdout'      => $stdout,
				'stderr'      => $stderr,
				'return_code' => $return_code,
				'command'     => $this->command,
				'cwd'         => $this->cwd,
				'env'         => $this->env,
				'run_time'    => $run_time,
			]
		);
	}

	/**
	 * Run the command, but throw an Exception on error.
	 *
	 * @return ProcessRun
	 */
	public function run_check() {
		$r = $this->run();

		if ( $r->return_code ) {
			throw new RuntimeException( $r );
		}

		return $r;
	}

	/**
	 * Run the command, but throw an Exception on error.
	 * Same as `run_check()` above, but checks the correct stderr.
	 *
	 * @return ProcessRun
	 */
	public function run_check_stderr() {
		$r = $this->run();

		if ( $r->return_code ) {
			throw new RuntimeException( $r );
		}

		if ( ! empty( $r->stderr ) ) {
			// If the only thing that STDERR caught was the Requests deprecated message, ignore it.
			// This is a temporary fix until we have a better solution for dealing with Requests
			// as a dependency shared between WP Core and WP-CLI.
			$stderr_lines = array_filter( explode( "\n", $r->stderr ) );
			if ( 1 === count( $stderr_lines ) ) {
				$stderr_line = $stderr_lines[0];
				if (
					false !== strpos(
						$stderr_line,
						'The PSR-0 `Requests_...` class names in the Request library are deprecated.'
					)
				) {
					return $r;
				}
			}

			throw new RuntimeException( $r );
		}

		return $r;
	}
}
<?php

namespace WP_CLI;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

			case 'csv':
				Utils\write_csv( STDOUT, $items, $fields );
				break;

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

				if ( 'json' === $this->args['format'] ) {
					if ( defined( 'JSON_PARTIAL_OUTPUT_ON_ERROR' ) ) {
						// phpcs:ignore PHPCompatibility.Constants.NewConstants.json_partial_output_on_errorFound
						echo json_encode( $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 $items Array of objects to show fields from
	 * @param string $field The field to show
	 */
	private function show_single_field( $items, $field ) {
		$key    = null;
		$values = [];

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

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

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

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

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

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

		return $key;
	}

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

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

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

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

			default:
				WP_CLI::error( 'Invalid format: ' . $format );

		}
	}

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

		$enabled = WP_CLI::get_runner()->in_color();
		if ( $enabled ) {
			Colors::disable( true );
		}

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

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

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

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

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

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

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

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

		return $rows;
	}

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

namespace WP_CLI\Traverser;

use UnexpectedValueException;
use WP_CLI\Exception\NonExistentKeyException;

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|null $parent_instance The parent instance of the traverser.
	 */
	public function __construct( &$data, $key = null, $parent_instance = null ) {
		$this->data   =& $data;
		$this->key    = $key;
		$this->parent = $parent_instance;
	}

	/**
	 * 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 self
	 */
	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 self( $this->data, $current, $this->parent ) );
			throw $exception;
		}

		foreach ( $this->data as $key => &$key_data ) {
			if ( $key === $current ) {
				$traverser = new self( $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;

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

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

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

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

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

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

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

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

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

		return array_slice( $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(
			[
				'type' => 'assoc',
			]
		);

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

		$to_unset = [];

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

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

					$to_unset[] = $key;
			}
		}

		return [ $errors, $to_unset ];
	}

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

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

		$known_assoc = [];

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

		return array_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 = [];

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

class ExitException extends Exception {}
<?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.6.0', '<' ) ) {
	printf( "Error: WP-CLI requires PHP %s or newer. You are running version %s.\n", '5.6.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\Bootstrap\BootstrapState;
use WP_CLI\Bootstrap\BootstrapStep;

/**
 * Get the list of ordered steps that need to be processed to bootstrap WP-CLI.
 *
 * Each entry is a fully qualified class name for a class implementing the
 * `WP_CLI\Bootstrap\BootstrapStep` interface.
 *
 * @return string[]
 */
function get_bootstrap_steps() {
	return [
		Bootstrap\DeclareFallbackFunctions::class,
		Bootstrap\LoadUtilityFunctions::class,
		Bootstrap\LoadDispatcher::class,
		Bootstrap\DeclareMainClass::class,
		Bootstrap\DeclareAbstractBaseCommand::class,
		Bootstrap\IncludeFrameworkAutoloader::class,
		Bootstrap\ConfigureRunner::class,
		Bootstrap\InitializeColorization::class,
		Bootstrap\InitializeLogger::class,
		Bootstrap\CheckRoot::class,
		Bootstrap\IncludeRequestsAutoloader::class,
		Bootstrap\DefineProtectedCommands::class,
		Bootstrap\LoadExecCommand::class,
		Bootstrap\LoadRequiredCommand::class,
		Bootstrap\IncludePackageAutoloader::class,
		Bootstrap\IncludeFallbackAutoloader::class,
		Bootstrap\RegisterFrameworkCommands::class,
		Bootstrap\RegisterDeferredCommands::class,
		Bootstrap\InitializeContexts::class,
		Bootstrap\LaunchRunner::class,
	];
}

/**
 * Register the classes needed for the bootstrap process.
 *
 * The Composer autoloader is not active yet at this point, so we need to use a
 * custom autoloader to fetch the bootstrap classes in a flexible way.
 */
function prepare_bootstrap() {
	require_once WP_CLI_ROOT . '/php/WP_CLI/Autoloader.php';

	$autoloader = new Autoloader();

	$autoloader->add_namespace(
		'WP_CLI\Bootstrap',
		WP_CLI_ROOT . '/php/WP_CLI/Bootstrap'
	)->register();
}

/**
 * Initialize and return the bootstrap state to pass from step to step.
 *
 * @return BootstrapState
 */
function initialize_bootstrap_state() {
	return new BootstrapState();
}

/**
 * Process the bootstrapping steps.
 *
 * Loops over each of the provided steps, instantiates it and then calls its
 * `process()` method.
 */
function bootstrap() {
	prepare_bootstrap();
	$state = initialize_bootstrap_state();

	foreach ( get_bootstrap_steps() as $step ) {
		/** @var BootstrapStep $step_instance */
		if ( class_exists( 'WP_CLI' ) ) {
			\WP_CLI::debug( "Processing bootstrap step: {$step}", 'bootstrap' );
		}

		$step_instance = new $step();
		$state         = $step_instance->process( $state );
	}
}
<?php

return [
	'path'              => [
		'runtime' => '=<path>',
		'file'    => '<path>',
		'desc'    => 'Path to the WordPress files.',
	],

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

	'ssh'               => [
		'runtime' => '=[<scheme>:][<user>@]<host|container>[:<port>][<path>]',
		'file'    => '[<scheme>:][<user>@]<host|container>[:<port>][<path>]',
		'desc'    => 'Perform operation against a remote server over SSH (or a container using scheme of "docker", "docker-compose", "docker-compose-run", "vagrant").',
	],

	'http'              => [
		'runtime' => '=<http>',
		'file'    => '<http>',
		'desc'    => 'Perform operation against a remote WordPress installation over HTTP.',
	],

	'blog'              => [
		'deprecated' => 'Use --url instead.',
		'runtime'    => '=<url>',
	],

	'user'              => [
		'runtime' => '=<id|login|email>',
		'file'    => '<id|login|email>',
		'desc'    => 'Set the WordPress user.',
	],

	'skip-plugins'      => [
		'runtime' => '[=<plugins>]',
		'file'    => '<list>',
		'desc'    => 'Skip loading all plugins, or a comma-separated list of plugins. Note: mu-plugins are still loaded.',
		'default' => '',
	],

	'skip-themes'       => [
		'runtime' => '[=<themes>]',
		'file'    => '<list>',
		'desc'    => 'Skip loading all themes, or a comma-separated list of themes.',
		'default' => '',
	],

	'skip-packages'     => [
		'runtime' => '',
		'file'    => '<bool>',
		'desc'    => 'Skip loading all installed packages.',
		'default' => false,
	],

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

	'exec'              => [
		'runtime'  => '=<php-code>',
		'file'     => '<php-code>',
		'desc'     => 'Execute PHP code before running the command (may be used more than once).',
		'multiple' => true,
		'default'  => [],
	],

	'context'           => [
		'runtime' => '=<context>',
		'file'    => '<context>',
		'default' => 'cli',
		'desc'    => 'Load WordPress in a given context.',
	],

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

	'color'             => [
		'runtime' => true,
		'file'    => '<bool>',
		'default' => 'auto',
		'desc'    => 'Whether to colorize the output.',
	],

	'debug'             => [
		'runtime' => '[=<group>]',
		'file'    => '<group>',
		'default' => false,
		'desc'    => 'Show all PHP errors and add verbosity to WP-CLI output. Built-in groups include: bootstrap, commandfactory, and help.',
	],

	'prompt'            => [
		'runtime' => '[=<assoc>]',
		'file'    => false,
		'default' => false,
		'desc'    => 'Prompt the user to enter values for all command arguments, or a subset specified as comma-separated values.',
	],

	'quiet'             => [
		'runtime' => '',
		'file'    => '<bool>',
		'default' => false,
		'desc'    => 'Suppress informational messages.',
	],

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

	# --allow-root => (NOT RECOMMENDED) Allow wp-cli to run as root. This poses
	# a security risk, so you probably do not want to do this.
	'allow-root'        => [
		'file'    => false, # Explicit. Just in case the default changes.
		'runtime' => '',
		'hidden'  => true,
	],

];
<?php

// Utilities that depend on WordPress code.

namespace WP_CLI\Utils;

use ReflectionClass;
use ReflectionParameter;
use WP_CLI;
use WP_CLI\UpgraderSkin;

/**
 * @return void
 */
function wp_not_installed() {
	global $wpdb, $table_prefix;
	if ( ! is_blog_installed() && ! defined( 'WP_INSTALLING' ) ) {
		$tables         = $wpdb->get_col( "SHOW TABLES LIKE '%_options'" );
		$found_prefixes = [];
		if ( count( $tables ) ) {
			foreach ( $tables as $table ) {
				$maybe_prefix = substr( $table, 0, - strlen( 'options' ) );
				if ( $maybe_prefix !== $table_prefix ) {
					$found_prefixes[] = $maybe_prefix;
				}
			}
		}
		if ( count( $found_prefixes ) ) {
			sort( $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.

/**
 * @return void
 */
function wp_debug_mode() {
	if ( WP_CLI::get_config( 'debug' ) ) {
		if ( ! defined( 'WP_DEBUG' ) ) {
			define( 'WP_DEBUG', true );
		}

		error_reporting( E_ALL & ~E_DEPRECATED );
	} 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 ( in_array( strtolower( (string) WP_DEBUG_LOG ), [ 'true', '1' ], true ) ) {
				$log_path = WP_CONTENT_DIR . '/debug.log';
			} elseif ( is_string( WP_DEBUG_LOG ) ) {
				$log_path = WP_DEBUG_LOG;
			} else {
				$log_path = false;
			}

			if ( false !== $log_path ) {
				ini_set( 'log_errors', 1 );
				ini_set( 'error_log', $log_path );
			}
		} 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

/**
 * @return void
 */
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';
		}
	);
}

/**
 * @return never
 */
function wp_die_handler( $message ) {

	if ( $message instanceof \WP_Error ) {
		$text_message = $message->get_error_message();
		$error_data   = $message->get_error_data( 'internal_server_error' );
		if ( ! empty( $error_data['error']['file'] )
			&& false !== stripos( $error_data['error']['file'], 'themes/functions.php' ) ) {
			$text_message = 'An unexpected functions.php file in the themes directory may have caused this internal server error.';
		}
	} else {
		$text_message = $message;
	}

	$text_message = wp_clean_error_message( $text_message );

	WP_CLI::error( $text_message );
}

/**
 * Clean HTML error message so suitable for text display.
 *
 * @param string $message
 * @return string
 */
function wp_clean_error_message( $message ) {
	$original_message = trim( $message );
	$message          = $original_message;
	if ( preg_match( '|^\<h1>(.+?)</h1>|', $original_message, $matches ) ) {
		$message = $matches[1] . '.';
	}
	if ( preg_match( '|\<p>(.+?)</p>|', $original_message, $matches ) ) {
		$message .= ' ' . $matches[1];
	}

	$search_replace = [
		'<code>'  => '`',
		'</code>' => '`',
	];
	$message        = str_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;
}

/**
 * @param string $url
 * @return string
 */
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;
}

/**
 * @param string $since Version number.
 * @param string $path File to include.
 * @return void
 */
function maybe_require( $since, $path ) {
	if ( wp_version_compare( $since, '>=' ) ) {
		require $path;
	}
}

/**
 *
 * @param class-string $class_name
 * @param bool         $insecure
 *
 * @return \WP_Upgrader Upgrader instance.
 * @throws \ReflectionException
 */
function get_upgrader( $class_name, $insecure = false ) {
	if ( ! class_exists( '\WP_Upgrader' ) ) {
		if ( file_exists( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' ) ) {
			include ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
		}
	}

	if ( ! class_exists( '\WP_Upgrader_Skin' ) ) {
		if ( file_exists( ABSPATH . 'wp-admin/includes/class-wp-upgrader-skin.php' ) ) {
			include ABSPATH . 'wp-admin/includes/class-wp-upgrader-skin.php';
		}
	}

	$uses_insecure_flag = false;

	$reflection  = new ReflectionClass( $class_name );
	$constructor = $reflection->getConstructor();
	if ( $constructor ) {
		$arguments = $constructor->getParameters();
		/** @var ReflectionParameter $argument */
		foreach ( $arguments as $argument ) {
			if ( 'insecure' === $argument->name ) {
				$uses_insecure_flag = true;
				break;
			}
		}
	}

	if ( $uses_insecure_flag ) {
		return new $class_name( new UpgraderSkin(), $insecure );
	} else {
		return new $class_name( new UpgraderSkin() );
	}
}

/**
 * Converts a plugin basename back into a friendly slug.
 *
 * @param string $basename
 * @return string
 */
function get_plugin_name( $basename ) {
	if ( false === strpos( $basename, '/' ) ) {
		$name = basename( $basename, '.php' );
	} else {
		$name = dirname( $basename );
	}

	return $name;
}

/**
 * Determine whether a plugin is skipped.
 *
 * @param string $file
 * @return bool
 */
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 );
}

/**
 * Get theme name from path.
 *
 * @param string $path
 * @return string
 */
function get_theme_name( $path ) {
	return basename( $path );
}

/**
 * Determine whether a theme is skipped.
 *
 * @param string $path
 * @return bool
 */
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.
 *
 * @return void
 */
function wp_register_unused_sidebar() {

	register_sidebar(
		[
			'name'          => __( 'Inactive Widgets' ),
			'id'            => 'wp_inactive_widgets',
			'class'         => 'inactive-sidebar',
			'description'   => __( 'Drag widgets here to remove them from the sidebar but keep their settings.' ),
			'before_widget' => '',
			'after_widget'  => '',
			'before_title'  => '',
			'after_title'   => '',
		]
	);
}

/**
 * Attempts to determine which object cache is being used.
 *
 * Note that the guesses made by this function are based on the WP_Object_Cache classes
 * that define the 3rd party object cache extension. Changes to those classes could render
 * problems with this function's ability to determine which object cache is being used.
 *
 * @return string
 */
function wp_get_cache_type() {
	global $_wp_using_ext_object_cache, $wp_object_cache;

	$message = 'Unknown';

	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 (https://wordpress.org/extend/plugins/memcached/)
		} elseif ( isset( $wp_object_cache->mc ) ) {
			$is_memcache = true;
			foreach ( $wp_object_cache->mc as $bucket ) {
				if ( ! $bucket instanceof \Memcache && ! $bucket instanceof \Memcached ) {
					$is_memcache = false;
				}
			}

			if ( $is_memcache ) {
				$message = 'Memcache';
			}

			// Test for Xcache object cache (https://plugins.svn.wordpress.org/xcache/trunk/object-cache.php)
		} elseif ( $wp_object_cache instanceof \XCache_Object_Cache ) {
			$message = 'Xcache';

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

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

			// Test for WP Redis (https://wordpress.org/plugins/wp-redis/)
		} elseif ( isset( $wp_object_cache->redis ) && $wp_object_cache->redis instanceof \Redis ) {
			$message = 'Redis';

			// Test for Redis Object Cache (https://wordpress.org/plugins/redis-cache/)
		} elseif ( method_exists( $wp_object_cache, 'redis_instance' ) && method_exists( $wp_object_cache, 'redis_status' ) ) {
			$message = 'Redis';

			// Test for Object Cache Pro (https://objectcache.pro/)
		} elseif ( method_exists( $wp_object_cache, 'config' ) && method_exists( $wp_object_cache, 'connection' ) ) {
			$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' );

			if ( $config->get_boolean( 'objectcache.enabled' ) ) {
				$message = 'W3TC ' . $config->get_string( 'objectcache.engine' );
			}
		}
	} 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
 *
 * @return void
 */
function wp_clear_object_cache() {
	global $wpdb, $wp_object_cache;

	$wpdb->queries = [];

	if ( function_exists( 'wp_cache_flush_runtime' ) && function_exists( 'wp_cache_supports' ) ) {
		if ( wp_cache_supports( 'flush_runtime' ) ) {
			wp_cache_flush_runtime();
			return;
		}
	}

	if ( ! is_object( $wp_object_cache ) ) {
		return;
	}

	// The following are Memcached (Redux) plugin specific (see https://core.trac.wordpress.org/ticket/31463).
	if ( isset( $wp_object_cache->group_ops ) ) {
		$wp_object_cache->group_ops = [];
	}
	if ( isset( $wp_object_cache->stats ) ) {
		$wp_object_cache->stats = [];
	}
	if ( isset( $wp_object_cache->memcache_debug ) ) {
		$wp_object_cache->memcache_debug = [];
	}
	// Used by `WP_Object_Cache` also.
	if ( isset( $wp_object_cache->cache ) ) {
		$wp_object_cache->cache = [];
	}
}

/**
 * Get a set of tables in the database.
 *
 * Interprets common command-line options into a resolved set of table names.
 *
 * @param array<string>         $args Provided table names, or tables with wildcards.
 * @param array<string, string> $assoc_args Optional flags for groups of tables (e.g. --network)
 * @return array<string>
 */
function wp_get_table_names( $args, $assoc_args = [] ) {
	global $wpdb;

	// 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, [ 'all', 'global', 'ms_global' ], true ) ? ( 'all' === $scope ? 'global' : $scope ) : '';
			$wp_tables            = array_values( $wpdb->tables( $network_global_scope ) );
			if ( in_array( $scope, [ 'all', 'blog' ], true ) ) {
				// Do directly for compat with old WP versions. Note: private, deleted, archived sites are not excluded.
				$blog_ids = $wpdb->get_col( "SELECT blog_id FROM $wpdb->blogs WHERE site_id = $wpdb->siteid" );
				foreach ( $blog_ids as $blog_id ) {
					$wp_tables = array_merge( $wp_tables, array_values( $wpdb->tables( 'blog', true /*prefix*/, $blog_id ) ) );
				}
			}
		} else {
			$wp_tables = array_values( $wpdb->tables( $scope ) );
		}

		// The global_terms_enabled() function has been deprecated with WP 6.1+.
		if ( wp_version_compare( '6.1', '>=' ) || ! global_terms_enabled() ) { // phpcs:ignore WordPress.WP.DeprecatedFunctions.global_terms_enabledFound
			// Only include sitecategories when it's actually enabled.
			$wp_tables = array_values( array_diff( $wp_tables, [ $wpdb->sitecategories ] ) );
		}

		// Note: BC change 1.5.0, tables are sorted (via TABLES view).
		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- uses esc_sql_ident() and $wpdb->_escape().
		$tables = $wpdb->get_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 = [];
		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
<?php

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

	public function __construct() {}
}
<?php

// Utilities that do NOT depend on WordPress code.

namespace WP_CLI\Utils;

use ArrayIterator;
use cli;
use cli\progress\Bar;
use cli\Shell;
use Closure;
use Composer\Semver\Comparator;
use Composer\Semver\Semver;
use Exception;
use Mustache_Engine;
use ReflectionFunction;
use RuntimeException;
use WP_CLI;
use WP_CLI\ExitException;
use WP_CLI\Formatter;
use WP_CLI\Inflector;
use WP_CLI\Iterators\Transform;
use WP_CLI\NoOp;
use WP_CLI\Process;
use WP_CLI\RequestsLibrary;

/**
 * File stream wrapper prefix for Phar archives.
 *
 * @var string
 */
const PHAR_STREAM_PREFIX = 'phar://';

/**
 * Regular expression pattern to match __FILE__ and __DIR__ constants.
 *
 * We try to be smart and only replace the constants when they are not within quotes.
 * Regular expressions being stateless, this is probably not 100% correct for edge cases.
 *
 * @see https://regex101.com/r/9hXp5d/11
 * @see https://stackoverflow.com/a/171499/933065
 *
 * @var string
 */
const FILE_DIR_PATTERN = '%(?>#.*?$)|(?>//.*?$)|(?>/\*.*?\*/)|(?>\'(?:(?=(\\\\?))\1.)*?\')|(?>"(?:(?=(\\\\?))\2.)*?")|(?<file>\b__FILE__\b)|(?<dir>\b__DIR__\b)%ms';

/**
 * Check if a certain path is within a Phar archive.
 *
 * If no path is provided, the function checks whether the current WP_CLI instance is
 * running from within a Phar archive.
 *
 * @param string|null $path Optional. Path to check. Defaults to null, which checks WP_CLI_ROOT.
 * @return bool Whether path is within a Phar archive.
 */
function inside_phar( $path = null ) {
	if ( null === $path ) {
		if ( ! defined( 'WP_CLI_ROOT' ) ) {
			return false;
		}

		$path = WP_CLI_ROOT;
	}

	return 0 === strpos( $path, PHAR_STREAM_PREFIX );
}

/**
 * Extract a file from a Phar archive.
 *
 * Files that need to be read by external programs have to be extracted from the Phar archive.
 * If the file is not within a Phar archive, the function returns the path unchanged.
 *
 * @param string $path Path to the file to extract.
 * @return string Path to the extracted file.
 */
function extract_from_phar( $path ) {
	if ( ! inside_phar( $path ) ) {
		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;
}

/**
 * Load dependencies.
 *
 * @return void|never
 */
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 );
	}
}

/**
 * Return vendor paths.
 *
 * @return array<string> List of paths.
 */
function get_vendor_paths() {
	$vendor_paths        = [
		WP_CLI_ROOT . '/../../../vendor',  // Part of a larger project / installed via Composer (preferred).
		WP_CLI_ROOT . '/vendor',           // Top-level project / installed as Git clone.
	];
	$maybe_composer_json = WP_CLI_ROOT . '/../../../composer.json';
	if ( file_exists( $maybe_composer_json ) && is_readable( $maybe_composer_json ) ) {
		$composer = json_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;
}

/**
 * Load a file.
 *
 * Using require() directly inside a class grants access
 * to private methods to the loaded code, hence this wrapper helper.
 *
 * @param string $path
 * @return void
 */
function load_file( $path ) {
	require_once $path;
}

/**
 * Load a command.
 *
 * @param string $name
 * @return void
 */
function load_command( $name ) {
	$path = WP_CLI_ROOT . "/php/commands/$name.php";

	if ( is_readable( $path ) ) {
		include_once $path;
	}
}

/**
 * Like array_map(), except it returns a new iterator, instead of a modified array.
 *
 * Example:
 *
 *     $arr = array('Football', 'Socker');
 *
 *     $it = iterator_map($arr, 'strtolower', function($val) {
 *       return str_replace('foo', 'bar', $val);
 *     });
 *
 *     foreach ( $it as $val ) {
 *       var_dump($val);
 *     }
 *
 * @param array|object $it Either a plain array or another iterator.
 * @param callable     $fn The function to apply to an element.
 * @return object An iterator that applies the given callback(s).
 */
function iterator_map( $it, $fn ) {
	if ( is_array( $it ) ) {
		$it = new ArrayIterator( $it );
	}

	if ( ! method_exists( $it, 'add_transform' ) ) {
		$it = new Transform( $it );
	}

	foreach ( array_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<string> $files      The files (or file) to search for.
 * @param string|null          $dir        The directory to start searching from; defaults to CWD.
 * @param callable             $stop_check Function which is passed the current dir each time a directory level is traversed.
 * @return null|string Null if the file was not found.
 */
function find_file_upward( $files, $dir = null, $stop_check = null ) {
	$files = (array) $files;
	if ( is_null( $dir ) ) {
		$dir = getcwd();
	}
	while ( is_readable( $dir ) ) {
		// Stop walking up when the supplied callable returns true being passed the $dir
		if ( is_callable( $stop_check ) && call_user_func( $stop_check, $dir ) ) {
			return null;
		}

		foreach ( $files as $file ) {
			$path = $dir . DIRECTORY_SEPARATOR . $file;
			if ( file_exists( $path ) ) {
				return $path;
			}
		}

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

/**
 * Determine whether a path is absolute.
 * @param string $path
 * @return bool
 */
function is_path_absolute( $path ) {
	// Windows.
	if ( isset( $path[1] ) && ':' === $path[1] ) {
		return true;
	}

	return isset( $path[0] ) && '/' === $path[0];
}

/**
 * Composes positional arguments into a command string.
 *
 * @param array<string> $args Positional arguments to compose.
 * @return string
 */
function args_to_str( $args ) {
	return ' ' . implode( ' ', array_map( 'escapeshellarg', $args ) );
}

/**
 * Composes associative arguments into a command string.
 *
 * @param array<string, string> $assoc_args Associative arguments to compose.
 * @return string
 */
function assoc_args_to_str( $assoc_args ) {
	$str = '';

	foreach ( $assoc_args as $key => $value ) {
		if ( true === $value ) {
			$str .= " --$key";
		} elseif ( is_array( $value ) ) {
			foreach ( $value as $v ) {
				$str .= assoc_args_to_str(
					[
						$key => $v,
					]
				);
			}
		} else {
			$str .= " --$key=" . escapeshellarg( $value );
		}
	}

	return $str;
}

/**
 * Given a template string and an arbitrary number of arguments,
 * returns the final command, with the parameters escaped.
 *
 * @param array<string> $cmd
 */
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;
}

/**
 * Compare a WordPress version.
 *
 * @param string $since
 * @param string $operator
 * @return bool
 */
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<mixed> $items  An array of items to output.
 * @param array<string>|string $fields Named fields for each item of data. Can be array or comma-separated list.
 */
function format_items( $format, $items, $fields ) {
	$assoc_args = [
		'format' => $format,
		'fields' => $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<string> $rows    Array of rows to output.
 * @param array<string> $headers List of CSV columns (optional).
 */
function write_csv( $fd, $rows, $headers = [] ) {
	if ( ! empty( $headers ) ) {
		$headers = array_map( __NAMESPACE__ . '\escape_csv_value', $headers );
		fputcsv( $fd, $headers, ',', '"', '\\' );
	}

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

		$row = array_map( __NAMESPACE__ . '\escape_csv_value', $row );
		fputcsv( $fd, array_values( $row ), ',', '"', '\\' );
	}
}

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

	if ( is_object( $item ) ) {
		foreach ( $fields as $field ) {
			$values[ $field ] = isset( $item->$field ) ? $item->$field : null;
		}
	} else {
		foreach ( $fields as $field ) {
			$values[ $field ] = isset( $item[ $field ] ) ? $item[ $field ] : null;
		}
	}

	return $values;
}

/**
 * Launch system's $EDITOR for the user to edit some text.
 *
 * @access public
 * @category Input
 *
 * @param string $input Some form of text to edit (e.g. post content).
 * @param string $title Title to display in the editor.
 * @param string $ext   Extension to use with the temp file.
 * @return string|bool  Edited text, if file is saved from editor; false, if no change to file.
 */
function launch_editor_for_input( $input, $title = 'WP-CLI', $ext = 'tmp' ) {

	check_proc_available( 'launch_editor_for_input' );

	$tmpdir = get_temp_dir();

	do {
		$tmpfile  = basename( $title );
		$tmpfile  = preg_replace( '|\.[^.]*$|', '', $tmpfile );
		$tmpfile .= '-' . substr( md5( (string) 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 = [ 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 $raw_host MySQL host string, as defined in wp-config.php.
 *
 * @return array<string, string|int>
 */
function mysql_host_to_cli_args( $raw_host ) {
	$assoc_args = [];

	/**
	 * If the host string begins with 'p:' for a persistent db connection,
	 * replace 'p:' with nothing.
	 */
	if ( substr( $raw_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;
}

/**
 * Run a MySQL command and optionally return the output.
 *
 * @since v2.5.0 Deprecated $descriptors argument.
 *
 * @param string                $cmd           Command to run.
 * @param array<string, mixed>  $assoc_args    Associative array of arguments to use.
 * @param mixed                 $_             Deprecated. Former $descriptors argument.
 * @param bool                  $send_to_shell Optional. Whether to send STDOUT and STDERR
 *                                             immediately to the shell. Defaults to true.
 * @param bool                  $interactive   Optional. Whether MySQL is meant to be
 *                                             executed as an interactive process. Defaults
 *                                             to false.
 *
 * @return array {
 *     Associative array containing STDOUT and STDERR output.
 *
 *     @type string $stdout    Output that was sent to STDOUT.
 *     @type string $stderr    Output that was sent to STDERR.
 *     @type int    $exit_code Exit code of the process.
 * }
 */
function run_mysql_command( $cmd, $assoc_args, $_ = null, $send_to_shell = true, $interactive = false ) {
	check_proc_available( 'run_mysql_command' );

	$descriptors = ( $interactive || $send_to_shell ) ?
		[
			0 => STDIN,
			1 => STDOUT,
			2 => STDERR,
		] :
		[
			0 => STDIN,
			1 => [ 'pipe', 'w' ],
			2 => [ 'pipe', 'w' ],
		];

	$stdout = '';
	$stderr = '';
	$pipes  = [];

	if ( isset( $assoc_args['host'] ) ) {
		// phpcs:ignore WordPress.DB.RestrictedFunctions.mysql_mysql_host_to_cli_args -- Misidentified as PHP native MySQL function.
		$assoc_args = array_merge( $assoc_args, mysql_host_to_cli_args( $assoc_args['host'] ) );
	}

	if ( isset( $assoc_args['pass'] ) ) {
		$old_password = getenv( 'MYSQL_PWD' );
		putenv( 'MYSQL_PWD=' . $assoc_args['pass'] );
		unset( $assoc_args['pass'] );
	}

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

	WP_CLI::debug( 'Final MySQL command: ' . $final_cmd, 'db' );
	$process = proc_open_compat( $final_cmd, $descriptors, $pipes );

	if ( isset( $old_password ) ) {
		putenv( 'MYSQL_PWD=' . $old_password );
	}

	if ( ! $process ) {
		WP_CLI::debug( 'Failed to create a valid process using proc_open_compat()', 'db' );
		exit( 1 );
	}

	if ( is_resource( $process ) && ! $send_to_shell && ! $interactive ) {
		$stdout = stream_get_contents( $pipes[1] );
		$stderr = stream_get_contents( $pipes[2] );

		fclose( $pipes[1] );
		fclose( $pipes[2] );
	}

	$exit_code = proc_close( $process );

	if ( $exit_code && ( $send_to_shell || $interactive ) ) {
		exit( $exit_code );
	}

	return [
		$stdout,
		$stderr,
		$exit_code,
	];
}

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

	$template = file_get_contents( $template_name );

	$mustache = new Mustache_Engine(
		[
			'escape' => function ( $val ) {
				return $val; },
		]
	);

	return $mustache->render( $template, $data );
}

/**
 * Create a progress bar to display percent completion of a given operation.
 *
 * Progress bar is written to STDOUT, and disabled when command is piped. Progress
 * advances with `$progress->tick()`, and completes with `$progress->finish()`.
 * Process bar also indicates elapsed time and expected total time.
 *
 * ```
 * # `wp user generate` ticks progress bar each time a new user is created.
 * #
 * # $ wp user generate --count=500
 * # Generating users  22 % [=======>                             ] 0:05 / 0:23
 *
 * $progress = \WP_CLI\Utils\make_progress_bar( 'Generating users', $count );
 * for ( $i = 0; $i < $count; $i++ ) {
 *     // uses wp_insert_user() to insert the user
 *     $progress->tick();
 * }
 * $progress->finish();
 * ```
 *
 * @access public
 * @category Output
 *
 * @param string  $message  Text to display before the progress bar.
 * @param integer $count    Total number of ticks to be performed.
 * @param int     $interval Optional. The interval in milliseconds between updates. Default 100.
 * @return \cli\progress\Bar|\WP_CLI\NoOp
 */
function make_progress_bar( $message, $count, $interval = 100 ) {
	if ( Shell::isPiped() ) {
		return new NoOp();
	}

	return new Bar( $message, $count, $interval );
}

/**
 * Helper function to use wp_parse_url when available or fall back to PHP's
 * parse_url if not.
 *
 * Additionally, this adds 'http://' to the URL if no scheme was found.
 *
 * @param string $url             The URL to parse.
 * @param int    $component       Optional. The specific component to retrieve.
 *                                Use one of the PHP predefined constants to
 *                                specify which one. Defaults to -1 (= return
 *                                all parts as an array).
 * @param bool   $auto_add_scheme Optional. Automatically add an http:// scheme if
 *                                none was found. Defaults to true.
 * @return mixed False on parse failure; Array of URL components on success;
 *               When a specific component has been requested: null if the
 *               component doesn't exist in the given URL; a string or - in the
 *               case of PHP_URL_PORT - integer when it does. See parse_url()'s
 *               return values.
 */
function parse_url( $url, $component = - 1, $auto_add_scheme = true ) {
	if (
		function_exists( 'wp_parse_url' )
		&& (
			-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.
 *
 * Replaces the __FILE__ and __DIR__ magic constants with the values they are
 * supposed to represent at runtime.
 *
 * @param string $source The PHP code to manipulate.
 * @param string $path The path to use instead of the magic constants.
 * @return string Adapted PHP code.
 */
function replace_path_consts( $source, $path ) {
	// Solve issue with Windows allowing single quotes in account names.
	$file = addslashes( $path );

	if ( file_exists( $file ) ) {
		$file = realpath( $file );
	}

	$dir = dirname( $file );

	// Replace __FILE__ and __DIR__ constants with value of $file or $dir.
	return preg_replace_callback(
		FILE_DIR_PATTERN,
		static function ( $matches ) use ( $file, $dir ) {
			if ( ! empty( $matches['file'] ) ) {
				return "'{$file}'";
			}

			if ( ! empty( $matches['dir'] ) ) {
				return "'{$dir}'";
			}

			return $matches[0];
		},
		$source
	);
}

/**
 * Make a HTTP request to a remote URL.
 *
 * Wraps the Requests HTTP library to ensure every request includes a cert.
 *
 * ```
 * # `wp core download` verifies the hash for a downloaded WordPress archive
 *
 * $md5_response = Utils\http_request( 'GET', $download_url . '.md5' );
 * if ( 20 != substr( $md5_response->status_code, 0, 2 ) ) {
 *      WP_CLI::error( "Couldn't access md5 hash for release (HTTP code {$response->status_code})" );
 * }
 * ```
 *
 * @access public
 *
 * @param string     $method  HTTP method (GET, POST, DELETE, etc.).
 * @param string     $url     URL to make the HTTP request to.
 * @param array|null $data    Data to send either as a query string for GET/HEAD requests,
 *                            or in the body for POST requests.
 * @param array      $headers Add specific headers to the request.
 * @param array      $options {
 *     Optional. An associative array of additional request options.
 *
 *     @type bool $halt_on_error Whether or not command execution should be halted on error. Default: true
 *     @type bool|string $verify A boolean to use enable/disable SSL verification
 *                               or string absolute path to CA cert to use.
 *                               Defaults to detected CA cert bundled with the Requests library.
 *     @type bool $insecure      Whether to retry automatically without certificate validation.
 * }
 * @return object
 * @throws RuntimeException If the request failed.
 * @throws ExitException If the request failed and $halt_on_error is true.
 */
function http_request( $method, $url, $data = null, $headers = [], $options = [] ) {
	$insecure      = isset( $options['insecure'] ) && (bool) $options['insecure'];
	$halt_on_error = ! isset( $options['halt_on_error'] ) || (bool) $options['halt_on_error'];
	unset( $options['halt_on_error'] );

	if ( ! isset( $options['verify'] ) ) {
		// 'curl.cainfo' enforces the CA file to use, otherwise fallback to system-wide defaults then use the embedded CA file.
		$options['verify'] = ! empty( ini_get( 'curl.cainfo' ) ) ? ini_get( 'curl.cainfo' ) : true;
	}

	$options = WP_CLI::do_hook( 'http_request_options', $options );

	RequestsLibrary::register_autoloader();

	$request_method = [ RequestsLibrary::get_class_name(), 'request' ];

	try {
		try {
			return $request_method( $url, $headers, $data, $method, $options );
		} catch ( Exception $exception ) {
			if ( RequestsLibrary::is_requests_exception( $exception ) ) {
				if (
					true !== $options['verify']
					|| 'curlerror' !== $exception->getType()
					|| curl_errno( $exception->getData() ) !== CURLE_SSL_CACERT
				) {
					throw $exception;
				}

				$options['verify'] = get_default_cacert( $halt_on_error );

				return $request_method( $url, $headers, $data, $method, $options );
			}
			throw $exception;
		}
	} catch ( Exception $exception ) {
		if ( RequestsLibrary::is_requests_exception( $exception ) ) {
			// CURLE_SSL_CACERT_BADFILE only defined for PHP >= 7.
			if (
				! $insecure
				||
				'curlerror' !== $exception->getType()
				||
				! in_array( curl_errno( $exception->getData() ), [ CURLE_SSL_CONNECT_ERROR, CURLE_SSL_CERTPROBLEM, 77 /*CURLE_SSL_CACERT_BADFILE*/ ], true )
			) {
				$error_msg = sprintf( "Failed to get url '%s': %s.", $url, $exception->getMessage() );
				if ( $halt_on_error ) {
					WP_CLI::error( $error_msg );
				}
				throw new RuntimeException( $error_msg, 0, $exception );
			}

			$warning = sprintf(
				"Re-trying without verify after failing to get verified url '%s' %s.",
				$url,
				$exception->getMessage()
			);
			WP_CLI::warning( $warning );

			// Disable certificate validation for the next try.
			$options['verify'] = false;

			try {
				return $request_method( $url, $headers, $data, $method, $options );
			} catch ( Exception $exception ) {
				if ( RequestsLibrary::is_requests_exception( $exception ) ) {
					$error_msg = sprintf( "Failed to get non-verified url '%s' %s.", $url, $exception->getMessage() );
					if ( $halt_on_error ) {
						WP_CLI::error( $error_msg );
					}
					throw new RuntimeException( $error_msg, 0, $exception );
				}
				throw $exception;
			}
		}
		throw $exception;
	}
}

/**
 * Gets the full path to the default CA cert.
 *
 * @param bool $halt_on_error Whether or not command execution should be halted on error. Default: false
 * @return string Absolute path to the default CA cert.
 * @throws RuntimeException If unable to locate the cert.
 * @throws ExitException If unable to locate the cert and $halt_on_error is true.
 */
function get_default_cacert( $halt_on_error = false ) {
	$cert_path = RequestsLibrary::get_bundled_certificate_path();
	$error_msg = 'Cannot find SSL certificate.';

	if ( inside_phar( $cert_path ) ) {
		// cURL can't read Phar archives.
		return extract_from_phar( $cert_path );
	}

	if ( file_exists( $cert_path ) ) {
		return $cert_path;
	}

	if ( $halt_on_error ) {
		WP_CLI::error( $error_msg );
	}

	throw new RuntimeException( $error_msg );
}

/**
 * Increments a version string using the "x.y.z-pre" format.
 *
 * Can increment the major, minor or patch number by one.
 * If $new_version == "same" the version string is not changed.
 * If $new_version is not a known keyword, it will be used as the new version string directly.
 *
 * @param string $current_version
 * @param string $new_version
 * @return string
 */
function increment_version( $current_version, $new_version ) {
	// split version assuming the format is x.y.z-pre.
	$current_version    = explode( '-', $current_version, 2 );
	$current_version[0] = explode( '.', $current_version[0] );

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

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

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

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

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

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

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

		default: // not a keyword.
			$current_version = [ [ $new_version ] ];
			break;
	}

	// Reconstruct version string.
	$current_version[0] = implode( '.', $current_version[0] );
	$current_version    = implode( '-', $current_version );

	return $current_version;
}

/**
 * Compare two version strings to get the named semantic version.
 *
 * @access public
 *
 * @param string $new_version
 * @param string $original_version
 * @return string 'major', 'minor', 'patch'
 */
function get_named_sem_ver( $new_version, $original_version ) {

	if ( ! Comparator::greaterThan( $new_version, $original_version ) ) {
		return '';
	}

	$parts = explode( '-', $original_version );
	$bits  = explode( '.', $parts[0] );
	$major = $bits[0];
	if ( isset( $bits[1] ) ) {
		$minor = $bits[1];
	}
	if ( isset( $bits[2] ) ) {
		$patch = $bits[2];
	}

	try {
		if ( isset( $minor ) && Semver::satisfies( $new_version, "{$major}.{$minor}.x" ) ) {
			return 'patch';
		}

		if ( Semver::satisfies( $new_version, "{$major}.x.x" ) ) {
			return 'minor';
		}
	} catch ( \UnexpectedValueException $e ) {
		return '';
	}

	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<string,string|bool>  $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 ) {
	if ( ! is_string( $string ) ) {
		return '/';
	}

	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
 *
 * @param string $url
 * @param int $component
 * @return mixed
 */
function parse_ssh_url( $url, $component = -1 ) {
	preg_match( '#^((docker|docker\-compose|docker\-compose\-run|ssh|vagrant):)?(([^@:]+)@)?([^:/~]+)(:([\d]*))?((/|~)(.+))?$#', $url, $matches );
	$bits = [];
	foreach ( [
		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).
 * @return void
 */
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<string>
 */
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 = $matches[0];
	return array_map(
		static function ( $arg ) {
			foreach ( [ '"', "'" ] as $char ) {
				if ( substr( $arg, 0, 1 ) === $char && substr( $arg, -1 ) === $char ) {
					$arg = substr( $arg, 1, -1 );
					break;
				}
			}
			return $arg;
		},
		$argv
	);
}

/**
 * Locale-independent version of basename()
 *
 * @access public
 *
 * @param string $path
 * @param string $suffix
 * @return string
 */
function basename( $path, $suffix = '' ) {
	// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.urlencode_urlencode -- Format required by wordpress.org API.
	return urldecode( \basename( str_replace( [ '%2F', '%5C' ], '/', urlencode( $path ) ), $suffix ) );
}

/**
 * Checks whether the output of the current script is a TTY or a pipe / redirect
 *
 * Returns `true` if `STDOUT` output is being redirected to a pipe or a file; `false` is
 * output is being sent directly to the terminal.
 *
 * If an env variable `SHELL_PIPE` exists, the 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`.
 * ```
 * SHELL_PIPE=0 wp plugin list | cat
 * ```
 *
 * Note that the db command forwards to the mysql client, which is unaware of the env
 * variable. For db commands, pass the `--table` option instead.
 * ```
 * wp db query --table "SELECT 1" | cat
 * ```
 *
 * @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<string>  $paths Single path as a string, or an array of paths.
 * @param int|'default'         $flags Optional. Flags to pass to glob. Defaults to GLOB_BRACE.
 * @return array<string> Expanded paths.
 */
function expand_globs( $paths, $flags = 'default' ) {
	// Compatibility for systems without GLOB_BRACE.
	$glob_func = 'glob';
	if ( 'default' === $flags ) {
		if ( ! defined( 'GLOB_BRACE' ) || getenv( 'WP_CLI_TEST_EXPAND_GLOBS_NO_GLOB_BRACE' ) ) {
			$glob_func = 'WP_CLI\Utils\glob_brace';
		} else {
			$flags = GLOB_BRACE;
		}
	}

	$expanded = [];

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

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

	return array_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 (https://framework.zend.com/)
 *
 * @link      https://github.com/zendframework/zf2 for the canonical source repository
 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (https://www.zend.com)
 * @license   https://framework.zend.com/license/new-bsd New BSD License
 *
 * @param string $pattern     Filename pattern.
 * @param void   $dummy_flags Not used.
 * @return array<string> Array of paths.
 */
function glob_brace( $pattern, $dummy_flags = null ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- $dummy_flags is needed for compatibility with the libc implementation.

	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 = [];
	$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 mistyped 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<string> $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 = [
		'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 . rtrim( WP_CLI_PHAR_PATH, '/' ) . '/',
		PHAR_STREAM_PREFIX,
		$path
	);
}

/**
 * Maybe prefix command string with "/usr/bin/env".
 * Removes (if there) if Windows, adds (if not there) if not.
 *
 * @param string $command
 * @return string
 */
function force_env_on_nix_systems( $command ) {
	$env_prefix     = '/usr/bin/env ';
	$env_prefix_len = strlen( $env_prefix );
	if ( is_windows() ) {
		if ( 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 = [
		'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() {
	// Phar installs always use PHP_BINARY.
	if ( inside_phar() ) {
		return PHP_BINARY;
	}

	$wp_cli_php_used = getenv( 'WP_CLI_PHP_USED' );
	if ( false !== $wp_cli_php_used ) {
		return $wp_cli_php_used;
	}

	$wp_cli_php = getenv( 'WP_CLI_PHP' );
	if ( false !== $wp_cli_php ) {
		return $wp_cli_php;
	}

	return PHP_BINARY;
}

/**
 * Windows compatible `proc_open()`.
 * Works around bug in PHP, and also deals with *nix-like `ENV_VAR=blah cmd` environment variable prefixes.
 *
 * @access public
 *
 * @param string                $cmd            Command to execute.
 * @param array<int, string>    $descriptorspec Indexed array of descriptor numbers and their values.
 * @param array<int, string>    &$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<string, string> $env            Array of environment variables.
 * @param array<string>         $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() ) {
		$cmd = _proc_open_compat_win_env( $cmd, $env );
	}
	return proc_open( $cmd, $descriptorspec, $pipes, $cwd, $env, $other_options );
}

/**
 * For use by `proc_open_compat()` only. Separated out for ease of testing. Windows only.
 * Turns *nix-like `ENV_VAR=blah command` environment variable prefixes into stripped `cmd` with prefixed environment variables added to passed in environment array.
 *
 * @access private
 *
 * @param string                $cmd  Command to execute.
 * @param array<string, string> &$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 = [];
			}
			$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 ) {
	global $wpdb;

	// Check if the esc_like() method exists on the global $wpdb object.
	// We need to do this because to ensure compatibilty layers like the
	// SQLite integration plugin still work.
	if ( null !== $wpdb && method_exists( $wpdb, 'esc_like' ) ) {
		return $wpdb->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<string> $idents A single identifier or an array of identifiers.
 * @return string|array<string> An escaped string if given a string, or an array of escaped strings if given an array of strings.
 */
function esc_sql_ident( $idents ) {
	$backtick = static function ( $v ) {
		// Escape any backticks in the identifier by doubling.
		return '`' . str_replace( '`', '``', $v ) . '`';
	};
	if ( is_string( $idents ) ) {
		return $backtick( $idents );
	}
	return array_map( $backtick, $idents );
}

/**
 * Check whether a given string is a valid JSON representation.
 *
 * @param string $argument       String to evaluate.
 * @param bool   $ignore_scalars Optional. Whether to ignore scalar values.
 *                               Defaults to true.
 * @return bool Whether the provided string is a valid JSON representation.
 */
function is_json( $argument, $ignore_scalars = true ) {
	if ( ! is_string( $argument ) || '' === $argument ) {
		return false;
	}

	if ( $ignore_scalars && ! in_array( $argument[0], [ '{', '[' ], true ) ) {
		return false;
	}

	json_decode( $argument, $assoc = true );

	return json_last_error() === JSON_ERROR_NONE;
}

/**
 * Parse known shell arrays included in the $assoc_args array.
 *
 * @param array<string, string> $assoc_args      Associative array of arguments.
 * @param array<string>         $array_arguments Array of argument keys that should receive an
 *                                               array through the shell.
 * @return array<string, string>
 */
function parse_shell_arrays( $assoc_args, $array_arguments ) {
	if ( empty( $assoc_args ) || empty( $array_arguments ) ) {
		return $assoc_args;
	}

	foreach ( $array_arguments as $key ) {
		if ( array_key_exists( $key, $assoc_args ) && is_json( $assoc_args[ $key ] ) ) {
			$assoc_args[ $key ] = json_decode( $assoc_args[ $key ], $assoc = true );
		}
	}

	return $assoc_args;
}

/**
 * Describe a callable as a string.
 *
 * @param callable $callable The callable to describe.
 * @return string String description of the callable.
 */
function describe_callable( $callable ) {
	try {
		if ( $callable instanceof Closure ) {
			$reflection = new ReflectionFunction( $callable );

			return "Closure in file {$reflection->getFileName()} at line {$reflection->getStartLine()}";
		}

		if ( is_array( $callable ) ) {
			if ( is_object( $callable[0] ) ) {
				return sprintf(
					'%s->%s()',
					get_class( $callable[0] ),
					$callable[1]
				);
			}

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

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

/**
 * Checks if the given class and method pair is a valid callable.
 *
 * This accommodates changes to `is_callable()` in PHP 8 that mean an array of a
 * classname and instance method is no longer callable.
 *
 * @param array<string> $pair The class and method pair to check.
 * @return bool
 */
function is_valid_class_and_method_pair( $pair ) {
	if ( ! is_array( $pair ) || 2 !== count( $pair ) ) {
		return false;
	}

	if ( ! is_string( $pair[0] ) || ! is_string( $pair[1] ) ) {
		return false;
	}

	if ( ! class_exists( $pair[0] ) ) {
		return false;
	}

	if ( ! method_exists( $pair[0], $pair[1] ) ) {
		return false;
	}

	return true;
}

/**
 * Pluralizes a noun in a grammatically correct way.
 *
 * @param string   $noun  Noun to be pluralized. Needs to be in singular form.
 * @param int|null $count Optional. Count of the nouns, to decide whether to
 *                        pluralize. Will pluralize unconditionally if none
 *                        provided.
 * @return string Pluralized noun.
 */
function pluralize( $noun, $count = null ) {
	if ( 1 === $count ) {
		return $noun;
	}

	return Inflector::pluralize( $noun );
}

/**
 * Return the detected database type.
 *
 * Can be either 'sqlite' (if in a WordPress installation with the SQLite drop-in),
 * 'mysql', or 'mariadb'.
 *
 * @return string Database type.
 */
function get_db_type() {
	static $db_type = null;

	if ( defined( 'SQLITE_DB_DROPIN_VERSION' ) ) {
		return 'sqlite';
	}

	if ( null !== $db_type ) {
		return $db_type;
	}

	$db_type = 'mysql';

	$binary = get_mysql_binary_path();

	if ( '' !== $binary ) {
		$result = Process::create( "$binary --version", null, null )->run();

		if ( 0 === $result->return_code ) {
			$db_type = ( false !== strpos( $result->stdout, 'MariaDB' ) ) ? 'mariadb' : 'mysql';
		}
	}

	return $db_type;
}

/**
 * Get the path to the MySQL or MariaDB binary.
 *
 * If the MySQL binary is provided by MariaDB (as determined by the version string),
 * prefers the actual MariaDB binary.
 *
 * @since 2.12.0 Now also checks for MariaDB.
 *
 * @return string Path to the MySQL/MariaDB binary, or an empty string if not found.
 */
function get_mysql_binary_path() {
	static $path = null;

	if ( null !== $path ) {
		return $path;
	}

	$path    = '';
	$mysql   = Process::create( '/usr/bin/env which mysql', null, null )->run();
	$mariadb = Process::create( '/usr/bin/env which mariadb', null, null )->run();

	$mysql_binary   = trim( $mysql->stdout );
	$mariadb_binary = trim( $mariadb->stdout );

	if ( 0 === $mysql->return_code ) {
		if ( '' !== $mysql_binary ) {
			$path   = $mysql_binary;
			$result = Process::create( "$mysql_binary --version", null, null )->run();

			// It's actually MariaDB disguised as MySQL.
			if ( 0 === $result->return_code && false !== strpos( $result->stdout, 'MariaDB' ) && 0 === $mariadb->return_code ) {
				$path = $mariadb_binary;
			}
		}
	} elseif ( 0 === $mariadb->return_code ) {
		$path = $mariadb_binary;
	}

	return $path;
}

/**
 * Get the version of the MySQL or MariaDB database.
 *
 * @since 2.12.0 Now also checks for MariaDB.
 *
 * @return string Version of the MySQL/MariaDB database,
 *                or an empty string if not found.
 */
function get_mysql_version() {
	static $version = null;

	if ( null !== $version ) {
		return $version;
	}

	$db_type = get_db_type();

	if ( 'sqlite' !== $db_type ) {
		$result = Process::create( "/usr/bin/env $db_type --version", null, null )->run();

		if ( 0 !== $result->return_code ) {
			$version = '';
		} else {
			$version = trim( $result->stdout );
		}
	}

	return $version;
}

/**
 * Returns the correct `dump` command based on the detected database type.
 *
 * @return string The appropriate dump command.
 */
function get_sql_dump_command() {
	return 'mariadb' === get_db_type() ? 'mariadb-dump' : 'mysqldump';
}

/**
 * Returns the correct `check` command based on the detected database type.
 *
 * @return string The appropriate check command.
 */
function get_sql_check_command() {
	return 'mariadb' === get_db_type() ? 'mariadb-check' : 'mysqlcheck';
}

/**
 * Get the SQL modes of the MySQL session.
 *
 * @return string[] Array of SQL modes, or an empty array if they couldn't be
 *                  read.
 */
function get_sql_modes() {
	static $sql_modes = null;

	if ( null !== $sql_modes ) {
		return $sql_modes;
	}

	$binary = get_mysql_binary_path();

	if ( '' === $binary ) {
		$sql_modes = [];
	} else {
		$result = Process::create( "$binary --no-auto-rehash --batch --skip-column-names --execute=\"SELECT @@SESSION.sql_mode\"", null, null )->run();

		if ( 0 !== $result->return_code ) {
			$sql_modes = [];
		} else {
			$sql_modes = array_filter(
				array_map(
					'trim',
					preg_split( "/\r\n|\n|\r/", $result->stdout )
				)
			);
		}
	}

	return $sql_modes;
}

/**
 * Get the WP-CLI cache directory.
 *
 * @return string
 */
function get_cache_dir() {
	$home = get_home_dir();
	return getenv( 'WP_CLI_CACHE_DIR' ) ? : "$home/.wp-cli/cache";
}

/**
 * Check whether any input is passed to STDIN.
 *
 * @return bool
 */
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;
}

/**
 * Return description of WP_CLI hooks used in @when tag
 *
 *  @param string $hook Name of WP_CLI hook
 *
 * @return string|null
 */
function get_hook_description( $hook ) {
	$events = [
		'find_command_to_run_pre'     => 'just before WP-CLI finds the command to run.',
		'before_registering_contexts' => 'before the contexts are registered.',
		'before_wp_load'              => 'just before the WP load process begins.',
		'before_wp_config_load'       => 'after wp-config.php has been located.',
		'after_wp_config_load'        => 'after wp-config.php has been loaded into scope.',
		'after_wp_load'               => 'just after the WP load process has completed.',
	];

	if ( array_key_exists( $hook, $events ) ) {
		return $events[ $hook ];
	}
	return null;
}

/**
 * Escape a value for CSV output.
 *
 * Values that start with the following characters are escaping with a single
 * quote: =, +, -, @, TAB (0x09) and CR (0x0D).
 *
 * @param string $value Value to escape.
 * @return string Escaped value.
 */
function escape_csv_value( $value ) {
	if ( null === $value ) {
		return '';
	}

	// Convert to string if not already
	$value = (string) $value;

	if (
		in_array(
			substr( $value, 0, 1 ),
			[ '=', '+', '-', '@', "\t", "\r" ],
			true
		)
	) {
		return "'{$value}";
	}

	return $value;
}
<?php
/**
 * A modified version of wp-settings.php, tailored for CLI use.
 *
 * Note: This is not being used anymore from WordPress 4.6-alpha-37575 onwards.
 *
 * @phpcs:disable WordPress.WP.GlobalVariablesOverride -- Setting the globals is the point of this file.
 * @phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- These are WP native constants which are needed.
 * @phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- All hook calls in this file are to WP native hooks.
 * @phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- Ensuring native WP variables are available.
 */

use WP_CLI\Utils;

/**
 * Stores the location of the WordPress directory of functions, classes, and core content.
 *
 * @since 1.0.0
 */
define( 'WPINC', 'wp-includes' );

// Include files required for initialization.
require ABSPATH . WPINC . '/load.php';
require ABSPATH . WPINC . '/default-constants.php';
require ABSPATH . WPINC . '/plugin.php';

/*
 * These can't be directly globalized in version.php. When updating,
 * we're including version.php from another installation and don't want
 * these values to be overridden if already set.
 */
global $wp_version, $wp_db_version, $tinymce_version, $required_php_version, $required_mysql_version, $wp_local_package, $upgrading;
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.
 *
 * @since 2.0.0
 * @global int $blog_id
 */
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.DateTime.RestrictedFunctions.timezone_change_date_default_timezone_set
date_default_timezone_set( 'UTC' );

// 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, $upgrading ) ) {
	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', [] );

// Run the installer if WordPress is not installed.
wp_not_installed();

// Load most of WordPress.
require ABSPATH . WPINC . '/class-wp-walker.php';
require ABSPATH . WPINC . '/class-wp-ajax-response.php';
require ABSPATH . WPINC . '/formatting.php';
require ABSPATH . WPINC . '/capabilities.php';
Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-roles.php' );
Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-role.php' );
Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-user.php' );
require ABSPATH . WPINC . '/query.php';
Utils\maybe_require( '3.7-alpha-25139', ABSPATH . WPINC . '/date.php' );
require ABSPATH . WPINC . '/theme.php';
require ABSPATH . WPINC . '/class-wp-theme.php';
require ABSPATH . WPINC . '/template.php';
require ABSPATH . WPINC . '/user.php';
Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-user-query.php' );
Utils\maybe_require( '4.0', ABSPATH . WPINC . '/session.php' );
require ABSPATH . WPINC . '/meta.php';
Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-meta-query.php' );
Utils\maybe_require( '4.5-alpha-35776', ABSPATH . WPINC . '/class-wp-metadata-lazyloader.php' );
require ABSPATH . WPINC . '/general-template.php';
require ABSPATH . WPINC . '/link-template.php';
require ABSPATH . WPINC . '/author-template.php';
require ABSPATH . WPINC . '/post.php';
Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-walker-page.php' );
Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-walker-page-dropdown.php' );
Utils\maybe_require( '4.6-alpha-37890', ABSPATH . WPINC . '/class-wp-post-type.php' );
Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-post.php' );
require ABSPATH . WPINC . '/post-template.php';
Utils\maybe_require( '3.6-alpha-23451', ABSPATH . WPINC . '/revision.php' );
Utils\maybe_require( '3.6-alpha-23451', ABSPATH . WPINC . '/post-formats.php' );
require ABSPATH . WPINC . '/post-thumbnail-template.php';
require ABSPATH . WPINC . '/category.php';
Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-walker-category.php' );
Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-walker-category-dropdown.php' );
require ABSPATH . WPINC . '/category-template.php';
require ABSPATH . WPINC . '/comment.php';
Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-comment.php' );
Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-comment-query.php' );
Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-walker-comment.php' );
require ABSPATH . WPINC . '/comment-template.php';
require ABSPATH . WPINC . '/rewrite.php';
Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-rewrite.php' );
require ABSPATH . WPINC . '/feed.php';
require ABSPATH . WPINC . '/bookmark.php';
require ABSPATH . WPINC . '/bookmark-template.php';
require ABSPATH . WPINC . '/kses.php';
require ABSPATH . WPINC . '/cron.php';
require ABSPATH . WPINC . '/deprecated.php';
require ABSPATH . WPINC . '/script-loader.php';
require ABSPATH . WPINC . '/taxonomy.php';
Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-term.php' );
Utils\maybe_require( '4.6-alpha-37575', ABSPATH . WPINC . '/class-wp-term-query.php' );
Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-tax-query.php' );
require ABSPATH . WPINC . '/update.php';
require ABSPATH . WPINC . '/canonical.php';
require ABSPATH . WPINC . '/shortcodes.php';
Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/embed.php' );
require ABSPATH . WPINC . '/class-wp-embed.php';
require ABSPATH . WPINC . '/media.php';
Utils\maybe_require( '4.4-alpha-34903', ABSPATH . WPINC . '/class-wp-oembed-controller.php' );
require ABSPATH . WPINC . '/http.php';
require_once ABSPATH . WPINC . '/class-http.php';
Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-http-streams.php' );
Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-http-curl.php' );
Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-http-proxy.php' );
Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-http-cookie.php' );
Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-http-encoding.php' );
Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-http-response.php' );
Utils\maybe_require( '4.6-alpha-37438', ABSPATH . WPINC . '/class-wp-http-requests-response.php' );
require ABSPATH . WPINC . '/widgets.php';
Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-widget.php' );
Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/class-wp-widget-factory.php' );
require ABSPATH . WPINC . '/nav-menu.php';
require ABSPATH . WPINC . '/nav-menu-template.php';
require ABSPATH . WPINC . '/admin-bar.php';
Utils\maybe_require( '4.4-alpha-34928', ABSPATH . WPINC . '/rest-api.php' );
Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/rest-api/class-wp-rest-server.php' );
Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/rest-api/class-wp-rest-response.php' );
Utils\maybe_require( '4.4-beta4-35719', ABSPATH . WPINC . '/rest-api/class-wp-rest-request.php' );

// Load multisite-specific files.
if ( is_multisite() ) {
	require ABSPATH . WPINC . '/ms-functions.php';
	require ABSPATH . WPINC . '/ms-default-filters.php';
	require ABSPATH . WPINC . '/ms-deprecated.php';
}

// Define constants that rely on the API to obtain the default value.
// Define must-use plugin directory constants, which may be overridden in the sunrise.php drop-in.
wp_plugin_directory_constants();

$symlinked_plugins_supported = function_exists( 'wp_register_plugin_realpath' );
if ( $symlinked_plugins_supported ) {
	$GLOBALS['wp_plugin_paths'] = [];
}

// Load must-use plugins.
foreach ( wp_get_mu_plugins() as $mu_plugin ) {
	include_once $mu_plugin;
}
unset( $mu_plugin );

// Load network activated plugins.
if ( is_multisite() ) {
	foreach ( wp_get_active_network_plugins() as $network_plugin ) {
		if ( $symlinked_plugins_supported ) {
			wp_register_plugin_realpath( $network_plugin );
		}
		include_once $network_plugin;
	}
	unset( $network_plugin );
}

do_action( 'muplugins_loaded' );

if ( is_multisite() ) {
	ms_cookie_constants();
}

// Define constants after multisite is loaded. Cookie-related constants may be overridden in ms_network_cookies().
wp_cookie_constants();

// Define and enforce our SSL constants.
wp_ssl_constants();

// Create common globals.
require ABSPATH . WPINC . '/vars.php';

// Make taxonomies and posts available to plugins and themes.
// @plugin authors: warning: these get registered again on the init hook.
create_initial_taxonomies();
create_initial_post_types();

// Register the default theme directory root
register_theme_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();

if ( ! function_exists( 'get_magic_quotes_gpc' ) ) {
	// Provide compat fallback for newer PHP version (7.4+) on older WordPress core versions.
	function get_magic_quotes_gpc() {
		return false;
	}
}

// Add magic quotes and set up $_REQUEST ( $_GET + $_POST )
wp_magic_quotes();

do_action( 'sanitize_comment_cookies' );

/**
 * WordPress Query object
 * @since 2.0.0
 * @global object $wp_the_query
 */
$GLOBALS['wp_the_query'] = new WP_Query();

/**
 * Holds the reference to @see $wp_the_query
 * Use this global for WordPress queries
 * @since 1.5.0
 * @global object $wp_query
 */
$GLOBALS['wp_query'] = $GLOBALS['wp_the_query'];

/**
 * Holds the WordPress Rewrite object for creating pretty URLs
 * @since 1.5.0
 * @global object $wp_rewrite
 */
$GLOBALS['wp_rewrite'] = new WP_Rewrite();

/**
 * WordPress Object
 * @since 2.0.0
 * @global object $wp
 */
$GLOBALS['wp'] = new WP();

/**
 * WordPress Widget Factory Object
 * @since 2.8.0
 * @global object $wp_widget_factory
 */
$GLOBALS['wp_widget_factory'] = new WP_Widget_Factory();

/**
 * WordPress User Roles
 * @since 2.0.0
 * @global object $wp_roles
 */
$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.
 * @since 2.1.0
 * @global object $wp_locale
 */
$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 https://developer.wordpress.org/plugins/javascript/ajax/
 *
 * @since 3.0.0
 */
do_action( 'wp_loaded' );
<?php

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

use Mustangostang\Spyc;
use WP_CLI\ExitException;
use WP_CLI\Utils;

/**
 * Retrieves, sets and updates aliases for WordPress Installations.
 *
 * Aliases are shorthand references to WordPress installs. For instance,
 * `@dev` could refer to a development install and `@prod` could refer to a production install.
 * This command gives you and option to add, update and delete, the registered aliases you have available.
 *
 * ## EXAMPLES
 *
 *     # List alias information.
 *     $ wp cli alias list
 *     list
 *     ---
 *     @all: Run command against every registered alias.
 *     @local:
 *       user: wpcli
 *       path: /Users/wpcli/sites/testsite
 *
 *     # Get alias information.
 *     $ wp cli alias get @dev
 *     ssh: 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, '', true );

		$this->validate_config_file( $config_path );

		$alias    = $args[0];
		$grouping = Utils\get_flag_value( $assoc_args, 'grouping' );

		$this->validate_input( $assoc_args, $grouping );

		if ( isset( $aliases[ $alias ] ) ) {
			WP_CLI::error( "Key '{$alias}' exists already." );
		}

		if ( null === $grouping ) {
			$aliases = $this->build_aliases( $aliases, $alias, $assoc_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 = Utils\get_flag_value( $assoc_args, 'grouping' );

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

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

	/**
	 * Check whether an alias is a group.
	 *
	 * ## OPTIONS
	 *
	 * <key>
	 * : Key for the alias.
	 *
	 * ## EXAMPLES
	 *
	 *     # Checks whether the alias is a group; exit status 0 if it is, otherwise 1.
	 *     $ wp cli alias is-group @prod
	 *     $ echo $?
	 *     1
	 *
	 * @subcommand is-group
	 */
	public function is_group( $args, $assoc_args = array() ) {
		$alias = $args[0];

		$aliases = WP_CLI::get_runner()->aliases;

		if ( empty( $aliases[ $alias ] ) ) {
			WP_CLI::error( "No alias found with key '{$alias}'." );
		}

		// how do we know the alias is a group?
		// + array keys are numeric
		// + array values begin with '@'

		$first_item       = $aliases[ $alias ];
		$first_item_key   = key( $first_item );
		$first_item_value = $first_item[ $first_item_key ];

		if ( is_numeric( $first_item_key ) && substr( $first_item_value, 0, 1 ) === '@' ) {
			WP_CLI::halt( 0 );
		}
		WP_CLI::halt( 1 );
	}

	/**
	 * Get config path and aliases data based on config type.
	 *
	 * @param string $config             Type of config to get data from.
	 * @param string $alias              Alias to be used for Add/Update/Delete.
	 * @param bool   $create_config_file Optional. If a config file doesn't exist,
	 *                                   should it be created? Defaults to false.
	 *
	 * @return array Config Path and Aliases in it.
	 * @throws ExitException
	 */
	private function get_aliases_data( $config, $alias, $create_config_file = false ) {

		$global_config_path = WP_CLI::get_runner()->get_global_config_path( $create_config_file );
		$global_aliases     = Spyc::YAMLLoad( $global_config_path );

		$project_config_path = WP_CLI::get_runner()->get_project_config_path();
		$project_aliases     = Spyc::YAMLLoad( $project_config_path );

		if ( 'global' === $config ) {
			$config_path = $global_config_path;
			$aliases     = $global_aliases;
		} elseif ( 'project' === $config ) {
			$config_path = $project_config_path;
			$aliases     = $project_aliases;
		} else {

			$is_global_alias  = array_key_exists( $alias, $global_aliases );
			$is_project_alias = array_key_exists( $alias, $project_aliases );

			if ( $is_global_alias && $is_project_alias ) {
				WP_CLI::error( "Key '{$alias}' found in more than one path. Please pass --config param." );
			} elseif ( $is_global_alias ) {
				$config_path = $global_config_path;
				$aliases     = $global_aliases;
			} else {
				$config_path = $project_config_path;
				$aliases     = $project_aliases;
			}
		}

		return [ $config_path, $aliases ];
	}

	/**
	 * Check if the config file exists and is writable.
	 *
	 * @param string $config_path Path to config file.
	 *
	 * @return void
	 */
	private function validate_config_file( $config_path ) {
		if ( ! file_exists( $config_path ) || ! is_writable( $config_path ) ) {
			WP_CLI::error( "Config file does not exist: {$config_path}" );
		}
	}

	/**
	 * Return aliases array.
	 *
	 * @param array  $aliases     Current aliases data.
	 * @param string $alias       Name of alias.
	 * @param array  $assoc_args  Associative arguments.
	 * @param bool   $is_grouping Check if its a grouping operation.
	 * @param string $grouping    Grouping value.
	 * @param bool   $is_update   Is this an update operation?
	 *
	 * @return mixed
	 */
	private function build_aliases( $aliases, $alias, $assoc_args, $is_grouping, $grouping = '', $is_update = false ) {
		$alias = $this->normalize_alias( $alias );

		if ( $is_grouping ) {
			$valid_assoc_args = [ '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;
					}
				}
			}
		} elseif ( ! empty( $grouping ) ) {

				$group_alias_list  = explode( ',', $grouping );
				$group_alias       = array_map(
					function ( $current_alias ) {
						return '@' . ltrim( $current_alias, '@' );
					},
					$group_alias_list
				);
				$aliases[ $alias ] = $group_alias;
		}

		return $aliases;
	}

	/**
	 * Validate input of passed arguments.
	 *
	 * @param array  $assoc_args Arguments array.
	 * @param string $grouping   Grouping argument value.
	 *
	 * @throws ExitException
	 */
	private function validate_input( $assoc_args, $grouping ) {
		// Check if valid arguments were passed.
		$arg_match = preg_grep( '/^set-(\w+)/i', array_keys( $assoc_args ) );

		// Verify passed-arguments.
		if ( empty( $grouping ) && empty( $arg_match ) ) {
			WP_CLI::error( 'No valid arguments passed.' );
		}

		// Check whether passed arguments contain value or not.
		$assoc_arg_values = array_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 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 = '' ) {
		$alias = $this->normalize_alias( $alias );

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

	/**
	 * Normalize the alias to an expected format.
	 *
	 * - Add @ if not present.
	 *
	 * @param string $alias Name of alias.
	 */
	private function normalize_alias( $alias ) {
		// Check if the alias starts with the @.
		// See: https://github.com/wp-cli/wp-cli/issues/5391
		if ( strpos( $alias, '@' ) !== 0 ) {
			$alias = '@' . ltrim( $alias, '@' );
		}

		return $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\Completions;
use WP_CLI\Formatter;
use WP_CLI\Process;
use WP_CLI\Utils;

/**
 * Reviews current WP-CLI info, checks for updates, or views defined aliases.
 *
 * ## EXAMPLES
 *
 *     # Display the version currently installed.
 *     $ wp cli version
 *     WP-CLI 0.24.1
 *
 *     # Check for updates to WP-CLI.
 *     $ wp cli check-update
 *     Success: WP-CLI is at the latest version.
 *
 *     # Update WP-CLI to the latest stable release.
 *     $ wp cli update
 *     You have version 0.24.0. Would you like to update to 0.24.1? [y/n] y
 *     Downloading from https://github.com/wp-cli/wp-cli/releases/download/v0.24.1/wp-cli-0.24.1.phar...
 *     New version works. Proceeding to replace.
 *     Success: Updated WP-CLI to 0.24.1.
 *
 *     # Clear the internal WP-CLI cache.
 *     $ wp cli cache clear
 *     Success: Cache cleared.
 *
 * @when before_wp_load
 */
class CLI_Command extends WP_CLI_Command {

	private function command_to_array( $command ) {
		$dump = [
			'name'        => $command->get_name(),
			'description' => $command->get_shortdesc(),
			'longdesc'    => $command->get_longdesc(),
			'hook'        => $command->get_hook(),
		];

		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://make.wordpress.org/cli/handbook/references/config/) for more details on global
	 * and project config YAML files.
	 *
	 * ## OPTIONS
	 *
	 * [--format=<format>]
	 * : Render output in a particular format.
	 * ---
	 * default: list
	 * options:
	 *   - list
	 *   - json
	 * ---
	 *
	 * ## EXAMPLES
	 *
	 *     # Display various data about the CLI environment.
	 *     $ wp cli info
	 *     OS:  Linux 4.10.0-42-generic #46~16.04.1-Ubuntu SMP Mon Dec 4 15:57:59 UTC 2017 x86_64
	 *     Shell:   /usr/bin/zsh
	 *     PHP binary:  /usr/bin/php
	 *     PHP version: 7.1.12-1+ubuntu16.04.1+deb.sury.org+1
	 *     php.ini used:    /etc/php/7.1/cli/php.ini
	 *     WP-CLI root dir:    phar://wp-cli.phar
	 *     WP-CLI packages dir:    /home/person/.wp-cli/packages/
	 *     WP-CLI global config:
	 *     WP-CLI project config:
	 *     WP-CLI version: 1.5.0
	 */
	public function info( $_, $assoc_args ) {
		// php_uname() $mode argument was only added with PHP 7.0+. Fall back to
		// entire string for older versions.
		$system_os = PHP_MAJOR_VERSION < 7
			? php_uname()
			: sprintf(
				'%s %s %s %s',
				php_uname( 's' ),
				php_uname( 'r' ),
				php_uname( 'v' ),
				php_uname( 'm' )
			);

		$shell = getenv( 'SHELL' );
		if ( ! $shell && Utils\is_windows() ) {
			$shell = getenv( 'ComSpec' );
		}

		$php_bin = Utils\get_php_binary();

		$runner = WP_CLI::get_runner();

		$packages_dir = $runner->get_packages_dir_path();
		if ( ! is_dir( $packages_dir ) ) {
			$packages_dir = null;
		}

		if ( Utils\get_flag_value( $assoc_args, 'format' ) === 'json' ) {
			$info = [
				'system_os'                => $system_os,
				'shell'                    => $shell,
				'php_binary_path'          => $php_bin,
				'php_version'              => PHP_VERSION,
				'php_ini_used'             => get_cfg_var( 'cfg_file_path' ),
				'mysql_binary_path'        => Utils\get_mysql_binary_path(),
				'mysql_version'            => Utils\get_mysql_version(),
				'sql_modes'                => Utils\get_sql_modes(),
				'wp_cli_dir_path'          => WP_CLI_ROOT,
				'wp_cli_vendor_path'       => WP_CLI_VENDOR_DIR,
				'wp_cli_phar_path'         => defined( 'WP_CLI_PHAR_PATH' ) ? WP_CLI_PHAR_PATH : '',
				'wp_cli_packages_dir_path' => $packages_dir,
				'wp_cli_cache_dir_path'    => Utils\get_cache_dir(),
				'global_config_path'       => $runner->global_config_path,
				'project_config_path'      => $runner->project_config_path,
				'wp_cli_version'           => WP_CLI_VERSION,
			];

			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( "MySQL binary:\t" . Utils\get_mysql_binary_path() );
			WP_CLI::line( "MySQL version:\t" . Utils\get_mysql_version() );
			WP_CLI::line( "SQL modes:\t" . implode( ',', Utils\get_sql_modes() ) );
			WP_CLI::line( "WP-CLI root dir:\t" . WP_CLI_ROOT );
			WP_CLI::line( "WP-CLI vendor dir:\t" . WP_CLI_VENDOR_DIR );
			WP_CLI::line( "WP_CLI phar path:\t" . ( defined( 'WP_CLI_PHAR_PATH' ) ? WP_CLI_PHAR_PATH : '' ) );
			WP_CLI::line( "WP-CLI packages dir:\t" . $packages_dir );
			WP_CLI::line( "WP-CLI cache dir:\t" . Utils\get_cache_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,status,requires_php.
	 *
	 * [--format=<format>]
	 * : Render output in a particular format.
	 * ---
	 * default: table
	 * options:
	 *   - table
	 *   - csv
	 *   - json
	 *   - count
	 *   - yaml
	 * ---
	 *
	 * ## EXAMPLES
	 *
	 *     # Check for update.
	 *     $ wp cli check-update
	 *     Success: WP-CLI is at the latest version.
	 *
	 *     # Check for update and new version is available.
	 *     $ wp cli check-update
	 *     +---------+-------------+-------------------------------------------------------------------------------+
	 *     | version | update_type | package_url                                                                   |
	 *     +---------+-------------+-------------------------------------------------------------------------------+
	 *     | 0.24.1  | patch       | https://github.com/wp-cli/wp-cli/releases/download/v0.24.1/wp-cli-0.24.1.phar |
	 *     +---------+-------------+-------------------------------------------------------------------------------+
	 *
	 * @subcommand check-update
	 */
	public function check_update( $_, $assoc_args ) {
		$updates = $this->get_updates( $assoc_args );

		if ( $updates ) {
			$formatter = new Formatter(
				$assoc_args,
				[ 'version', 'update_type', 'package_url', 'status', 'requires_php' ]
			);
			$formatter->display_items( $updates );
		} elseif ( empty( $assoc_args['format'] ) || 'table' === $assoc_args['format'] ) {
			$update_type = $this->get_update_type_str( $assoc_args );
			WP_CLI::success( "WP-CLI is at the latest{$update_type}version." );
		}
	}

	/**
	 * Updates WP-CLI to the latest release.
	 *
	 * Default behavior is to check the releases API for the newest stable
	 * version, and prompt if one is available.
	 *
	 * Use `--stable` to install or reinstall the latest stable version.
	 *
	 * Use `--nightly` to install the latest built version of the master branch.
	 * While not recommended for production, nightly contains the latest and
	 * greatest, and should be stable enough for development and staging
	 * environments.
	 *
	 * Only works for the Phar installation mechanism.
	 *
	 * ## OPTIONS
	 *
	 * [--patch]
	 * : Only perform patch updates.
	 *
	 * [--minor]
	 * : Only perform minor updates.
	 *
	 * [--major]
	 * : Only perform major updates.
	 *
	 * [--stable]
	 * : Update to the latest stable release. Skips update check.
	 *
	 * [--nightly]
	 * : Update to the latest built version of the master branch. Potentially unstable.
	 *
	 * [--yes]
	 * : Do not prompt for confirmation.
	 *
	 * [--insecure]
	 * : Retry without certificate validation if TLS handshake fails. Note: This makes the request vulnerable to a MITM attack.
	 *
	 * ## EXAMPLES
	 *
	 *     # Update CLI.
	 *     $ wp cli update
	 *     You are currently using WP-CLI 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 are currently using WP-CLI version %s. Would you like to update to the latest nightly version?', 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';
			$sha512_url   = 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli-nightly.phar.sha512';
		} elseif ( Utils\get_flag_value( $assoc_args, 'stable' ) ) {
			WP_CLI::confirm( sprintf( 'You are currently using WP-CLI 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';
			$sha512_url   = 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar.sha512';
		} else {

			$updates = $this->get_updates( $assoc_args );

			$newest = $this->array_find(
				$updates,
				static function ( $update ) {
					return 'available' === $update['status'];
				}
			);

			if ( ! $newest ) {
				$update_type = $this->get_update_type_str( $assoc_args );
				WP_CLI::success( "WP-CLI is at the latest{$update_type}version." );
				return;
			}

			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 );
			$sha512_url   = str_replace( '.phar', '.phar.sha512', $download_url );
		}

		WP_CLI::log( sprintf( 'Downloading from %s...', $download_url ) );

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

		$headers = [];
		$options = [
			'timeout'  => 600,  // 10 minutes ought to be enough for everybody.
			'filename' => $temp,
			'insecure' => (bool) Utils\get_flag_value( $assoc_args, 'insecure', false ),
		];

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

		unset( $options['filename'] );

		$this->validate_hashes( $temp, $sha512_url, $md5_url );

		$allow_root = WP_CLI::get_runner()->config['allow-root'] ? '--allow-root' : '';
		$php_binary = Utils\get_php_binary();
		$process    = Process::create( "{$php_binary} $temp --info {$allow_root}" );
		$result     = $process->run();
		if ( 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', false ) ) {
			$updated_version = 'the latest nightly release';
		} elseif ( Utils\get_flag_value( $assoc_args, 'stable', false ) ) {
			$updated_version = 'the latest stable release';
		} else {
			$updated_version = isset( $newest['version'] ) ? $newest['version'] : '<not provided>';
		}
		WP_CLI::success( sprintf( 'Updated WP-CLI to %s.', $updated_version ) );
	}

	/**
	 * @param string $file       Release file path.
	 * @param string $sha512_url URL to sha512 hash.
	 * @param string $md5_url    URL to md5 hash.
	 *
	 * @return void
	 * @throws \WP_CLI\ExitException
	 */
	private function validate_hashes( $file, $sha512_url, $md5_url ) {
		$algos = [
			'sha512' => $sha512_url,
			'md5'    => $md5_url,
		];

		foreach ( $algos as $algo => $url ) {
			$response = Utils\http_request( 'GET', $url );
			if ( '20' !== substr( $response->status_code, 0, 2 ) ) {
				WP_CLI::log( "Couldn't access $algo hash for release (HTTP code {$response->status_code})." );
				continue;
			}

			$file_hash = hash_file( $algo, $file );

			$release_hash = trim( $response->body );
			if ( $file_hash === $release_hash ) {
				WP_CLI::log( "$algo hash verified: $release_hash" );
				return;
			} else {
				WP_CLI::error( "$algo hash for download ($file_hash) is different than the release hash ($release_hash)." );
			}
		}

		WP_CLI::error( 'Release hash verification failed.' );
	}

	/**
	 * Returns update information.
	 */
	private function get_updates( $assoc_args ) {
		$url = 'https://api.github.com/repos/wp-cli/wp-cli/releases?per_page=100';

		$options = [
			'timeout'  => 30,
			'insecure' => (bool) Utils\get_flag_value( $assoc_args, 'insecure', false ),
		];

		$headers = [
			'Accept' => 'application/json',
		];

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

		$response = Utils\http_request( 'GET', $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 = [
			'major' => false,
			'minor' => false,
			'patch' => false,
		];

		$updates_unavailable = [];

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

			// Release is older than one we already have on file.
			if ( ! empty( $updates[ $update_type ] ) && ! Comparator::greaterThan( $release_version, $updates[ $update_type ]['version'] ) ) {
				continue;
			}

			$package_url   = null;
			$manifest_data = null;

			foreach ( $release->assets as $asset ) {
				if ( ! isset( $asset->browser_download_url ) ) {
					continue;
				}

				if ( substr( $asset->browser_download_url, - strlen( '.phar' ) ) === '.phar' ) {
					$package_url = $asset->browser_download_url;
				}

				// The manifest.json file, if it exists, contains information about PHP version requirements and similar.
				if ( substr( $asset->browser_download_url, - strlen( 'manifest.json' ) ) === 'manifest.json' ) {
					$response = Utils\http_request( 'GET', $asset->browser_download_url, null, $headers, $options );

					if ( $response->success ) {
						$manifest_data = json_decode( $response->body );
					}
				}
			}

			if ( ! $package_url ) {
				continue;
			}

			// Release requires a newer version of PHP.
			if (
				isset( $manifest_data->requires_php ) &&
				! Comparator::greaterThanOrEqualTo( PHP_VERSION, $manifest_data->requires_php )
			) {
				$updates_unavailable[] = [
					'version'      => $release_version,
					'update_type'  => $update_type,
					'package_url'  => $release->assets[0]->browser_download_url,
					'status'       => 'unavailable',
					'requires_php' => $manifest_data->requires_php,
				];
			} else {
				$updates[ $update_type ] = [
					'version'      => $release_version,
					'update_type'  => $update_type,
					'package_url'  => $release->assets[0]->browser_download_url,
					'status'       => 'available',
					'requires_php' => isset( $manifest_data->requires_php ) ? $manifest_data->requires_php : '',
				];
			}
		}

		foreach ( $updates as $type => $value ) {
			if ( empty( $value ) ) {
				unset( $updates[ $type ] );
			}
		}

		foreach ( [ 'major', 'minor', 'patch' ] as $type ) {
			if ( true === Utils\get_flag_value( $assoc_args, $type ) ) {
				return ! empty( $updates[ $type ] ) ? [ $updates[ $type ] ] : false;
			}
		}

		if ( empty( $updates ) && preg_match( '#-alpha-(.+)$#', WP_CLI_VERSION, $matches ) ) {
			$version_url = 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/NIGHTLY_VERSION';
			$response    = Utils\http_request( 'GET', $version_url, null, [], $options );
			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 ) {
				$manifest_data = null;

				// The manifest.json file, if it exists, contains information about PHP version requirements and similar.
				$response = Utils\http_request( 'GET', 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli-nightly.manifest.json', null, $headers, $options );

				if ( $response->success ) {
					$manifest_data = json_decode( $response->body );
				}

				// Release requires a newer version of PHP.
				if (
					isset( $manifest_data->requires_php ) &&
					! Comparator::greaterThanOrEqualTo( PHP_VERSION, $manifest_data->requires_php )
				) {
					$updates_unavailable[] = [
						'version'      => $nightly_version,
						'update_type'  => 'nightly',
						'package_url'  => 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli-nightly.phar',
						'status'       => 'unvailable',
						'requires_php' => $manifest_data->requires_php,
					];
				} else {
					$updates['nightly'] = [
						'version'      => $nightly_version,
						'update_type'  => 'nightly',
						'package_url'  => 'https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli-nightly.phar',
						'status'       => 'available',
						'requires_php' => isset( $manifest_data->requires_php ) ? $manifest_data->requires_php : '',
					];
				}
			}
		}

		return array_merge( $updates_unavailable, array_values( $updates ) );
	}

	/**
	 * Returns the the first element of the passed array for which the
	 * callback returns true.
	 *
	 * Polyfill for the `array_find()` function introduced in PHP 8.3.
	 *
	 * @param array    $arr      Array to search.
	 * @param callable $callback The callback function for each element in the array.
	 * @return mixed First array element for which the callback returns true, null otherwise.
	 */
	private function array_find( $arr, $callback ) {
		if ( function_exists( '\array_find' ) ) {
			// phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.array_findFound
			return \array_find( $arr, $callback );
		}

		foreach ( $arr as $key => $value ) {
			if ( $callback( $value, $key ) ) {
				return $value;
			}
		}

		return null;
	}

	/**
	 * Dumps the list of global parameters, as JSON or in var_export format.
	 *
	 * ## OPTIONS
	 *
	 * [--with-values]
	 * : Display current values also.
	 *
	 * [--format=<format>]
	 * : Render output in a particular format.
	 * ---
	 * default: json
	 * options:
	 *   - var_export
	 *   - json
	 * ---
	 *
	 * ## EXAMPLES
	 *
	 *     # Dump the list of global parameters.
	 *     $ wp cli param-dump --format=var_export
	 *     array (
	 *       'path' =>
	 *       array (
	 *         'runtime' => '=<path>',
	 *         'file' => '<path>',
	 *         'synopsis' => '',
	 *         'default' => NULL,
	 *         'multiple' => false,
	 *         'desc' => 'Path to the WordPress files.',
	 *       ),
	 *       'url' =>
	 *       array (
	 *
	 * @subcommand param-dump
	 */
	public function param_dump( $_, $assoc_args ) {
		$spec = WP_CLI::get_configurator()->get_spec();

		if ( Utils\get_flag_value( $assoc_args, 'with-values' ) ) {
			$config = WP_CLI::get_configurator()->to_array();
			// Copy current config values to $spec.
			foreach ( $spec as $key => $value ) {
				$current = null;
				if ( isset( $config[0][ $key ] ) ) {
					$current = $config[0][ $key ];
				}
				$spec[ $key ]['current'] = $current;
			}
		}

		if ( 'var_export' === Utils\get_flag_value( $assoc_args, 'format' ) ) {
			var_export( $spec );
		} else {
			echo json_encode( $spec );
		}
	}

	/**
	 * Dumps the list of installed commands, as JSON.
	 *
	 * ## EXAMPLES
	 *
	 *     # Dump the list of installed commands.
	 *     $ wp cli cmd-dump
	 *     {"name":"wp","description":"Manage WordPress through the command-line.","longdesc":"\n\n## GLOBAL PARAMETERS\n\n  --path=<path>\n      Path to the WordPress files.\n\n  --ssh=<ssh>\n      Perform operation against a remote server over SSH (or a container using scheme of "docker" or "docker-compose").\n\n  --url=<url>\n      Pretend request came from given URL. In multisite, this argument is how the target site is specified. \n\n  --user=<id|login|email>\n
	 *
	 * @subcommand cmd-dump
	 */
	public function cmd_dump() {
		echo json_encode( $this->command_to_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 Completions( $line );
		$compl->render();
	}

	/**
	 * Get a string representing the type of update being checked for.
	 */
	private function get_update_type_str( $assoc_args ) {
		$update_type = ' ';
		foreach ( [ 'major', 'minor', 'patch' ] as $type ) {
			if ( true === Utils\get_flag_value( $assoc_args, $type ) ) {
				$update_type = ' ' . $type . ' ';
				break;
			}
		}
		return $update_type;
	}

	/**
	 * Detects if a command exists
	 *
	 * This commands checks if a command is registered with WP-CLI.
	 * If the command is found then it returns with exit status 0.
	 * If the command doesn't exist, then it will exit with status 1.
	 *
	 * ## OPTIONS
	 * <command_name>...
	 * : The command
	 *
	 * ## EXAMPLES
	 *
	 *     # The "site delete" command is registered.
	 *     $ wp cli has-command "site delete"
	 *     $ echo $?
	 *     0
	 *
	 *     # The "foo bar" command is not registered.
	 *     $ wp cli has-command "foo bar"
	 *     $ echo $?
	 *     1
	 *
	 *     # Install a WP-CLI package if not already installed
	 *     $ if ! $(wp cli has-command doctor); then wp package install wp-cli/doctor-command; fi
	 *     Installing package wp-cli/doctor-command (dev-main || dev-master || dev-trunk)
	 *     Updating /home/person/.wp-cli/packages/composer.json to require the package...
	 *     Using Composer to install the package...
	 *     ---
	 *     Success: Package installed.
	 *
	 * @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

use cli\Shell;
use WP_CLI\Dispatcher;
use WP_CLI\Utils;

class Help_Command extends WP_CLI_Command {

	/**
	 * Gets help on WP-CLI, or on a specific command.
	 *
	 * ## OPTIONS
	 *
	 * [<command>...]
	 * : Get help on a specific command.
	 *
	 * ## EXAMPLES
	 *
	 *     # get help for `core` command
	 *     wp help core
	 *
	 *     # get help for `core download` subcommand
	 *     wp help core download
	 */
	public function __invoke( $args, $assoc_args ) {
		$r = WP_CLI::get_runner()->find_command_to_run( $args );

		if ( is_array( $r ) ) {
			list( $command ) = $r;

			self::show_help( $command );
			exit;
		}
	}

	private static function show_help( $command ) {
		$out = self::get_initial_markdown( $command );

		// Remove subcommands if in columns - will wordwrap separately.
		$subcommands       = '';
		$column_subpattern = '[ \t]+[^\t]+\t+';
		if ( preg_match( '/(^## SUBCOMMANDS[^\n]*\n+' . $column_subpattern . '.+?)(?:^##|\z)/ms', $out, $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', [ __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 = 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 https://msdn.microsoft.com/en-us/library/windows/desktop/ms724832.aspx) but probably harmless anyway.
			$cp = getenv( 'WP_CLI_WINDOWS_CODE_PAGE' ) ?: 1252; // Code page 1252 is the most used so probably the most compat.
			// phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions -- Wrapped in function_exists() call.
			sapi_windows_cp_set( $cp ); // `sapi_windows_cp_set()` introduced PHP 7.1.
		}

		// Convert string to file handle.
		$fd = fopen( 'php://temp', 'r+b' );
		fwrite( $fd, $out );
		rewind( $fd );

		$descriptorspec = [
			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 = [
			'name'      => $name,
			'shortdesc' => $command->get_shortdesc(),
		];

		$binding['synopsis'] = "$name " . $command->get_synopsis();

		$alias = $command->get_alias();
		if ( $alias ) {
			$binding['alias'] = $alias;
		}
		$hook_name        = $command->get_hook();
		$hook_description = $hook_name ? Utils\get_hook_description( $hook_name ) : null;
		if ( $hook_description && 'after_wp_load' !== $hook_name ) {
			if ( $command->can_have_subcommands() ) {
				$binding['shortdesc'] .= "\n\nUnless overridden, these commands run on the '$hook_name' hook, $hook_description";
			} else {
				$binding['shortdesc'] .= "\n\nThis command runs on the '$hook_name' hook, $hook_description";
			}
		}

		if ( $command->can_have_subcommands() ) {
			$binding['has-subcommands']['subcommands'] = self::render_subcommands( $command );
		}

		return Utils\mustache_render( 'man.mustache', $binding );
	}

	private static function render_subcommands( $command ) {
		$subcommands = [];
		foreach ( $command->get_subcommands() as $subcommand ) {

			if ( WP_CLI::get_runner()->is_command_disabled( $subcommand ) ) {
				continue;
			}

			$subcommands[ $subcommand->get_name() ] = $subcommand->get_shortdesc();
		}

		$max_len = self::get_max_len( array_keys( $subcommands ) );

		$lines = [];
		foreach ( $subcommands as $name => $desc ) {
			$lines[] = str_pad( $name, $max_len ) . "\t\t\t" . $desc;
		}

		return $lines;
	}

	private static function get_max_len( $strings ) {
		$max_len = 0;
		foreach ( $strings as $str ) {
			$len = strlen( $str );
			if ( $len > $max_len ) {
				$max_len = $len;
			}
		}

		return $max_len;
	}

	/**
	 * Parse reference links from longdescription.
	 *
	 * @param  string $longdesc The longdescription from the `$command->get_longdesc()`.
	 * @return string The longdescription which has links as footnote.
	 */
	private static function parse_reference_links( $longdesc ) {
		$description = '';
		foreach ( explode( "\n", $longdesc ) as $line ) {
			if ( 0 === strpos( $line, '#' ) ) {
				break;
			}
			$description .= $line . "\n";
		}

		// Fires if it has description text at the head of `$longdesc`.
		if ( $description ) {
			$links   = []; // An array of URLs from the description.
			$pattern = '/\[.+?\]\((https?:\/\/.+?)\)/';
			$newdesc = preg_replace_callback(
				$pattern,
				function ( $matches ) use ( &$links ) {
					static $count = 0;
					$count++;
					$links[] = $matches[1];
					return str_replace( '(' . $matches[1] . ')', '[' . $count . ']', $matches[0] );
				},
				$description
			);

			$footnote   = '';
			$link_count = count( $links );
			for ( $i = 0; $i < $link_count; $i++ ) {
				$n         = $i + 1;
				$footnote .= '[' . $n . '] ' . $links[ $i ] . "\n";
			}

			if ( $footnote ) {
				$newdesc  = trim( $newdesc ) . "\n\n---\n" . $footnote;
				$longdesc = str_replace( trim( $description ), trim( $newdesc ), $longdesc );
			}
		}

		return $longdesc;
	}
}
<?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

if ( ! class_exists( 'Help_Command' ) ) {
	require_once __DIR__ . '/src/Help_Command.php';
}

WP_CLI::add_command( 'help', 'Help_Command' );
<?php

if ( 'cli' !== PHP_SAPI ) {
	echo "WP-CLI only works correctly from the command line, using the 'cli' PHP SAPI.\n",
		"You're currently executing the WP-CLI binary via the '" . PHP_SAPI . "' PHP SAPI.\n",
		"In case you were trying to run this file with a web browser, know that this cannot technically work.\n",
		"When running the WP-CLI binary on the command line, you can ensure you're using the right PHP SAPI",
		"by checking that `php -v` has the word 'cli' in the first line of output.\n";
	die( -1 );
}

// Store the path to the Phar early on for `Utils\phar-safe-path()` function.
define( 'WP_CLI_PHAR_PATH', Phar::running( true ) );

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, date('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 Cache interface.
 *
 * Interface for caching and loading Mustache_Template classes
 * generated by the Mustache_Compiler.
 */
interface Mustache_Cache
{
    /**
     * Load a compiled Mustache_Template class from cache.
     *
     * @param string $key
     *
     * @return bool indicates successfully class load
     */
    public function load($key);

    /**
     * Cache and load a compiled Mustache_Template class.
     *
     * @param string $key
     * @param string $value
     */
    public function cache($key, $value);

    /**
     * Set a logger instance.
     *
     * @param Mustache_Logger|Psr\Log\LoggerInterface $logger
     */
    public function setLogger($logger = null);
}
<?php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

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

/**
 * A Mustache implementation in PHP.
 *
 * {@link http://defunkt.github.com/mustache}
 *
 * Mustache is a framework-agnostic logic-less templating language. It enforces separation of view
 * logic from template files. In fact, it is not even possible to embed logic in the template.
 *
 * This is very, very rad.
 *
 * @author Justin Hileman {@link http://justinhileman.com}
 */
class Mustache_Engine
{
    const VERSION      = '2.14.2';
    const SPEC_VERSION = '1.3.0';

    const PRAGMA_FILTERS       = 'FILTERS';
    const PRAGMA_BLOCKS        = 'BLOCKS';
    const PRAGMA_ANCHORED_DOT  = 'ANCHORED-DOT';
    const PRAGMA_DYNAMIC_NAMES = 'DYNAMIC-NAMES';

    // Known pragmas
    private static $knownPragmas = array(
        self::PRAGMA_FILTERS       => true,
        self::PRAGMA_BLOCKS        => true,
        self::PRAGMA_ANCHORED_DOT  => true,
        self::PRAGMA_DYNAMIC_NAMES => 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;
    private $buggyPropertyShadowing = false;

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

        if (isset($options['buggy_property_shadowing'])) {
            $this->buggyPropertyShadowing = (bool) $options['buggy_property_shadowing'];
        }
    }

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

    /**
     * Check whether to use buggy property shadowing.
     *
     * See https://github.com/bobthecow/mustache.php/pull/410
     */
    public function useBuggyPropertyShadowing()
    {
        return $this->buggyPropertyShadowing;
    }

    /**
     * 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, $cache = null)
    {
        $className = $this->getTemplateClassName($source);

        if (!isset($this->templates[$className])) {
            if ($cache === null || ! $cache instanceof Mustache_Cache) {
                $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.
 */

/**
 * 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 filter exception.
 */
class Mustache_Exception_UnknownFilterException extends UnexpectedValueException implements Mustache_Exception
{
    protected $filterName;

    /**
     * @param string    $filterName
     * @param Exception $previous
     */
    public function __construct($filterName, $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.
 */

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

    /**
     * @param string    $helperName
     * @param Exception $previous
     */
    public function __construct($helperName, $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, $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.
 */

/**
 * 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 template exception.
 */
class Mustache_Exception_UnknownTemplateException extends InvalidArgumentException implements Mustache_Exception
{
    protected $templateName;

    /**
     * @param string    $templateName
     * @param Exception $previous
     */
    public function __construct($templateName, $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

/*
 * 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;
    private $pragmaDynamicNames;

    /**
     * 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]);
        $this->pragmaDynamicNames = isset($this->pragmas[Mustache_Engine::PRAGMA_DYNAMIC_NAMES]);

        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, $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 ($token[Mustache_Tokenizer::TYPE] !== Mustache_Tokenizer::T_COMMENT) {
                if ($this->pragmaDynamicNames && isset($token[Mustache_Tokenizer::NAME])) {
                    list($name, $isDynamic) = $this->getDynamicName($token);
                    if ($isDynamic) {
                        $token[Mustache_Tokenizer::NAME]    = $name;
                        $token[Mustache_Tokenizer::DYNAMIC] = true;
                    }
                }

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

                    $sameName = $token[Mustache_Tokenizer::NAME] !== $parent[Mustache_Tokenizer::NAME];
                    $tokenDynamic = isset($token[Mustache_Tokenizer::DYNAMIC]) && $token[Mustache_Tokenizer::DYNAMIC];
                    $parentDynamic = isset($parent[Mustache_Tokenizer::DYNAMIC]) && $parent[Mustache_Tokenizer::DYNAMIC];

                    if ($sameName || ($tokenDynamic !== $parentDynamic)) {
                        $msg = sprintf(
                            'Nesting error: %s%s (on line %d) vs. %s%s (on line %d)',
                            $parentDynamic ? '*' : '',
                            $parent[Mustache_Tokenizer::NAME],
                            $parent[Mustache_Tokenizer::LINE],
                            $tokenDynamic ? '*' : '',
                            $token[Mustache_Tokenizer::NAME],
                            $token[Mustache_Tokenizer::LINE]
                        );
                        throw new Mustache_Exception_SyntaxException($msg, $token);
                    }

                    $this->clearStandaloneLines($nodes, $tokens);
                    $parent[Mustache_Tokenizer::END]   = $token[Mustache_Tokenizer::INDEX];
                    $parent[Mustache_Tokenizer::NODES] = $nodes;

                    return $parent;

                case Mustache_Tokenizer::T_PARTIAL:
                    $this->checkIfTokenIsAllowedInParent($parent, $token);
                    //store the whitespace prefix for laters!
                    if ($indent = $this->clearStandaloneLines($nodes, $tokens)) {
                        $token[Mustache_Tokenizer::INDENT] = $indent[Mustache_Tokenizer::VALUE];
                    }
                    $nodes[] = $token;
                    break;

                case Mustache_Tokenizer::T_PARENT:
                    $this->checkIfTokenIsAllowedInParent($parent, $token);
                    $nodes[] = $this->buildTree($tokens, $token);
                    break;

                case Mustache_Tokenizer::T_BLOCK_VAR:
                    if ($this->pragmaBlocks) {
                        // BLOCKS pragma is enabled, let's do this!
                        if (isset($parent) && $parent[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_PARENT) {
                            $token[Mustache_Tokenizer::TYPE] = Mustache_Tokenizer::T_BLOCK_ARG;
                        }
                        $this->clearStandaloneLines($nodes, $tokens);
                        $nodes[] = $this->buildTree($tokens, $token);
                    } else {
                        // pretend this was just a normal "escaped" token...
                        $token[Mustache_Tokenizer::TYPE] = Mustache_Tokenizer::T_ESCAPED;
                        // TODO: figure out how to figure out if there was a space after this dollar:
                        $token[Mustache_Tokenizer::NAME] = '$' . $token[Mustache_Tokenizer::NAME];
                        $nodes[] = $token;
                    }
                    break;

                case Mustache_Tokenizer::T_PRAGMA:
                    $this->enablePragma($token[Mustache_Tokenizer::NAME]);
                    // no break

                case Mustache_Tokenizer::T_COMMENT:
                    $this->clearStandaloneLines($nodes, $tokens);
                    $nodes[] = $token;
                    break;

                default:
                    $nodes[] = $token;
                    break;
            }
        }

        if (isset($parent)) {
            $msg = sprintf(
                'Missing closing tag: %s opened on line %d',
                $parent[Mustache_Tokenizer::NAME],
                $parent[Mustache_Tokenizer::LINE]
            );
            throw new Mustache_Exception_SyntaxException($msg, $parent);
        }

        return $nodes;
    }

    /**
     * Clear standalone line tokens.
     *
     * Returns a whitespace token for indenting partials, if applicable.
     *
     * @param array $nodes  Parsed nodes
     * @param array $tokens Tokens to be parsed
     *
     * @return array|null Resulting indent token, if any
     */
    private function clearStandaloneLines(array &$nodes, array &$tokens)
    {
        if ($this->lineTokens > 1) {
            // this is the third or later node on this line, so it can't be standalone
            return;
        }

        $prev = null;
        if ($this->lineTokens === 1) {
            // this is the second node on this line, so it can't be standalone
            // unless the previous node is whitespace.
            if ($prev = end($nodes)) {
                if (!$this->tokenIsWhitespace($prev)) {
                    return;
                }
            }
        }

        if ($next = reset($tokens)) {
            // If we're on a new line, bail.
            if ($next[Mustache_Tokenizer::LINE] !== $this->lineNum) {
                return;
            }

            // If the next token isn't whitespace, bail.
            if (!$this->tokenIsWhitespace($next)) {
                return;
            }

            if (count($tokens) !== 1) {
                // Unless it's the last token in the template, the next token
                // must end in newline for this to be standalone.
                if (substr($next[Mustache_Tokenizer::VALUE], -1) !== "\n") {
                    return;
                }
            }

            // Discard the whitespace suffix
            array_shift($tokens);
        }

        if ($prev) {
            // Return the whitespace prefix, if any
            return array_pop($nodes);
        }
    }

    /**
     * Check whether token is a whitespace token.
     *
     * True if token type is T_TEXT and value is all whitespace characters.
     *
     * @param array $token
     *
     * @return bool True if token is a whitespace token
     */
    private function tokenIsWhitespace(array $token)
    {
        if ($token[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_TEXT) {
            return preg_match('/^\s*$/', $token[Mustache_Tokenizer::VALUE]);
        }

        return false;
    }

    /**
     * Check whether a token is allowed inside a parent tag.
     *
     * @throws Mustache_Exception_SyntaxException if an invalid token is found inside a parent tag
     *
     * @param array|null $parent
     * @param array      $token
     */
    private function checkIfTokenIsAllowedInParent($parent, array $token)
    {
        if (isset($parent) && $parent[Mustache_Tokenizer::TYPE] === Mustache_Tokenizer::T_PARENT) {
            throw new Mustache_Exception_SyntaxException('Illegal content in < parent tag', $token);
        }
    }

    /**
     * Parse dynamic names.
     *
     * @throws Mustache_Exception_SyntaxException when a tag does not allow *
     * @throws Mustache_Exception_SyntaxException on multiple *s, or dots or filters with *
     */
    private function getDynamicName(array $token)
    {
        $name = $token[Mustache_Tokenizer::NAME];
        $isDynamic = false;

        if (preg_match('/^\s*\*\s*/', $name)) {
            $this->ensureTagAllowsDynamicNames($token);
            $name = preg_replace('/^\s*\*\s*/', '', $name);
            $isDynamic = true;
        }

        return array($name, $isDynamic);
    }

    /**
     * Check whether the given token supports dynamic tag names.
     *
     * @throws Mustache_Exception_SyntaxException when a tag does not allow *
     *
     * @param array $token
     */
    private function ensureTagAllowsDynamicNames(array $token)
    {
        switch ($token[Mustache_Tokenizer::TYPE]) {
            case Mustache_Tokenizer::T_PARTIAL:
            case Mustache_Tokenizer::T_PARENT:
            case Mustache_Tokenizer::T_END_SECTION:
                return;
        }

        $msg = sprintf(
            'Invalid dynamic name: %s in %s tag',
            $token[Mustache_Tokenizer::NAME],
            Mustache_Tokenizer::getTagName($token[Mustache_Tokenizer::TYPE])
        );

        throw new Mustache_Exception_SyntaxException($msg, $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;

            case Mustache_Engine::PRAGMA_DYNAMIC_NAMES:
                $this->pragmaDynamicNames = 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 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 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.
 */

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

/**
 * 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 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 mutable Loader interface.
 */
interface Mustache_Loader_MutableLoader
{
    /**
     * Set an associative array of Template sources for this loader.
     *
     * @param array $templates
     */
    public function setTemplates(array $templates);

    /**
     * Set a Template source by name.
     *
     * @param string $name
     * @param string $template Mustache Template source
     */
    public function setTemplate($name, $template);
}
<?php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Mustache Cache in-memory implementation.
 *
 * The in-memory cache is used for uncached lambda section templates. It's also useful during development, but is not
 * recommended for production use.
 */
class Mustache_Cache_NoopCache extends Mustache_Cache_AbstractCache
{
    /**
     * Loads nothing. Move along.
     *
     * @param string $key
     *
     * @return bool
     */
    public function load($key)
    {
        return false;
    }

    /**
     * Loads the compiled Mustache Template class without caching.
     *
     * @param string $key
     * @param string $value
     */
    public function cache($key, $value)
    {
        $this->log(
            Mustache_Logger::WARNING,
            'Template cache disabled, evaluating "{className}" class at runtime',
            array('className' => $key)
        );
        eval('?>' . $value);
    }
}
<?php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Mustache Cache filesystem implementation.
 *
 * A FilesystemCache instance caches Mustache Template classes from the filesystem by name:
 *
 *     $cache = new Mustache_Cache_FilesystemCache(dirname(__FILE__).'/cache');
 *     $cache->cache($className, $compiledSource);
 *
 * The FilesystemCache benefits from any opcode caching that may be setup in your environment. So do that, k?
 */
class Mustache_Cache_FilesystemCache extends Mustache_Cache_AbstractCache
{
    private $baseDir;
    private $fileMode;

    /**
     * Filesystem cache constructor.
     *
     * @param string $baseDir  Directory for compiled templates
     * @param int    $fileMode Override default permissions for cache files. Defaults to using the system-defined umask
     */
    public function __construct($baseDir, $fileMode = null)
    {
        $this->baseDir = $baseDir;
        $this->fileMode = $fileMode;
    }

    /**
     * Load the class from cache using `require_once`.
     *
     * @param string $key
     *
     * @return bool
     */
    public function load($key)
    {
        $fileName = $this->getCacheFilename($key);
        if (!is_file($fileName)) {
            return false;
        }

        require_once $fileName;

        return true;
    }

    /**
     * Cache and load the compiled class.
     *
     * @param string $key
     * @param string $value
     */
    public function cache($key, $value)
    {
        $fileName = $this->getCacheFilename($key);

        $this->log(
            Mustache_Logger::DEBUG,
            'Writing to template cache: "{fileName}"',
            array('fileName' => $fileName)
        );

        $this->writeFile($fileName, $value);
        $this->load($key);
    }

    /**
     * Build the cache filename.
     * Subclasses should override for custom cache directory structures.
     *
     * @param string $name
     *
     * @return string
     */
    protected function getCacheFilename($name)
    {
        return sprintf('%s/%s.php', $this->baseDir, $name);
    }

    /**
     * Create cache directory.
     *
     * @throws Mustache_Exception_RuntimeException If unable to create directory
     *
     * @param string $fileName
     *
     * @return string
     */
    private function buildDirectoryForFilename($fileName)
    {
        $dirName = dirname($fileName);
        if (!is_dir($dirName)) {
            $this->log(
                Mustache_Logger::INFO,
                'Creating Mustache template cache directory: "{dirName}"',
                array('dirName' => $dirName)
            );

            @mkdir($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.
 */

/**
 * Abstract Mustache Cache class.
 *
 * Provides logging support to child implementations.
 *
 * @abstract
 */
abstract class Mustache_Cache_AbstractCache implements Mustache_Cache
{
    private $logger = null;

    /**
     * Get the current logger instance.
     *
     * @return Mustache_Logger|Psr\Log\LoggerInterface
     */
    public function getLogger()
    {
        return $this->logger;
    }

    /**
     * Set a logger instance.
     *
     * @param Mustache_Logger|Psr\Log\LoggerInterface $logger
     */
    public function setLogger($logger = null)
    {
        if ($logger !== null && !($logger instanceof Mustache_Logger || is_a($logger, 'Psr\\Log\\LoggerInterface'))) {
            throw new Mustache_Exception_InvalidArgumentException('Expected an instance of Mustache_Logger or Psr\\Log\\LoggerInterface.');
        }

        $this->logger = $logger;
    }

    /**
     * Add a log record if logging is enabled.
     *
     * @param string $level   The logging level
     * @param string $message The log message
     * @param array  $context The log context
     */
    protected function log($level, $message, array $context = array())
    {
        if (isset($this->logger)) {
            $this->logger->log($level, $message, $context);
        }
    }
}
<?php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * 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 Template rendering Context.
 */
class Mustache_Context
{
    private $stack      = array();
    private $blockStack = array();

    private $buggyPropertyShadowing = false;

    /**
     * Mustache rendering Context constructor.
     *
     * @param mixed $context                Default rendering context (default: null)
     * @param bool  $buggyPropertyShadowing See Mustache_Engine::useBuggyPropertyShadowing (default: false)
     */
    public function __construct($context = null, $buggyPropertyShadowing = false)
    {
        if ($context !== null) {
            $this->stack = array($context);
        }

        $this->buggyPropertyShadowing = $buggyPropertyShadowing;
    }

    /**
     * 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
     * @param bool   $strictCallables (default: false)
     *
     * @return mixed Variable value, or '' if not found
     */
    public function findDot($id, $strictCallables = false)
    {
        $chunks = explode('.', $id);
        $first  = array_shift($chunks);
        $value  = $this->findVariableInStack($first, $this->stack);

        // This wasn't really a dotted name, so we can just return the value.
        if (empty($chunks)) {
            return $value;
        }

        foreach ($chunks as $chunk) {
            $isCallable = $strictCallables ? (is_object($value) && is_callable($value)) : (!is_string($value) && is_callable($value));

            if ($isCallable) {
                $value = $value();
            } elseif ($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;
                        }

                        // Preserve backwards compatibility with a property shadowing bug in
                        // Mustache.php <= 2.14.2
                        // See https://github.com/bobthecow/mustache.php/pull/410
                        if ($this->buggyPropertyShadowing) {
                            if ($frame instanceof ArrayAccess && isset($frame[$id])) {
                                return $frame[$id];
                            }
                        } else {
                            if (property_exists($frame, $id)) {
                                $rp = new \ReflectionProperty($frame, $id);
                                if ($rp->isPublic()) {
                                    return $frame->$id;
                                }
                            }

                            if ($frame instanceof ArrayAccess && $frame->offsetExists($id)) {
                                return $frame[$id];
                            }
                        }
                    }
                    break;

                case 'array':
                    if (array_key_exists($id, $frame)) {
                        return $frame[$id];
                    }
                    break;
            }
        }

        return '';
    }
}
<?php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Describes a Mustache logger instance.
 *
 * This is identical to the Psr\Log\LoggerInterface.
 *
 * The message MUST be a string or object implementing __toString().
 *
 * The message MAY contain placeholders in the form: {foo} where foo
 * will be replaced by the context data in key "foo".
 *
 * The context array can contain arbitrary data, the only assumption that
 * can be made by implementors is that if an Exception instance is given
 * to produce a stack trace, it MUST be in a key named "exception".
 *
 * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
 * for the full interface specification.
 */
interface Mustache_Logger
{
    /**
     * Psr\Log compatible log levels.
     */
    const EMERGENCY = 'emergency';
    const ALERT     = 'alert';
    const CRITICAL  = 'critical';
    const ERROR     = 'error';
    const WARNING   = 'warning';
    const NOTICE    = 'notice';
    const INFO      = 'info';
    const DEBUG     = 'debug';

    /**
     * System is unusable.
     *
     * @param string $message
     * @param array  $context
     */
    public function emergency($message, array $context = array());

    /**
     * Action must be taken immediately.
     *
     * Example: Entire website down, database unavailable, etc. This should
     * trigger the SMS alerts and wake you up.
     *
     * @param string $message
     * @param array  $context
     */
    public function alert($message, array $context = array());

    /**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     *
     * @param string $message
     * @param array  $context
     */
    public function critical($message, array $context = array());

    /**
     * Runtime errors that do not require immediate action but should typically
     * be logged and monitored.
     *
     * @param string $message
     * @param array  $context
     */
    public function error($message, array $context = array());

    /**
     * Exceptional occurrences that are not errors.
     *
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
     * that are not necessarily wrong.
     *
     * @param string $message
     * @param array  $context
     */
    public function warning($message, array $context = array());

    /**
     * Normal but significant events.
     *
     * @param string $message
     * @param array  $context
     */
    public function notice($message, array $context = array());

    /**
     * Interesting events.
     *
     * Example: User logs in, SQL logs.
     *
     * @param string $message
     * @param array  $context
     */
    public function info($message, array $context = array());

    /**
     * Detailed debug information.
     *
     * @param string $message
     * @param array  $context
     */
    public function debug($message, array $context = array());

    /**
     * Logs with an arbitrary level.
     *
     * @param mixed  $level
     * @param string $message
     * @param array  $context
     */
    public function log($level, $message, array $context = array());
}
<?php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * 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(null, $this->mustache->useBuggyPropertyShadowing());

        $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)) {
            $result = call_user_func($value);

            if (is_string($result)) {
                return $this->mustache
                    ->loadLambda($result)
                    ->renderInternal($context);
            }

            return $result;
        }

        return $value;
    }
}
<?php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

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

    private static $tagNames = array(
        self::T_SECTION      => 'section',
        self::T_INVERTED     => 'inverted section',
        self::T_END_SECTION  => 'section end',
        self::T_COMMENT      => 'comment',
        self::T_PARTIAL      => 'partial',
        self::T_PARENT       => 'parent',
        self::T_DELIM_CHANGE => 'set delimiter',
        self::T_ESCAPED      => 'variable',
        self::T_UNESCAPED    => 'unescaped variable',
        self::T_UNESCAPED_2  => 'unescaped variable',
        self::T_PRAGMA       => 'pragma',
        self::T_BLOCK_VAR    => 'block variable',
        self::T_BLOCK_ARG    => 'block variable',
    );

    // Token properties
    const TYPE    = 'type';
    const NAME    = 'name';
    const DYNAMIC = 'dynamic';
    const OTAG    = 'otag';
    const CTAG    = 'ctag';
    const LINE    = 'line';
    const INDEX   = 'index';
    const END     = 'end';
    const INDENT  = 'indent';
    const NODES   = 'nodes';
    const VALUE   = 'value';
    const FILTERS = 'filters';

    private $state;
    private $tagType;
    private $buffer;
    private $tokens;
    private $seenTag;
    private $line;

    private $otag;
    private $otagChar;
    private $otagLen;

    private $ctag;
    private $ctagChar;
    private $ctagLen;

    /**
     * Scan and tokenize template source.
     *
     * @throws Mustache_Exception_SyntaxException when mismatched section tags are encountered
     * @throws Mustache_Exception_InvalidArgumentException when $delimiters string is invalid
     *
     * @param string $text       Mustache template source to tokenize
     * @param string $delimiters Optionally, pass initial opening and closing delimiters (default: empty string)
     *
     * @return array Set of Mustache tokens
     */
    public function scan($text, $delimiters = '')
    {
        // Setting mbstring.func_overload makes things *really* slow.
        // Let's do everyone a favor and scan this string as ASCII instead.
        //
        // The INI directive was removed in PHP 8.0 so we don't need to check there (and can drop it
        // when we remove support for older versions of PHP).
        //
        // @codeCoverageIgnoreStart
        $encoding = null;
        if (version_compare(PHP_VERSION, '8.0.0', '<')) {
            if (function_exists('mb_internal_encoding') && ini_get('mbstring.func_overload') & 2) {
                $encoding = mb_internal_encoding();
                mb_internal_encoding('ASCII');
            }
        }
        // @codeCoverageIgnoreEnd

        $this->reset();

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

        $len = strlen($text);
        for ($i = 0; $i < $len; $i++) {
            switch ($this->state) {
                case self::IN_TEXT:
                    $char = $text[$i];
                    // Test whether it's time to change tags.
                    if ($char === $this->otagChar && substr($text, $i, $this->otagLen) === $this->otag) {
                        $i--;
                        $this->flushBuffer();
                        $this->state = self::IN_TAG_TYPE;
                    } else {
                        $this->buffer .= $char;
                        if ($char === "\n") {
                            $this->flushBuffer();
                            $this->line++;
                        }
                    }
                    break;

                case self::IN_TAG_TYPE:
                    $i += $this->otagLen - 1;
                    $char = $text[$i + 1];
                    if (isset(self::$tagTypes[$char])) {
                        $tag = $char;
                        $this->tagType = $tag;
                    } else {
                        $tag = null;
                        $this->tagType = self::T_ESCAPED;
                    }

                    if ($this->tagType === self::T_DELIM_CHANGE) {
                        $i = $this->changeDelimiters($text, $i);
                        $this->state = self::IN_TEXT;
                    } elseif ($this->tagType === self::T_PRAGMA) {
                        $i = $this->addPragma($text, $i);
                        $this->state = self::IN_TEXT;
                    } else {
                        if ($tag !== null) {
                            $i++;
                        }
                        $this->state = self::IN_TAG;
                    }
                    $this->seenTag = $i;
                    break;

                default:
                    $char = $text[$i];
                    // Test whether it's time to change tags.
                    if ($char === $this->ctagChar && substr($text, $i, $this->ctagLen) === $this->ctag) {
                        $token = array(
                            self::TYPE  => $this->tagType,
                            self::NAME  => trim($this->buffer),
                            self::OTAG  => $this->otag,
                            self::CTAG  => $this->ctag,
                            self::LINE  => $this->line,
                            self::INDEX => ($this->tagType === self::T_END_SECTION) ? $this->seenTag - $this->otagLen : $i + $this->ctagLen,
                        );

                        if ($this->tagType === self::T_UNESCAPED) {
                            // Clean up `{{{ tripleStache }}}` style tokens.
                            if ($this->ctag === '}}') {
                                if (($i + 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 .= $char;
                    }
                    break;
            }
        }

        if ($this->state !== self::IN_TEXT) {
            $this->throwUnclosedTagException();
        }

        $this->flushBuffer();

        // Restore the user's encoding...
        // @codeCoverageIgnoreStart
        if ($encoding) {
            mb_internal_encoding($encoding);
        }
        // @codeCoverageIgnoreEnd

        return $this->tokens;
    }

    /**
     * Helper function to reset tokenizer internal state.
     */
    private function reset()
    {
        $this->state    = self::IN_TEXT;
        $this->tagType  = null;
        $this->buffer   = '';
        $this->tokens   = array();
        $this->seenTag  = false;
        $this->line     = 0;

        $this->otag     = '{{';
        $this->otagChar = '{';
        $this->otagLen  = 2;

        $this->ctag     = '}}';
        $this->ctagChar = '}';
        $this->ctagLen  = 2;
    }

    /**
     * Flush the current buffer to a token.
     */
    private function flushBuffer()
    {
        if (strlen($this->buffer) > 0) {
            $this->tokens[] = array(
                self::TYPE  => self::T_TEXT,
                self::LINE  => $this->line,
                self::VALUE => $this->buffer,
            );
            $this->buffer   = '';
        }
    }

    /**
     * Change the current Mustache delimiters. Set new `otag` and `ctag` values.
     *
     * @throws Mustache_Exception_SyntaxException when delimiter string is invalid
     *
     * @param string $text  Mustache template source
     * @param int    $index Current tokenizer index
     *
     * @return int New index value
     */
    private function changeDelimiters($text, $index)
    {
        $startIndex = strpos($text, '=', $index) + 1;
        $close      = '=' . $this->ctag;
        $closeIndex = strpos($text, $close, $index);

        if ($closeIndex === false) {
            $this->throwUnclosedTagException();
        }

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

        try {
            $this->setDelimiters(trim(substr($text, $startIndex, $closeIndex - $startIndex)));
        } catch (Mustache_Exception_InvalidArgumentException $e) {
            throw new Mustache_Exception_SyntaxException($e->getMessage(), $token);
        }

        $this->tokens[] = $token;

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

    /**
     * Set the current Mustache `otag` and `ctag` delimiters.
     *
     * @throws Mustache_Exception_InvalidArgumentException when delimiter string is invalid
     *
     * @param string $delimiters
     */
    private function setDelimiters($delimiters)
    {
        if (!preg_match('/^\s*(\S+)\s+(\S+)\s*$/', $delimiters, $matches)) {
            throw new Mustache_Exception_InvalidArgumentException(sprintf('Invalid delimiters: %s', $delimiters));
        }

        list($_, $otag, $ctag) = $matches;

        $this->otag     = $otag;
        $this->otagChar = $otag[0];
        $this->otagLen  = strlen($otag);

        $this->ctag     = $ctag;
        $this->ctagChar = $ctag[0];
        $this->ctagLen  = strlen($ctag);
    }

    /**
     * Add pragma token.
     *
     * Pragmas are hoisted to the front of the template, so all pragma tokens
     * will appear at the front of the token list.
     *
     * @param string $text
     * @param int    $index
     *
     * @return int New index value
     */
    private function addPragma($text, $index)
    {
        $end    = strpos($text, $this->ctag, $index);
        if ($end === false) {
            $this->throwUnclosedTagException();
        }

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


    private function throwUnclosedTagException()
    {
        $name = trim($this->buffer);
        if ($name !== '') {
            $msg = sprintf('Unclosed tag: %s on line %d', $name, $this->line);
        } else {
            $msg = sprintf('Unclosed tag on line %d', $this->line);
        }

        throw new Mustache_Exception_SyntaxException($msg, array(
            self::TYPE  => $this->tagType,
            self::NAME  => $name,
            self::OTAG  => $this->otag,
            self::CTAG  => $this->ctag,
            self::LINE  => $this->line,
            self::INDEX => $this->seenTag - $this->otagLen,
        ));
    }

    /**
     * Get the human readable name for a tag type.
     *
     * @param string $tagType One of the tokenizer T_* constants
     *
     * @return string
     */
    static function getTagName($tagType)
    {
        return isset(self::$tagNames[$tagType]) ? self::$tagNames[$tagType] : 'unknown';
    }
}
<?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.
 */

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

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

/**
 * 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 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::DYNAMIC]) ? $node[Mustache_Tokenizer::DYNAMIC] : false,
                        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::DYNAMIC]) ? $node[Mustache_Tokenizer::DYNAMIC] : false,
                        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);
        $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 = '
        $value = $context->%s(%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;
                $value = call_user_func($value, $source, %s);

                if (is_string($value)) {
                    if (strpos($value, \'{{\') === false) {
                        return $value;
                    }

                    return $this->mustache
                        ->loadLambda($value%s)
                        ->renderInternal($context);
                }
            }

            if (!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);
        $findArg = $this->getFindMethodArgs($method);
        $filters = $this->getFilters($filters, $level);

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

    const INVERTED_SECTION = '
        $value = $context->%s(%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);
        $findArg = $this->getFindMethodArgs($method);
        $filters = $this->getFilters($filters, $level);

        return sprintf($this->prepare(self::INVERTED_SECTION, $level), $method, $id, $findArg, $filters, $this->walk($nodes, $level));
    }

    const DYNAMIC_NAME = '$this->resolveValue($context->%s(%s%s), $context)';

    /**
     * Generate Mustache Template dynamic name resolution PHP source.
     *
     * @param string $id      Tag name
     * @param bool   $dynamic True if the name is dynamic
     *
     * @return string Dynamic name resolution PHP source code
     */
    private function resolveDynamicName($id, $dynamic)
    {
        if (!$dynamic) {
            return var_export($id, true);
        }

        $method  = $this->getFindMethod($id);
        $id      = ($method !== 'last') ? var_export($id, true) : '';
        $findArg = $this->getFindMethodArgs($method);

        // TODO: filters?

        return sprintf(self::DYNAMIC_NAME, $method, $id, $findArg);
    }

    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 bool   $dynamic Partial name is dynamic
     * @param string $indent  Whitespace indent to apply to partial
     * @param int    $level
     *
     * @return string Generated partial call PHP source code
     */
    private function partial($id, $dynamic, $indent, $level)
    {
        if ($indent !== '') {
            $indentParam = sprintf(self::PARTIAL_INDENT, var_export($indent, true));
        } else {
            $indentParam = '';
        }

        return sprintf(
            $this->prepare(self::PARTIAL, $level),
            $this->resolveDynamicName($id, $dynamic),
            $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 bool   $dynamic  Tag name is dynamic
     * @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, $dynamic, $indent, array $children, $level)
    {
        $realChildren = array_filter($children, array(__CLASS__, 'onlyBlockArgs'));
        $partialName = $this->resolveDynamicName($id, $dynamic);

        if (empty($realChildren)) {
            return sprintf($this->prepare(self::PARENT_NO_CONTEXT, $level), $partialName);
        }

        return sprintf(
            $this->prepare(self::PARENT, $level),
            $partialName,
            $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%s), $context);%s
        $buffer .= %s($value === null ? \'\' : %s);
    ';

    /**
     * Generate Mustache Template variable interpolation PHP source.
     *
     * @param string   $id      Variable name
     * @param string[] $filters Array of filters
     * @param bool     $escape  Escape the variable value for output?
     * @param int      $level
     *
     * @return string Generated variable interpolation PHP source
     */
    private function variable($id, $filters, $escape, $level)
    {
        $method  = $this->getFindMethod($id);
        $id      = ($method !== 'last') ? var_export($id, true) : '';
        $findArg = $this->getFindMethodArgs($method);
        $filters = $this->getFilters($filters, $level);
        $value   = $escape ? $this->getEscape() : '$value';

        return sprintf($this->prepare(self::VARIABLE, $level), $method, $id, $findArg, $filters, $this->flushIndent(), $value);
    }

    const FILTER = '
        $filter = $context->%s(%s%s);
        if (!(%s)) {
            throw new Mustache_Exception_UnknownFilterException(%s);
        }
        $value = call_user_func($filter, %s);%s
    ';
    const FILTER_FIRST_VALUE = '$this->resolveValue($value, $context)';
    const FILTER_VALUE = '$value';

    /**
     * Generate Mustache Template variable filtering PHP source.
     *
     * If the initial $value is a lambda it will be resolved before starting the filter chain.
     *
     * @param string[] $filters Array of filters
     * @param int      $level
     * @param bool     $first (default: false)
     *
     * @return string Generated filter PHP source
     */
    private function getFilters(array $filters, $level, $first = true)
    {
        if (empty($filters)) {
            return '';
        }

        $name     = array_shift($filters);
        $method   = $this->getFindMethod($name);
        $filter   = ($method !== 'last') ? var_export($name, true) : '';
        $findArg  = $this->getFindMethodArgs($method);
        $callable = $this->getCallable('$filter');
        $msg      = var_export($name, true);
        $value    = $first ? self::FILTER_FIRST_VALUE : self::FILTER_VALUE;

        return sprintf($this->prepare(self::FILTER, $level), $method, $filter, $findArg, $callable, $msg, $value, $this->getFilters($filters, $level, false));
    }

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

    /**
     * Get the args needed for a given find method.
     *
     * In this case, it's "true" iff it's a "find dot" method and strict callables is enabled.
     *
     * @param string $method Find method name
     */
    private function getFindMethodArgs($method)
    {
        if (($method === 'findDot' || $method === 'findAnchoredDot') && $this->strictCallables) {
            return ', true';
        }

        return '';
    }

    const IS_CALLABLE        = '!is_string(%s) && is_callable(%s)';
    const STRICT_IS_CALLABLE = 'is_object(%s) && is_callable(%s)';

    /**
     * Helper function to compile strict vs lax "is callable" logic.
     *
     * @param string $variable (default: '$value')
     *
     * @return string "is callable" logic
     */
    private function getCallable($variable = '$value')
    {
        $tpl = $this->strictCallables ? self::STRICT_IS_CALLABLE : self::IS_CALLABLE;

        return sprintf($tpl, $variable, $variable);
    }

    const LINE_INDENT = '$indent . ';

    /**
     * Get the current $indent prefix to write to the buffer.
     *
     * @return string "$indent . " or ""
     */
    private function flushIndent()
    {
        if (!$this->indentNextLine) {
            return '';
        }

        $this->indentNextLine = false;

        return self::LINE_INDENT;
    }
}
<?php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * Mustache template Filesystem Source.
 *
 * This template Source uses stat() to generate the Source key, so that using
 * pre-compiled templates doesn't require hitting the disk to read the source.
 * It is more suitable for production use, and is used by default in the
 * ProductionFilesystemLoader.
 */
class Mustache_Source_FilesystemSource implements Mustache_Source
{
    private $fileName;
    private $statProps;
    private $stat;

    /**
     * Filesystem Source constructor.
     *
     * @param string $fileName
     * @param array  $statProps
     */
    public function __construct($fileName, array $statProps)
    {
        $this->fileName = $fileName;
        $this->statProps = $statProps;
    }

    /**
     * Get the Source key (used to generate the compiled class name).
     *
     * @throws Mustache_Exception_RuntimeException when a source file cannot be read
     *
     * @return string
     */
    public function getKey()
    {
        $chunks = array(
            'fileName' => $this->fileName,
        );

        if (!empty($this->statProps)) {
            if (!isset($this->stat)) {
                $this->stat = @stat($this->fileName);
            }

            if ($this->stat === false) {
                throw new Mustache_Exception_RuntimeException(sprintf('Failed to read source file "%s".', $this->fileName));
            }

            foreach ($this->statProps as $prop) {
                $chunks[$prop] = $this->stat[$prop];
            }
        }

        return json_encode($chunks);
    }

    /**
     * Get the template Source.
     *
     * @return string
     */
    public function getSource()
    {
        return file_get_contents($this->fileName);
    }
}
<?php

/*
 * This file is part of Mustache.php.
 *
 * (c) 2010-2017 Justin Hileman
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

/**
 * 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 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 the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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 $haveReadSupport;

    public function __construct($ttyMode, $ptyMode, $input, $haveReadSupport)
    {
        $this->ttyMode = (bool) $ttyMode;
        $this->ptyMode = (bool) $ptyMode;
        $this->haveReadSupport = (bool) $haveReadSupport;

        parent::__construct($input);
    }

    public function __destruct()
    {
        $this->close();
    }

    /**
     * {@inheritdoc}
     */
    public function getDescriptors()
    {
        if (!$this->haveReadSupport) {
            $nullstream = fopen('/dev/null', 'c');

            return [
                ['pipe', 'r'],
                $nullstream,
                $nullstream,
            ];
        }

        if ($this->ttyMode) {
            return [
                ['file', '/dev/tty', 'r'],
                ['file', '/dev/tty', 'w'],
                ['file', '/dev/tty', 'w'],
            ];
        }

        if ($this->ptyMode && Process::isPtySupported()) {
            return [
                ['pty'],
                ['pty'],
                ['pty'],
            ];
        }

        return [
            ['pipe', 'r'],
            ['pipe', 'w'], // stdout
            ['pipe', 'w'], // stderr
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function getFiles()
    {
        return [];
    }

    /**
     * {@inheritdoc}
     */
    public function readAndWrite($blocking, $close = false)
    {
        $this->unblock();
        $w = $this->write();

        $read = $e = [];
        $r = $this->pipes;
        unset($r[0]);

        // let's have a look if something changed in streams
        set_error_handler([$this, 'handleError']);
        if (($r || $w) && false === stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
            restore_error_handler();
            // if a system call has been interrupted, forget about it, let's try again
            // otherwise, an error occurred, let's reset pipes
            if (!$this->hasSystemCallBeenInterrupted()) {
                $this->pipes = [];
            }

            return $read;
        }
        restore_error_handler();

        foreach ($r as $pipe) {
            // prior PHP 5.4 the array passed to stream_select is modified and
            // lose key association, we have to find back the key
            $read[$type = array_search($pipe, $this->pipes, true)] = '';

            do {
                $data = @fread($pipe, self::CHUNK_SIZE);
                $read[$type] .= $data;
            } while (isset($data[0]) && ($close || isset($data[self::CHUNK_SIZE - 1])));

            if (!isset($read[$type][0])) {
                unset($read[$type]);
            }

            if ($close && feof($pipe)) {
                fclose($pipe);
                unset($this->pipes[$type]);
            }
        }

        return $read;
    }

    /**
     * {@inheritdoc}
     */
    public function haveReadSupport()
    {
        return $this->haveReadSupport;
    }

    /**
     * {@inheritdoc}
     */
    public function areOpen()
    {
        return (bool) $this->pipes;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Pipes;

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

/**
 * WindowsPipes implementation uses temporary files as handles.
 *
 * @see https://bugs.php.net/51800
 * @see https://bugs.php.net/65650
 *
 * @author Romain Neutron <imprec@gmail.com>
 *
 * @internal
 */
class WindowsPipes extends AbstractPipes
{
    private $files = [];
    private $fileHandles = [];
    private $lockHandles = [];
    private $readBytes = [
        Process::STDOUT => 0,
        Process::STDERR => 0,
    ];
    private $haveReadSupport;

    public function __construct($input, $haveReadSupport)
    {
        $this->haveReadSupport = (bool) $haveReadSupport;

        if ($this->haveReadSupport) {
            // Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.
            // Workaround for this problem is to use temporary files instead of pipes on Windows platform.
            //
            // @see https://bugs.php.net/51800
            $pipes = [
                Process::STDOUT => Process::OUT,
                Process::STDERR => Process::ERR,
            ];
            $tmpDir = sys_get_temp_dir();
            $lastError = 'unknown reason';
            set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; });
            for ($i = 0;; ++$i) {
                foreach ($pipes as $pipe => $name) {
                    $file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name);

                    if (!$h = fopen($file.'.lock', 'w')) {
                        if (file_exists($file.'.lock')) {
                            continue 2;
                        }
                        restore_error_handler();
                        throw new RuntimeException('A temporary file could not be opened to write the process output: '.$lastError);
                    }
                    if (!flock($h, \LOCK_EX | \LOCK_NB)) {
                        continue 2;
                    }
                    if (isset($this->lockHandles[$pipe])) {
                        flock($this->lockHandles[$pipe], \LOCK_UN);
                        fclose($this->lockHandles[$pipe]);
                    }
                    $this->lockHandles[$pipe] = $h;

                    if (!fclose(fopen($file, 'w')) || !$h = fopen($file, 'r')) {
                        flock($this->lockHandles[$pipe], \LOCK_UN);
                        fclose($this->lockHandles[$pipe]);
                        unset($this->lockHandles[$pipe]);
                        continue 2;
                    }
                    $this->fileHandles[$pipe] = $h;
                    $this->files[$pipe] = $file;
                }
                break;
            }
            restore_error_handler();
        }

        parent::__construct($input);
    }

    public function __destruct()
    {
        $this->close();
    }

    /**
     * {@inheritdoc}
     */
    public function getDescriptors()
    {
        if (!$this->haveReadSupport) {
            $nullstream = fopen('NUL', 'c');

            return [
                ['pipe', 'r'],
                $nullstream,
                $nullstream,
            ];
        }

        // We're not using pipe on Windows platform as it hangs (https://bugs.php.net/51800)
        // We're not using file handles as it can produce corrupted output https://bugs.php.net/65650
        // So we redirect output within the commandline and pass the nul device to the process
        return [
            ['pipe', 'r'],
            ['file', 'NUL', 'w'],
            ['file', 'NUL', 'w'],
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function getFiles()
    {
        return $this->files;
    }

    /**
     * {@inheritdoc}
     */
    public function readAndWrite($blocking, $close = false)
    {
        $this->unblock();
        $w = $this->write();
        $read = $r = $e = [];

        if ($blocking) {
            if ($w) {
                @stream_select($r, $w, $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 haveReadSupport()
    {
        return $this->haveReadSupport;
    }

    /**
     * {@inheritdoc}
     */
    public function areOpen()
    {
        return $this->pipes && $this->fileHandles;
    }

    /**
     * {@inheritdoc}
     */
    public function close()
    {
        parent::close();
        foreach ($this->fileHandles as $type => $handle) {
            ftruncate($handle, 0);
            fclose($handle);
            flock($this->lockHandles[$type], \LOCK_UN);
            fclose($this->lockHandles[$type]);
        }
        $this->fileHandles = $this->lockHandles = [];
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Pipes;

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

    /**
     * Returns if pipes are able to read output.
     *
     * @return bool
     */
    public function haveReadSupport();

    /**
     * Closes file handles and pipes.
     */
    public function close();
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Pipes;

use Symfony\Component\Process\Exception\InvalidArgumentException;

/**
 * @author Romain Neutron <imprec@gmail.com>
 *
 * @internal
 */
abstract class AbstractPipes implements PipesInterface
{
    public $pipes = [];

    private $inputBuffer = '';
    private $input;
    private $blocked = true;
    private $lastError;

    /**
     * @param resource|string|int|float|bool|\Iterator|null $input
     */
    public function __construct($input)
    {
        if (\is_resource($input) || $input instanceof \Iterator) {
            $this->input = $input;
        } elseif (\is_string($input)) {
            $this->inputBuffer = $input;
        } else {
            $this->inputBuffer = (string) $input;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function close()
    {
        foreach ($this->pipes as $pipe) {
            fclose($pipe);
        }
        $this->pipes = [];
    }

    /**
     * Returns true if a system call has been interrupted.
     *
     * @return bool
     */
    protected function hasSystemCallBeenInterrupted()
    {
        $lastError = $this->lastError;
        $this->lastError = null;

        // stream_select returns false when the `select` system call is interrupted by an incoming signal
        return null !== $lastError && false !== stripos($lastError, 'interrupted system call');
    }

    /**
     * Unblocks streams.
     */
    protected function unblock()
    {
        if (!$this->blocked) {
            return;
        }

        foreach ($this->pipes as $pipe) {
            stream_set_blocking($pipe, 0);
        }
        if (\is_resource($this->input)) {
            stream_set_blocking($this->input, 0);
        }

        $this->blocked = false;
    }

    /**
     * Writes input to stdin.
     *
     * @return array|null
     *
     * @throws InvalidArgumentException When an input iterator yields a non supported value
     */
    protected function write()
    {
        if (!isset($this->pipes[0])) {
            return null;
        }
        $input = $this->input;

        if ($input instanceof \Iterator) {
            if (!$input->valid()) {
                $input = null;
            } elseif (\is_resource($input = $input->current())) {
                stream_set_blocking($input, 0);
            } elseif (!isset($this->inputBuffer[0])) {
                if (!\is_string($input)) {
                    if (!is_scalar($input)) {
                        throw new InvalidArgumentException(sprintf('"%s" yielded a value of type "%s", but only scalars and stream resources are supported.', \get_class($this->input), \gettype($input)));
                    }
                    $input = (string) $input;
                }
                $this->inputBuffer = $input;
                $this->input->next();
                $input = null;
            } else {
                $input = null;
            }
        }

        $r = $e = [];
        $w = [$this->pipes[0]];

        // let's have a look if something changed in streams
        if (false === @stream_select($r, $w, $e, 0, 0)) {
            return null;
        }

        foreach ($w as $stdin) {
            if (isset($this->inputBuffer[0])) {
                $written = fwrite($stdin, $this->inputBuffer);
                $this->inputBuffer = substr($this->inputBuffer, $written);
                if (isset($this->inputBuffer[0])) {
                    return [$this->pipes[0]];
                }
            }

            if ($input) {
                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 [$this->pipes[0]];
                    }
                }
                if (feof($input)) {
                    if ($this->input instanceof \Iterator) {
                        $this->input->next();
                    } else {
                        $this->input = null;
                    }
                }
            }
        }

        // no input to read on resource, buffer is empty
        if (!isset($this->inputBuffer[0]) && !($this->input instanceof \Iterator ? $this->input->valid() : $this->input)) {
            $this->input = null;
            fclose($this->pipes[0]);
            unset($this->pipes[0]);
        } elseif (!$w) {
            return [$this->pipes[0]];
        }

        return null;
    }

    /**
     * @internal
     */
    public function handleError($type, $msg)
    {
        $this->lastError = $msg;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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;

/**
 * RuntimeException for the Process Component.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Exception;

/**
 * InvalidArgumentException for the Process Component.
 *
 * @author Romain Neutron <imprec@gmail.com>
 */
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process\Exception;

use Symfony\Component\Process\Process;

/**
 * Exception that is thrown when a process times out.
 *
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class ProcessTimedOutException extends RuntimeException
{
    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;

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

/**
 * Generic executable finder.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class ExecutableFinder
{
    private $suffixes = [];

    /**
     * Replaces default suffixes of executable.
     */
    public function setSuffixes(array $suffixes)
    {
        $this->suffixes = $suffixes;
    }

    /**
     * Adds new possible suffix to check for executable.
     *
     * @param string $suffix
     */
    public function addSuffix($suffix)
    {
        $this->suffixes[] = $suffix;
    }

    /**
     * Finds an executable by name.
     *
     * @param string      $name      The executable name (without the extension)
     * @param string|null $default   The default to return if no executable is found
     * @param array       $extraDirs Additional dirs to check into
     *
     * @return string|null The executable path or default value
     */
    public function find($name, $default = null, array $extraDirs = [])
    {
        if (ini_get('open_basedir')) {
            $searchPath = array_merge(explode(\PATH_SEPARATOR, ini_get('open_basedir')), $extraDirs);
            $dirs = [];
            foreach ($searchPath as $path) {
                // Silencing against https://bugs.php.net/69240
                if (@is_dir($path)) {
                    $dirs[] = $path;
                } else {
                    if (basename($path) == $name && @is_executable($path)) {
                        return $path;
                    }
                }
            }
        } else {
            $dirs = array_merge(
                explode(\PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')),
                $extraDirs
            );
        }

        $suffixes = [];
        if ('\\' === \DIRECTORY_SEPARATOR) {
            $pathExt = getenv('PATHEXT');
            $suffixes = $this->suffixes;
            $suffixes = array_merge($suffixes, $pathExt ? explode(\PATH_SEPARATOR, $pathExt) : ['.exe', '.bat', '.cmd', '.com']);
        }
        $suffixes = '' !== pathinfo($name, PATHINFO_EXTENSION) ? array_merge([''], $suffixes) : array_merge($suffixes, ['']);
        foreach ($suffixes as $suffix) {
            foreach ($dirs as $dir) {
                if ('' === $dir) {
                    $dir = '.';
                }
                if (@is_file($file = $dir.\DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === \DIRECTORY_SEPARATOR || @is_executable($file))) {
                    return $file;
                }
 
                if (!@is_dir($dir) && basename($dir) === $name.$suffix && @is_executable($dir)) {
                    return $dir;
                }
            }
        }

        if ('\\' === \DIRECTORY_SEPARATOR || !\function_exists('exec') || \strlen($name) !== strcspn($name, '/'.\DIRECTORY_SEPARATOR)) {
            return $default;
        }

        $execResult = exec('command -v -- '.escapeshellarg($name));

        if (($executablePath = substr($execResult, 0, strpos($execResult, \PHP_EOL) ?: null)) && @is_executable($executablePath)) {
            return $executablePath;
        }

        return $default;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process;

use Symfony\Component\Process\Exception\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
     *
     * @deprecated since version 3.3, to be removed in 4.0. Use a command line array or give env vars to the `Process::start/run()` method instead.
     */
    public static function escapeArgument($argument)
    {
        @trigger_error('The '.__METHOD__.'() method is deprecated since Symfony 3.3 and will be removed in 4.0. Use a command line array or give env vars to the Process::start/run() method instead.', \E_USER_DEPRECATED);

        //Fix for PHP bug #43784 escapeshellarg removes % from given string
        //Fix for PHP bug #49446 escapeshellarg doesn't work on Windows
        //@see https://bugs.php.net/43784
        //@see https://bugs.php.net/49446
        if ('\\' === \DIRECTORY_SEPARATOR) {
            if ('' === $argument) {
                return escapeshellarg($argument);
            }

            $escapedArgument = '';
            $quote = false;
            foreach (preg_split('/(")/', $argument, -1, \PREG_SPLIT_NO_EMPTY | \PREG_SPLIT_DELIM_CAPTURE) as $part) {
                if ('"' === $part) {
                    $escapedArgument .= '\\"';
                } elseif (self::isSurroundedBy($part, '%')) {
                    // Avoid environment variable expansion
                    $escapedArgument .= '^%"'.substr($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
     */
    public static function validateInput($caller, $input)
    {
        if (null !== $input) {
            if (\is_resource($input)) {
                return $input;
            }
            if (\is_string($input)) {
                return $input;
            }
            if (is_scalar($input)) {
                return (string) $input;
            }
            if ($input instanceof Process) {
                return $input->getIterator($input::ITER_SKIP_ERR);
            }
            if ($input instanceof \Iterator) {
                return $input;
            }
            if ($input instanceof \Traversable) {
                return new \IteratorIterator($input);
            }

            throw new InvalidArgumentException(sprintf('"%s" only accepts strings, Traversable objects or stream resources.', $caller));
        }

        return $input;
    }

    private static function isSurroundedBy($arg, $char)
    {
        return 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;

@trigger_error(sprintf('The %s class is deprecated since Symfony 3.4 and will be removed in 4.0. Use the Process class instead.', ProcessBuilder::class), \E_USER_DEPRECATED);

use Symfony\Component\Process\Exception\InvalidArgumentException;
use Symfony\Component\Process\Exception\LogicException;

/**
 * @author Kris Wallsmith <kris@symfony.com>
 *
 * @deprecated since version 3.4, to be removed in 4.0. Use the Process class instead.
 */
class ProcessBuilder
{
    private $arguments;
    private $cwd;
    private $env = [];
    private $input;
    private $timeout = 60;
    private $options;
    private $inheritEnv = true;
    private $prefix = [];
    private $outputDisabled = false;

    /**
     * @param string[] $arguments An array of arguments
     */
    public function __construct(array $arguments = [])
    {
        $this->arguments = $arguments;
    }

    /**
     * Creates a process builder instance.
     *
     * @param string[] $arguments An array of arguments
     *
     * @return static
     */
    public static function create(array $arguments = [])
    {
        return new static($arguments);
    }

    /**
     * Adds an unescaped argument to the command string.
     *
     * @param string $argument A command argument
     *
     * @return $this
     */
    public function add($argument)
    {
        $this->arguments[] = $argument;

        return $this;
    }

    /**
     * Adds a prefix to the command string.
     *
     * The prefix is preserved when resetting arguments.
     *
     * @param string|array $prefix A command prefix or an array of command prefixes
     *
     * @return $this
     */
    public function setPrefix($prefix)
    {
        $this->prefix = \is_array($prefix) ? $prefix : [$prefix];

        return $this;
    }

    /**
     * Sets the arguments of the process.
     *
     * Arguments must not be escaped.
     * Previous arguments are removed.
     *
     * @param string[] $arguments
     *
     * @return $this
     */
    public function setArguments(array $arguments)
    {
        $this->arguments = $arguments;

        return $this;
    }

    /**
     * Sets the working directory.
     *
     * @param string|null $cwd The working directory
     *
     * @return $this
     */
    public function setWorkingDirectory($cwd)
    {
        $this->cwd = $cwd;

        return $this;
    }

    /**
     * Sets whether environment variables will be inherited or not.
     *
     * @param bool $inheritEnv
     *
     * @return $this
     */
    public function inheritEnvironmentVariables($inheritEnv = true)
    {
        $this->inheritEnv = $inheritEnv;

        return $this;
    }

    /**
     * Sets an environment variable.
     *
     * Setting a variable overrides its previous value. Use `null` to unset a
     * defined environment variable.
     *
     * @param string      $name  The variable name
     * @param string|null $value The variable value
     *
     * @return $this
     */
    public function setEnv($name, $value)
    {
        $this->env[$name] = $value;

        return $this;
    }

    /**
     * Adds a set of environment variables.
     *
     * Already existing environment variables with the same name will be
     * overridden by the new values passed to this method. Pass `null` to unset
     * a variable.
     *
     * @param array $variables The variables
     *
     * @return $this
     */
    public function addEnvironmentVariables(array $variables)
    {
        $this->env = array_replace($this->env, $variables);

        return $this;
    }

    /**
     * Sets the input of the process.
     *
     * @param resource|string|int|float|bool|\Traversable|null $input The input content
     *
     * @return $this
     *
     * @throws InvalidArgumentException In case the argument is invalid
     */
    public function setInput($input)
    {
        $this->input = ProcessUtils::validateInput(__METHOD__, $input);

        return $this;
    }

    /**
     * Sets the process timeout.
     *
     * To disable the timeout, set this value to null.
     *
     * @param float|null $timeout
     *
     * @return $this
     *
     * @throws InvalidArgumentException
     */
    public function setTimeout($timeout)
    {
        if (null === $timeout) {
            $this->timeout = null;

            return $this;
        }

        $timeout = (float) $timeout;

        if ($timeout < 0) {
            throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
        }

        $this->timeout = $timeout;

        return $this;
    }

    /**
     * Adds a proc_open option.
     *
     * @param string $name  The option name
     * @param string $value The option value
     *
     * @return $this
     */
    public function setOption($name, $value)
    {
        $this->options[$name] = $value;

        return $this;
    }

    /**
     * Disables fetching output and error output from the underlying process.
     *
     * @return $this
     */
    public function disableOutput()
    {
        $this->outputDisabled = true;

        return $this;
    }

    /**
     * Enables fetching output and error output from the underlying process.
     *
     * @return $this
     */
    public function enableOutput()
    {
        $this->outputDisabled = false;

        return $this;
    }

    /**
     * Creates a Process instance and returns it.
     *
     * @return Process
     *
     * @throws LogicException In case no arguments have been provided
     */
    public function getProcess()
    {
        if (0 === \count($this->prefix) && 0 === \count($this->arguments)) {
            throw new LogicException('You must add() command arguments before calling getProcess().');
        }

        $arguments = array_merge($this->prefix, $this->arguments);
        $process = new Process($arguments, $this->cwd, $this->env, $this->input, $this->timeout, $this->options);
        // to preserve the BC with symfony <3.3, we convert the array structure
        // to a string structure to avoid the prefixing with the exec command
        $process->setCommandLine($process->getCommandLine());

        if ($this->inheritEnv) {
            $process->inheritEnvironmentVariables();
        }
        if ($this->outputDisabled) {
            $process->disableOutput();
        }

        return $process;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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 implements \IteratorAggregate
{
    const ERR = 'err';
    const OUT = 'out';

    const STATUS_READY = 'ready';
    const STATUS_STARTED = 'started';
    const STATUS_TERMINATED = 'terminated';

    const STDIN = 0;
    const STDOUT = 1;
    const STDERR = 2;

    // Timeout Precision in seconds.
    const TIMEOUT_PRECISION = 0.2;

    const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking
    const ITER_KEEP_OUTPUT = 2;  // By default, outputs are cleared while iterating, use this flag to keep them in memory
    const ITER_SKIP_OUT = 4;     // Use this flag to skip STDOUT while iterating
    const ITER_SKIP_ERR = 8;     // Use this flag to skip STDERR while iterating

    private $callback;
    private $hasCallback = false;
    private $commandline;
    private $cwd;
    private $env;
    private $input;
    private $starttime;
    private $lastOutputTime;
    private $timeout;
    private $idleTimeout;
    private $options = ['suppress_errors' => true];
    private $exitcode;
    private $fallbackStatus = [];
    private $processInformation;
    private $outputDisabled = false;
    private $stdout;
    private $stderr;
    private $enhanceWindowsCompatibility = true;
    private $enhanceSigchildCompatibility;
    private $process;
    private $status = self::STATUS_READY;
    private $incrementalOutputOffset = 0;
    private $incrementalErrorOutputOffset = 0;
    private $tty = false;
    private $pty;
    private $inheritEnv = false;

    private $useFileHandles = false;
    /** @var PipesInterface */
    private $processPipes;

    private $latestSignal;

    private static $sigchild;

    /**
     * Exit codes translation table.
     *
     * User-defined errors must use exit codes in the 64-113 range.
     */
    public static $exitCodes = [
        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|array   $commandline The command line to run
     * @param string|null    $cwd         The working directory or null to use the working dir of the current PHP process
     * @param array|null     $env         The environment variables or null to use the same environment as the current PHP process
     * @param mixed|null     $input       The input as stream resource, scalar or \Traversable, or null for no input
     * @param int|float|null $timeout     The timeout in seconds or null to disable
     * @param array          $options     An array of options for proc_open
     *
     * @throws RuntimeException When proc_open is not installed
     */
    public function __construct($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = null)
    {
        if (!\function_exists('proc_open')) {
            throw new RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.');
        }

        $this->commandline = $commandline;
        $this->cwd = $cwd;

        // on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started
        // on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected
        // @see : https://bugs.php.net/51800
        // @see : https://bugs.php.net/50524
        if (null === $this->cwd && (\defined('ZEND_THREAD_SAFE') || '\\' === \DIRECTORY_SEPARATOR)) {
            $this->cwd = getcwd();
        }
        if (null !== $env) {
            $this->setEnv($env);
        }

        $this->setInput($input);
        $this->setTimeout($timeout);
        $this->useFileHandles = '\\' === \DIRECTORY_SEPARATOR;
        $this->pty = false;
        $this->enhanceSigchildCompatibility = '\\' !== \DIRECTORY_SEPARATOR && $this->isSigchildEnabled();
        if (null !== $options) {
            @trigger_error(sprintf('The $options parameter of the %s constructor is deprecated since Symfony 3.3 and will be removed in 4.0.', __CLASS__), \E_USER_DEPRECATED);
            $this->options = array_replace($this->options, $options);
        }
    }

    /**
     * Creates a Process instance as a command-line to be run in a shell wrapper.
     *
     * Command-lines are parsed by the shell of your OS (/bin/sh on Unix-like, cmd.exe on Windows.)
     * This allows using e.g. pipes or conditional execution. In this mode, signals are sent to the
     * shell wrapper and not to your commands.
     *
     * In order to inject dynamic values into command-lines, we strongly recommend using placeholders.
     * This will save escaping values, which is not portable nor secure anyway:
     *
     *   $process = Process::fromShellCommandline('my_command "${:MY_VAR}"');
     *   $process->run(null, ['MY_VAR' => $theValue]);
     *
     * @param string         $command The command line to pass to the shell of the OS
     * @param string|null    $cwd     The working directory or null to use the working dir of the current PHP process
     * @param array|null     $env     The environment variables or null to use the same environment as the current PHP process
     * @param mixed          $input   The input as stream resource, scalar or \Traversable, or null for no input
     * @param int|float|null $timeout The timeout in seconds or null to disable
     *
     * @return static
     *
     * @throws LogicException When proc_open is not installed
     */
    public static function fromShellCommandline($command, $cwd = null, array $env = null, $input = null, $timeout = 60)
    {
        $process = new static([], $cwd, $env, $input, $timeout);
        $process->commandline = $command;

        return $process;
    }

    public function __destruct()
    {
        $this->stop(0);
    }

    public function __clone()
    {
        $this->resetProcessData();
    }

    /**
     * Runs the process.
     *
     * The callback receives the type of output (out or err) and
     * some bytes from the output in real-time. It allows to have feedback
     * from the independent process during execution.
     *
     * The STDOUT and STDERR are also available after the process is finished
     * via the getOutput() and getErrorOutput() methods.
     *
     * @param callable|null $callback A PHP callback to run whenever there is some
     *                                output available on STDOUT or STDERR
     *
     * @return int The exit status code
     *
     * @throws RuntimeException When process can't be launched
     * @throws RuntimeException When process stopped after receiving signal
     * @throws LogicException   In case a callback is provided and output has been disabled
     *
     * @final since version 3.3
     */
    public function run($callback = null/*, array $env = []*/)
    {
        $env = 1 < \func_num_args() ? func_get_arg(1) : null;
        $this->start($callback, $env);

        return $this->wait();
    }

    /**
     * Runs the process.
     *
     * This is identical to run() except that an exception is thrown if the process
     * exits with a non-zero exit code.
     *
     * @return $this
     *
     * @throws RuntimeException       if PHP was compiled with --enable-sigchild and the enhanced sigchild compatibility mode is not enabled
     * @throws ProcessFailedException if the process didn't terminate successfully
     *
     * @final since version 3.3
     */
    public function mustRun(callable $callback = null/*, array $env = []*/)
    {
        if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
            throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.');
        }
        $env = 1 < \func_num_args() ? func_get_arg(1) : null;

        if (0 !== $this->run($callback, $env)) {
            throw new ProcessFailedException($this);
        }

        return $this;
    }

    /**
     * Starts the process and returns after writing the input to STDIN.
     *
     * This method blocks until all STDIN data is sent to the process then it
     * returns while the process runs in the background.
     *
     * The termination of the process can be awaited with wait().
     *
     * The callback receives the type of output (out or err) and some bytes from
     * the output in real-time while writing the standard input to the process.
     * It allows to have feedback from the independent process during execution.
     *
     * @param callable|null $callback A PHP callback to run whenever there is some
     *                                output available on STDOUT or STDERR
     *
     * @throws RuntimeException When process can't be launched
     * @throws RuntimeException When process is already running
     * @throws LogicException   In case a callback is provided and output has been disabled
     */
    public function start(callable $callback = null/*, array $env = [*/)
    {
        if ($this->isRunning()) {
            throw new RuntimeException('Process is already running.');
        }
        if (2 <= \func_num_args()) {
            $env = func_get_arg(1);
        } else {
            if (__CLASS__ !== static::class) {
                $r = new \ReflectionMethod($this, __FUNCTION__);
                if (__CLASS__ !== $r->getDeclaringClass()->getName() && (2 > $r->getNumberOfParameters() || 'env' !== $r->getParameters()[1]->name)) {
                    @trigger_error(sprintf('The %s::start() method expects a second "$env" argument since Symfony 3.3. It will be made mandatory in 4.0.', static::class), \E_USER_DEPRECATED);
                }
            }
            $env = null;
        }

        $this->resetProcessData();
        $this->starttime = $this->lastOutputTime = microtime(true);
        $this->callback = $this->buildCallback($callback);
        $this->hasCallback = null !== $callback;
        $descriptors = $this->getDescriptors();
        $inheritEnv = $this->inheritEnv;

        if (\is_array($commandline = $this->commandline)) {
            $commandline = implode(' ', array_map([$this, 'escapeArgument'], $commandline));

            if ('\\' !== \DIRECTORY_SEPARATOR) {
                // exec is mandatory to deal with sending a signal to the process
                $commandline = 'exec '.$commandline;
            }
        }

        if (null === $env) {
            $env = $this->env;
        } else {
            if ($this->env) {
                $env += $this->env;
            }
            $inheritEnv = true;
        }

        if (null !== $env && $inheritEnv) {
            $env += $this->getDefaultEnv();
        } elseif (null !== $env) {
            @trigger_error('Not inheriting environment variables is deprecated since Symfony 3.3 and will always happen in 4.0. Set "Process::inheritEnvironmentVariables()" to true instead.', \E_USER_DEPRECATED);
        } else {
            $env = $this->getDefaultEnv();
        }
        if ('\\' === \DIRECTORY_SEPARATOR && $this->enhanceWindowsCompatibility) {
            $this->options['bypass_shell'] = true;
            $commandline = $this->prepareWindowsCommandLine($commandline, $env);
        } elseif (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
            // last exit code is output on the fourth pipe and caught to work around --enable-sigchild
            $descriptors[3] = ['pipe', 'w'];

            // See https://unix.stackexchange.com/questions/71205/background-process-pipe-input
            $commandline = '{ ('.$commandline.') <&3 3<&- 3>/dev/null & } 3<&0;';
            $commandline .= 'pid=$!; echo $pid >&3; wait $pid; code=$?; echo $code >&3; exit $code';

            // Workaround for the bug, when PTS functionality is enabled.
            // @see : https://bugs.php.net/69442
            $ptsWorkaround = fopen(__FILE__, 'r');
        }
        if (\defined('HHVM_VERSION')) {
            $envPairs = $env;
        } else {
            $envPairs = [];
            foreach ($env as $k => $v) {
                if (false !== $v) {
                    $envPairs[] = $k.'='.$v;
                }
            }
        }

        if (!is_dir($this->cwd)) {
            @trigger_error('The provided cwd does not exist. Command is currently ran against getcwd(). This behavior is deprecated since Symfony 3.4 and will be removed in 4.0.', \E_USER_DEPRECATED);
        }

        $this->process = @proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $this->options);

        if (!\is_resource($this->process)) {
            throw new RuntimeException('Unable to launch a new process.');
        }
        $this->status = self::STATUS_STARTED;

        if (isset($descriptors[3])) {
            $this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]);
        }

        if ($this->tty) {
            return;
        }

        $this->updateStatus(false);
        $this->checkTimeout();
    }

    /**
     * Restarts the process.
     *
     * Be warned that the process is cloned before being started.
     *
     * @param callable|null $callback A PHP callback to run whenever there is some
     *                                output available on STDOUT or STDERR
     *
     * @return static
     *
     * @throws RuntimeException When process can't be launched
     * @throws RuntimeException When process is already running
     *
     * @see start()
     *
     * @final since version 3.3
     */
    public function restart(callable $callback = null/*, array $env = []*/)
    {
        if ($this->isRunning()) {
            throw new RuntimeException('Process is already running.');
        }
        $env = 1 < \func_num_args() ? func_get_arg(1) : null;

        $process = clone $this;
        $process->start($callback, $env);

        return $process;
    }

    /**
     * Waits for the process to terminate.
     *
     * The callback receives the type of output (out or err) and some bytes
     * from the output in real-time while writing the standard input to the process.
     * It allows to have feedback from the independent process during execution.
     *
     * @param callable|null $callback A valid PHP callback
     *
     * @return int The exitcode of the process
     *
     * @throws RuntimeException When process timed out
     * @throws RuntimeException When process stopped after receiving signal
     * @throws LogicException   When process is not yet started
     */
    public function wait(callable $callback = null)
    {
        $this->requireProcessIsStarted(__FUNCTION__);

        $this->updateStatus(false);

        if (null !== $callback) {
            if (!$this->processPipes->haveReadSupport()) {
                $this->stop(0);
                throw new \LogicException('Pass the callback to the Process::start method or enableOutput to use a callback with Process::wait.');
            }
            $this->callback = $this->buildCallback($callback);
        }

        do {
            $this->checkTimeout();
            $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
            $this->readPipes($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
        } while ($running);

        while ($this->isRunning()) {
            $this->checkTimeout();
            usleep(1000);
        }

        if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) {
            throw new RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig']));
        }

        return $this->exitcode;
    }

    /**
     * Returns the Pid (process identifier), if applicable.
     *
     * @return int|null The process id if running, null otherwise
     */
    public function getPid()
    {
        return $this->isRunning() ? $this->processInformation['pid'] : null;
    }

    /**
     * Sends a POSIX signal to the process.
     *
     * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants)
     *
     * @return $this
     *
     * @throws LogicException   In case the process is not running
     * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
     * @throws RuntimeException In case of failure
     */
    public function signal($signal)
    {
        $this->doSignal($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;
    }

    /**
     * Returns an iterator to the output of the process, with the output type as keys (Process::OUT/ERR).
     *
     * @param int $flags A bit field of Process::ITER_* flags
     *
     * @throws LogicException in case the output has been disabled
     * @throws LogicException In case the process is not started
     *
     * @return \Generator
     */
    public function getIterator($flags = 0)
    {
        $this->readPipesForOutput(__FUNCTION__, false);

        $clearOutput = !(self::ITER_KEEP_OUTPUT & $flags);
        $blocking = !(self::ITER_NON_BLOCKING & $flags);
        $yieldOut = !(self::ITER_SKIP_OUT & $flags);
        $yieldErr = !(self::ITER_SKIP_ERR & $flags);

        while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) {
            if ($yieldOut) {
                $out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);

                if (isset($out[0])) {
                    if ($clearOutput) {
                        $this->clearOutput();
                    } else {
                        $this->incrementalOutputOffset = ftell($this->stdout);
                    }

                    yield self::OUT => $out;
                }
            }

            if ($yieldErr) {
                $err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);

                if (isset($err[0])) {
                    if ($clearOutput) {
                        $this->clearErrorOutput();
                    } else {
                        $this->incrementalErrorOutputOffset = ftell($this->stderr);
                    }

                    yield self::ERR => $err;
                }
            }

            if (!$blocking && !isset($out[0]) && !isset($err[0])) {
                yield self::OUT => '';
            }

            $this->checkTimeout();
            $this->readPipesForOutput(__FUNCTION__, $blocking);
        }
    }

    /**
     * Clears the process output.
     *
     * @return $this
     */
    public function clearOutput()
    {
        ftruncate($this->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 null;
        }

        return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error';
    }

    /**
     * Checks if the process ended successfully.
     *
     * @return bool true if the process ended successfully, false otherwise
     */
    public function isSuccessful()
    {
        return 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|null The exit-code of the process or null if it's not running
     */
    public function stop($timeout = 10, $signal = null)
    {
        $timeoutMicro = microtime(true) + $timeout;
        if ($this->isRunning()) {
            // given `SIGTERM` may not be defined and that `proc_terminate` uses the constant value and not the constant itself, we use the same here
            $this->doSignal(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 \is_array($this->commandline) ? implode(' ', array_map([$this, 'escapeArgument'], $this->commandline)) : $this->commandline;
    }

    /**
     * Sets the command line to be executed.
     *
     * @param string|array $commandline The command to execute
     *
     * @return $this
     */
    public function setCommandLine($commandline)
    {
        $this->commandline = $commandline;

        return $this;
    }

    /**
     * Gets the process timeout (max. runtime).
     *
     * @return float|null The timeout in seconds or null if it's disabled
     */
    public function getTimeout()
    {
        return $this->timeout;
    }

    /**
     * Gets the process idle timeout (max. time since last output).
     *
     * @return float|null The timeout in seconds or null if it's disabled
     */
    public function getIdleTimeout()
    {
        return $this->idleTimeout;
    }

    /**
     * Sets the process timeout (max. runtime) in seconds.
     *
     * To disable the timeout, set this value to null.
     *
     * @param int|float|null $timeout The timeout in seconds
     *
     * @return $this
     *
     * @throws InvalidArgumentException if the timeout is negative
     */
    public function setTimeout($timeout)
    {
        $this->timeout = $this->validateTimeout($timeout);

        return $this;
    }

    /**
     * Sets the process idle timeout (max. time since last output).
     *
     * To disable the timeout, set this value to null.
     *
     * @param int|float|null $timeout The timeout in seconds
     *
     * @return $this
     *
     * @throws LogicException           if the output is disabled
     * @throws InvalidArgumentException if the timeout is negative
     */
    public function setIdleTimeout($timeout)
    {
        if (null !== $timeout && $this->outputDisabled) {
            throw new LogicException('Idle timeout can not be set while the output is disabled.');
        }

        $this->idleTimeout = $this->validateTimeout($timeout);

        return $this;
    }

    /**
     * Enables or disables the TTY mode.
     *
     * @param bool $tty True to enabled and false to disable
     *
     * @return $this
     *
     * @throws RuntimeException In case the TTY mode is not supported
     */
    public function setTty($tty)
    {
        if ('\\' === \DIRECTORY_SEPARATOR && $tty) {
            throw new RuntimeException('TTY mode is not supported on Windows platform.');
        }
        if ($tty) {
            static $isTtySupported;

            if (null === $isTtySupported) {
                $isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes);
            }

            if (!$isTtySupported) {
                throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.');
            }
        }

        $this->tty = (bool) $tty;

        return $this;
    }

    /**
     * Checks if the TTY mode is enabled.
     *
     * @return bool true if the TTY mode is enabled, false otherwise
     */
    public function isTty()
    {
        return $this->tty;
    }

    /**
     * Sets PTY mode.
     *
     * @param bool $bool
     *
     * @return $this
     */
    public function setPty($bool)
    {
        $this->pty = (bool) $bool;

        return $this;
    }

    /**
     * Returns PTY state.
     *
     * @return bool
     */
    public function isPty()
    {
        return $this->pty;
    }

    /**
     * Gets the working directory.
     *
     * @return string|null The current working directory or null on failure
     */
    public function getWorkingDirectory()
    {
        if (null === $this->cwd) {
            // getcwd() will return false if any one of the parent directories does not have
            // the readable or search mode set, even if the current directory does
            return getcwd() ?: null;
        }

        return $this->cwd;
    }

    /**
     * Sets the current working directory.
     *
     * @param string $cwd The new working directory
     *
     * @return $this
     */
    public function setWorkingDirectory($cwd)
    {
        $this->cwd = $cwd;

        return $this;
    }

    /**
     * Gets the environment variables.
     *
     * @return array The current environment variables
     */
    public function getEnv()
    {
        return $this->env;
    }

    /**
     * Sets the environment variables.
     *
     * Each environment variable value should be a string.
     * If it is an array, the variable is ignored.
     * If it is false or null, it will be removed when
     * env vars are otherwise inherited.
     *
     * That happens in PHP when 'argv' is registered into
     * the $_ENV array for instance.
     *
     * @param array $env The new environment variables
     *
     * @return $this
     */
    public function setEnv(array $env)
    {
        // Process can not handle env values that are arrays
        $env = array_filter($env, function ($value) {
            return !\is_array($value);
        });

        $this->env = $env;

        return $this;
    }

    /**
     * Gets the Process input.
     *
     * @return resource|string|\Iterator|null The Process input
     */
    public function getInput()
    {
        return $this->input;
    }

    /**
     * Sets the input.
     *
     * This content will be passed to the underlying process standard input.
     *
     * @param string|int|float|bool|resource|\Traversable|null $input The content
     *
     * @return $this
     *
     * @throws LogicException In case the process is running
     */
    public function setInput($input)
    {
        if ($this->isRunning()) {
            throw new LogicException('Input can not be set while the process is running.');
        }

        $this->input = ProcessUtils::validateInput(__METHOD__, $input);

        return $this;
    }

    /**
     * Gets the options for proc_open.
     *
     * @return array The current options
     *
     * @deprecated since version 3.3, to be removed in 4.0.
     */
    public function getOptions()
    {
        @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0.', __METHOD__), \E_USER_DEPRECATED);

        return $this->options;
    }

    /**
     * Sets the options for proc_open.
     *
     * @param array $options The new options
     *
     * @return $this
     *
     * @deprecated since version 3.3, to be removed in 4.0.
     */
    public function setOptions(array $options)
    {
        @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0.', __METHOD__), \E_USER_DEPRECATED);

        $this->options = $options;

        return $this;
    }

    /**
     * Gets whether or not Windows compatibility is enabled.
     *
     * This is true by default.
     *
     * @return bool
     *
     * @deprecated since version 3.3, to be removed in 4.0. Enhanced Windows compatibility will always be enabled.
     */
    public function getEnhanceWindowsCompatibility()
    {
        @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Enhanced Windows compatibility will always be enabled.', __METHOD__), \E_USER_DEPRECATED);

        return $this->enhanceWindowsCompatibility;
    }

    /**
     * Sets whether or not Windows compatibility is enabled.
     *
     * @param bool $enhance
     *
     * @return $this
     *
     * @deprecated since version 3.3, to be removed in 4.0. Enhanced Windows compatibility will always be enabled.
     */
    public function setEnhanceWindowsCompatibility($enhance)
    {
        @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Enhanced Windows compatibility will always be enabled.', __METHOD__), \E_USER_DEPRECATED);

        $this->enhanceWindowsCompatibility = (bool) $enhance;

        return $this;
    }

    /**
     * Returns whether sigchild compatibility mode is activated or not.
     *
     * @return bool
     *
     * @deprecated since version 3.3, to be removed in 4.0. Sigchild compatibility will always be enabled.
     */
    public function getEnhanceSigchildCompatibility()
    {
        @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Sigchild compatibility will always be enabled.', __METHOD__), \E_USER_DEPRECATED);

        return $this->enhanceSigchildCompatibility;
    }

    /**
     * Activates sigchild compatibility mode.
     *
     * Sigchild compatibility mode is required to get the exit code and
     * determine the success of a process when PHP has been compiled with
     * the --enable-sigchild option
     *
     * @param bool $enhance
     *
     * @return $this
     *
     * @deprecated since version 3.3, to be removed in 4.0.
     */
    public function setEnhanceSigchildCompatibility($enhance)
    {
        @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Sigchild compatibility will always be enabled.', __METHOD__), \E_USER_DEPRECATED);

        $this->enhanceSigchildCompatibility = (bool) $enhance;

        return $this;
    }

    /**
     * Sets whether environment variables will be inherited or not.
     *
     * @param bool $inheritEnv
     *
     * @return $this
     */
    public function inheritEnvironmentVariables($inheritEnv = true)
    {
        if (!$inheritEnv) {
            @trigger_error('Not inheriting environment variables is deprecated since Symfony 3.3 and will always happen in 4.0. Set "Process::inheritEnvironmentVariables()" to true instead.', \E_USER_DEPRECATED);
        }

        $this->inheritEnv = (bool) $inheritEnv;

        return $this;
    }

    /**
     * Returns whether environment variables will be inherited or not.
     *
     * @return bool
     *
     * @deprecated since version 3.3, to be removed in 4.0. Environment variables will always be inherited.
     */
    public function areEnvironmentVariablesInherited()
    {
        @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Environment variables will always be inherited.', __METHOD__), \E_USER_DEPRECATED);

        return $this->inheritEnv;
    }

    /**
     * Performs a check between the timeout definition and the time the process started.
     *
     * In case you run a background process (with the start method), you should
     * trigger this method regularly to ensure the process timeout
     *
     * @throws ProcessTimedOutException In case the timeout was reached
     */
    public function checkTimeout()
    {
        if (self::STATUS_STARTED !== $this->status) {
            return;
        }

        if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) {
            $this->stop(0);

            throw new ProcessTimedOutException($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', [['pty'], ['pty'], ['pty']], $pipes);
    }

    /**
     * Creates the descriptors needed by the proc_open.
     *
     * @return array
     */
    private function getDescriptors()
    {
        if ($this->input instanceof \Iterator) {
            $this->input->rewind();
        }
        if ('\\' === \DIRECTORY_SEPARATOR) {
            $this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $this->hasCallback);
        } else {
            $this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $this->hasCallback);
        }

        return $this->processPipes->getDescriptors();
    }

    /**
     * Builds up the callback used by wait().
     *
     * The callbacks adds all occurred output to the specific buffer and calls
     * the user callback (if present) with the received output.
     *
     * @param callable|null $callback The user defined PHP callback
     *
     * @return \Closure A PHP closure
     */
    protected function buildCallback(callable $callback = null)
    {
        if ($this->outputDisabled) {
            return function ($type, $data) use ($callback) {
                if (null !== $callback) {
                    \call_user_func($callback, $type, $data);
                }
            };
        }

        $out = self::OUT;

        return function ($type, $data) use ($callback, $out) {
            if ($out == $type) {
                $this->addOutput($data);
            } else {
                $this->addErrorOutput($data);
            }

            if (null !== $callback) {
                \call_user_func($callback, $type, $data);
            }
        };
    }

    /**
     * Updates the status of the process, reads pipes.
     *
     * @param bool $blocking Whether to use a blocking read call
     */
    protected function updateStatus($blocking)
    {
        if (self::STATUS_STARTED !== $this->status) {
            return;
        }

        $this->processInformation = proc_get_status($this->process);
        $running = $this->processInformation['running'];

        $this->readPipes($running && $blocking, '\\' !== \DIRECTORY_SEPARATOR || !$running);

        if ($this->fallbackStatus && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
            $this->processInformation = $this->fallbackStatus + $this->processInformation;
        }

        if (!$running) {
            $this->close();
        }
    }

    /**
     * Returns whether PHP has been compiled with the '--enable-sigchild' option or not.
     *
     * @return bool
     */
    protected function isSigchildEnabled()
    {
        if (null !== self::$sigchild) {
            return self::$sigchild;
        }

        if (!\function_exists('phpinfo') || \defined('HHVM_VERSION')) {
            return self::$sigchild = false;
        }

        ob_start();
        phpinfo(\INFO_GENERAL);

        return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
    }

    /**
     * Reads pipes for the freshest output.
     *
     * @param string $caller   The name of the method that needs fresh outputs
     * @param bool   $blocking Whether to use blocking calls or not
     *
     * @throws LogicException in case output has been disabled or process is not started
     */
    private function readPipesForOutput($caller, $blocking = false)
    {
        if ($this->outputDisabled) {
            throw new LogicException('Output has been disabled.');
        }

        $this->requireProcessIsStarted($caller);

        $this->updateStatus($blocking);
    }

    /**
     * Validates and returns the filtered timeout.
     *
     * @param int|float|null $timeout
     *
     * @return float|null
     *
     * @throws InvalidArgumentException if the given timeout is a negative number
     */
    private function validateTimeout($timeout)
    {
        $timeout = (float) $timeout;

        if (0.0 === $timeout) {
            $timeout = null;
        } elseif ($timeout < 0) {
            throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
        }

        return $timeout;
    }

    /**
     * Reads pipes, executes callback.
     *
     * @param bool $blocking Whether to use blocking calls or not
     * @param bool $close    Whether to close file handles or not
     */
    private function readPipes($blocking, $close)
    {
        $result = $this->processPipes->readAndWrite($blocking, $close);

        $callback = $this->callback;
        foreach ($result as $type => $data) {
            if (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 = [];
        $this->processInformation = null;
        $this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+b');
        $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+b');
        $this->process = null;
        $this->latestSignal = null;
        $this->status = self::STATUS_READY;
        $this->incrementalOutputOffset = 0;
        $this->incrementalErrorOutputOffset = 0;
    }

    /**
     * Sends a POSIX signal to the process.
     *
     * @param int  $signal         A valid POSIX signal (see https://php.net/pcntl.constants)
     * @param bool $throwException Whether to throw exception in case signal failed
     *
     * @return bool True if the signal was sent successfully, false otherwise
     *
     * @throws LogicException   In case the process is not running
     * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
     * @throws RuntimeException In case of failure
     */
    private function doSignal($signal, $throwException)
    {
        if (null === $pid = $this->getPid()) {
            if ($throwException) {
                throw new LogicException('Can not send signal on a non running process.');
            }

            return false;
        }

        if ('\\' === \DIRECTORY_SEPARATOR) {
            exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode);
            if ($exitCode && $this->isRunning()) {
                if ($throwException) {
                    throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output)));
                }

                return false;
            }
        } else {
            if (!$this->enhanceSigchildCompatibility || !$this->isSigchildEnabled()) {
                $ok = @proc_terminate($this->process, $signal);
            } elseif (\function_exists('posix_kill')) {
                $ok = @posix_kill($pid, $signal);
            } elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), [2 => ['pipe', 'w']], $pipes)) {
                $ok = false === fgets($pipes[2]);
            }
            if (!$ok) {
                if ($throwException) {
                    throw new RuntimeException(sprintf('Error while sending signal `%s`.', $signal));
                }

                return false;
            }
        }

        $this->latestSignal = (int) $signal;
        $this->fallbackStatus['signaled'] = true;
        $this->fallbackStatus['exitcode'] = -1;
        $this->fallbackStatus['termsig'] = $this->latestSignal;

        return true;
    }

    private function prepareWindowsCommandLine($cmd, array &$env)
    {
        $uid = uniqid('', true);
        $varCount = 0;
        $varCache = [];
        $cmd = preg_replace_callback(
            '/"(?:(
                [^"%!^]*+
                (?:
                    (?: !LF! | "(?:\^[%!^])?+" )
                    [^"%!^]*+
                )++
            ) | [^"]*+ )"/x',
            function ($m) use (&$env, &$varCache, &$varCount, $uid) {
                if (!isset($m[1])) {
                    return $m[0];
                }
                if (isset($varCache[$m[0]])) {
                    return $varCache[$m[0]];
                }
                if (false !== strpos($value = $m[1], "\0")) {
                    $value = str_replace("\0", '?', $value);
                }
                if (false === strpbrk($value, "\"%!\n")) {
                    return '"'.$value.'"';
                }

                $value = str_replace(['!LF!', '"^!"', '"^%"', '"^^"', '""'], ["\n", '!', '%', '^', '"'], $value);
                $value = '"'.preg_replace('/(\\\\*)"/', '$1$1\\"', $value).'"';
                $var = $uid.++$varCount;

                $env[$var] = $value;

                return $varCache[$m[0]] = '!'.$var.'!';
            },
            $cmd
        );

        $cmd = 'cmd /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')';
        static $comSpec;
 
        if (!$comSpec && $comSpec = (new ExecutableFinder())->find('cmd.exe')) {
            // Escape according to CommandLineToArgvW rules
            $comSpec = '"'.preg_replace('{(\\\\*+)"}', '$1$1\"', $comSpec) .'"';
        }
 
        $cmd = ($comSpec !== null ? $comSpec : 'cmd').' /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')';

        foreach ($this->processPipes->getFiles() as $offset => $filename) {
            $cmd .= ' '.$offset.'>"'.$filename.'"';
        }

        return $cmd;
    }

    /**
     * Ensures the process is running or terminated, throws a LogicException if the process has a not started.
     *
     * @param string $functionName The function name that was called
     *
     * @throws LogicException if the process has not run
     */
    private function requireProcessIsStarted($functionName)
    {
        if (!$this->isStarted()) {
            throw new LogicException(sprintf('Process must be started before calling "%s()".', $functionName));
        }
    }

    /**
     * Ensures the process is terminated, throws a LogicException if the process has a status different than `terminated`.
     *
     * @param string $functionName The function name that was called
     *
     * @throws LogicException if the process is not yet terminated
     */
    private function requireProcessIsTerminated($functionName)
    {
        if (!$this->isTerminated()) {
            throw new LogicException(sprintf('Process must be terminated before calling "%s()".', $functionName));
        }
    }

    /**
     * Escapes a string to be used as a shell argument.
     *
     * @param string $argument The argument that will be escaped
     *
     * @return string The escaped argument
     */
    private function escapeArgument($argument)
    {
        if ('\\' !== \DIRECTORY_SEPARATOR) {
            return "'".str_replace("'", "'\\''", $argument)."'";
        }
        if ('' === $argument = (string) $argument) {
            return '""';
        }
        if (false !== strpos($argument, "\0")) {
            $argument = str_replace("\0", '?', $argument);
        }
        if (!preg_match('/[\/()%!^"<>&|\s]/', $argument)) {
            return $argument;
        }
        $argument = preg_replace('/(\\\\+)$/', '$1$1', $argument);

        return '"'.str_replace(['"', '^', '%', '!', "\n"], ['""', '"^^"', '"^%"', '"^!"', '!LF!'], $argument).'"';
    }

    private function getDefaultEnv()
    {
        $env = [];

        foreach ($_SERVER as $k => $v) {
            if (\is_string($v) && false !== $v = getenv($k)) {
                $env[$k] = $v;
            }
        }

        foreach ($_ENV as $k => $v) {
            if (\is_string($v)) {
                $env[$k] = $v;
            }
        }

        return $env;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@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 = null)
    {
        $executableFinder = new PhpExecutableFinder();
        if (false === $php = $executableFinder->find(false)) {
            $php = null;
        } else {
            $php = array_merge([$php], $executableFinder->findArguments());
        }
        if ('phpdbg' === \PHP_SAPI) {
            $file = tempnam(sys_get_temp_dir(), 'dbg');
            file_put_contents($file, $script);
            register_shutdown_function('unlink', $file);
            $php[] = $file;
            $script = null;
        }
        if (null !== $options) {
            @trigger_error(sprintf('The $options parameter of the %s constructor is deprecated since Symfony 3.3 and will be removed in 4.0.', __CLASS__), \E_USER_DEPRECATED);
        }

        parent::__construct($php, $cwd, $env, $script, $timeout, $options);
    }

    /**
     * Sets the path to the PHP binary to use.
     */
    public function setPhpBinary($php)
    {
        $this->setCommandLine($php);
    }

    /**
     * {@inheritdoc}
     */
    public function start(callable $callback = null/*, array $env = []*/)
    {
        if (null === $this->getCommandLine()) {
            throw new RuntimeException('Unable to find the PHP executable.');
        }
        $env = 1 < \func_num_args() ? func_get_arg(1) : null;

        parent::start($callback, $env);
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process;

/**
 * An executable finder specifically designed for the PHP executable.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Johannes M. Schmitt <schmittjoh@gmail.com>
 */
class PhpExecutableFinder
{
    private $executableFinder;

    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)
    {
        if ($php = getenv('PHP_BINARY')) {
            if (!is_executable($php) && !$php = $this->executableFinder->find($php)) {
                return false;
            }

            if (@is_dir($php)) {
                return false;
            }

            return $php;
        }

        $args = $this->findArguments();
        $args = $includeArgs && $args ? ' '.implode(' ', $args) : '';

        // HHVM support
        if (\defined('HHVM_VERSION')) {
            return (getenv('PHP_BINARY') ?: \PHP_BINARY).$args;
        }

        // PHP_BINARY return the current sapi executable
        if (\PHP_BINARY && \in_array(\PHP_SAPI, ['cli', 'cli-server', 'phpdbg'], true)) {
            return \PHP_BINARY.$args;
        }

        if ($php = getenv('PHP_PATH')) {
            if (!@is_executable($php)) {
                return false;
            }

            return $php;
        }

        if ($php = getenv('PHP_PEAR_PHP_BIN')) {
            if (@is_executable($php)) {
                return $php;
            }
        }

        if (@is_executable($php = \PHP_BINDIR.('\\' === \DIRECTORY_SEPARATOR ? '\\php.exe' : '/php'))) {
            return $php;
        }

        $dirs = [\PHP_BINDIR];
        if ('\\' === \DIRECTORY_SEPARATOR) {
            $dirs[] = 'C:\xampp\php\\';
        }

        return $this->executableFinder->find('php', false, $dirs);
    }

    /**
     * Finds the PHP executable arguments.
     *
     * @return array The PHP executable arguments
     */
    public function findArguments()
    {
        $arguments = [];

        if (\defined('HHVM_VERSION')) {
            $arguments[] = '--php';
        } elseif ('phpdbg' === \PHP_SAPI) {
            $arguments[] = '-qrr';
        }

        return $arguments;
    }
}
<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Process;

use Symfony\Component\Process\Exception\RuntimeException;

/**
 * Provides a way to continuously write to the input of a Process until the InputStream is closed.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 */
class InputStream implements \IteratorAggregate
{
    /** @var callable|null */
    private $onEmpty = null;
    private $input = [];
    private $open = true;

    /**
     * Sets a callback that is called when the write buffer becomes empty.
     */
    public function onEmpty(callable $onEmpty = null)
    {
        $this->onEmpty = $onEmpty;
    }

    /**
     * Appends an input to the write buffer.
     *
     * @param resource|string|int|float|bool|\Traversable|null $input The input to append as scalar,
     *                                                                stream resource or \Traversable
     */
    public function write($input)
    {
        if (null === $input) {
            return;
        }
        if ($this->isClosed()) {
            throw new RuntimeException(sprintf('"%s" is closed.', static::class));
        }
        $this->input[] = ProcessUtils::validateInput(__METHOD__, $input);
    }

    /**
     * Closes the write buffer.
     */
    public function close()
    {
        $this->open = false;
    }

    /**
     * Tells whether the write buffer is closed or not.
     */
    public function isClosed()
    {
        return !$this->open;
    }

    public function getIterator()
    {
        $this->open = true;

        while ($this->open || $this->input) {
            if (!$this->input) {
                yield '';
                continue;
            }
            $current = array_shift($this->input);

            if ($current instanceof \Iterator) {
                foreach ($current as $cur) {
                    yield $cur;
                }
            } else {
                yield $current;
            }
            if (!$this->input && $this->open && null !== $onEmpty = $this->onEmpty) {
                $this->write($onEmpty($this));
            }
        }
    }
}
<?php

namespace eftec\bladeone;

/**
 * Trait BladeOneLang
 * It adds the next tags
 * <code>
 * select:
 * @ _e('hello')
 * @ _n('Product','Products',$n)
 * @ _ef('hello %s',$user)
 * </code>
 *
 * @package    eftec\bladeone
 * @version    1.1 2019-08-09
 * @link       https://github.com/EFTEC/BladeOne
 * @author     Jorge Patricio Castro Castillo <jcastro arroba eftec dot cl>
 * @copyright  2017 Jorge Patricio Castro Castillo MIT License. Don't delete this comment, its part of the license.
 * @deprecated Note: It is not needing anymore (BladeOne already includes the same functionalities). It is keep for compatibility purpose.
 */
trait BladeOneLang
{
    /** @var string The path to the missing translations log file. If empty then every missing key is not saved. */
    public $missingLog = '';

    /** @var array Hold dictionary of translations */
    public static $dictionary = [];

    /**
     * Tries to translate the word if its in the array defined by BladeOneLang::$dictionary
     * If the operation fails then, it returns the original expression without translation.
     *
     * @param $phrase
     *
     * @return string
     */
    public function _e($phrase)
    {
        if ((!\array_key_exists($phrase, static::$dictionary))) {
            $this->missingTranslation($phrase);
            return $phrase;
        } else {
            return static::$dictionary[$phrase];
        }
    }

    /**
     * Its the same than @_e, however it parses the text (using sprintf).
     * If the operation fails then, it returns the original expression without translation.
     *
     * @param $phrase
     *
     * @return string
     */
    public function _ef($phrase)
    {
        $argv = \func_get_args();
        $r = $this->_e($phrase);
        $argv[0] = $r; // replace the first argument with the translation.
        $result = @\call_user_func_array("sprintf", $argv);
        $result = ($result === false) ? $r : $result;
        return $result;
    }

    /**
     * if num is more than one then it returns the phrase in plural, otherwise the phrase in singular.
     * Note: the translation should be as follow: $msg['Person']='Person' $msg=['Person']['p']='People'
     *
     * @param string $phrase
     * @param string $phrases
     * @param int    $num
     *
     * @return string
     */
    public function _n($phrase, $phrases, $num = 0)
    {
        if ((!\array_key_exists($phrase, static::$dictionary))) {
            $this->missingTranslation($phrase);
            return ($num <= 1) ? $phrase : $phrases;
        } else {
            return ($num <= 1) ? $this->_e($phrase) : $this->_e($phrases);
        }
    }

    //<editor-fold desc="compile">

    /**
     * Used for @_e directive.
     *
     * @param $expression
     *
     * @return string
     */
    protected function compile_e($expression)
    {
        return $this->phpTag . "echo \$this->_e{$expression}; ?>";
    }

    /**
     * Used for @_ef directive.
     *
     * @param $expression
     *
     * @return string
     */
    protected function compile_ef($expression)
    {
        return $this->phpTag . "echo \$this->_ef{$expression}; ?>";
    }

    /**
     * Used for @_n directive.
     *
     * @param $expression
     *
     * @return string
     */
    protected function compile_n($expression)
    {
        return $this->phpTag . "echo \$this->_n{$expression}; ?>";
    }

    //</editor-fold>

    /**
     * Log a missing translation into the file $this->missingLog.<br>
     * If the file is not defined, then it doesn't write the log.
     *
     * @param string $txt Message to write on.
     */
    private function missingTranslation($txt)
    {
        if (!$this->missingLog) {
            return; // if there is not a file assigned then it skips saving.
        }

        $fz = @\filesize($this->missingLog);
        $mode = 'a';

        if (\is_object($txt) || \is_array($txt)) {
            $txt = \print_r($txt, true);
        }

        // Rewrite file if more than 100000 bytes
        if ($fz > 100000) {
            $mode = 'w';
        }

        $fp = \fopen($this->missingLog, 'w');
        \fwrite($fp, $txt . "\n");
        \fclose($fp);
    }
}
<?php /** @noinspection HtmlUnknownTarget */
/** @noinspection HtmlUnknownAttribute */
/** @noinspection PhpFullyQualifiedNameUsageInspection */

/** @noinspection PhpUnused */

namespace eftec\bladeone;

/**
 * trait BladeOneHtml
 * Copyright (c) 2016 Jorge Patricio Castro Castillo MIT License. Don't delete this comment, its part of the license.
 * Extends the tags of the class BladeOne.  Its optional
 * It adds the next tags
 * <code>
 * select:
 * @ select('idCountry','value',[,$extra])
 * @ item('0','--select a country'[,$extra])
 * @ items($countries,'id','name',$currentCountry[,$extra])
 * @ endselect()
 * input:
 * @ input('iduser',$currentUser,'text'[,$extra])
 * button:
 * @ commandbutton('idbutton','value','text'[,$extra])
 *
 * </code>
 * Note. The names of the tags are based in Java Server Faces (JSF)
 *
 * @package  BladeOneHtml
 * @version  1.9.2 2020-05-28 (1)
 * @link     https://github.com/EFTEC/BladeOne
 * @author   Jorge Patricio Castro Castillo <jcastro arroba eftec dot cl>
 * @deprecated use https://github.com/eftec/BladeOneHtml
 */
trait BladeOneHtml
{
    protected $htmlItem = []; // indicates the type of the current tag. such as select/selectgroup/etc.
    protected $htmlCurrentId = []; //indicates the id of the current tag.

    //<editor-fold desc="compile function">
    protected function compileSelect($expression)
    {
        $this->htmlItem[] = 'select';
        return $this->phpTag . "echo \$this->select{$expression}; ?>";
    }

    protected function compileListBoxes($expression)
    {
        return $this->phpTag . "echo \$this->listboxes{$expression}; ?>";
    }

    protected function compileLink($expression)
    {
        return $this->phpTag . "echo \$this->link{$expression}; ?>";
    }

    protected function compileSelectGroup($expression)
    {
        $this->htmlItem[] = 'selectgroup';
        $this->compilePush('');
        return $this->phpTag . "echo \$this->select{$expression}; ?>";
    }

    protected function compileRadio($expression)
    {
        $this->htmlItem[] = 'radio';
        return $this->phpTag . "echo \$this->radio{$expression}; ?>";
    }

    protected function compileCheckbox($expression)
    {
        $this->htmlItem[] = 'checkbox';
        return $this->phpTag . "echo \$this->checkbox{$expression}; ?>";
    }

    protected function compileEndSelect()
    {
        $r = @\array_pop($this->htmlItem);
        if (\is_null($r)) {
            $this->showError("@endselect", "Missing @select or so many @endselect", true);
        }
        return $this->phpTag . "echo '</select>'; ?>";
    }

    protected function compileEndRadio()
    {
        $r = @\array_pop($this->htmlItem);
        if (\is_null($r)) {
            return $this->showError("@EndRadio", "Missing @Radio or so many @EndRadio", true);
        }
        return '';
    }

    protected function compileEndCheckbox()
    {
        $r = @\array_pop($this->htmlItem);
        if (\is_null($r)) {
            return $this->showError("@EndCheckbox", "Missing @Checkbox or so many @EndCheckbox", true);
        }
        return '';
    }

    protected function compileItem($expression)
    {
        // we add a new attribute with the type of the current open tag
        $r = \end($this->htmlItem);
        $x = \trim($expression);
        $x = "('{$r}'," . \substr($x, 1);
        return $this->phpTag . "echo \$this->item{$x}; ?>";
    }

    protected function compileItems($expression)
    {
        // we add a new attribute with the type of the current open tag
        $r = \end($this->htmlItem);
        $x = \trim($expression);
        $x = "('{$r}'," . \substr($x, 1);
        return $this->phpTag . "echo \$this->items{$x}; ?>";
    }

    protected function compileTrio($expression)
    {
        // we add a new attribute with the type of the current open tag
        $r = \end($this->htmlItem);
        $x = \trim($expression);
        $x = "('{$r}'," . \substr($x, 1);
        return $this->phpTag . "echo \$this->trio{$x}; ?>";
    }

    protected function compileTrios($expression)
    {
        // we add a new attribute with the type of the current open tag
        $r = \end($this->htmlItem);
        $x = \trim($expression);
        $x = "('{$r}'," . \substr($x, 1);
        return $this->phpTag . "echo \$this->trios{$x}; ?>";
    }

    protected function compileInput($expression)
    {
        return $this->phpTag . "echo \$this->input{$expression}; ?>";
    }

    protected function compileFile($expression)
    {
        return $this->phpTag . "echo \$this->file{$expression}; ?>";
    }

    protected function compileImage($expression)
    {
        return $this->phpTag . "echo \$this->image{$expression}; ?>";
    }

    protected function compileTextArea($expression)
    {
        return $this->phpTag . "echo \$this->textArea{$expression}; ?>";
    }

    protected function compileHidden($expression)
    {
        return $this->phpTag . "echo \$this->hidden{$expression}; ?>";
    }

    protected function compileLabel($expression)
    {
        return $this->phpTag . "// {$expression} \n echo \$this->label{$expression}; ?>";
    }

    protected function compileCommandButton($expression)
    {
        return $this->phpTag . "echo \$this->commandButton{$expression}; ?>";
    }

    protected function compileForm($expression)
    {
        return $this->phpTag . "echo \$this->form{$expression}; ?>";
    }

    protected function compileEndForm()
    {
        return $this->phpTag . "echo '</form>'; ?>";
    }
    //</editor-fold>

    //<editor-fold desc="used function">
    public function select($name, $value, $extra = '')
    {
        if (\strpos($extra, 'readonly') === false) {
            return "<select id='" . static::e($name) . "' name='" . static::e($name) . "' {$this->convertArg($extra)}>\n";
        }

        return "
            <input id='" . static::e($name) . "' name='" . static::e($name) . "' type='hidden' value='" . static::e($value) . "' />
            <select id='" . static::e($name) . "_disable' name='" . static::e($name) . "_disable' disabled {$this->convertArg($extra)}>\n";
    }

    public function link($url, $label, $extra = '')
    {
        return "<a href='{$url}' {$this->convertArg($extra)}>{$label}</a>";
    }

    /**
     * Find an element in a array of arrays
     * If the element doesn't exist in the array then it returns false, otherwise returns true
     *
     * @param string $find
     * @param array $array array of primitives or objects
     * @param string $field field to search
     * @return bool
     */
    private function listboxesFindArray($find, $array, $field)
    {
        if (\count($array) == 0) {
            return false;
        }
        if (!\is_array($array[0])) {
            return \in_array($find, $array);
        }

        foreach ($array as $elem) {
            if ($elem[$field] == $find) {
                return true;
            }
        }
        return false;
    }

    public function listboxes($name, $allvalues, $fieldId, $fieldText, $selectedId, $extra = '')
    {
        $html = "";
        $html .= "<table>\n";
        $html .= "    <tr>\n";
        $html .= "	    <td>\n";
        $html .= "		    <select id='{$name}_noselected' size='6' multiple='multiple' $extra>\n";
        if (\count($allvalues) == 0) {
            $allvalues = [];
        }
        $html2 = "";
        foreach ($allvalues as $v) {
            if (\is_object($v)) {
                $v = (array)$v;
            }
            if (!$this->listboxesFindArray($v[$fieldId], $selectedId, $fieldId)) {
                $html .= "<option value='" . $v[$fieldId] . "'>" . $v[$fieldText] . "</option>\n";
            } else {
                $html2 .= "<option value='" . $v[$fieldId] . "'>" . $v[$fieldText] . "</option>\n";
            }
        }
        $html .= "			</select>\n";
        $html .= "		</td>\n";
        $html .= "		<td style='text-align:center;'>\n";
        $html .= "            <input type='button' value='>' id='{$name}_add'/><br>\n";
        $html .= "            <input type='button' value='>>' id='{$name}_addall'/><br>\n";
        $html .= "            <input type='button' value='<' id='{$name}_delete'/><br>\n";
        $html .= "            <input type='button' value='<<' id='{$name}_deleteall'/><br>\n";
        $html .= "		</td>\n";
        $html .= "		<td>\n";
        $html .= "			<select id='{$name}' name='{$name}' size='6' multiple='multiple'>\n";
        $html .= $html2;
        $html .= "			</select>\n";
        $html .= "		</td>\n";
        $html .= "	</tr>\n";
        $html .= "</table>\n";
        return $html;
    }

    public function selectGroup($name, $extra = '')
    {
        return $this->selectGroup($name, $extra);
    }

    public function radio($id, $value = '', $text = '', $valueSelected = '', $extra = '')
    {
        $num = \func_num_args();
        if ($num > 2) {
            if ($value == $valueSelected) {
                if (\is_array($extra)) {
                    $extra['checked'] = 'checked';
                } else {
                    $extra .= ' checked="checked"';
                }
            }
            return $this->input($id, $value, 'radio', $extra) . ' ' . $text;
        }

        $this->htmlCurrentId[] = $id;
        return '';
    }

    /**
     * @param             $id
     * @param string      $value
     * @param string      $text
     * @param string|null $valueSelected
     * @param string|array      $extra
     * @return string
     */
    public function checkbox($id, $value = '', $text = '', $valueSelected = '', $extra = '')
    {
        $num = \func_num_args();
        if ($num > 2) {
            if ($value == $valueSelected) {
                if (\is_array($extra)) {
                    $extra['checked'] = 'checked';
                } else {
                    $extra .= ' checked="checked"';
                }
            }
            return $this->input($id, $value, 'checkbox', $extra) . ' ' . $text;
        }

        $this->htmlCurrentId[] = $id;
        return '';
    }

    /**
     * @param string       $type         type of the current open tag
     * @param array|string $valueId      if is an array then the first value is used as value, the second is used as
     *                                   extra
     * @param              $valueText
     * @param array|string $selectedItem Item selected (optional)
     * @param string       $wrapper      Wrapper of the element.  For example, <li>%s</li>
     * @param string       $extra
     * @return string
     * @internal param string $fieldId Field of the id
     * @internal param string $fieldText Field of the value visible
     */
    public function item($type, $valueId, $valueText, $selectedItem = '', $wrapper = '', $extra = '')
    {
        $id = @\end($this->htmlCurrentId);
        $wrapper = ($wrapper == '') ? '%s' : $wrapper;
        if (\is_array($selectedItem)) {
            $found = \in_array($valueId, $selectedItem);
        } else {
            $found = $valueId == $selectedItem;
        }

        $valueHtml = (!\is_array($valueId)) ? "value='{$valueId}'" : "value='{$valueId[0]}' data='{$valueId[1]}'";
        switch ($type) {
            case 'select':
                $selected = ($found) ? 'selected' : '';
                return \sprintf($wrapper, "<option $valueHtml $selected " .
                    $this->convertArg($extra) . ">{$valueText}</option>\n");
                break;
            case 'radio':
                $selected = ($found) ? 'checked' : '';
                return \sprintf($wrapper, "<input type='radio' id='" . static::e($id)
                    . "' name='" . static::e($id) . "' $valueHtml $selected "
                    . $this->convertArg($extra) . "> {$valueText}\n");
                break;
            case 'checkbox':
                $selected = ($found) ? 'checked' : '';
                return \sprintf($wrapper, "<input type='checkbox' id='" . static::e($id)
                    . "' name='" . static::e($id) . "' $valueHtml $selected "
                    . $this->convertArg($extra) . "> {$valueText}\n");
                break;

            default:
                return '???? type undefined: [$type] on @item<br>';
        }
    }

    /**
     * @param string       $type            type of the current open tag
     * @param array        $arrValues       Array of objects/arrays to show.
     * @param string       $fieldId         Field of the id (for arrValues)
     * @param string       $fieldText       Field of the id of selectedItem
     * @param array|string $selectedItem    Item selected (optional)
     * @param string       $selectedFieldId field of the selected item.
     * @param string       $wrapper         Wrapper of the element.  For example, <li>%s</li>
     * @param string       $extra           (optional) is used for add additional information for the html object (such
     *                                      as class)
     * @return string
     * @version 1.1 2017
     */
    public function items(
        $type,
        $arrValues,
        $fieldId,
        $fieldText,
        $selectedItem = '',
        $selectedFieldId = '',
        $wrapper = '',
        $extra = ''
    ) {
        if (\count($arrValues) == 0) {
            return "";
        }

        if (\is_object(@$arrValues[0])) {
            $arrValues = (array)$arrValues;
        }
        if (\is_array($selectedItem)) {
            if (\is_object(@$selectedItem[0])) {
                $primitiveArray = [];
                foreach ($selectedItem as $v) {
                    $primitiveArray[] = $v->{$selectedFieldId};
                }
                $selectedItem = $primitiveArray;
            }
        }
        $result = '';
        if (\is_object($selectedItem)) {
            $selectedItem = (array)$selectedItem;
        }
        foreach ($arrValues as $v) {
            if (\is_object($v)) {
                $v = (array)$v;
            }
            $result .= $this->item($type, $v[$fieldId], $v[$fieldText], $selectedItem, $wrapper, $extra);
        }
        return $result;
    }

    /**
     * @param string       $type         type of the current open tag
     * @param string       $valueId      value of the trio
     * @param string       $valueText    visible value of the trio.
     * @param string       $value3       extra third value for select value or visual
     * @param array|string $selectedItem Item selected (optional)
     * @param string       $wrapper      Wrapper of the element.  For example, <li>%s</li>
     * @param string       $extra
     * @return string
     * @internal param string $fieldId Field of the id
     * @internal param string $fieldText Field of the value visible
     */
    public function trio($type, $valueId, $valueText, $value3 = '', $selectedItem = '', $wrapper = '', $extra = '')
    {
        $id = @\end($this->htmlCurrentId);
        $wrapper = ($wrapper == '') ? '%s' : $wrapper;
        if (\is_array($selectedItem)) {
            $found = \in_array($valueId, $selectedItem);
        } else {
            $found = $valueId == $selectedItem;
        }
        switch ($type) {
            case 'selectgroup':
                $selected = ($found) ? 'selected' : '';
                return \sprintf($wrapper, "<option value='{$valueId}' $selected " .
                    $this->convertArg($extra) . ">{$valueText}</option>\n");
                break;
            default:
                return '???? type undefined: [$type] on @item<br>';
        }
    }

    /**
     * @param string       $type         type of the current open tag
     * @param array        $arrValues    Array of objects/arrays to show.
     * @param string       $fieldId      Field of the id
     * @param string       $fieldText    Field of the value visible
     * @param string       $fieldThird
     * @param array|string $selectedItem Item selected (optional)
     * @param string       $wrapper      Wrapper of the element.  For example, <li>%s</li>
     * @param string       $extra        (optional) is used for add additional information for the html object (such as
     *                                   class)
     * @return string
     * @version 1.0
     */
    public function trios(
        $type,
        $arrValues,
        $fieldId,
        $fieldText,
        $fieldThird,
        $selectedItem = '',
        $wrapper = '',
        $extra = ''
    ) {
        if (\count($arrValues) === 0) {
            return "";
        }
        if (\is_object($arrValues[0])) {
            $arrValues = (array)$arrValues;
        }
        $result = '';
        $oldV3 = "";
        foreach ($arrValues as $v) {
            if (\is_object($v)) {
                $v = (array)$v;
            }
            $v3 = $v[$fieldThird];
            if ($type === 'selectgroup') {
                if ($v3 != $oldV3) {
                    if ($oldV3 != "") {
                        $result .= "</optgroup>";
                    }
                    $oldV3 = $v3;
                    $result .= "<optgroup label='{$v3}'>";
                }
            }
            if ($result) {
                $result .= $this->trio($type, $v[$fieldId], $v[$fieldText], $v3, $selectedItem, $wrapper, $extra);
            }
        }
        if ($type === 'selectgroup' && $oldV3 != "") {
            $result .= "</optgroup>";
        }
        return $result;
    }
    protected $paginationStructure=['selHtml'=>'<li class="selected" %3s><a href="%1s">%2s</a></li>'
                                    ,'html'=>'<li %3s><a href="%1s">%2s</a></li>'
                                    ,'maxItem'=>5
                                    ,'url'=>''];
    public function pagination($id, $curPage, $maxPage, $baseUrl, $extra='')
    {
        $r="<ul $extra>";
        
        $r.="</ul>";
        return $r;
    }

    public function input($id, $value = '', $type = 'text', $extra = '')
    {
        return "<input id='" . static::e($id) . "' name='" . static::e($id) . "' type='" . $type . "' " . $this->convertArg($extra) . " value='" . static::e($value) . "' />\n";
    }

    public function file($id, $fullfilepath = '', $file = '', $extra = '')
    {
        return "<a href='$fullfilepath'>$file</a>
        <input id='" . static::e($id) . "_file' name='" . static::e($id) . "_file' type='hidden' value='" . static::e($file) . "' />
        <input id='" . static::e($id) . "' name='" . static::e($id) . "' type='file' " . $this->convertArg($extra) . " value='" . static::e($fullfilepath) . "' />\n";
    }

    public function textArea($id, $value = '', $extra = '')
    {
        $value = \str_replace('\n', "\n", $value);
        return "<textarea id='" . static::e($id) . "' name='" . static::e($id) . "' " . $this->convertArg($extra) . " >$value</textarea>\n";
    }

    public function hidden($id, $value = '', $extra = '')
    {
        return $this->input($id, $value, 'hidden', $extra);
    }

    public function label($id, $value = '', $extra = '')
    {
        return "<label for='{$id}' {$this->convertArg($extra)}>{$value}</label>";
    }

    public function commandButton($id, $value = '', $text = 'Button', $type = 'submit', $extra = '')
    {
        return "<button type='{$type}' id='" . static::e($id) . "' name='" . static::e($id) . "' value='" . static::e($value) . "' {$this->convertArg($extra)}>{$text}</button>\n";
    }

    public function form($action, $method = 'post', $extra = '')
    {
        return "<form action='{$action}' method='{$method}' {$this->convertArg($extra)}>";
    }

    //</editor-fold>
}
<?php


namespace eftec\bladeone;

use Redis;

/**
 * trait BladeOneCacheRedis
 * Copyright (c) 2016 Jorge Patricio Castro Castillo MIT License. Don't delete this comment, its part of the license.
 * Extends the tags of the class BladeOne.  Its optional
 * It adds the next tags to the template
 * <code>
 * @ cache([cacheid],[duration=86400]).  The id is optional. The duration of the cache is in seconds
 * // content here
 * @ endcache()
 * </code>
 * It also adds a new function (optional) to the business or logic layer
 * <code>
 * if ($blade->cacheExpired('hellocache',1,5)) {   //'helloonecache' =template, =1 id cache, 5=duration (seconds)
 *    // cache expired, so we should do some stuff (such as read from the database)
 * }
 * </code>
 *
 * @package  BladeOneCacheRedis
 * @version  0.1 2017-12-15 NOT YET IMPLEMENTED, ITS A WIP!!!!!!!!
 * @link     https://github.com/EFTEC/BladeOne
 * @author   Jorge Patricio Castro Castillo <jcastro arroba eftec dot cl>
 */
const CACHEREDIS_SCOPEURL = 1;

trait BladeOneCacheRedis
{
    protected $curCacheId = 0;
    protected $curCacheDuration = "";
    protected $curCachePosition = 0;
    protected $cacheRunning = false;
    /** @var \Redis $redis */
    protected $redis;
    protected $redisIP = '127.0.0.1';
    protected $redisPort = 6379;
    protected $redisTimeOut = 2.5;
    protected $redisConnected = false;
    protected $redisNamespace = 'bladeonecache:';
    protected $redisBase = 0;
    private $cacheExpired = []; // avoids to compare the file different times.

    //<editor-fold desc="compile">
    public function compileCache($expression)
    {
        // get id of template
        // if the file exists then
        //     compare date.
        //     if the date is too old then re-save.
        // else
        // save for the first time.

        return $this->phpTag . "echo \$this->cacheStart{$expression}; if(!\$this->cacheRunning) { ?>";
    }

    public function compileEndCache($expression)
    {
        return $this->phpTag . "} // if cacheRunning\necho \$this->cacheEnd{$expression}; ?>";
    }
    //</editor-fold>

    public function connect($redisIP = null, $redisPort = null, $redisTimeOut = null)
    {
        if ($this->redisConnected) {
            return true;
        }
        if (!\class_exists('Redis')) {
            return false; // it requires redis.
        }
        if ($redisIP !== null) {
            $this->redisIP = $redisIP;
            $this->redisPort = $redisPort;
            $this->redisTimeOut = $redisTimeOut;
        }
        $this->redis = new Redis();
        // 2.5 sec timeout.
        $this->redisConnected = $this->redis->connect($this->redisIP, $this->redisPort, $this->redisTimeOut);

        return $this->redisConnected;
    }

    /**
     * Returns true if the cache expired (or doesn't exist), otherwise false.
     *
     * @param string $templateName  name of the template to use (such hello for template hello.blade.php)
     * @param string $id            (id of cache, optional, if not id then it adds automatically a number)
     * @param int    $scope         scope of the cache.
     * @param int    $cacheDuration (duration of the cache in seconds)
     * @return bool (return if the cache expired)
     */
    public function cacheExpired($templateName, $id, $scope, $cacheDuration)
    {
        if ($this->getMode() & 1) {
            return true; // forced mode, hence it always expires. (fast mode is ignored).
        }
        $compiledFile = $this->getCompiledFile($templateName) . '_cache' . $id;
        if (isset($this->cacheExpired[$compiledFile])) {
            // if the information is already in the array then returns it.
            return $this->cacheExpired[$compiledFile];
        }
        $date = @\filemtime($compiledFile);
        if ($date) {
            if ($date + $cacheDuration < \time()) {
                $this->cacheExpired[$compiledFile] = true;
                return true; // time-out.
            }
        } else {
            $this->cacheExpired[$compiledFile] = true;
            return true; // no file
        }
        $this->cacheExpired[$compiledFile] = false;
        return false; // cache active.
    }

    public function cacheStart($id = "", $cacheDuration = 86400)
    {
        $this->curCacheId = ($id == "") ? ($this->curCacheId + 1) : $id;
        $this->curCacheDuration = $cacheDuration;
        $this->curCachePosition = \strlen(\ob_get_contents());
        $compiledFile = $this->getCompiledFile() . '_cache' . $this->curCacheId;
        if ($this->cacheExpired('', $id, $cacheDuration)) {
            $this->cacheRunning = false;
        } else {
            $this->cacheRunning = true;
            $content = $this->getFile($compiledFile);
            echo $content;
        }
        // getFile($fileName)
    }

    public function cacheEnd()
    {
        if (!$this->cacheRunning) {
            $txt = \substr(\ob_get_contents(), $this->curCachePosition);
            $compiledFile = $this->getCompiledFile() . '_cache' . $this->curCacheId;
            \file_put_contents($compiledFile, $txt);
        }
        $this->cacheRunning = false;
    }

    private function keyByScope($scope)
    {
        $key = '';
        if ($scope && CACHEREDIS_SCOPEURL) {
            $key .= $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];
        }
    }
}
<?php /** @noinspection PhpUnusedParameterInspection */

/** @noinspection SyntaxError
 * @noinspection ForgottenDebugOutputInspection
 * @noinspection UnknownInspectionInspection
 * @noinspection TypeUnsafeComparisonInspection
 * @noinspection NonSecureExtractUsageInspection
 * @noinspection PregQuoteUsageInspection
 * @noinspection NotOptimalRegularExpressionsInspection
 * @noinspection SubStrUsedAsStrPosInspection
 * @noinspection ThrowRawExceptionInspection
 * @noinspection Annotator
 * @noinspection IsNullFunctionUsageInspection
 * @noinspection CallableParameterUseCaseInTypeContextInspection
 * @noinspection PhpUnused
 * @noinspection PhpFullyQualifiedNameUsageInspection
 * @noinspection PhpComposerExtensionStubsInspection
 * @noinspection Php
 */

namespace eftec\bladeone;

use ArrayAccess;
use BadMethodCallException;
use Closure;
use Countable;
use Exception;
use InvalidArgumentException;

/**
 * BladeOne - A Blade Template implementation in a single file
 *
 * @package   BladeOne
 * @author    Jorge Patricio Castro Castillo <jcastro arroba eftec dot cl>
 * @copyright Copyright (c) 2016-2021 Jorge Patricio Castro Castillo MIT License.
 *            Don't delete this comment, its part of the license.
 *            Part of this code is based in the work of Laravel PHP Components.
 * @version   3.52
 * @link      https://github.com/EFTEC/BladeOne
 */
class BladeOne
{
    //<editor-fold desc="fields">

    /** @var int BladeOne reads if the compiled file has changed. If has changed,then the file is replaced. */
    const MODE_AUTO = 0;
    /** @var int Then compiled file is always replaced. It's slow and it's useful for development. */
    const MODE_SLOW = 1;
    /** @var int The compiled file is never replaced. It's fast and it's useful for production. */
    const MODE_FAST = 2;
    /** @var int DEBUG MODE, the file is always compiled and the filename is identifiable. */
    const MODE_DEBUG = 5;
    /** @var array Hold dictionary of translations */
    public static $dictionary = [];
    /** @var string PHP tag. You could use < ?php or < ? (if shorttag is active in php.ini) */
    public $phpTag = '<?php '; // hello hello hello.
    public $phpTagEcho = '<?php echo ';
    /** @var string $currentUser Current user. Example: john */
    public $currentUser;
    /** @var string $currentRole Current role. Example: admin */
    public $currentRole;
    /** @var string[] $currentPermission Current permission. Example ['edit','add'] */
    public $currentPermission = [];
    /** @var callable callback of validation. It is used for @can,@cannot */
    public $authCallBack;
    /** @var callable callback of validation. It is used for @canany */
    public $authAnyCallBack;
    /** @var callable callback of errors. It is used for @error */
    public $errorCallBack;
    /** @var bool if true then if the operation fails and it is critic, then it throws an error */
    public $throwOnError = false;
    /** @var string security token */
    public $csrf_token = '';
    /** @var string The path to the missing translations log file. If empty then every missing key is not saved. */
    public $missingLog = '';
    public $pipeEnable = false;
    /** @var array Alias (with or without namespace) of the classes) */
    public $aliasClasses = [];
    /**
     * @var bool if true then the variables defined in the include as argumentsare scoped to work only
     * inside the include.<br>
     * If false (default value), then the variables defined in the include as arguments are defined globally.<br>
     * <b>Example: (includeScope=false)</b><br>
     * <pre>
     * @include("template",['a1'=>'abc']) // a1 is equals to abc
     * @include("template",[]) // a1 is equals to abc
     * </pre>
     * <b>Example: (includeScope=true)</b><br>
     * <pre>
     * @include("template",['a1'=>'abc']) // a1 is equals to abc
     * @include("template",[]) // a1 is not defined
     * </pre>
     */
    public $includeScope = false;
    /**
     * @var callable[] It allows to parse the compiled output using a function.
     * This function doesn't require to return a value<br>
     * <b>Example:</b> this converts all compiled result in uppercase (note, content is a ref)
     * <pre>
     * $this->compileCallbacks[]= static function (&$content, $templatename=null) {
     *      $content=strtoupper($content);
     * };
     * </pre>
     */
    public $compileCallbacks = [];
    /** @var array All of the registered extensions. */
    protected $extensions = [];
    /** @var array All of the finished, captured sections. */
    protected $sections = [];
    /** @var string The template currently being compiled. For example "folder.template" */
    protected $fileName;
    protected $currentView;
    protected $notFoundPath;
    /** @var string File extension for the template files. */
    protected $fileExtension = '.blade.php';
    /** @var array The stack of in-progress sections. */
    protected $sectionStack = [];
    /** @var array The stack of in-progress loops. */
    protected $loopsStack = [];
    /** @var array Dictionary of variables */
    protected $variables = [];
    /** @var null Dictionary of global variables */
    protected $variablesGlobal = [];
    /** @var array All of the available compiler functions. */
    protected $compilers = [
        'Extensions',
        'Statements',
        'Comments',
        'Echos',
    ];
    /** @var string|null it allows to sets the stack */
    protected $viewStack;
    /** @var array used by $this->composer() */
    protected $composerStack = [];
    /** @var array The stack of in-progress push sections. */
    protected $pushStack = [];
    /** @var array All of the finished, captured push sections. */
    protected $pushes = [];
    /** @var int The number of active rendering operations. */
    protected $renderCount = 0;
    /** @var string[] Get the template path for the compiled views. */
    protected $templatePath;
    /** @var string Get the compiled path for the compiled views. If null then it uses the default path */
    protected $compiledPath;
    /** @var string the extension of the compiled file. */
    protected $compileExtension = '.bladec';
    /** @var array Custom "directive" dictionary. Those directives run at compile time. */
    protected $customDirectives = [];
    /** @var bool[] Custom directive dictionary. Those directives run at runtime. */
    protected $customDirectivesRT = [];
    /** @var callable Function used for resolving injected classes. */
    protected $injectResolver;
    /** @var array Used for conditional if. */
    protected $conditions = [];
    /** @var int Unique counter. It's used for extends */
    protected $uidCounter = 0;
    /** @var string The main url of the system. Don't use raw $_SERVER values unless the value is sanitized */
    protected $baseUrl = '.';
    /** @var string|null The base domain of the system */
    protected $baseDomain;
    /** @var string|null It stores the current canonical url. */
    protected $canonicalUrl;
    /** @var string|null It stores the current url including arguments */
    protected $currentUrl;
    /** @var string it is a relative path calculated between baseUrl and the current url. Example ../../ */
    protected $relativePath = '';
    /** @var string[] Dictionary of assets */
    protected $assetDict;
    /** @var bool if true then it removes tabs and unneeded spaces */
    protected $optimize = true;
    /** @var bool if false, then the template is not compiled (but executed on memory). */
    protected $isCompiled = true;
    /** @var bool */
    protected $isRunFast = false; // stored for historical purpose.
    /** @var array Array of opening and closing tags for raw echos. */
    protected $rawTags = ['{!!', '!!}'];
    /** @var array Array of opening and closing tags for regular echos. */
    protected $contentTags = ['{{', '}}'];
    /** @var array Array of opening and closing tags for escaped echos. */
    protected $escapedTags = ['{{{', '}}}'];
    /** @var string The "regular" / legacy echo string format. */
    protected $echoFormat = '\htmlentities(%s, ENT_QUOTES, \'UTF-8\', false)';
    protected $echoFormatOld = 'static::e(%s)';
    /** @var array Lines that will be added at the footer of the template */
    protected $footer = [];
    /** @var string Placeholder to temporary mark the position of verbatim blocks. */
    protected $verbatimPlaceholder = '$__verbatim__$';
    /** @var array Array to temporary store the verbatim blocks found in the template. */
    protected $verbatimBlocks = [];
    /** @var int Counter to keep track of nested forelse statements. */
    protected $forelseCounter = 0;
    /** @var array The components being rendered. */
    protected $componentStack = [];
    /** @var array The original data passed to the component. */
    protected $componentData = [];
    /** @var array The slot contents for the component. */
    protected $slots = [];
    /** @var array The names of the slots being rendered. */
    protected $slotStack = [];
    /** @var string tag unique */
    protected $PARENTKEY = '@parentXYZABC';
    /**
     * Indicates the compile mode.
     * if the constant BLADEONE_MODE is defined, then it is used instead of this field.
     *
     * @var int=[BladeOne::MODE_AUTO,BladeOne::MODE_DEBUG,BladeOne::MODE_SLOW,BladeOne::MODE_FAST][$i]
     */
    protected $mode;
    /** @var int Indicates the number of open switches */
    private $switchCount = 0;
    /** @var bool Indicates if the switch is recently open */
    private $firstCaseInSwitch = true;

    //</editor-fold>

    //<editor-fold desc="constructor">

    /**
     * Bob the constructor.
     * The folder at $compiledPath is created in case it doesn't exist.
     *
     * @param string|array $templatePath If null then it uses (caller_folder)/views
     * @param string       $compiledPath If null then it uses (caller_folder)/compiles
     * @param int          $mode         =[BladeOne::MODE_AUTO,BladeOne::MODE_DEBUG,BladeOne::MODE_FAST,BladeOne::MODE_SLOW][$i]
     */
    public function __construct($templatePath = null, $compiledPath = null, $mode = 0)
    {
        if ($templatePath === null) {
            $templatePath = \getcwd() . '/views';
        }
        if ($compiledPath === null) {
            $compiledPath = \getcwd() . '/compiles';
        }
        $this->templatePath = (is_array($templatePath)) ? $templatePath : [$templatePath];
        $this->compiledPath = $compiledPath;
        $this->setMode($mode);
        $this->authCallBack = function ($action = null, /** @noinspection PhpUnusedParameterInspection */ $subject = null) {
            return \in_array($action, $this->currentPermission, true);
        };

        $this->authAnyCallBack = function ($array = []) {
            foreach ($array as $permission) {
                if (\in_array($permission, $this->currentPermission, true)) {
                    return true;
                }
            }
            return false;
        };

        $this->errorCallBack = static function (/** @noinspection PhpUnusedParameterInspection */ $key = null) {
            return false;
        };

        if (!\is_dir($this->compiledPath)) {
            $ok = @\mkdir($this->compiledPath, 0777, true);
            if ($ok === false) {
                $this->showError(
                    'Constructing',
                    "Unable to create the compile folder [$this->compiledPath]. Check the permissions of it's parent folder.",
                    true
                );
            }
        }
        // If the traits has "Constructors", then we call them.
        // Requisites.
        // 1- the method must be public or protected
        // 2- it must doesn't have arguments
        // 3- It must have the name of the trait. i.e. trait=MyTrait, method=MyTrait()
        $traits = get_declared_traits();
        if ($traits !== null) {
            foreach ($traits as $trait) {
                $r = explode('\\', $trait);
                $name = end($r);
                if (is_callable([$this, $name]) && method_exists($this, $name)) {
                    $this->{$name}();
                }
            }
        }
    }
    //</editor-fold>
    //<editor-fold desc="common">

    /**
     * Show an error in the web.
     *
     * @param string $id          Title of the error
     * @param string $text        Message of the error
     * @param bool   $critic      if true then the compilation is ended, otherwise it continues
     * @param bool   $alwaysThrow if true then it always throw a runtime exception.
     * @return string
     * @throws \RuntimeException
     */
    public function showError($id, $text, $critic = false, $alwaysThrow = false)
    {
        \ob_get_clean();
        if (($this->throwOnError || $alwaysThrow) && $critic === true) {
            throw new \RuntimeException("BladeOne Error [$id] $text");
        } else {
            echo "<div style='background-color: red; color: black; padding: 3px; border: solid 1px black;'>";
            echo "BladeOne Error [$id]:<br>";
            echo "<span style='color:white'>$text</span><br></div>\n";
            if ($critic) {
                die(1);
            }
            if ($this->throwOnError) {
                error_log("BladeOne Error [$id] $text");
            }
        }
        return '';
    }

    /**
     * Escape HTML entities in a string.
     *
     * @param string $value
     * @return string
     */
    public static function e($value)
    {
        return (\is_array($value) || \is_object($value))
            ? \htmlentities(\print_r($value, true), ENT_QUOTES, 'UTF-8', false)
            : \htmlentities($value, ENT_QUOTES, 'UTF-8', false);
    }

    protected static function convertArgCallBack($k, $v)
    {
        return $k . "='$v' ";
    }

    /**
     * @param mixed|\DateTime $variable
     * @param string|null     $format
     * @return string
     */
    public function format($variable, $format = null)
    {
        if ($variable instanceof \DateTime) {
            $format = $format === null ? 'Y/m/d' : $format;
            return $variable->format($format);
        }
        $format = $format === null ? '%s' : $format;
        return sprintf($format, $variable);
    }

    /**
     * It converts a text into a php code with echo<br>
     * <b>Example:</b><br>
     * <pre>
     * $this->wrapPHP('$hello'); // "< ?php echo $this->e($hello); ? >"
     * $this->wrapPHP('$hello',''); // < ?php echo $this->e($hello); ? >
     * $this->wrapPHP('$hello','',false); // < ?php echo $hello; ? >
     * $this->wrapPHP('"hello"'); // "< ?php echo $this->e("hello"); ? >"
     * $this->wrapPHP('hello()'); // "< ?php echo $this->e(hello()); ? >"
     * </pre>
     *
     * @param string $input The input value
     * @param string $quote The quote used (to quote the result)
     * @param bool   $parse If the result will be parsed or not. If false then it's returned without $this->e
     * @return string
     */
    public function wrapPHP($input, $quote = '"', $parse = true)
    {
        if (strpos($input, '(') !== false && !$this->isQuoted($input)) {
            if ($parse) {
                return $quote . $this->phpTagEcho . '$this->e(' . $input . ');?>' . $quote;
            }

            return $quote . $this->phpTagEcho . $input . ';?>' . $quote;
        }
        if (strpos($input, '$') === false) {
            if ($parse) {
                return self::enq($input);
            }

            return $input;
        }
        if ($parse) {
            return $quote . $this->phpTagEcho . '$this->e(' . $input . ');?>' . $quote;
        }
        return $quote . $this->phpTagEcho . $input . ';?>' . $quote;
    }

    /**
     * Returns true if the text is surrounded by quotes (double or single quote)
     *
     * @param string|null $text
     * @return bool
     */
    public function isQuoted($text)
    {
        if (!$text || strlen($text) < 2) {
            return false;
        }
        if ($text[0] === '"' && substr($text, -1) === '"') {
            return true;
        }
        return ($text[0] === "'" && substr($text, -1) === "'");
    }

    /**
     * Escape HTML entities in a string.
     *
     * @param string $value
     * @return string
     */
    public static function enq($value)
    {
        if (\is_array($value) || \is_object($value)) {
            return \htmlentities(\print_r($value, true), ENT_NOQUOTES, 'UTF-8', false);
        }
        return \htmlentities($value, ENT_NOQUOTES, 'UTF-8', false);
    }

    /**
     * @param string      $view  example "folder.template"
     * @param string|null $alias example "mynewop". If null then it uses the name of the template.
     */
    public function addInclude($view, $alias = null)
    {
        if (!isset($alias)) {
            $alias = \explode('.', $view);
            $alias = \end($alias);
        }
        $this->directive($alias, function ($expression) use ($view) {
            $expression = $this->stripParentheses($expression) ?: '[]';
            return "$this->phpTag echo \$this->runChild('$view', $expression); ?>";
        });
    }

    /**
     * Register a handler for custom directives.
     *
     * @param string   $name
     * @param callable $handler
     * @return void
     */
    public function directive($name, callable $handler)
    {
        $this->customDirectives[$name] = $handler;
        $this->customDirectivesRT[$name] = false;
    }

    /**
     * Strip the parentheses from the given expression.
     *
     * @param string $expression
     * @return string
     */
    public function stripParentheses($expression)
    {
        if (static::startsWith($expression, '(')) {
            $expression = \substr($expression, 1, -1);
        }
        return $expression;
    }

    /**
     * Determine if a given string starts with a given substring.
     *
     * @param string       $haystack
     * @param string|array $needles
     * @return bool
     */
    public static function startsWith($haystack, $needles)
    {
        foreach ((array)$needles as $needle) {
            if ($needle != '') {
                if (\function_exists('mb_strpos')) {
                    if (\mb_strpos($haystack, $needle) === 0) {
                        return true;
                    }
                } elseif (\strpos($haystack, $needle) === 0) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * If false then the file is not compiled and it is executed directly from the memory.<br>
     * By default the value is true<br>
     * It also sets the mode to MODE_SLOW
     *
     * @param bool $bool
     * @return BladeOne
     * @see \eftec\bladeone\BladeOne::setMode
     */
    public function setIsCompiled($bool = false)
    {
        $this->isCompiled = $bool;
        if (!$bool) {
            $this->setMode(self::MODE_SLOW);
        }
        return $this;
    }

    /**
     * It sets the template and compile path (without trailing slash).
     * <p>Example:setPath("somefolder","otherfolder");
     *
     * @param null|string|string[] $templatePath If null then it uses the current path /views folder
     * @param null|string          $compiledPath If null then it uses the current path /views folder
     */
    public function setPath($templatePath, $compiledPath)
    {
        if ($templatePath === null) {
            $templatePath = \getcwd() . '/views';
        }
        if ($compiledPath === null) {
            $compiledPath = \getcwd() . '/compiles';
        }
        $this->templatePath = (is_array($templatePath)) ? $templatePath : [$templatePath];
        $this->compiledPath = $compiledPath;
    }

    /**
     * @return array
     */
    public function getAliasClasses()
    {
        return $this->aliasClasses;
    }

    /**
     * @param array $aliasClasses
     */
    public function setAliasClasses($aliasClasses)
    {
        $this->aliasClasses = $aliasClasses;
    }

    /**
     * @param string $aliasName
     * @param string $classWithNS
     */
    public function addAliasClasses($aliasName, $classWithNS)
    {
        $this->aliasClasses[$aliasName] = $classWithNS;
    }
    //</editor-fold>
    //<editor-fold desc="compile">

    /**
     * Authentication. Sets with a user,role and permission
     *
     * @param string $user
     * @param null   $role
     * @param array  $permission
     */
    public function setAuth($user = '', $role = null, $permission = [])
    {
        $this->currentUser = $user;
        $this->currentRole = $role;
        $this->currentPermission = $permission;
    }

    /**
     * run the blade engine. It returns the result of the code.
     *
     * @param string HTML to parse
     * @param array $data
     * @return string
     * @throws Exception
     */
    public function runString($string, $data = [])
    {
        $php = $this->compileString($string);

        $obLevel = \ob_get_level();
        \ob_start();
        \extract($data, EXTR_SKIP);

        $previousError = \error_get_last();

        try {
            @eval('?' . '>' . $php);
        } catch (Exception $e) {
            while (\ob_get_level() > $obLevel) {
                \ob_end_clean();
            }
            throw $e;
        } catch (ParseError $e) { // PHP 7
            while (\ob_get_level() > $obLevel) {
                \ob_end_clean();
            }
            $this->showError('runString', $e->getMessage(). ' '.$e->getCode(), true);
            return '';
        }

        $lastError = \error_get_last(); // PHP 5.6
        if ($previousError != $lastError && $lastError['type'] == E_PARSE) {
            while (\ob_get_level() > $obLevel) {
                \ob_end_clean();
            }
            $this->showError('runString', $lastError['message']. ' '.$lastError['type'], true);
            return '';
        }

        return \ob_get_clean();
    }

    /**
     * Compile the given Blade template contents.
     *
     * @param string $value
     * @return string
     */
    public function compileString($value)
    {
        $result = '';
        if (\strpos($value, '@verbatim') !== false) {
            $value = $this->storeVerbatimBlocks($value);
        }
        $this->footer = [];
        // Here we will loop through all of the tokens returned by the Zend lexer and
        // parse each one into the corresponding valid PHP. We will then have this
        // template as the correctly rendered PHP that can be rendered natively.
        foreach (\token_get_all($value) as $token) {
            $result .= \is_array($token) ? $this->parseToken($token) : $token;
        }
        if (!empty($this->verbatimBlocks)) {
            $result = $this->restoreVerbatimBlocks($result);
        }
        // If there are any footer lines that need to get added to a template we will
        // add them here at the end of the template. This gets used mainly for the
        // template inheritance via the extends keyword that should be appended.
        if (\count($this->footer) > 0) {
            $result = \ltrim($result, PHP_EOL)
                . PHP_EOL . \implode(PHP_EOL, \array_reverse($this->footer));
        }
        return $result;
    }

    /**
     * Store the verbatim blocks and replace them with a temporary placeholder.
     *
     * @param string $value
     * @return string
     */
    protected function storeVerbatimBlocks($value)
    {
        return \preg_replace_callback('/(?<!@)@verbatim(.*?)@endverbatim/s', function ($matches) {
            $this->verbatimBlocks[] = $matches[1];
            return $this->verbatimPlaceholder;
        }, $value);
    }

    /**
     * Parse the tokens from the template.
     *
     * @param array $token
     *
     * @return string
     *
     * @see \eftec\bladeone\BladeOne::compileStatements
     * @see \eftec\bladeone\BladeOne::compileExtends
     * @see \eftec\bladeone\BladeOne::compileComments
     * @see \eftec\bladeone\BladeOne::compileEchos
     */
    protected function parseToken($token)
    {
        list($id, $content) = $token;
        if ($id == T_INLINE_HTML) {
            foreach ($this->compilers as $type) {
                $content = $this->{"compile$type"}($content);
            }
        }
        return $content;
    }

    /**
     * Replace the raw placeholders with the original code stored in the raw blocks.
     *
     * @param string $result
     * @return string
     */
    protected function restoreVerbatimBlocks($result)
    {
        $result = \preg_replace_callback('/' . \preg_quote($this->verbatimPlaceholder) . '/', function () {
            return \array_shift($this->verbatimBlocks);
        }, $result);
        $this->verbatimBlocks = [];
        return $result;
    }

    /**
     * it calculates the relative path of a web.<br>
     * This function uses the current url and the baseurl
     *
     * @param string $relativeWeb . Example img/images.jpg
     * @return string  Example ../../img/images.jpg
     */
    public function relative($relativeWeb)
    {
        if (isset($this->assetDict[$relativeWeb])) {
            return $this->assetDict[$relativeWeb];
        }
        // relativepath is calculated when
        return $this->relativePath . $relativeWeb;
    }

    /**
     * It add an alias to the link of the resources.<br>
     * addAssetDict('name','url/res.jpg')<br>
     * addAssetDict(['name'=>'url/res.jpg','name2'=>'url/res2.jpg');
     *
     * @param string|array $name example 'css/style.css', you could also add an array
     * @param string       $url  example https://www.web.com/style.css'
     */
    public function addAssetDict($name, $url = '')
    {
        if (\is_array($name)) {
            if ($this->assetDict === null) {
                $this->assetDict = $name;
            } else {
                $this->assetDict = \array_merge($this->assetDict, $name);
            }
        } else {
            $this->assetDict[$name] = $url;
        }
    }

    /**
     * Compile the push statements into valid PHP.
     *
     * @param string $expression
     * @return string
     */
    public function compilePush($expression)
    {
        return $this->phpTag . "\$this->startPush$expression; ?>";
    }

    /**
     * Compile the push statements into valid PHP.
     *
     * @param string $expression
     * @return string
     */
    public function compilePushOnce($expression)
    {
        $key = '$__pushonce__' . \trim(\substr($expression, 2, -2));
        return $this->phpTag . "if(!isset($key)): $key=1;  \$this->startPush$expression; ?>";
    }

    /**
     * Compile the push statements into valid PHP.
     *
     * @param string $expression
     * @return string
     */
    public function compilePrepend($expression)
    {
        return $this->phpTag . "\$this->startPush$expression; ?>";
    }

    /**
     * Start injecting content into a push section.
     *
     * @param string $section
     * @param string $content
     * @return void
     */
    public function startPush($section, $content = '')
    {
        if ($content === '') {
            if (\ob_start()) {
                $this->pushStack[] = $section;
            }
        } else {
            $this->extendPush($section, $content);
        }
    }

    /*
     * endswitch tag
     */

    /**
     * Append content to a given push section.
     *
     * @param string $section
     * @param string $content
     * @return void
     */
    protected function extendPush($section, $content)
    {
        if (!isset($this->pushes[$section])) {
            $this->pushes[$section] = []; // start an empty section
        }
        if (!isset($this->pushes[$section][$this->renderCount])) {
            $this->pushes[$section][$this->renderCount] = $content;
        } else {
            $this->pushes[$section][$this->renderCount] .= $content;
        }
    }

    /**
     * Start injecting content into a push section.
     *
     * @param string $section
     * @param string $content
     * @return void
     */
    public function startPrepend($section, $content = '')
    {
        if ($content === '') {
            if (\ob_start()) {
                \array_unshift($this->pushStack[], $section);
            }
        } else {
            $this->extendPush($section, $content);
        }
    }

    /**
     * Stop injecting content into a push section.
     *
     * @return string
     */
    public function stopPush()
    {
        if (empty($this->pushStack)) {
            $this->showError('stopPush', 'Cannot end a section without first starting one', true);
        }
        $last = \array_pop($this->pushStack);
        $this->extendPush($last, \ob_get_clean());
        return $last;
    }

    /**
     * Stop injecting content into a push section.
     *
     * @return string
     */
    public function stopPrepend()
    {
        if (empty($this->pushStack)) {
            $this->showError('stopPrepend', 'Cannot end a section without first starting one', true);
        }
        $last = \array_shift($this->pushStack);
        $this->extendStartPush($last, \ob_get_clean());
        return $last;
    }

    /**
     * Append content to a given push section.
     *
     * @param string $section
     * @param string $content
     * @return void
     */
    protected function extendStartPush($section, $content)
    {
        if (!isset($this->pushes[$section])) {
            $this->pushes[$section] = []; // start an empty section
        }
        if (!isset($this->pushes[$section][$this->renderCount])) {
            $this->pushes[$section][$this->renderCount] = $content;
        } else {
            $this->pushes[$section][$this->renderCount] = $content . $this->pushes[$section][$this->renderCount];
        }
    }

    /**
     * Get the string contents of a push section.
     *
     * @param string $section
     * @param string $default
     * @return string
     */
    public function yieldPushContent($section, $default = '')
    {
        if (!isset($this->pushes[$section])) {
            return $default;
        }
        return \implode(\array_reverse($this->pushes[$section]));
    }

    /**
     * Get the string contents of a push section.
     *
     * @param int|string $each if int, then it split the foreach every $each numbers.<br>
     *                         if string, "c3" it means that it will split in 3 columns<br>
     * @param string     $splitText
     * @param string     $splitEnd
     * @return string
     */
    public function splitForeach($each = 1, $splitText = ',', $splitEnd = '')
    {
        $loopStack = static::last($this->loopsStack); // array(7) { ["index"]=> int(0) ["remaining"]=> int(6) ["count"]=> int(5) ["first"]=> bool(true) ["last"]=> bool(false) ["depth"]=> int(1) ["parent"]=> NULL }
        if (($loopStack['index']) == $loopStack['count'] - 1) {
            return $splitEnd;
        }
        $eachN = 0;
        if (is_numeric($each)) {
            $eachN = $each;
        } elseif (strlen($each) > 1) {
            if ($each[0] === 'c') {
                $eachN = $loopStack['count'] / substr($each, 1);
            }
        } else {
            $eachN = PHP_INT_MAX;
        }

        if (($loopStack['index'] + 1) % $eachN === 0) {
            return $splitText;
        }
        return '';
    }

    /**
     * Return the last element in an array passing a given truth test.
     *
     * @param array         $array
     * @param callable|null $callback
     * @param mixed         $default
     * @return mixed
     */
    public static function last($array, callable $callback = null, $default = null)
    {
        if (\is_null($callback)) {
            return empty($array) ? static::value($default) : \end($array);
        }
        return static::first(\array_reverse($array), $callback, $default);
    }

    /**
     * Return the default value of the given value.
     *
     * @param mixed $value
     * @return mixed
     */
    public static function value($value)
    {
        return $value instanceof Closure ? $value() : $value;
    }

    /**
     * Return the first element in an array passing a given truth test.
     *
     * @param array         $array
     * @param callable|null $callback
     * @param mixed         $default
     * @return mixed
     */
    public static function first($array, callable $callback = null, $default = null)
    {
        if (\is_null($callback)) {
            return empty($array) ? static::value($default) : \reset($array);
        }
        foreach ($array as $key => $value) {
            if ($callback($key, $value)) {
                return $value;
            }
        }
        return static::value($default);
    }

    /**
     * @param string $name
     * @param        $args []
     * @return string
     * @throws BadMethodCallException
     */
    public function __call($name, $args)
    {
        if ($name === 'if') {
            return $this->registerIfStatement(isset($args[0]) ? $args[0] : null, isset($args[1]) ? $args[1] : null);
        }
        $this->showError('call', "function $name is not defined<br>", true, true);
        return '';
    }

    /**
     * Register an "if" statement directive.
     *
     * @param string   $name
     * @param callable $callback
     * @return string
     */
    public function registerIfStatement($name, callable $callback)
    {
        $this->conditions[$name] = $callback;

        $this->directive($name, function ($expression) use ($name) {
            $tmp = $this->stripParentheses($expression);
            return $expression !== ''
                ? $this->phpTag . " if (\$this->check('$name', $tmp)): ?>"
                : $this->phpTag . " if (\$this->check('$name')): ?>";
        });

        $this->directive('else' . $name, function ($expression) use ($name) {
            $tmp = $this->stripParentheses($expression);
            return $expression !== ''
                ? $this->phpTag . " elseif (\$this->check('$name', $tmp)): ?>"
                : $this->phpTag . " elseif (\$this->check('$name')): ?>";
        });

        $this->directive('end' . $name, function () {
            return $this->phpTag . ' endif; ?>';
        });
        return '';
    }

    /**
     * Check the result of a condition.
     *
     * @param string $name
     * @param array  $parameters
     * @return bool
     */
    public function check($name, ...$parameters)
    {
        return \call_user_func($this->conditions[$name], ...$parameters);
    }

    /**
     * @param bool   $bool
     * @param string $view  name of the view
     * @param array  $value arrays of values
     * @return string
     * @throws Exception
     */
    public function includeWhen($bool = false, $view = '', $value = [])
    {
        if ($bool) {
            return $this->runChild($view, $value);
        }
        return '';
    }

    /**
     * Macro of function run
     *
     * @param       $view
     * @param array $variables
     * @return string
     * @throws Exception
     */
    public function runChild($view, $variables = [])
    {
        if (\is_array($variables)) {
            if ($this->includeScope) {
                $backup = $this->variables;
            } else {
                $backup = null;
            }
            $newVariables = \array_merge($this->variables, $variables);
        } else {
            if ($variables === null) {
                $newVariables = $this->variables;
                var_dump($newVariables);
                die(1);
            }

            $this->showError('run/include', "RunChild: Include/run variables should be defined as array ['idx'=>'value']", true);
            return '';
        }
        $r = $this->runInternal($view, $newVariables, false, false, $this->isRunFast);
        if ($backup !== null) {
            $this->variables = $backup;
        }
        return $r;
    }

    /**
     * run the blade engine. It returns the result of the code.
     *
     * @param string $view
     * @param array  $variables
     * @param bool   $forced  if true then it recompiles no matter if the compiled file exists or not.
     * @param bool   $isParent
     * @param bool   $runFast if true then the code is not compiled neither checked and it runs directly the compiled
     *                        version.
     * @return string
     * @throws Exception
     * @noinspection PhpUnusedParameterInspection
     */
    private function runInternal($view, $variables = [], $forced = false, $isParent = true, $runFast = false)
    {
        $this->currentView = $view;
        if (@\count($this->composerStack)) {
            $this->evalComposer($view);
        }
        if (@\count($this->variablesGlobal) > 0) {
            $this->variables = \array_merge($variables, $this->variablesGlobal);
            $this->variablesGlobal = []; // used so we delete it.
        } else {
            $this->variables = $variables;
        }
        if (!$runFast) {
            // a) if the compile is forced then we compile the original file, then save the file.
            // b) if the compile is not forced then we read the datetime of both file and we compared.
            // c) in both cases, if the compiled doesn't exist then we compile.
            if ($view) {
                $this->fileName = $view;
            }
            $result = $this->compile($view, $forced);
            if (!$this->isCompiled) {
                return $this->evaluateText($result, $this->variables);
            }
        } elseif ($view) {
            $this->fileName = $view;
        }
        $this->isRunFast = $runFast;
        return $this->evaluatePath($this->getCompiledFile(), $this->variables);
    }

    protected function evalComposer($view)
    {
        foreach ($this->composerStack as $viewKey => $fn) {
            if ($this->wildCardComparison($view, $viewKey)) {
                if (is_callable($fn)) {
                    $fn($this);
                } elseif ($this->methodExistsStatic($fn, 'composer')) {
                    // if the method exists statically then $fn is the class and 'composer' is the name of the method
                    $fn::composer($this);
                } elseif (is_object($fn) || class_exists($fn)) {
                    // if $fn is an object or it is a class and the class exists.
                    $instance = (is_object($fn)) ? $fn : new $fn();
                    if (method_exists($instance, 'composer')) {
                        // and the method exists inside the instance.
                        $instance->composer($this);
                    } else {
                        if ($this->mode === self::MODE_DEBUG) {
                            $this->showError('evalComposer', "BladeOne: composer() added an incorrect method [$fn]", true, true);
                            return;
                        }
                        $this->showError('evalComposer', 'BladeOne: composer() added an incorrect method', true, true);
                        return;
                    }
                } else {
                    $this->showError('evalComposer', 'BladeOne: composer() added an incorrect method', true, true);
                }
            }
        }
    }

    /**
     * It compares with wildcards (*) and returns true if both strings are equals<br>
     * The wildcards only works at the beginning and/or at the end of the string.<br>
     * <b>Example:<b><br>
     * <pre>
     * Text::wildCardComparison('abcdef','abc*'); // true
     * Text::wildCardComparison('abcdef','*def'); // true
     * Text::wildCardComparison('abcdef','*abc*'); // true
     * Text::wildCardComparison('abcdef','*cde*'); // true
     * Text::wildCardComparison('abcdef','*cde'); // false
     *
     * </pre>
     *
     * @param string      $text
     * @param string|null $textWithWildcard
     *
     * @return bool
     */
    protected function wildCardComparison($text, $textWithWildcard)
    {
        if (($textWithWildcard === null && $textWithWildcard === '')
            || strpos($textWithWildcard, '*') === false) {
            // if the text with wildcard is null or empty or it contains two ** or it contains no * then..
            return $text == $textWithWildcard;
        }
        if ($textWithWildcard === '*' || $textWithWildcard === '**') {
            return true;
        }
        $c0 = $textWithWildcard[0];
        $c1 = substr($textWithWildcard, -1);
        $textWithWildcardClean = str_replace('*', '', $textWithWildcard);
        $p0 = strpos($text, $textWithWildcardClean);
        if ($p0 === false) {
            // no matches.
            return false;
        }
        if ($c0 === '*' && $c1 === '*') {
            // $textWithWildcard='*asasasas*'
            return true;
        }
        if ($c1 === '*') {
            // $textWithWildcard='asasasas*'
            return $p0 === 0;
        }
        // $textWithWildcard='*asasasas'
        $len = strlen($textWithWildcardClean);
        return (substr($text, -$len) === $textWithWildcardClean);
    }

    protected function methodExistsStatic($class, $method)
    {
        try {
            $mc = new \ReflectionMethod($class, $method);
            return $mc->isStatic();
        } catch (\ReflectionException $e) {
            return false;
        }
    }

    /**
     * Compile the view at the given path.
     *
     * @param string $templateName The name of the template. Example folder.template
     * @param bool   $forced       If the compilation will be forced (always compile) or not.
     * @return boolean|string True if the operation was correct, or false (if not exception)
     *                             if it fails. It returns a string (the content compiled) if isCompiled=false
     * @throws Exception
     */
    public function compile($templateName = null, $forced = false)
    {
        $compiled = $this->getCompiledFile($templateName);
        $template = $this->getTemplateFile($templateName);
        if (!$this->isCompiled) {
            $contents = $this->compileString($this->getFile($template));
            $this->compileCallBacks($contents, $templateName);
            return $contents;
        }
        if ($forced || $this->isExpired($templateName)) {
            // compile the original file
            $contents = $this->compileString($this->getFile($template));
            $this->compileCallBacks($contents, $templateName);
            $dir = \dirname($compiled);
            if (!\is_dir($dir)) {
                $ok = @\mkdir($dir, 0777, true);
                if ($ok === false) {
                    $this->showError(
                        'Compiling',
                        "Unable to create the compile folder [$dir]. Check the permissions of it's parent folder.",
                        true
                    );
                    return false;
                }
            }
            if ($this->optimize) {
                // removes space and tabs and replaces by a single space
                $contents = \preg_replace('/^ {2,}/m', ' ', $contents);
                $contents = \preg_replace('/^\t{2,}/m', ' ', $contents);
            }
            $ok = @\file_put_contents($compiled, $contents);
            if ($ok === false) {
                $this->showError(
                    'Compiling',
                    "Unable to save the file [$compiled]. Check the compile folder is defined and has the right permission"
                );
                return false;
            }
        }
        return true;
    }

    /**
     * Get the full path of the compiled file.
     *
     * @param string $templateName
     * @return string
     */
    public function getCompiledFile($templateName = '')
    {
        $templateName = (empty($templateName)) ? $this->fileName : $templateName;
        if ($this->getMode() == self::MODE_DEBUG) {
            return $this->compiledPath . '/' . $templateName . $this->compileExtension;
        }

        return $this->compiledPath . '/' . \sha1($templateName) . $this->compileExtension;
    }

    /**
     * Get the mode of the engine.See BladeOne::MODE_* constants
     *
     * @return int=[self::MODE_AUTO,self::MODE_DEBUG,self::MODE_FAST,self::MODE_SLOW][$i]
     */
    public function getMode()
    {
        if (\defined('BLADEONE_MODE')) {
            $this->mode = BLADEONE_MODE;
        }
        return $this->mode;
    }

    /**
     * Set the compile mode
     *
     * @param $mode int=[self::MODE_AUTO,self::MODE_DEBUG,self::MODE_FAST,self::MODE_SLOW][$i]
     * @return void
     */
    public function setMode($mode)
    {
        $this->mode = $mode;
    }

    /**
     * Get the full path of the template file.
     * <p>Example: getTemplateFile('.abc.def')</p>
     *
     * @param string $templateName template name. If not template is set then it uses the base template.
     * @return string
     */
    public function getTemplateFile($templateName = '')
    {
        $templateName = (empty($templateName)) ? $this->fileName : $templateName;
        if (\strpos($templateName, '/') !== false) {
            return $this->locateTemplate($templateName); // it's a literal
        }
        $arr = \explode('.', $templateName);
        $c = \count($arr);
        if ($c == 1) {
            // its in the root of the template folder.
            return $this->locateTemplate($templateName . $this->fileExtension);
        }

        $file = $arr[$c - 1];
        \array_splice($arr, $c - 1, $c - 1); // delete the last element
        $path = \implode('/', $arr);
        return $this->locateTemplate($path . '/' . $file . $this->fileExtension);
    }

    /**
     * Find template file with the given name in all template paths in the order the paths were written
     *
     * @param string $name Filename of the template (without path)
     * @return string template file
     */
    private function locateTemplate($name)
    {
        $this->notFoundPath = '';
        foreach ($this->templatePath as $dir) {
            $path = $dir . '/' . $name;
            if (\is_file($path)) {
                return $path;
            }

            $this->notFoundPath .= $path . ",";
        }
        return '';
    }

    /**
     * Get the contents of a file.
     *
     * @param string $fullFileName It gets the content of a filename or returns ''.
     *
     * @return string
     */
    public function getFile($fullFileName)
    {
        if (\is_file($fullFileName)) {
            return \file_get_contents($fullFileName);
        }
        $this->showError('getFile', "File does not exist at paths (separated by comma) [$this->notFoundPath] or permission denied", true);
        return '';
    }

    protected function compileCallBacks(&$contents, $templateName)
    {
        if (!empty($this->compileCallbacks)) {
            foreach ($this->compileCallbacks as $callback) {
                if (is_callable($callback)) {
                    $callback($contents, $templateName);
                }
            }
        }
    }

    /**
     * Determine if the view has expired.
     *
     * @param string|null $fileName
     * @return bool
     */
    public function isExpired($fileName)
    {
        $compiled = $this->getCompiledFile($fileName);
        $template = $this->getTemplateFile($fileName);
        if (!\is_file($template)) {
            if ($this->mode == self::MODE_DEBUG) {
                $this->showError('Read file', 'Template not found :' . $this->fileName . " on file: $template", true);
            } else {
                $this->showError('Read file', 'Template not found :' . $this->fileName, true);
            }
        }
        // If the compiled file doesn't exist we will indicate that the view is expired
        // so that it can be re-compiled. Else, we will verify the last modification
        // of the views is less than the modification times of the compiled views.
        if (!$this->compiledPath || !\is_file($compiled)) {
            return true;
        }
        return \filemtime($compiled) < \filemtime($template);
    }

    /**
     * Evaluates a text (string) using the current variables
     *
     * @param string $content
     * @param array  $variables
     * @return string
     * @throws Exception
     */
    protected function evaluateText($content, $variables)
    {
        \ob_start();
        \extract($variables);
        // We'll evaluate the contents of the view inside a try/catch block so we can
        // flush out any stray output that might get out before an error occurs or
        // an exception is thrown. This prevents any partial views from leaking.
        try {
            eval(' ?>' . $content . $this->phpTag);
        } catch (Exception $e) {
            $this->handleViewException($e);
        }
        return \ltrim(\ob_get_clean());
    }

    /**
     * Handle a view exception.
     *
     * @param Exception $e
     * @return void
     * @throws $e
     */
    protected function handleViewException($e)
    {
        \ob_get_clean();
        throw $e;
    }

    /**
     * Evaluates a compiled file using the current variables
     *
     * @param string $compiledFile full path of the compile file.
     * @param array  $variables
     * @return string
     * @throws Exception
     */
    protected function evaluatePath($compiledFile, $variables)
    {
        \ob_start();
        // note, the variables are extracted locally inside this method,
        // they are not global variables :-3
        \extract($variables);
        // We'll evaluate the contents of the view inside a try/catch block so we can
        // flush out any stray output that might get out before an error occurs or
        // an exception is thrown. This prevents any partial views from leaking.
        try {
            /** @noinspection PhpIncludeInspection */
            include $compiledFile;
        } catch (Exception $e) {
            $this->handleViewException($e);
        }
        return \ltrim(\ob_get_clean());
    }

    /**
     * @param array $views array of views
     * @param array $value
     * @return string
     * @throws Exception
     */
    public function includeFirst($views = [], $value = [])
    {
        foreach ($views as $view) {
            if ($this->templateExist($view)) {
                return $this->runChild($view, $value);
            }
        }
        return '';
    }

    /**
     * Returns true if the template exists. Otherwise it returns false
     *
     * @param $templateName
     * @return bool
     */
    private function templateExist($templateName)
    {
        $file = $this->getTemplateFile($templateName);
        return \is_file($file);
    }

    /**
     * Convert an array such as ["class1"=>"myclass","style="mystyle"] to class1='myclass' style='mystyle' string
     *
     * @param array|string $array array to convert
     * @return string
     */
    public function convertArg($array)
    {
        if (!\is_array($array)) {
            return $array;  // nothing to convert.
        }
        return \implode(' ', \array_map('static::convertArgCallBack', \array_keys($array), $array));
    }

    /**
     * Returns the current token. if there is not a token then it generates a new one.
     * It could require an open session.
     *
     * @param bool   $fullToken It returns a token with the current ip.
     * @param string $tokenId   [optional] Name of the token.
     *
     * @return string
     */
    public function getCsrfToken($fullToken = false, $tokenId = '_token')
    {
        if ($this->csrf_token == '') {
            $this->regenerateToken($tokenId);
        }
        if ($fullToken) {
            return $this->csrf_token . '|' . $this->ipClient();
        }
        return $this->csrf_token;
    }

    /**
     * Regenerates the csrf token and stores in the session.
     * It requires an open session.
     *
     * @param string $tokenId [optional] Name of the token.
     */
    public function regenerateToken($tokenId = '_token')
    {
        try {
            $this->csrf_token = \bin2hex(\random_bytes(10));
        } catch (Exception $e) {
            $this->csrf_token = '123456789012345678901234567890'; // unable to generates a random token.
        }
        @$_SESSION[$tokenId] = $this->csrf_token . '|' . $this->ipClient();
    }

    public function ipClient()
    {
        if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])
            && \preg_match('/^([d]{1,3}).([d]{1,3}).([d]{1,3}).([d]{1,3})$/', $_SERVER['HTTP_X_FORWARDED_FOR'])) {
            return $_SERVER['HTTP_X_FORWARDED_FOR'];
        }
        return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';
    }

    /**
     * Validates if the csrf token is valid or not.<br>
     * It requires an open session.
     *
     * @param bool   $alwaysRegenerate [optional] Default is false.<br>
     *                                 If <b>true</b> then it will generate a new token regardless
     *                                 of the method.<br>
     *                                 If <b>false</b>, then it will generate only if the method is POST.<br>
     *                                 Note: You must not use true if you want to use csrf with AJAX.
     *
     * @param string $tokenId          [optional] Name of the token.
     *
     * @return bool It returns true if the token is valid or it is generated. Otherwise, false.
     */
    public function csrfIsValid($alwaysRegenerate = false, $tokenId = '_token')
    {
        if (@$_SERVER['REQUEST_METHOD'] === 'POST' && $alwaysRegenerate === false) {
            $this->csrf_token = isset($_POST[$tokenId]) ? $_POST[$tokenId] : null; // ping pong the token.
            return $this->csrf_token . '|' . $this->ipClient() === (isset($_SESSION[$tokenId]) ? $_SESSION[$tokenId] : null);
        }

        if ($this->csrf_token == '' || $alwaysRegenerate) {
            // if not token then we generate a new one
            $this->regenerateToken($tokenId);
        }
        return true;
    }

    /**
     * Stop injecting content into a section and return its contents.
     *
     * @return string
     */
    public function yieldSection()
    {
        $sc = $this->stopSection();
        return isset($this->sections[$sc]) ? $this->sections[$sc] : null;
    }

    /**
     * Stop injecting content into a section.
     *
     * @param bool $overwrite
     * @return string
     */
    public function stopSection($overwrite = false)
    {
        if (empty($this->sectionStack)) {
            $this->showError('stopSection', 'Cannot end a section without first starting one.', true, true);
        }
        $last = \array_pop($this->sectionStack);
        if ($overwrite) {
            $this->sections[$last] = \ob_get_clean();
        } else {
            $this->extendSection($last, \ob_get_clean());
        }
        return $last;
    }

    /**
     * Append content to a given section.
     *
     * @param string $section
     * @param string $content
     * @return void
     */
    protected function extendSection($section, $content)
    {
        if (isset($this->sections[$section])) {
            $content = \str_replace($this->PARENTKEY, $content, $this->sections[$section]);
        }
        $this->sections[$section] = $content;
    }

    public function dump($object, $jsconsole = false)
    {
        if (!$jsconsole) {
            echo '<pre>';
            \var_dump($object);
            echo '</pre>';
        } else {
            /** @noinspection BadExpressionStatementJS */
            /** @noinspection JSVoidFunctionReturnValueUsed */
            echo '<script>console.log(' . \json_encode($object) . ')</script>';
        }
    }

    /**
     * Start injecting content into a section.
     *
     * @param string $section
     * @param string $content
     * @return void
     */
    public function startSection($section, $content = '')
    {
        if ($content === '') {
            \ob_start() && $this->sectionStack[] = $section;
        } else {
            $this->extendSection($section, $content);
        }
    }

    /**
     * Stop injecting content into a section and append it.
     *
     * @return string
     * @throws InvalidArgumentException
     */
    public function appendSection()
    {
        if (empty($this->sectionStack)) {
            $this->showError('appendSection', 'Cannot end a section without first starting one.', true, true);
        }
        $last = \array_pop($this->sectionStack);
        if (isset($this->sections[$last])) {
            $this->sections[$last] .= \ob_get_clean();
        } else {
            $this->sections[$last] = \ob_get_clean();
        }
        return $last;
    }

    /**
     * Adds a global variable. If <b>$varname</b> is an array then it merges all the values.
     * <b>Example:</b>
     * <pre>
     * $this->share('variable',10.5);
     * $this->share('variable2','hello');
     * // or we could add the two variables as:
     * $this->share(['variable'=>10.5,'variable2'=>'hello']);
     * </pre>
     *
     * @param string|array $varname It is the name of the variable or it is an associative array
     * @param mixed        $value
     * @return $this
     * @see \eftec\bladeone\BladeOne::share
     */
    public function with($varname, $value = null)
    {
        return $this->share($varname, $value);
    }

    /**
     * Adds a global variable. If <b>$varname</b> is an array then it merges all the values.
     * <b>Example:</b>
     * <pre>
     * $this->share('variable',10.5);
     * $this->share('variable2','hello');
     * // or we could add the two variables as:
     * $this->share(['variable'=>10.5,'variable2'=>'hello']);
     * </pre>
     *
     * @param string|array $varname It is the name of the variable or it is an associative array
     * @param mixed        $value
     * @return $this
     */
    public function share($varname, $value = null)
    {
        if (is_array($varname)) {
            $this->variablesGlobal = \array_merge($this->variablesGlobal, $varname);
        } else {
            $this->variablesGlobal[$varname] = $value;
        }
        return $this;
    }

    /**
     * Get the string contents of a section.
     *
     * @param string $section
     * @param string $default
     * @return string
     */
    public function yieldContent($section, $default = '')
    {
        if (isset($this->sections[$section])) {
            return \str_replace($this->PARENTKEY, $default, $this->sections[$section]);
        }

        return $default;
    }

    /**
     * Register a custom Blade compiler.
     *
     * @param callable $compiler
     * @return void
     */
    public function extend(callable $compiler)
    {
        $this->extensions[] = $compiler;
    }

    /**
     * Register a handler for custom directives for run at runtime
     *
     * @param string   $name
     * @param callable $handler
     * @return void
     */
    public function directiveRT($name, callable $handler)
    {
        $this->customDirectives[$name] = $handler;
        $this->customDirectivesRT[$name] = true;
    }

    /**
     * Sets the escaped content tags used for the compiler.
     *
     * @param string $openTag
     * @param string $closeTag
     * @return void
     */
    public function setEscapedContentTags($openTag, $closeTag)
    {
        $this->setContentTags($openTag, $closeTag, true);
    }

    /**
     * Gets the content tags used for the compiler.
     *
     * @return array
     */
    public function getContentTags()
    {
        return $this->getTags();
    }

    /**
     * Sets the content tags used for the compiler.
     *
     * @param string $openTag
     * @param string $closeTag
     * @param bool   $escaped
     * @return void
     */
    public function setContentTags($openTag, $closeTag, $escaped = false)
    {
        $property = ($escaped === true) ? 'escapedTags' : 'contentTags';
        $this->{$property} = [\preg_quote($openTag), \preg_quote($closeTag)];
    }

    /**
     * Gets the tags used for the compiler.
     *
     * @param bool $escaped
     * @return array
     */
    protected function getTags($escaped = false)
    {
        $tags = $escaped ? $this->escapedTags : $this->contentTags;
        return \array_map('stripcslashes', $tags);
    }

    /**
     * Gets the escaped content tags used for the compiler.
     *
     * @return array
     */
    public function getEscapedContentTags()
    {
        return $this->getTags(true);
    }

    /**
     * Sets the function used for resolving classes with inject.
     *
     * @param callable $function
     */
    public function setInjectResolver(callable $function)
    {
        $this->injectResolver = $function;
    }

    /**
     * Get the file extension for template files.
     *
     * @return string
     */
    public function getFileExtension()
    {
        return $this->fileExtension;
    }

    /**
     * Set the file extension for the template files.
     * It must includes the leading dot e.g. .blade.php
     *
     * @param string $fileExtension Example: .prefix.ext
     */
    public function setFileExtension($fileExtension)
    {
        $this->fileExtension = $fileExtension;
    }

    /**
     * Get the file extension for template files.
     *
     * @return string
     */
    public function getCompiledExtension()
    {
        return $this->compileExtension;
    }

    /**
     * Set the file extension for the compiled files.
     * Including the leading dot for the extension is required, e.g. .bladec
     *
     * @param $fileExtension
     */
    public function setCompiledExtension($fileExtension)
    {
        $this->compileExtension = $fileExtension;
    }

    /**
     * Add new loop to the stack.
     *
     * @param array|Countable $data
     * @return void
     */
    public function addLoop($data)
    {
        $length = \is_array($data) || $data instanceof Countable ? \count($data) : null;
        $parent = static::last($this->loopsStack);
        $this->loopsStack[] = [
            'index' => -1,
            'iteration' => 0,
            'remaining' => isset($length) ? $length + 1 : null,
            'count' => $length,
            'first' => true,
            'even' => true,
            'odd' => false,
            'last' => isset($length) ? $length == 1 : null,
            'depth' => \count($this->loopsStack) + 1,
            'parent' => $parent ? (object)$parent : null,
        ];
    }

    /**
     * Increment the top loop's indices.
     *
     * @return object
     */
    public function incrementLoopIndices()
    {
        $c = \count($this->loopsStack) - 1;
        $loop = &$this->loopsStack[$c];

        $loop['index']++;
        $loop['iteration']++;
        $loop['first'] = $loop['index'] == 0;
        $loop['even'] = $loop['index'] % 2 == 0;
        $loop['odd'] = !$loop['even'];
        if (isset($loop['count'])) {
            $loop['remaining']--;
            $loop['last'] = $loop['index'] == $loop['count'] - 1;
        }
        return (object)$loop;
    }

    /**
     * Pop a loop from the top of the loop stack.
     *
     * @return void
     */
    public function popLoop()
    {
        \array_pop($this->loopsStack);
    }

    /**
     * Get an instance of the first loop in the stack.
     *
     * @return object
     */
    public function getFirstLoop()
    {
        return ($last = static::last($this->loopsStack)) ? (object)$last : null;
    }

    /**
     * Get the rendered contents of a partial from a loop.
     *
     * @param string $view
     * @param array  $data
     * @param string $iterator
     * @param string $empty
     * @return string
     * @throws Exception
     */
    public function renderEach($view, $data, $iterator, $empty = 'raw|')
    {
        $result = '';

        if (\count($data) > 0) {
            // If is actually data in the array, we will loop through the data and append
            // an instance of the partial view to the final result HTML passing in the
            // iterated value of this data array, allowing the views to access them.
            foreach ($data as $key => $value) {
                $data = ['key' => $key, $iterator => $value];
                $result .= $this->runChild($view, $data);
            }
        } elseif (static::startsWith($empty, 'raw|')) {
            $result = \substr($empty, 4);
        } else {
            $result = $this->run($empty, []);
        }
        return $result;
    }

    /**
     * Run the blade engine. It returns the result of the code.
     *
     * @param string|null $view      The name of the cache. Ex: "folder.folder.view" ("/folder/folder/view.blade")
     * @param array       $variables An associative arrays with the values to display.
     * @return string
     * @throws Exception
     */
    public function run($view = null, $variables = [])
    {
        $mode = $this->getMode();

        if ($view === null) {
            $view = $this->viewStack;
        }
        $this->viewStack = null;
        if ($view === null) {
            $this->showError('run', 'BladeOne: view not set', true);
            return '';
        }

        $forced = $mode & 1; // mode=1 forced:it recompiles no matter if the compiled file exists or not.
        $runFast = $mode & 2; // mode=2 runfast: the code is not compiled neither checked and it runs directly the compiled
        $this->sections = [];
        if ($mode == 3) {
            $this->showError('run', "we can't force and run fast at the same time", true);
        }
        return $this->runInternal($view, $variables, $forced, true, $runFast);
    }

    /**
     * It sets the current view<br>
     * This value is cleared when it is used (method run).<br>
     * <b>Example:<b><br>
     * <pre>
     * $this->setView('folder.view')->share(['var1'=>20])->run(); // or $this->run('folder.view',['var1'=>20]);
     * </pre>
     *
     * @param string $view
     * @return BladeOne
     */
    public function setView($view)
    {
        $this->viewStack = $view;
        return $this;
    }

    /**
     * It injects a function, an instance, or a method class when a view is called.<br>
     * It could be stacked.   If it sets null then it clears all definitions.
     * <b>Example:<b><br>
     * <pre>
     * $this->composer('folder.view',function($bladeOne) { $bladeOne->share('newvalue','hi there'); });
     * $this->composer('folder.view','namespace1\namespace2\SomeClass'); // SomeClass must exists and it must has the
     *                                                                   // method 'composer'
     * $this->composer('folder.*',$instance); // $instance must has the method called 'composer'
     * $this->composer(); // clear all composer.
     * </pre>
     *
     * @param string|array|null    $view It could contains wildcards (*). Example: 'aa.bb.cc','*.bb.cc','aa.bb.*','*.bb.*'
     *
     * @param callable|string|null $functionOrClass
     * @return BladeOne
     */
    public function composer($view = null, $functionOrClass = null)
    {
        if ($view === null && $functionOrClass === null) {
            $this->composerStack = [];
            return $this;
        }
        if (is_array($view)) {
            foreach ($view as $v) {
                $this->composerStack[$v] = $functionOrClass;
            }
        } else {
            $this->composerStack[$view] = $functionOrClass;
        }

        return $this;
    }

    /**
     * Start a component rendering process.
     *
     * @param string $name
     * @param array  $data
     * @return void
     */
    public function startComponent($name, array $data = [])
    {
        if (\ob_start()) {
            $this->componentStack[] = $name;

            $this->componentData[$this->currentComponent()] = $data;

            $this->slots[$this->currentComponent()] = [];
        }
    }

    /**
     * Get the index for the current component.
     *
     * @return int
     */
    protected function currentComponent()
    {
        return \count($this->componentStack) - 1;
    }

    /**
     * Render the current component.
     *
     * @return string
     * @throws Exception
     */
    public function renderComponent()
    {
        //echo "<hr>render<br>";
        $name = \array_pop($this->componentStack);
        //return $this->runChild($name, $this->componentData());
        $cd = $this->componentData();
        $clean = array_keys($cd);
        $r = $this->runChild($name, $cd);
        // we clean variables defined inside the component (so they are garbaged when the component is used)
        foreach ($clean as $key) {
            unset($this->variables[$key]);
        }
        return $r;
    }

    /**
     * Get the data for the given component.
     *
     * @return array
     */
    protected function componentData()
    {
        $cs = count($this->componentStack);
        //echo "<hr>";
        //echo "<br>data:<br>";
        //var_dump($this->componentData);
        //echo "<br>datac:<br>";
        //var_dump(count($this->componentStack));
        return array_merge(
            $this->componentData[$cs],
            ['slot' => trim(ob_get_clean())],
            $this->slots[$cs]
        );
    }

    /**
     * Start the slot rendering process.
     *
     * @param string      $name
     * @param string|null $content
     * @return void
     */
    public function slot($name, $content = null)
    {
        if (\count(\func_get_args()) === 2) {
            $this->slots[$this->currentComponent()][$name] = $content;
        } elseif (\ob_start()) {
            $this->slots[$this->currentComponent()][$name] = '';

            $this->slotStack[$this->currentComponent()][] = $name;
        }
    }

    /**
     * Save the slot content for rendering.
     *
     * @return void
     */
    public function endSlot()
    {
        static::last($this->componentStack);

        $currentSlot = \array_pop(
            $this->slotStack[$this->currentComponent()]
        );

        $this->slots[$this->currentComponent()]
        [$currentSlot] = \trim(\ob_get_clean());
    }

    /**
     * @return string
     */
    public function getPhpTag()
    {
        return $this->phpTag;
    }

    /**
     * @param string $phpTag
     */
    public function setPhpTag($phpTag)
    {
        $this->phpTag = $phpTag;
    }

    /**
     * @return string
     */
    public function getCurrentUser()
    {
        return $this->currentUser;
    }

    /**
     * @param string $currentUser
     */
    public function setCurrentUser($currentUser)
    {
        $this->currentUser = $currentUser;
    }

    /**
     * @return string
     */
    public function getCurrentRole()
    {
        return $this->currentRole;
    }

    /**
     * @param string $currentRole
     */
    public function setCurrentRole($currentRole)
    {
        $this->currentRole = $currentRole;
    }

    /**
     * @return string[]
     */
    public function getCurrentPermission()
    {
        return $this->currentPermission;
    }

    /**
     * @param string[] $currentPermission
     */
    public function setCurrentPermission($currentPermission)
    {
        $this->currentPermission = $currentPermission;
    }

    /**
     * Returns the current base url without trailing slash.
     *
     * @return string
     */
    public function getBaseUrl()
    {
        return $this->baseUrl;
    }

    /**
     * It sets the base url and it also calculates the relative path.<br>
     * The base url defines the "root" of the project, not always the level of the domain but it could be
     * any folder.<br>
     * This value is used to calculate the relativity of the resources but it is also used to set the domain.<br>
     * <b>Note:</b> The trailing slash is removed automatically if it's present.<br>
     * <b>Note:</b> We should not use arguments or name of the script.<br>
     * <b>Examples:</b><br>
     * <pre>
     * $this->setBaseUrl('http://domain.dom/myblog');
     * $this->setBaseUrl('http://domain.dom/corporate/erp');
     * $this->setBaseUrl('http://domain.dom/blog.php?args=20'); // avoid this one.
     * $this->setBaseUrl('http://another.dom');
     * </pre>
     *
     * @param string $baseUrl Example http://www.web.com/folder  https://www.web.com/folder/anotherfolder
     * @return BladeOne
     */
    public function setBaseUrl($baseUrl)
    {
        $this->baseUrl = \rtrim($baseUrl, '/'); // base with the url trimmed
        $this->baseDomain = @parse_url($this->baseUrl)['host'];
        $currentUrl = $this->getCurrentUrlCalculated();
        if ($currentUrl === '') {
            $this->relativePath = '';
            return $this;
        }
        if (\strpos($currentUrl, $this->baseUrl) === 0) {
            $part = \str_replace($this->baseUrl, '', $currentUrl);
            $numf = \substr_count($part, '/') - 1;
            $numf = ($numf > 10) ? 10 : $numf; // avoid overflow
            $this->relativePath = ($numf < 0) ? '' : \str_repeat('../', $numf);
        } else {
            $this->relativePath = '';
        }
        return $this;
    }

    /**
     * It gets the full current url calculated with the information sends by the user.<br>
     * <b>Note:</b> If we set baseurl, then it always uses the baseurl as domain (it's safe).<br>
     * <b>Note:</b> This information could be forged/faked by the end-user.<br>
     * <b>Note:</b> It returns empty '' if it is called in a command line interface / non-web.<br>
     * <b>Note:</b> It doesn't returns the user and password.<br>
     * @param bool $noArgs if true then it excludes the arguments.
     * @return string
     */
    public function getCurrentUrlCalculated($noArgs = false)
    {
        if (!isset($_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI'])) {
            return '';
        }
        $host = $this->baseDomain !== null ? $this->baseDomain : $_SERVER['HTTP_HOST']; // <-- it could be forged!
        $link = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http");
        $port = $_SERVER['SERVER_PORT'];
        $port2 = (($link === 'http' && $port === '80') || ($link === 'https' && $port === '443')) ? '' : ':' . $port;
        $link .= "://$host{$port2}$_SERVER[REQUEST_URI]";
        if ($noArgs) {
            $link = @explode('?', $link)[0];
        }
        return $link;
    }

    /**
     * It returns the relative path to the base url or empty if not set<br>
     * <b>Example:</b><br>
     * <pre>
     * // current url='http://domain.dom/page/subpage/web.php?aaa=2
     * $this->setBaseUrl('http://domain.dom/');
     * $this->getRelativePath(); // '../../'
     * $this->setBaseUrl('http://domain.dom/');
     * $this->getRelativePath(); // '../../'
     * </pre>
     * <b>Note:</b>The relative path is calculated when we set the base url.
     *
     * @return string
     * @see \eftec\bladeone\BladeOne::setBaseUrl
     */
    public function getRelativePath()
    {
        return $this->relativePath;
    }

    /**
     * It gets the full current canonical url.<br>
     * <b>Example:</b> https://www.mysite.com/aaa/bb/php.php?aa=bb
     * <ul>
     * <li>It returns the $this->canonicalUrl value if is not null</li>
     * <li>Otherwise, it returns the $this->currentUrl if not null</li>
     * <li>Otherwise, the url is calculated with the information sends by the user</li>
     * </ul>
     *
     * @return string|null
     */
    public function getCanonicalUrl()
    {
        return $this->canonicalUrl !== null ? $this->canonicalUrl : $this->getCurrentUrl();
    }

    /**
     * It sets the full canonical url.<br>
     * <b>Example:</b> https://www.mysite.com/aaa/bb/php.php?aa=bb
     *
     * @param string|null $canonUrl
     * @return BladeOne
     */
    public function setCanonicalUrl($canonUrl = null)
    {
        $this->canonicalUrl = $canonUrl;
        return $this;
    }

    /**
     * It gets the full current url<br>
     * <b>Example:</b> https://www.mysite.com/aaa/bb/php.php?aa=bb
     * <ul>
     * <li>It returns the $this->currentUrl if not null</li>
     * <li>Otherwise, the url is calculated with the information sends by the user</li>
     * </ul>
     *
     * @param bool $noArgs if true then it ignore the arguments.
     * @return string|null
     */
    public function getCurrentUrl($noArgs = false)
    {
        $link = $this->currentUrl !== null ? $this->currentUrl : $this->getCurrentUrlCalculated();
        if ($noArgs) {
            $link = @explode('?', $link)[0];
        }
        return $link;
    }

    /**
     * It sets the full current url.<br>
     * <b>Example:</b> https://www.mysite.com/aaa/bb/php.php?aa=bb
     * <b>Note:</b> If the current url is not set, then the system could calculate the current url.
     *
     * @param string|null $currentUrl
     * @return BladeOne
     */
    public function setCurrentUrl($currentUrl = null)
    {
        $this->currentUrl = $currentUrl;
        return $this;
    }

    /**
     * If true then it optimizes the result (it removes tab and extra spaces).
     *
     * @param bool $bool
     * @return BladeOne
     */
    public function setOptimize($bool = false)
    {
        $this->optimize = $bool;
        return $this;
    }

    /**
     * It sets the callback function for authentication. It is used by @can and @cannot
     *
     * @param callable $fn
     */
    public function setCanFunction(callable $fn)
    {
        $this->authCallBack = $fn;
    }

    /**
     * It sets the callback function for authentication. It is used by @canany
     *
     * @param callable $fn
     */
    public function setAnyFunction(callable $fn)
    {
        $this->authAnyCallBack = $fn;
    }

    /**
     * It sets the callback function for errors. It is used by @error
     *
     * @param callable $fn
     */
    public function setErrorFunction(callable $fn)
    {
        $this->errorCallBack = $fn;
    }

    //</editor-fold>
    //<editor-fold desc="push">

    /**
     * Get the entire loop stack.
     *
     * @return array
     */
    public function getLoopStack()
    {
        return $this->loopsStack;
    }

    /**
     * It adds a string inside a quoted string<br>
     * <b>example:</b><br>
     * <pre>
     * $this->addInsideQuote("'hello'"," world"); // 'hello world'
     * $this->addInsideQuote("hello"," world"); // hello world
     * </pre>
     *
     * @param $quoted
     * @param $newFragment
     * @return string
     */
    public function addInsideQuote($quoted, $newFragment)
    {
        if ($this->isQuoted($quoted)) {
            return substr($quoted, 0, -1) . $newFragment . substr($quoted, -1);
        }
        return $quoted . $newFragment;
    }

    /**
     * Return true if the string is a php variable (it starts with $)
     *
     * @param string|null $text
     * @return bool
     */
    public function isVariablePHP($text)
    {
        if (!$text || strlen($text) < 2) {
            return false;
        }
        return $text[0] === '$';
    }

    /**
     * Its the same than @_e, however it parses the text (using sprintf).
     * If the operation fails then, it returns the original expression without translation.
     *
     * @param $phrase
     *
     * @return string
     */
    public function _ef($phrase)
    {
        $argv = \func_get_args();
        $r = $this->_e($phrase);
        $argv[0] = $r; // replace the first argument with the translation.
        $result = @sprintf(...$argv);
        return ($result == false) ? $r : $result;
    }

    /**
     * Tries to translate the word if its in the array defined by BladeOneLang::$dictionary
     * If the operation fails then, it returns the original expression without translation.
     *
     * @param $phrase
     *
     * @return string
     */
    public function _e($phrase)
    {
        if ((!\array_key_exists($phrase, static::$dictionary))) {
            $this->missingTranslation($phrase);
            return $phrase;
        }

        return static::$dictionary[$phrase];
    }

    /**
     * Log a missing translation into the file $this->missingLog.<br>
     * If the file is not defined, then it doesn't write the log.
     *
     * @param string $txt Message to write on.
     */
    private function missingTranslation($txt)
    {
        if (!$this->missingLog) {
            return; // if there is not a file assigned then it skips saving.
        }
        $fz = @\filesize($this->missingLog);
        if (\is_object($txt) || \is_array($txt)) {
            $txt = \print_r($txt, true);
        }
        // Rewrite file if more than 100000 bytes
        $mode = ($fz > 100000) ? 'w' : 'a';
        $fp = \fopen($this->missingLog, $mode);
        \fwrite($fp, $txt . "\n");
        \fclose($fp);
    }

    /**
     * if num is more than one then it returns the phrase in plural, otherwise the phrase in singular.
     * Note: the translation should be as follow: $msg['Person']='Person' $msg=['Person']['p']='People'
     *
     * @param string $phrase
     * @param string $phrases
     * @param int    $num
     *
     * @return string
     */
    public function _n($phrase, $phrases, $num = 0)
    {
        if ((!\array_key_exists($phrase, static::$dictionary))) {
            $this->missingTranslation($phrase);
            return ($num <= 1) ? $phrase : $phrases;
        }

        return ($num <= 1) ? $this->_e($phrase) : $this->_e($phrases);
    }

    /**
     * @param $expression
     * @return string
     * @see \eftec\bladeone\BladeOne::getCanonicalUrl
     */
    public function compileCanonical($expression = null)
    {
        return '<link rel="canonical" href="' . $this->phpTag
            . ' echo $this->getCanonicalUrl();?>" />';
    }

    /**
     * @param $expression
     * @return string
     * @see \eftec\bladeone\BladeOne::getBaseUrl()
     */
    public function compileBase($expression = null)
    {
        return '<base rel="canonical" href="' . $this->phpTag
            . ' echo $this->getBaseUrl() ;?>" />';
    }

    protected function compileUse($expression)
    {
        return $this->phpTag . 'use ' . $this->stripParentheses($expression) . '; ?>';
    }

    protected function compileSwitch($expression)
    {
        $this->switchCount++;
        $this->firstCaseInSwitch = true;
        return $this->phpTag . "switch $expression {";
    }
    //</editor-fold>
    //<editor-fold desc="compile extras">

    protected function compileDump($expression)
    {
        return $this->phpTagEcho . " \$this->dump$expression;?>";
    }

    protected function compileRelative($expression)
    {
        return $this->phpTagEcho . " \$this->relative$expression;?>";
    }

    protected function compileMethod($expression)
    {
        $v = $this->stripParentheses($expression);

        return "<input type='hidden' name='_method' value='{$this->phpTag}echo $v; " . "?>'/>";
    }

    protected function compilecsrf($expression = null)
    {
        $expression = ($expression === null) ? "'_token'" : $expression;
        return "<input type='hidden' name='$this->phpTag echo $expression; ?>' value='{$this->phpTag}echo \$this->csrf_token; " . "?>'/>";
    }

    protected function compileDd($expression)
    {
        return $this->phpTagEcho . " '<pre>'; var_dump$expression; echo '</pre>';?>";
    }

    /**
     * Execute the case tag.
     *
     * @param $expression
     * @return string
     */
    protected function compileCase($expression)
    {
        if ($this->firstCaseInSwitch) {
            $this->firstCaseInSwitch = false;
            return 'case ' . $expression . ': ?>';
        }
        return $this->phpTag . "case $expression: ?>";
    }

    /**
     * Compile the while statements into valid PHP.
     *
     * @param string $expression
     * @return string
     */
    protected function compileWhile($expression)
    {
        return $this->phpTag . "while$expression: ?>";
    }

    /**
     * default tag used for switch/case
     *
     * @return string
     */
    protected function compileDefault()
    {
        if ($this->firstCaseInSwitch) {
            return $this->showError('@default', '@switch without any @case', true);
        }
        return $this->phpTag . 'default: ?>';
    }

    protected function compileEndSwitch()
    {
        --$this->switchCount;
        if ($this->switchCount < 0) {
            return $this->showError('@endswitch', 'Missing @switch', true);
        }
        return $this->phpTag . '} // end switch ?>';
    }

    /**
     * Compile while statements into valid PHP.
     *
     * @param string $expression
     * @return string
     */
    protected function compileInject($expression)
    {
        $ex = $this->stripParentheses($expression);
        $p0 = \strpos($ex, ',');
        if ($p0 == false) {
            $var = $this->stripQuotes($ex);
            $namespace = '';
        } else {
            $var = $this->stripQuotes(\substr($ex, 0, $p0));
            $namespace = $this->stripQuotes(\substr($ex, $p0 + 1));
        }
        return $this->phpTag . "\$$var = \$this->injectClass('$namespace', '$var'); ?>";
    }

    /**
     * Remove first and end quote from a quoted string of text
     *
     * @param mixed $text
     * @return null|string|string[]
     */
    public function stripQuotes($text)
    {
        if (!$text || strlen($text) < 2) {
            return $text;
        }
        $text = trim($text);
        $p0 = $text[0];
        $p1 = \substr($text, -1);
        if ($p0 === $p1 && ($p0 === '"' || $p0 === "'")) {
            return \substr($text, 1, -1);
        }
        return $text;
    }

    /**
     * Execute the user defined extensions.
     *
     * @param string $value
     * @return string
     */
    protected function compileExtensions($value)
    {
        foreach ($this->extensions as $compiler) {
            $value = $compiler($value, $this);
        }
        return $value;
    }

    /**
     * Compile Blade comments into valid PHP.
     *
     * @param string $value
     * @return string
     */
    protected function compileComments($value)
    {
        $pattern = \sprintf('/%s--(.*?)--%s/s', $this->contentTags[0], $this->contentTags[1]);
        return \preg_replace($pattern, $this->phpTag . '/*$1*/ ?>', $value);
    }

    /**
     * Compile Blade echos into valid PHP.
     *
     * @param string $value
     * @return string
     */
    protected function compileEchos($value)
    {
        foreach ($this->getEchoMethods() as $method => $length) {
            $value = $this->$method($value);
        }
        return $value;
    }

    /**
     * Get the echo methods in the proper order for compilation.
     *
     * @return array
     */
    protected function getEchoMethods()
    {
        $methods = [
            'compileRawEchos' => \strlen(\stripcslashes($this->rawTags[0])),
            'compileEscapedEchos' => \strlen(\stripcslashes($this->escapedTags[0])),
            'compileRegularEchos' => \strlen(\stripcslashes($this->contentTags[0])),
        ];
        \uksort($methods, static function ($method1, $method2) use ($methods) {
            // Ensure the longest tags are processed first
            if ($methods[$method1] > $methods[$method2]) {
                return -1;
            }
            if ($methods[$method1] < $methods[$method2]) {
                return 1;
            }
            // Otherwise give preference to raw tags (assuming they've overridden)
            if ($method1 === 'compileRawEchos') {
                return -1;
            }
            if ($method2 === 'compileRawEchos') {
                return 1;
            }
            if ($method1 === 'compileEscapedEchos') {
                return -1;
            }
            if ($method2 === 'compileEscapedEchos') {
                return 1;
            }
            throw new BadMethodCallException("Method [$method1] not defined");
        });
        return $methods;
    }

    /**
     * Compile Blade statements that start with "@".
     *
     * @param string $value
     *
     * @return array|string|string[]|null
     */
    protected function compileStatements($value)
    {
        /**
         * @param array $match
         *                    [0]=full expression with @ and parenthesis
         *                    [1]=expression without @ and argument
         *                    [2]=????
         *                    [3]=argument with parenthesis and without the first @
         *                    [4]=argument without parenthesis.
         *
         * @return mixed|string
         */
        $callback = function ($match) {
            if (static::contains($match[1], '@')) {
                // @@escaped tag
                $match[0] = isset($match[3]) ? $match[1] . $match[3] : $match[1];
            } else {
                if (strpos($match[1], '::') !== false) {
                    // Someclass::method
                    return $this->compileStatementClass($match);
                }
                if (isset($this->customDirectivesRT[$match[1]])) {
                    if ($this->customDirectivesRT[$match[1]] == true) {
                        $match[0] = $this->compileStatementCustom($match);
                    } else {
                        $match[0] = \call_user_func(
                            $this->customDirectives[$match[1]],
                            $this->stripParentheses(static::get($match, 3))
                        );
                    }
                } elseif (\method_exists($this, $method = 'compile' . \ucfirst($match[1]))) {
                    // it calls the function compile<name of the tag>
                    $match[0] = $this->$method(static::get($match, 3));
                } else {
                    return $match[0];
                }
            }
            return isset($match[3]) ? $match[0] : $match[0] . $match[2];
        };
        /* return \preg_replace_callback('/\B@(@?\w+)([ \t]*)(\( ( (?>[^()]+) | (?3) )* \))?/x', $callback, $value); */
        return preg_replace_callback('/\B@(@?\w+(?:::\w+)?)([ \t]*)(\( ( (?>[^()]+) | (?3) )* \))?/x', $callback, $value);
    }

    /**
     * Determine if a given string contains a given substring.
     *
     * @param string       $haystack
     * @param string|array $needles
     * @return bool
     */
    public static function contains($haystack, $needles)
    {
        foreach ((array)$needles as $needle) {
            if ($needle != '') {
                if (\function_exists('mb_strpos')) {
                    if (\mb_strpos($haystack, $needle) !== false) {
                        return true;
                    }
                } elseif (\strpos($haystack, $needle) !== false) {
                    return true;
                }
            }
        }

        return false;
    }

    private function compileStatementClass($match)
    {
        if (isset($match[3])) {
            return $this->phpTagEcho . $this->fixNamespaceClass($match[1]) . $match[3] . '; ?>';
        }

        return $this->phpTagEcho . $this->fixNamespaceClass($match[1]) . '(); ?>';
    }

    /**
     * Util method to fix namespace of a class<br>
     * Example: "SomeClass::method()" -> "\namespace\SomeClass::method()"<br>
     *
     * @param string $text
     *
     * @return string
     * @see \eftec\bladeone\BladeOne::$aliasClasses
     */
    private function fixNamespaceClass($text)
    {
        if (strpos($text, '::') === false) {
            return $text;
        }
        $classPart = explode('::', $text, 2);
        if (isset($this->aliasClasses[$classPart[0]])) {
            $classPart[0] = $this->aliasClasses[$classPart[0]];
        }
        return $classPart[0] . '::' . $classPart[1];
    }

    /**
     * For compile custom directive at runtime.
     *
     * @param $match
     * @return string
     */
    protected function compileStatementCustom($match)
    {
        $v = $this->stripParentheses(static::get($match, 3));
        $v = ($v == '') ? '' : ',' . $v;
        return $this->phpTag . 'call_user_func($this->customDirectives[\'' . $match[1] . '\']' . $v . '); ?>';
    }

    /**
     * Get an item from an array using "dot" notation.
     *
     * @param ArrayAccess|array $array
     * @param string            $key
     * @param mixed             $default
     * @return mixed
     */
    public static function get($array, $key, $default = null)
    {
        $accesible = \is_array($array) || $array instanceof ArrayAccess;
        if (!$accesible) {
            return static::value($default);
        }
        if (\is_null($key)) {
            return $array;
        }
        if (static::exists($array, $key)) {
            return $array[$key];
        }
        foreach (\explode('.', $key) as $segment) {
            if (static::exists($array, $segment)) {
                $array = $array[$segment];
            } else {
                return static::value($default);
            }
        }
        return $array;
    }

    /**
     * Determine if the given key exists in the provided array.
     *
     * @param ArrayAccess|array $array
     * @param string|int        $key
     * @return bool
     */
    public static function exists($array, $key)
    {
        if ($array instanceof ArrayAccess) {
            return $array->offsetExists($key);
        }
        return \array_key_exists($key, $array);
    }

    /**
     * This method removes the parenthesis of the expression and parse the arguments.
     * @param string $expression
     * @return array
     */
    protected function getArgs($expression)
    {
        return $this->parseArgs($this->stripParentheses($expression), ' ');
    }

    /**
     * It separates a string using a separator and excluding quotes and double quotes.
     *
     * @param string $text
     * @param string $separator
     * @return array
     */
    public function parseArgs($text, $separator = ',')
    {
        if ($text === null || $text === '') {
            return []; //nothing to convert.
        }
        $chars = str_split($text);
        $parts = [];
        $nextpart = '';
        $strL = count($chars);
        /** @noinspection ForeachInvariantsInspection */
        for ($i = 0; $i < $strL; $i++) {
            $char = $chars[$i];
            if ($char === '"' || $char === "'") {
                $inext = strpos($text, $char, $i + 1);
                $inext = $inext === false ? $strL : $inext;
                $nextpart .= substr($text, $i, $inext - $i + 1);
                $i = $inext;
            } else {
                $nextpart .= $char;
            }
            if ($char === $separator) {
                $parts[] = substr($nextpart, 0, -1);
                $nextpart = '';
            }
        }
        if ($nextpart !== '') {
            $parts[] = $nextpart;
        }
        $result = [];
        foreach ($parts as $part) {
            $r = explode('=', $part, 2);
            $result[trim($r[0])] = count($r) === 2 ? trim($r[1]) : null;
        }
        return $result;
    }

    /**
     * Compile the "raw" echo statements.
     *
     * @param string $value
     * @return string
     */
    protected function compileRawEchos($value)
    {
        $pattern = \sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $this->rawTags[0], $this->rawTags[1]);
        $callback = function ($matches) {
            $whitespace = empty($matches[3]) ? '' : $matches[3] . $matches[3];
            return $matches[1] ? \substr(
                $matches[0],
                1
            ) : $this->phpTagEcho . $this->compileEchoDefaults($matches[2]) . '; ?>' . $whitespace;
        };
        return \preg_replace_callback($pattern, $callback, $value);
    }

    /**
     * Compile the default values for the echo statement.
     *
     * @param string $value
     * @return string
     */
    protected function compileEchoDefaults($value)
    {
        $result = \preg_replace('/^(?=\$)(.+?)(?:\s+or\s+)(.+?)$/s', 'isset($1) ? $1 : $2', $value);
        if (!$this->pipeEnable) {
            return $this->fixNamespaceClass($result);
        }
        return $this->pipeDream($this->fixNamespaceClass($result));
    }

    /**
     * It converts a string separated by pipes | into an filtered expression.<br>
     * If the method exists (as directive), then it is used<br>
     * If the method exists (in this class) then it is used<br>
     * Otherwise, it uses a global function.<br>
     * If you want to escape the "|", then you could use "/|"<br>
     * <b>Note:</b> It only works if $this->pipeEnable=true and by default it is false<br>
     * <b>Example:</b><br>
     * <pre>
     * $this->pipeDream('$name | strtolower | substr:0,4'); // strtolower(substr($name ,0,4)
     * $this->pipeDream('$name| getMode') // $this->getMode($name)
     * </pre>
     *
     * @param string $result
     * @return string
     * @\eftec\bladeone\BladeOne::$pipeEnable
     */
    protected function pipeDream($result)
    {
        $array = preg_split('~\\\\.(*SKIP)(*FAIL)|\|~s', $result);
        $c = count($array) - 1; // base zero.
        if ($c === 0) {
            return $result;
        }

        $prev = '';
        for ($i = $c; $i >= 1; $i--) {
            $r = @explode(':', $array[$i], 2);
            $fnName = trim($r[0]);
            $fnNameF = $fnName[0]; // first character
            if ($fnNameF === '"' || $fnNameF === '\'' || $fnNameF === '$' || is_numeric($fnNameF)) {
                $fnName = '!isset(' . $array[0] . ') ? ' . $fnName . ' : ';
            } elseif (isset($this->customDirectives[$fnName])) {
                $fnName = '$this->customDirectives[\'' . $fnName . '\']';
            } elseif (method_exists($this, $fnName)) {
                $fnName = '$this->' . $fnName;
            }
            if ($i === 1) {
                $prev = $fnName . '(' . $array[0];
                if (count($r) === 2) {
                    $prev .= ',' . $r[1];
                }
                $prev .= ')';
            } else {
                $prev = $fnName . '(' . $prev;
                if (count($r) === 2) {
                    if ($i === 2) {
                        $prev .= ',';
                    }
                    $prev .= $r[1] . ')';
                }
            }
        }
        return $prev;
    }

    /**
     * Compile the "regular" echo statements. {{ }}
     *
     * @param string $value
     * @return string
     */
    protected function compileRegularEchos($value)
    {
        $pattern = \sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $this->contentTags[0], $this->contentTags[1]);
        $callback = function ($matches) {
            $whitespace = empty($matches[3]) ? '' : $matches[3] . $matches[3];
            $wrapped = \sprintf($this->echoFormat, $this->compileEchoDefaults($matches[2]));
            return $matches[1] ? \substr($matches[0], 1) : $this->phpTagEcho . $wrapped . '; ?>' . $whitespace;
        };
        return \preg_replace_callback($pattern, $callback, $value);
    }

    /**
     * Compile the escaped echo statements. {!! !!}
     *
     * @param string $value
     * @return string
     */
    protected function compileEscapedEchos($value)
    {
        $pattern = \sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $this->escapedTags[0], $this->escapedTags[1]);
        $callback = function ($matches) {
            $whitespace = empty($matches[3]) ? '' : $matches[3] . $matches[3];

            return $matches[1] ? $matches[0] : $this->phpTag
                . \sprintf($this->echoFormat, $this->compileEchoDefaults($matches[2])) . '; ?>'
                . $whitespace;
            //return $matches[1] ? $matches[0] : $this->phpTag
            // . 'echo static::e(' . $this->compileEchoDefaults($matches[2]) . '); ? >' . $whitespace;
        };
        return \preg_replace_callback($pattern, $callback, $value);
    }

    /**
     * Compile the each statements into valid PHP.
     *
     * @param string $expression
     * @return string
     */
    protected function compileEach($expression)
    {
        return $this->phpTagEcho . "\$this->renderEach$expression; ?>";
    }

    protected function compileSet($expression)
    {
        //$segments = \explode('=', \preg_replace("/[()\\\']/", '', $expression));
        $segments = \explode('=', $this->stripParentheses($expression));
        $value = (\count($segments) >= 2) ? '=@' . $segments[1] : '++';
        return $this->phpTag . \trim($segments[0]) . $value . ';?>';
    }

    /**
     * Compile the yield statements into valid PHP.
     *
     * @param string $expression
     * @return string
     */
    protected function compileYield($expression)
    {
        return $this->phpTagEcho . "\$this->yieldContent$expression; ?>";
    }

    /**
     * Compile the show statements into valid PHP.
     *
     * @return string
     */
    protected function compileShow()
    {
        return $this->phpTagEcho . '$this->yieldSection(); ?>';
    }

    /**
     * Compile the section statements into valid PHP.
     *
     * @param string $expression
     * @return string
     */
    protected function compileSection($expression)
    {
        return $this->phpTag . "\$this->startSection$expression; ?>";
    }

    /**
     * Compile the append statements into valid PHP.
     *
     * @return string
     */
    protected function compileAppend()
    {
        return $this->phpTag . '$this->appendSection(); ?>';
    }

    /**
     * Compile the auth statements into valid PHP.
     *
     * @param string $expression
     * @return string
     */
    protected function compileAuth($expression = '')
    {
        $role = $this->stripParentheses($expression);
        if ($role == '') {
            return $this->phpTag . 'if(isset($this->currentUser)): ?>';
        }

        return $this->phpTag . "if(isset(\$this->currentUser) && \$this->currentRole==$role): ?>";
    }

    /**
     * Compile the elseauth statements into valid PHP.
     *
     * @param string $expression
     * @return string
     */
    protected function compileElseAuth($expression = '')
    {
        $role = $this->stripParentheses($expression);
        if ($role == '') {
            return $this->phpTag . 'else: ?>';
        }

        return $this->phpTag . "elseif(isset(\$this->currentUser) && \$this->currentRole==$role): ?>";
    }

    /**
     * Compile the end-auth statements into valid PHP.
     *
     * @return string
     */
    protected function compileEndAuth()
    {
        return $this->phpTag . 'endif; ?>';
    }

    protected function compileCan($expression)
    {
        $v = $this->stripParentheses($expression);
        return $this->phpTag . 'if (call_user_func($this->authCallBack,' . $v . ')): ?>';
    }

    /**
     * Compile the else statements into valid PHP.
     *
     * @param string $expression
     * @return string
     */
    protected function compileElseCan($expression = '')
    {
        $v = $this->stripParentheses($expression);
        if ($v) {
            return $this->phpTag . 'elseif (call_user_func($this->authCallBack,' . $v . ')): ?>';
        }

        return $this->phpTag . 'else: ?>';
    }
    //</editor-fold>
    //<editor-fold desc="file members">

    protected function compileCannot($expression)
    {
        $v = $this->stripParentheses($expression);
        return $this->phpTag . 'if (!call_user_func($this->authCallBack,' . $v . ')): ?>';
    }

    /**
     * Compile the elsecannot statements into valid PHP.
     *
     * @param string $expression
     * @return string
     */
    protected function compileElseCannot($expression = '')
    {
        $v = $this->stripParentheses($expression);
        if ($v) {
            return $this->phpTag . 'elseif (!call_user_func($this->authCallBack,' . $v . ')): ?>';
        }

        return $this->phpTag . 'else: ?>';
    }

    /**
     * Compile the canany statements into valid PHP.
     * canany(['edit','write'])
     *
     * @param $expression
     * @return string
     */
    protected function compileCanAny($expression)
    {
        $role = $this->stripParentheses($expression);
        return $this->phpTag . 'if (call_user_func($this->authAnyCallBack,' . $role . ')): ?>';
    }

    /**
     * Compile the else statements into valid PHP.
     *
     * @param $expression
     * @return string
     */
    protected function compileElseCanAny($expression)
    {
        $role = $this->stripParentheses($expression);
        if ($role == '') {
            return $this->phpTag . 'else: ?>';
        }
        return $this->phpTag . 'elseif (call_user_func($this->authAnyCallBack,' . $role . ')): ?>';
    }

    /**
     * Compile the guest statements into valid PHP.
     *
     * @param null $expression
     * @return string
     */
    protected function compileGuest($expression = null)
    {
        if ($expression === null) {
            return $this->phpTag . 'if(!isset($this->currentUser)): ?>';
        }

        $role = $this->stripParentheses($expression);
        if ($role == '') {
            return $this->phpTag . 'if(!isset($this->currentUser)): ?>';
        }

        return $this->phpTag . "if(!isset(\$this->currentUser) || \$this->currentRole!=$role): ?>";
    }

    /**
     * Compile the else statements into valid PHP.
     *
     * @param $expression
     * @return string
     */
    protected function compileElseGuest($expression)
    {
        $role = $this->stripParentheses($expression);
        if ($role == '') {
            return $this->phpTag . 'else: ?>';
        }

        return $this->phpTag . "elseif(!isset(\$this->currentUser) || \